export interface DataEntry { key: string value: any } export interface ContractSegmentPackage { version: number exportedAt: string packageType?: 'contract-segments' project?: { industry: string } storage?: { localforageEntries: DataEntry[] keyedEntries?: DataEntry[] } contracts: Array<{ id: string name: string order: number createdAt: string }> projectIndustry?: string localforageEntries?: DataEntry[] keyedEntries?: DataEntry[] pinia?: { zxFwPricing?: { contracts?: Record servicePricingStates?: Record htFeeMainStates?: Record htFeeMethodStates?: Record } } piniaState?: { zxFwPricing?: { contracts?: Record servicePricingStates?: Record htFeeMainStates?: Record htFeeMethodStates?: Record } } } export const CONTRACT_SEGMENT_FILE_EXTENSION = '.htzw' export const CONTRACT_SEGMENT_VERSION = 3 export const CONTRACT_KEY_PREFIX = 'ht-info-v3-' export const SERVICE_KEY_PREFIX = 'zxFW-' export const CONTRACT_CONSULT_FACTOR_KEY_PREFIX = 'ht-consult-category-factor-v1-' export const CONTRACT_MAJOR_FACTOR_KEY_PREFIX = 'ht-major-factor-v1-' export const PRICING_KEY_PREFIXES = ['tzGMF-', 'ydGMF-', 'gzlF-', 'hourlyPricing-', 'htExtraFee-'] as const export const PROJECT_INFO_KEY = 'xm-base-info-v1' export const PROJECT_SCALE_KEY = 'xm-info-v3' export const SERVICE_PRICING_METHODS = ['investScale', 'landScale', 'workload', 'hourly'] as const export const formatExportTimestamp = (date: Date): string => { const yyyy = date.getFullYear() const mm = String(date.getMonth() + 1).padStart(2, '0') const dd = String(date.getDate()).padStart(2, '0') const hh = String(date.getHours()).padStart(2, '0') const mi = String(date.getMinutes()).padStart(2, '0') return `${yyyy}${mm}${dd}-${hh}${mi}` } export const isRecord = (value: unknown): value is Record => Boolean(value && typeof value === 'object' && !Array.isArray(value)) export const cloneJson = (value: T): T => JSON.parse(JSON.stringify(value)) as T export const normalizeDataEntries = (value: unknown): DataEntry[] => { if (!Array.isArray(value)) return [] return value .filter(item => item && typeof item === 'object' && typeof (item as DataEntry).key === 'string') .map(item => ({ key: String((item as DataEntry).key), value: (item as DataEntry).value })) } export const normalizeContractSegmentPackage = (payload: ContractSegmentPackage) => ({ projectIndustry: typeof payload.project?.industry === 'string' && payload.project.industry.trim() ? payload.project.industry.trim() : (typeof payload.projectIndustry === 'string' ? payload.projectIndustry.trim() : ''), localforageEntries: normalizeDataEntries(payload.storage?.localforageEntries ?? payload.localforageEntries), keyedEntries: normalizeDataEntries(payload.storage?.keyedEntries ?? payload.keyedEntries), piniaState: payload.pinia ?? payload.piniaState }) export const isContractSegmentPackage = (value: unknown): value is ContractSegmentPackage => { const payload = value as Partial | null return Boolean(payload && typeof payload === 'object' && Array.isArray(payload.contracts)) } export const isContractRelatedForageKey = (key: string, contractId: string) => { if (key === `${CONTRACT_KEY_PREFIX}${contractId}`) return true if (key === `${SERVICE_KEY_PREFIX}${contractId}`) return true if (key === `${CONTRACT_CONSULT_FACTOR_KEY_PREFIX}${contractId}`) return true if (key === `${CONTRACT_MAJOR_FACTOR_KEY_PREFIX}${contractId}`) return true if (PRICING_KEY_PREFIXES.some(prefix => key.startsWith(`${prefix}${contractId}-`))) return true return false } export const isContractRelatedKeyedStateKey = (key: string, contractId: string) => { if (key === `ht-base-info-${contractId}`) return true if (key.startsWith(`work-content-${contractId}-`)) return true if (key.startsWith(`work-content-htExtraFee-${contractId}-`)) return true return false } export const rewriteKeyWithContractId = (key: string, fromId: string, toId: string) => { if (key === `${CONTRACT_KEY_PREFIX}${fromId}`) return `${CONTRACT_KEY_PREFIX}${toId}` if (key === `${SERVICE_KEY_PREFIX}${fromId}`) return `${SERVICE_KEY_PREFIX}${toId}` if (key === `${CONTRACT_CONSULT_FACTOR_KEY_PREFIX}${fromId}`) { return `${CONTRACT_CONSULT_FACTOR_KEY_PREFIX}${toId}` } if (key === `${CONTRACT_MAJOR_FACTOR_KEY_PREFIX}${fromId}`) { return `${CONTRACT_MAJOR_FACTOR_KEY_PREFIX}${toId}` } if (key === `ht-base-info-${fromId}`) return `ht-base-info-${toId}` if (key.startsWith(`work-content-${fromId}-`)) { return key.replace(`work-content-${fromId}-`, `work-content-${toId}-`) } if (key.startsWith(`work-content-htExtraFee-${fromId}-`)) { return key.replace(`work-content-htExtraFee-${fromId}-`, `work-content-htExtraFee-${toId}-`) } for (const prefix of PRICING_KEY_PREFIXES) { if (key.startsWith(`${prefix}${fromId}-`)) { return key.replace(`${prefix}${fromId}-`, `${prefix}${toId}-`) } } return key } export const generateContractId = (usedIds: Set) => { let nextId = '' while (!nextId || usedIds.has(nextId)) { nextId = `ct-${Date.now()}-${Math.random().toString(16).slice(2, 8)}` } usedIds.add(nextId) return nextId }