import localforage from 'localforage' import { expertList, getMajorDictEntries, getMajorIdAliasMap, getServiceDictById, taskList } from '@/sql' import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal' import { toFiniteNumberOrNull } from '@/lib/number' import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee' interface StoredDetailRowsState { detailRows?: T[] } interface StoredFactorState { detailRows?: Array<{ id: string standardFactor?: number | null budgetValue?: number | null }> } type MaybeNumber = number | null | undefined interface ScaleRow { id: string amount: number | null landArea: number | null consultCategoryFactor: number | null majorFactor: number | null workStageFactor: number | null workRatio: number | null } interface WorkloadRow { id: string conversion: number | null workload: number | null basicFee: number | null budgetAdoptedUnitPrice: number | null consultCategoryFactor: number | null } interface HourlyRow { id: string adoptedBudgetUnitPrice: number | null personnelCount: number | null workdayCount: number | null } interface MajorLite { code: string defCoe: number | null hasCost?: boolean hasArea?: boolean industryId?: string | number | null } interface ServiceLite { defCoe: number | null onlyCostScale?: boolean | null } interface TaskLite { serviceID: number conversion: number | null defPrice: number | null } interface ExpertLite { defPrice: number | null manageCoe: number | null } interface XmBaseInfoState { projectIndustry?: string } export interface PricingMethodTotals { investScale: number | null landScale: number | null workload: number | null hourly: number | null } interface PricingMethodTotalsOptions { excludeInvestmentCostAndAreaRows?: boolean } interface PricingMethodDetailDbKeys { investScale: string landScale: string workload: string hourly: string } interface PricingMethodDefaultDetailRows { investScale: unknown[] landScale: unknown[] workload: unknown[] hourly: unknown[] } interface PricingMethodDefaultBuildContext { htData: StoredDetailRowsState | null consultCategoryFactorMap: Map majorFactorMap: Map industryId: string excludeInvestmentCostAndAreaRows: boolean } const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__' const hasOwn = (obj: unknown, key: string) => Object.prototype.hasOwnProperty.call(obj || {}, key) const toRowMap = (rows?: TRow[]) => { const map = new Map() for (const row of rows || []) { map.set(String(row.id), row) } return map } const getDefaultConsultCategoryFactor = (serviceId: string | number) => { const service = (getServiceDictById() as Record)[String(serviceId)] return toFiniteNumberOrNull(service?.defCoe) } const isOnlyCostScaleService = (serviceId: string | number) => { const service = (getServiceDictById() as Record)[String(serviceId)] return service?.onlyCostScale === true } const majorById = new Map(getMajorDictEntries().map(({ id, item }) => [id, item as MajorLite])) const majorIdAliasMap = getMajorIdAliasMap() const getDefaultMajorFactorById = (id: string) => { const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id const major = majorById.get(resolvedId) return toFiniteNumberOrNull(major?.defCoe) } const isCostMajorById = (id: string) => { const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id const major = majorById.get(resolvedId) if (!major) return false return major.hasCost !== false } const isAreaMajorById = (id: string) => { const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id const major = majorById.get(resolvedId) if (!major) return false return major.hasArea !== false } const isDualScaleMajorById = (id: string) => { const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id const major = majorById.get(resolvedId) if (!major) return false const hasCost = major.hasCost !== false const hasArea = major.hasArea !== false return hasCost && hasArea } const getIndustryMajorEntryByIndustryId = (industryId: string | null | undefined) => { const key = String(industryId || '').trim() if (!key) return null for (const [id, item] of majorById.entries()) { const majorIndustryId = String(item?.industryId ?? '').trim() if (majorIndustryId === key && !String(item?.code || '').includes('-')) { return { id, item } } } return null } const resolveFactorValue = ( row: { budgetValue?: number | null; standardFactor?: number | null } | undefined, fallback: number | null ) => { if (!row) return fallback const budgetValue = toFiniteNumberOrNull(row.budgetValue) if (budgetValue != null) return budgetValue const standardFactor = toFiniteNumberOrNull(row.standardFactor) if (standardFactor != null) return standardFactor return fallback } const buildConsultCategoryFactorMap = (state: StoredFactorState | null) => { const map = new Map() const serviceDict = getServiceDictById() as Record for (const [id, item] of Object.entries(serviceDict)) { map.set(String(id), toFiniteNumberOrNull(item?.defCoe)) } for (const row of state?.detailRows || []) { if (!row?.id) continue const id = String(row.id) map.set(id, resolveFactorValue(row, map.get(id) ?? null)) } return map } const buildMajorFactorMap = (state: StoredFactorState | null) => { const map = new Map() for (const [id, item] of majorById.entries()) { map.set(String(id), toFiniteNumberOrNull(item?.defCoe)) } for (const row of state?.detailRows || []) { if (!row?.id) continue const rowId = String(row.id) const id = map.has(rowId) ? rowId : majorIdAliasMap.get(rowId) || rowId map.set(id, resolveFactorValue(row, map.get(id) ?? null)) } return map } const getMajorLeafIds = () => getMajorDictEntries() .filter(({ item }) => Boolean(item?.code && String(item.code).includes('-'))) .map(({ id }) => id) const buildDefaultScaleRows = ( serviceId: string | number, consultCategoryFactorMap?: Map, majorFactorMap?: Map ): ScaleRow[] => { const defaultConsultCategoryFactor = consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) return getMajorLeafIds().map(id => ({ id, amount: null, landArea: null, consultCategoryFactor: defaultConsultCategoryFactor, majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactorById(id), workStageFactor: 1, workRatio: 100 })) } const mergeScaleRows = ( serviceId: string | number, rowsFromDb: Array & Pick> | undefined, consultCategoryFactorMap?: Map, majorFactorMap?: Map ): ScaleRow[] => { const dbValueMap = toRowMap(rowsFromDb) for (const row of rowsFromDb || []) { const rowId = String(row.id) const nextId = majorIdAliasMap.get(rowId) if (nextId && !dbValueMap.has(nextId)) { dbValueMap.set(nextId, row as ScaleRow) } } const defaultConsultCategoryFactor = consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap).map(row => { const fromDb = dbValueMap.get(row.id) if (!fromDb) return row const hasConsultCategoryFactor = hasOwn(fromDb, 'consultCategoryFactor') const hasMajorFactor = hasOwn(fromDb, 'majorFactor') const hasWorkStageFactor = hasOwn(fromDb, 'workStageFactor') const hasWorkRatio = hasOwn(fromDb, 'workRatio') return { ...row, amount: toFiniteNumberOrNull(fromDb.amount), landArea: toFiniteNumberOrNull(fromDb.landArea), consultCategoryFactor: toFiniteNumberOrNull(fromDb.consultCategoryFactor) ?? (hasConsultCategoryFactor ? null : defaultConsultCategoryFactor), majorFactor: toFiniteNumberOrNull(fromDb.majorFactor) ?? (hasMajorFactor ? null : (majorFactorMap?.get(row.id) ?? getDefaultMajorFactorById(row.id))), workStageFactor: toFiniteNumberOrNull((fromDb as Partial).workStageFactor) ?? (hasWorkStageFactor ? null : row.workStageFactor), workRatio: toFiniteNumberOrNull((fromDb as Partial).workRatio) ?? (hasWorkRatio ? null : row.workRatio) } }) } const getBenchmarkBudgetByAmount = (amount: MaybeNumber) => getBenchmarkBudgetByScale(amount, 'cost') const getBenchmarkBudgetByLandArea = (landArea: MaybeNumber) => getBenchmarkBudgetByScale(landArea, 'area') const getInvestmentBudgetFee = (row: ScaleRow) => { return getScaleBudgetFee({ benchmarkBudget: getBenchmarkBudgetByAmount(row.amount), majorFactor: row.majorFactor, consultCategoryFactor: row.consultCategoryFactor, workStageFactor: row.workStageFactor, workRatio: row.workRatio }) } const getOnlyCostScaleBudgetFee = ( serviceId: string, rowsFromDb: Array> | undefined, consultCategoryFactorMap?: Map, majorFactorMap?: Map, industryId?: string | null ) => { const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId) const sourceRows = rowsFromDb || [] const defaultConsultCategoryFactor = consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) const defaultMajorFactor = (industryMajorEntry ? majorFactorMap?.get(industryMajorEntry.id) ?? null : null) ?? toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ?? 1 // 新版 onlyCostScale 支持“按项目行”存储(如 1::majorId、2::majorId),每行需独立计费后求和。 const usePerRowCalculation = sourceRows.some(row => { if (typeof row?.projectIndex === 'number' && Number.isFinite(row.projectIndex)) return true const id = String(row?.id || '') return /^\d+::/.test(id) }) if (usePerRowCalculation) { return sumByNumber(sourceRows, row => { const amount = toFiniteNumberOrNull(row?.amount) if (amount == null) return null return getScaleBudgetFee({ benchmarkBudget: getBenchmarkBudgetByAmount(amount), majorFactor: toFiniteNumberOrNull(row?.majorFactor) ?? defaultMajorFactor, consultCategoryFactor: toFiniteNumberOrNull(row?.consultCategoryFactor) ?? defaultConsultCategoryFactor, workStageFactor: toFiniteNumberOrNull(row?.workStageFactor) ?? 1, workRatio: toFiniteNumberOrNull(row?.workRatio) ?? 100 }) }) } const totalAmount = sumByNumber(sourceRows, row => typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null ) const onlyRow = sourceRows.find(row => String(row?.id || '') === ONLY_COST_SCALE_ROW_ID) || sourceRows.find(row => hasOwn(row, 'consultCategoryFactor') || hasOwn(row, 'majorFactor')) || sourceRows[0] const consultCategoryFactor = toFiniteNumberOrNull(onlyRow?.consultCategoryFactor) ?? defaultConsultCategoryFactor const majorFactor = toFiniteNumberOrNull(onlyRow?.majorFactor) ?? defaultMajorFactor const workStageFactor = toFiniteNumberOrNull(onlyRow?.workStageFactor) ?? 1 const workRatio = toFiniteNumberOrNull(onlyRow?.workRatio) ?? 100 return getScaleBudgetFee({ benchmarkBudget: getBenchmarkBudgetByAmount(totalAmount), majorFactor, consultCategoryFactor, workStageFactor, workRatio }) } const buildOnlyCostScaleDetailRows = ( serviceId: string, rowsFromDb: Array> | undefined, consultCategoryFactorMap?: Map, majorFactorMap?: Map, industryId?: string | null ) => { const totalAmount = sumByNumber(rowsFromDb || [], row => typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null ) const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId) const onlyCostRowId = industryMajorEntry?.id || ONLY_COST_SCALE_ROW_ID const onlyRow = (rowsFromDb || []).find(row => String(row?.id || '') === ONLY_COST_SCALE_ROW_ID) || (rowsFromDb || []).find(row => String(row?.id || '') === onlyCostRowId) const consultCategoryFactor = toFiniteNumberOrNull(onlyRow?.consultCategoryFactor) ?? consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) const majorFactor = toFiniteNumberOrNull(onlyRow?.majorFactor) ?? (industryMajorEntry ? majorFactorMap?.get(industryMajorEntry.id) ?? null : null) ?? toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ?? 1 const workStageFactor = toFiniteNumberOrNull(onlyRow?.workStageFactor) ?? 1 const workRatio = toFiniteNumberOrNull(onlyRow?.workRatio) ?? 100 return [ { id: onlyCostRowId, amount: totalAmount, consultCategoryFactor, majorFactor, workStageFactor, workRatio, benchmarkBudgetBasicChecked: typeof onlyRow?.benchmarkBudgetBasicChecked === 'boolean' ? onlyRow.benchmarkBudgetBasicChecked : true, benchmarkBudgetOptionalChecked: typeof onlyRow?.benchmarkBudgetOptionalChecked === 'boolean' ? onlyRow.benchmarkBudgetOptionalChecked : true } ] } const getLandBudgetFee = (row: ScaleRow) => { return getScaleBudgetFee({ benchmarkBudget: getBenchmarkBudgetByLandArea(row.landArea), majorFactor: row.majorFactor, consultCategoryFactor: row.consultCategoryFactor, workStageFactor: row.workStageFactor, workRatio: row.workRatio }) } const getTaskEntriesByServiceId = (serviceId: string | number) => Object.entries(taskList as Record) .sort((a, b) => Number(a[0]) - Number(b[0])) .filter(([, task]) => Number(task.serviceID) === Number(serviceId)) const buildDefaultWorkloadRows = ( serviceId: string | number, consultCategoryFactorMap?: Map ): WorkloadRow[] => { const defaultConsultCategoryFactor = consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) return getTaskEntriesByServiceId(serviceId).map(([taskId, task], order) => ({ id: `task-${taskId}-${order}`, conversion: toFiniteNumberOrNull(task.conversion), workload: null, basicFee: null, budgetAdoptedUnitPrice: toFiniteNumberOrNull(task.defPrice), consultCategoryFactor: defaultConsultCategoryFactor })) } const mergeWorkloadRows = ( serviceId: string | number, rowsFromDb: Array & Pick> | undefined, consultCategoryFactorMap?: Map ): WorkloadRow[] => { const dbValueMap = toRowMap(rowsFromDb) return buildDefaultWorkloadRows(serviceId, consultCategoryFactorMap).map(row => { const fromDb = dbValueMap.get(row.id) if (!fromDb) return row return { ...row, workload: toFiniteNumberOrNull(fromDb.workload), basicFee: toFiniteNumberOrNull(fromDb.basicFee), budgetAdoptedUnitPrice: toFiniteNumberOrNull(fromDb.budgetAdoptedUnitPrice), consultCategoryFactor: toFiniteNumberOrNull(fromDb.consultCategoryFactor) } }) } const calcWorkloadBasicFee = (row: WorkloadRow) => { if ( row.budgetAdoptedUnitPrice == null || row.conversion == null || row.workload == null ) { return null } return roundTo( toDecimal(row.budgetAdoptedUnitPrice).mul(row.conversion).mul(row.workload), 2 ) } const calcWorkloadServiceFee = (row: WorkloadRow) => { if (row.consultCategoryFactor == null) { return null } const basicFee = row.basicFee ?? calcWorkloadBasicFee(row) if (basicFee == null) return null return roundTo( toDecimal(basicFee).mul(row.consultCategoryFactor), 2 ) } const getExpertEntries = () => Object.entries(expertList as Record).sort((a, b) => Number(a[0]) - Number(b[0])) const getDefaultHourlyAdoptedPrice = (expert: ExpertLite) => { if (expert.defPrice == null || expert.manageCoe == null) return null return roundTo(toDecimal(expert.defPrice).mul(expert.manageCoe), 2) } const buildDefaultHourlyRows = (): HourlyRow[] => getExpertEntries().map(([expertId, expert]) => ({ id: `expert-${expertId}`, adoptedBudgetUnitPrice: getDefaultHourlyAdoptedPrice(expert), personnelCount: null, workdayCount: null })) const mergeHourlyRows = ( rowsFromDb: Array & Pick> | undefined ): HourlyRow[] => { const dbValueMap = toRowMap(rowsFromDb) return buildDefaultHourlyRows().map(row => { const fromDb = dbValueMap.get(row.id) if (!fromDb) return row return { ...row, adoptedBudgetUnitPrice: toFiniteNumberOrNull(fromDb.adoptedBudgetUnitPrice), personnelCount: toFiniteNumberOrNull(fromDb.personnelCount), workdayCount: toFiniteNumberOrNull(fromDb.workdayCount) } }) } const calcHourlyServiceBudget = (row: HourlyRow) => { if (row.adoptedBudgetUnitPrice == null || row.personnelCount == null || row.workdayCount == null) return null return roundTo(toDecimal(row.adoptedBudgetUnitPrice).mul(row.personnelCount).mul(row.workdayCount), 2) } const resolveScaleRows = ( serviceId: string, pricingData: StoredDetailRowsState | null, htData: StoredDetailRowsState | null, consultCategoryFactorMap?: Map, majorFactorMap?: Map ) => { if (pricingData?.detailRows != null) { return mergeScaleRows( serviceId, pricingData.detailRows as any, consultCategoryFactorMap, majorFactorMap ) } if (htData?.detailRows != null) { return mergeScaleRows( serviceId, htData.detailRows as any, consultCategoryFactorMap, majorFactorMap ) } return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap) } export const getPricingMethodDetailDbKeys = ( contractId: string, serviceId: string | number ): PricingMethodDetailDbKeys => { const normalizedServiceId = String(serviceId) return { investScale: `tzGMF-${contractId}-${normalizedServiceId}`, landScale: `ydGMF-${contractId}-${normalizedServiceId}`, workload: `gzlF-${contractId}-${normalizedServiceId}`, hourly: `hourlyPricing-${contractId}-${normalizedServiceId}` } } const loadPricingMethodDefaultBuildContext = async ( contractId: string, options?: PricingMethodTotalsOptions ): Promise => { const htDbKey = `ht-info-v3-${contractId}` const consultFactorDbKey = `ht-consult-category-factor-v1-${contractId}` const majorFactorDbKey = `ht-major-factor-v1-${contractId}` const baseInfoDbKey = 'xm-base-info-v1' const [htData, consultFactorData, majorFactorData, baseInfo] = await Promise.all([ localforage.getItem(htDbKey), localforage.getItem(consultFactorDbKey), localforage.getItem(majorFactorDbKey), localforage.getItem(baseInfoDbKey) ]) return { htData, consultCategoryFactorMap: buildConsultCategoryFactorMap(consultFactorData), majorFactorMap: buildMajorFactorMap(majorFactorData), industryId: typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : '', excludeInvestmentCostAndAreaRows: options?.excludeInvestmentCostAndAreaRows === true } } const buildDefaultPricingMethodDetailRows = ( serviceId: string, context: PricingMethodDefaultBuildContext ): PricingMethodDefaultDetailRows => { const onlyCostScale = isOnlyCostScaleService(serviceId) const scaleRows = resolveScaleRows( serviceId, null, context.htData, context.consultCategoryFactorMap, context.majorFactorMap ) const investScale = onlyCostScale ? buildOnlyCostScaleDetailRows( serviceId, context.htData?.detailRows as Array> | undefined, context.consultCategoryFactorMap, context.majorFactorMap, context.industryId ) : scaleRows.filter(row => { if (!isCostMajorById(row.id)) return false if (context.excludeInvestmentCostAndAreaRows && isDualScaleMajorById(row.id)) return false return true }) const landScale = scaleRows.filter(row => isAreaMajorById(row.id)) return { investScale, landScale, workload: buildDefaultWorkloadRows(serviceId, context.consultCategoryFactorMap), hourly: buildDefaultHourlyRows() } } export const persistDefaultPricingMethodDetailRowsForServices = async (params: { contractId: string serviceIds: Array options?: PricingMethodTotalsOptions }) => { const uniqueServiceIds = Array.from(new Set(params.serviceIds.map(serviceId => String(serviceId)))) if (uniqueServiceIds.length === 0) return const context = await loadPricingMethodDefaultBuildContext(params.contractId, params.options) await Promise.all( uniqueServiceIds.map(async serviceId => { const dbKeys = getPricingMethodDetailDbKeys(params.contractId, serviceId) const defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context) console.log(dbKeys,defaultRows) await Promise.all([ localforage.setItem(dbKeys.investScale, { detailRows: defaultRows.investScale }), localforage.setItem(dbKeys.landScale, { detailRows: defaultRows.landScale }), localforage.setItem(dbKeys.workload, { detailRows: defaultRows.workload }), localforage.setItem(dbKeys.hourly, { detailRows: defaultRows.hourly }) ]) }) ) } export const getPricingMethodTotalsForService = async (params: { contractId: string serviceId: string | number options?: PricingMethodTotalsOptions }): Promise => { const serviceId = String(params.serviceId) const htDbKey = `ht-info-v3-${params.contractId}` const consultFactorDbKey = `ht-consult-category-factor-v1-${params.contractId}` const majorFactorDbKey = `ht-major-factor-v1-${params.contractId}` const baseInfoDbKey = 'xm-base-info-v1' const investDbKey = `tzGMF-${params.contractId}-${serviceId}` const landDbKey = `ydGMF-${params.contractId}-${serviceId}` const workloadDbKey = `gzlF-${params.contractId}-${serviceId}` const hourlyDbKey = `hourlyPricing-${params.contractId}-${serviceId}` const [investData, landData, workloadData, hourlyData, htData, consultFactorData, majorFactorData, baseInfo] = await Promise.all([ localforage.getItem(investDbKey), localforage.getItem(landDbKey), localforage.getItem(workloadDbKey), localforage.getItem(hourlyDbKey), localforage.getItem(htDbKey), localforage.getItem(consultFactorDbKey), localforage.getItem(majorFactorDbKey), localforage.getItem(baseInfoDbKey) ]) const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData) const majorFactorMap = buildMajorFactorMap(majorFactorData) const onlyCostScale = isOnlyCostScaleService(serviceId) const industryId = typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : '' // 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。 const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true const investScale = onlyCostScale ? getOnlyCostScaleBudgetFee( serviceId, (investData?.detailRows as Array> | undefined) || (htData?.detailRows as Array> | undefined), consultCategoryFactorMap, majorFactorMap, industryId ) : (() => { const investRows = resolveScaleRows( serviceId, investData, htData, consultCategoryFactorMap, majorFactorMap ) return sumByNumber(investRows, row => { if (!isCostMajorById(row.id)) return null if (excludeInvestmentCostAndAreaRows && isDualScaleMajorById(row.id)) return null return getInvestmentBudgetFee(row) }) })() const landRows = resolveScaleRows( serviceId, landData, htData, consultCategoryFactorMap, majorFactorMap ) const landScale = sumByNumber(landRows, row => (isAreaMajorById(row.id) ? getLandBudgetFee(row) : null)) const defaultWorkloadRows = buildDefaultWorkloadRows(serviceId, consultCategoryFactorMap) const workload = defaultWorkloadRows.length === 0 ? null : sumByNumber( workloadData?.detailRows != null ? mergeWorkloadRows(serviceId, workloadData.detailRows as any, consultCategoryFactorMap) : defaultWorkloadRows, row => calcWorkloadServiceFee(row) ) const hourlyRows = hourlyData?.detailRows != null ? mergeHourlyRows(hourlyData.detailRows as any) : buildDefaultHourlyRows() const hourly = sumByNumber(hourlyRows, row => calcHourlyServiceBudget(row)) return { investScale, landScale, workload, hourly } } export const ensurePricingMethodDetailRowsForServices = async (params: { contractId: string serviceIds: Array options?: PricingMethodTotalsOptions }) => { const uniqueServiceIds = Array.from(new Set(params.serviceIds.map(serviceId => String(serviceId)))) if (uniqueServiceIds.length === 0) return const context = await loadPricingMethodDefaultBuildContext(params.contractId, params.options) await Promise.all( uniqueServiceIds.map(async serviceId => { const dbKeys = getPricingMethodDetailDbKeys(params.contractId, serviceId) const [investData, landData, workloadData, hourlyData] = await Promise.all([ localforage.getItem(dbKeys.investScale), localforage.getItem(dbKeys.landScale), localforage.getItem(dbKeys.workload), localforage.getItem(dbKeys.hourly) ]) const shouldInitInvest = !Array.isArray(investData?.detailRows) || investData!.detailRows!.length === 0 const shouldInitLand = !Array.isArray(landData?.detailRows) || landData!.detailRows!.length === 0 const shouldInitWorkload = !Array.isArray(workloadData?.detailRows) || workloadData!.detailRows!.length === 0 const shouldInitHourly = !Array.isArray(hourlyData?.detailRows) || hourlyData!.detailRows!.length === 0 const writeTasks: Promise[] = [] let defaultRows: PricingMethodDefaultDetailRows | null = null const getDefaultRows = () => { if (!defaultRows) { defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context) } return defaultRows } if (shouldInitInvest) { writeTasks.push(localforage.setItem(dbKeys.investScale, { detailRows: getDefaultRows().investScale })) } if (shouldInitLand) { writeTasks.push(localforage.setItem(dbKeys.landScale, { detailRows: getDefaultRows().landScale })) } if (shouldInitWorkload) { writeTasks.push(localforage.setItem(dbKeys.workload, { detailRows: getDefaultRows().workload })) } if (shouldInitHourly) { writeTasks.push(localforage.setItem(dbKeys.hourly, { detailRows: getDefaultRows().hourly })) } if (writeTasks.length > 0) { await Promise.all(writeTasks) } }) ) } export const getPricingMethodTotalsForServices = async (params: { contractId: string serviceIds: Array options?: PricingMethodTotalsOptions }) => { const result = new Map() await Promise.all( params.serviceIds.map(async serviceId => { const totals = await getPricingMethodTotalsForService({ contractId: params.contractId, serviceId, options: params.options }) result.set(String(serviceId), totals) }) ) return result }