Files
calculator2026/src/lib/contractSegment.ts
T
2026-04-07 16:27:49 +08:00

142 lines
5.4 KiB
TypeScript

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<string, unknown>
servicePricingStates?: Record<string, unknown>
htFeeMainStates?: Record<string, unknown>
htFeeMethodStates?: Record<string, unknown>
}
}
piniaState?: {
zxFwPricing?: {
contracts?: Record<string, unknown>
servicePricingStates?: Record<string, unknown>
htFeeMainStates?: Record<string, unknown>
htFeeMethodStates?: Record<string, unknown>
}
}
}
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<string, unknown> =>
Boolean(value && typeof value === 'object' && !Array.isArray(value))
export const cloneJson = <T>(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<ContractSegmentPackage> | 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<string>) => {
let nextId = ''
while (!nextId || usedIds.has(nextId)) {
nextId = `ct-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`
}
usedIds.add(nextId)
return nextId
}