修复数据导出不正确
This commit is contained in:
parent
99684c04f2
commit
aed6fe2bfa
@ -863,6 +863,10 @@ const getSelectedServiceIdsWithoutFixed = () =>
|
|||||||
const ensurePricingDetailRowsForCurrentSelection = async () => {
|
const ensurePricingDetailRowsForCurrentSelection = async () => {
|
||||||
const serviceIds = getSelectedServiceIdsWithoutFixed()
|
const serviceIds = getSelectedServiceIdsWithoutFixed()
|
||||||
if (serviceIds.length === 0) return
|
if (serviceIds.length === 0) return
|
||||||
|
console.log('[zxfw][ensure-current-selection] ' + JSON.stringify({
|
||||||
|
contractId: props.contractId,
|
||||||
|
serviceIds
|
||||||
|
}))
|
||||||
await ensurePricingMethodDetailRowsForServices({
|
await ensurePricingMethodDetailRowsForServices({
|
||||||
contractId: props.contractId,
|
contractId: props.contractId,
|
||||||
serviceIds,
|
serviceIds,
|
||||||
@ -875,7 +879,6 @@ const ensurePricingDetailRowsForCurrentSelection = async () => {
|
|||||||
* 计价法变更场景统一走这里,最终会触发 applyFixedRowTotals。
|
* 计价法变更场景统一走这里,最终会触发 applyFixedRowTotals。
|
||||||
*/
|
*/
|
||||||
const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => {
|
const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => {
|
||||||
|
|
||||||
const currentState = getCurrentContractState()
|
const currentState = getCurrentContractState()
|
||||||
const targetIds = Array.from(
|
const targetIds = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
@ -893,6 +896,21 @@ const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[zxfw][fill-pricing-totals][start] ' + JSON.stringify({
|
||||||
|
contractId: props.contractId,
|
||||||
|
requestedIds: serviceIds,
|
||||||
|
targetIds,
|
||||||
|
currentRows: currentState.detailRows.map(row => ({
|
||||||
|
id: row.id,
|
||||||
|
investScale: row.investScale,
|
||||||
|
landScale: row.landScale,
|
||||||
|
workload: row.workload,
|
||||||
|
hourly: row.hourly,
|
||||||
|
subtotal: row.subtotal,
|
||||||
|
finalFee: row.finalFee
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
await ensurePricingMethodDetailRowsForServices({
|
await ensurePricingMethodDetailRowsForServices({
|
||||||
contractId: props.contractId,
|
contractId: props.contractId,
|
||||||
serviceIds: targetIds,
|
serviceIds: targetIds,
|
||||||
@ -905,6 +923,15 @@ const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => {
|
|||||||
options: PRICING_TOTALS_OPTIONS
|
options: PRICING_TOTALS_OPTIONS
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log('[zxfw][fill-pricing-totals][totals] ' + JSON.stringify({
|
||||||
|
contractId: props.contractId,
|
||||||
|
targetIds,
|
||||||
|
totals: targetIds.map(id => ({
|
||||||
|
serviceId: id,
|
||||||
|
totals: totalsByServiceId.get(String(id)) || null
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
const targetSet = new Set(targetIds.map(id => String(id)))
|
const targetSet = new Set(targetIds.map(id => String(id)))
|
||||||
const nextRows = currentState.detailRows.map(row => {
|
const nextRows = currentState.detailRows.map(row => {
|
||||||
if (isFixedRow(row) || !targetSet.has(String(row.id))) return row
|
if (isFixedRow(row) || !targetSet.has(String(row.id))) return row
|
||||||
@ -1014,15 +1041,23 @@ const applySelection = async (codes: string[]) => {
|
|||||||
* 服务勾选变化入口:先更新行,再刷新新增服务的计价汇总。
|
* 服务勾选变化入口:先更新行,再刷新新增服务的计价汇总。
|
||||||
*/
|
*/
|
||||||
const handleServiceSelectionChange = async (ids: string[]) => {
|
const handleServiceSelectionChange = async (ids: string[]) => {
|
||||||
|
|
||||||
const prevIds = [...selectedIds.value]
|
const prevIds = [...selectedIds.value]
|
||||||
|
console.log('[zxfw][selection-change][start] ' + JSON.stringify({
|
||||||
|
contractId: props.contractId,
|
||||||
|
prevIds,
|
||||||
|
nextIds: ids
|
||||||
|
}))
|
||||||
await applySelection(ids)
|
await applySelection(ids)
|
||||||
const nextSelectedIds = getCurrentContractState().selectedIds || []
|
const nextSelectedIds = getCurrentContractState().selectedIds || []
|
||||||
const nextSelectedSet = new Set(nextSelectedIds)
|
const nextSelectedSet = new Set(nextSelectedIds)
|
||||||
const addedIds = nextSelectedIds.filter(id => !prevIds.includes(id) && nextSelectedSet.has(id))
|
const addedIds = nextSelectedIds.filter(id => !prevIds.includes(id) && nextSelectedSet.has(id))
|
||||||
|
console.log('[zxfw][selection-change][after-apply] ' + JSON.stringify({
|
||||||
|
contractId: props.contractId,
|
||||||
|
nextSelectedIds,
|
||||||
|
addedIds
|
||||||
|
}))
|
||||||
await ensureWorkContentStateForServices(addedIds)
|
await ensureWorkContentStateForServices(addedIds)
|
||||||
await fillPricingTotalsForServiceIds(addedIds)
|
await fillPricingTotalsForServiceIds(addedIds)
|
||||||
await ensurePricingDetailRowsForCurrentSelection()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -138,6 +138,16 @@ const hasMeaningfulFactorValue = (rows: SourceRow[] | undefined) =>
|
|||||||
return hasBudgetValue || hasRemark
|
return hasBudgetValue || hasRemark
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasUsablePersistedRows = (state: GridState | null | undefined) =>
|
||||||
|
Array.isArray(state?.detailRows) &&
|
||||||
|
state.detailRows.some(row => {
|
||||||
|
const hasFactor =
|
||||||
|
typeof row?.budgetValue === 'number' ||
|
||||||
|
typeof row?.standardFactor === 'number'
|
||||||
|
const hasRemark = typeof row?.remark === 'string' && row.remark.trim() !== ''
|
||||||
|
return hasFactor || hasRemark || String(row?.id || '').trim() !== ''
|
||||||
|
})
|
||||||
|
|
||||||
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): FactorRow[] => {
|
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): FactorRow[] => {
|
||||||
const dbValueMap = new Map<string, SourceRow>()
|
const dbValueMap = new Map<string, SourceRow>()
|
||||||
for (const row of rowsFromDb || []) {
|
for (const row of rowsFromDb || []) {
|
||||||
@ -308,7 +318,7 @@ const saveFactorChangeState = async (changedRowIds: string[]) => {
|
|||||||
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
||||||
if (!storageKey) return null
|
if (!storageKey) return null
|
||||||
const piniaData = await zxFwPricingStore.loadKeyState<GridState>(storageKey)
|
const piniaData = await zxFwPricingStore.loadKeyState<GridState>(storageKey)
|
||||||
if (piniaData?.detailRows && Array.isArray(piniaData.detailRows)) return piniaData
|
if (hasUsablePersistedRows(piniaData)) return piniaData
|
||||||
|
|
||||||
// 兼容历史 kvStore 数据:命中后迁移到 pinia keyed state。
|
// 兼容历史 kvStore 数据:命中后迁移到 pinia keyed state。
|
||||||
const legacyData = await kvStore.getItem<GridState>(storageKey)
|
const legacyData = await kvStore.getItem<GridState>(storageKey)
|
||||||
|
|||||||
@ -987,15 +987,31 @@ const getPiniaPersistStores = () =>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasExportableFactorRows = (rows: FactorRowLike[] | undefined) =>
|
||||||
|
Array.isArray(rows) &&
|
||||||
|
rows.some(row => {
|
||||||
|
const rowId = toSafeInteger(row?.id)
|
||||||
|
const coe = toFiniteNumber(row?.budgetValue) ?? toFiniteNumber(row?.standardFactor)
|
||||||
|
const remark = typeof row?.remark === 'string' ? row.remark.trim() : ''
|
||||||
|
return rowId != null || coe != null || remark !== ''
|
||||||
|
})
|
||||||
|
|
||||||
const loadFactorRowsState = async (storageKey: string) => {
|
const loadFactorRowsState = async (storageKey: string) => {
|
||||||
const [piniaData, kvData] = await Promise.all([
|
const [piniaData, kvData] = await Promise.all([
|
||||||
zxFwPricingStore.loadKeyState<DetailRowsStorageLike<FactorRowLike>>(storageKey),
|
zxFwPricingStore.loadKeyState<DetailRowsStorageLike<FactorRowLike>>(storageKey),
|
||||||
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(storageKey)
|
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(storageKey)
|
||||||
])
|
])
|
||||||
|
const piniaRows = Array.isArray(piniaData?.detailRows) ? piniaData.detailRows : undefined
|
||||||
|
const kvRows = Array.isArray(kvData?.detailRows) ? kvData.detailRows : undefined
|
||||||
|
const resolved = hasExportableFactorRows(piniaRows)
|
||||||
|
? piniaData
|
||||||
|
: hasExportableFactorRows(kvRows)
|
||||||
|
? kvData
|
||||||
|
: (piniaData ?? kvData ?? null)
|
||||||
return {
|
return {
|
||||||
piniaData,
|
piniaData,
|
||||||
kvData,
|
kvData,
|
||||||
resolved: piniaData || kvData || null
|
resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1197,6 +1213,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
|
|
||||||
const projectServiceCoes = buildProjectServiceCoes(resolveExportFactorRows(consultCategoryFactorState))
|
const projectServiceCoes = buildProjectServiceCoes(resolveExportFactorRows(consultCategoryFactorState))
|
||||||
const projectMajorCoes = buildProjectMajorCoes(resolveExportFactorRows(majorFactorState))
|
const projectMajorCoes = buildProjectMajorCoes(resolveExportFactorRows(majorFactorState))
|
||||||
|
|
||||||
const projectName = isNonEmptyString(projectInfo.projectName)
|
const projectName = isNonEmptyString(projectInfo.projectName)
|
||||||
? projectInfo.projectName.trim()
|
? projectInfo.projectName.trim()
|
||||||
: t('tab.messages.defaultProjectName')
|
: t('tab.messages.defaultProjectName')
|
||||||
@ -1352,10 +1369,22 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[export][service-methods]', {
|
console.log('[export][service-methods] ' + JSON.stringify({
|
||||||
contractId,
|
contractId,
|
||||||
serviceId: serviceIdText,
|
serviceId: serviceIdText,
|
||||||
methodAvailability,
|
methodAvailability,
|
||||||
|
rawLengths: {
|
||||||
|
method1: Array.isArray(method1Raw?.detailRows) ? method1Raw.detailRows.length : -1,
|
||||||
|
method2: Array.isArray(method2Raw?.detailRows) ? method2Raw.detailRows.length : -1,
|
||||||
|
method3: Array.isArray(method3Raw?.detailRows) ? method3Raw.detailRows.length : -1,
|
||||||
|
method4: Array.isArray(method4Raw?.detailRows) ? method4Raw.detailRows.length : -1
|
||||||
|
},
|
||||||
|
rawSamples: {
|
||||||
|
method1: Array.isArray(method1Raw?.detailRows) ? method1Raw.detailRows[0] : null,
|
||||||
|
method2: Array.isArray(method2Raw?.detailRows) ? method2Raw.detailRows[0] : null,
|
||||||
|
method3: Array.isArray(method3Raw?.detailRows) ? method3Raw.detailRows[0] : null,
|
||||||
|
method4: Array.isArray(method4Raw?.detailRows) ? method4Raw.detailRows[0] : null
|
||||||
|
},
|
||||||
exported: {
|
exported: {
|
||||||
method1: Boolean(method1),
|
method1: Boolean(method1),
|
||||||
method2: Boolean(method2),
|
method2: Boolean(method2),
|
||||||
@ -1364,7 +1393,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
},
|
},
|
||||||
fee,
|
fee,
|
||||||
finalFee
|
finalFee
|
||||||
})
|
}))
|
||||||
|
|
||||||
const service: ExportService = {
|
const service: ExportService = {
|
||||||
id: serviceId,
|
id: serviceId,
|
||||||
@ -2343,3 +2372,4 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped src="@/features/tab/tab.css"></style>
|
<style scoped src="@/features/tab/tab.css"></style>
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,8 @@ const getOnlyCostScaleSummaryAmount = (
|
|||||||
|
|
||||||
interface ScaleRow {
|
interface ScaleRow {
|
||||||
id: string
|
id: string
|
||||||
|
hasCost?: boolean
|
||||||
|
hasArea?: boolean
|
||||||
amount: number | null
|
amount: number | null
|
||||||
landArea: number | null
|
landArea: number | null
|
||||||
benchmarkBudgetBasicChecked: boolean
|
benchmarkBudgetBasicChecked: boolean
|
||||||
@ -327,6 +329,8 @@ const buildDefaultScaleRows = (
|
|||||||
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
||||||
return getMajorLeafIds().map(id => ({
|
return getMajorLeafIds().map(id => ({
|
||||||
id,
|
id,
|
||||||
|
hasCost: isCostMajorById(id),
|
||||||
|
hasArea: isAreaMajorById(id),
|
||||||
amount: null,
|
amount: null,
|
||||||
landArea: null,
|
landArea: null,
|
||||||
benchmarkBudgetBasicChecked: true,
|
benchmarkBudgetBasicChecked: true,
|
||||||
@ -367,6 +371,14 @@ const mergeScaleRows = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
|
hasCost:
|
||||||
|
typeof (fromDb as { hasCost?: unknown }).hasCost === 'boolean'
|
||||||
|
? Boolean((fromDb as { hasCost?: unknown }).hasCost)
|
||||||
|
: row.hasCost,
|
||||||
|
hasArea:
|
||||||
|
typeof (fromDb as { hasArea?: unknown }).hasArea === 'boolean'
|
||||||
|
? Boolean((fromDb as { hasArea?: unknown }).hasArea)
|
||||||
|
: row.hasArea,
|
||||||
amount: toFiniteNumberOrNull(fromDb.amount),
|
amount: toFiniteNumberOrNull(fromDb.amount),
|
||||||
landArea: toFiniteNumberOrNull(fromDb.landArea),
|
landArea: toFiniteNumberOrNull(fromDb.landArea),
|
||||||
benchmarkBudgetBasicChecked:
|
benchmarkBudgetBasicChecked:
|
||||||
@ -490,6 +502,8 @@ const buildInvestScaleSingleTotalDetailRows = (
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: onlyCostRowId,
|
id: onlyCostRowId,
|
||||||
|
hasCost: true,
|
||||||
|
hasArea: false,
|
||||||
amount: resolvedTotalAmount,
|
amount: resolvedTotalAmount,
|
||||||
landArea: null,
|
landArea: null,
|
||||||
consultCategoryFactor,
|
consultCategoryFactor,
|
||||||
@ -657,6 +671,8 @@ const normalizeScopedScaleRows = (
|
|||||||
const hasWorkRatio = hasOwn(row, 'workRatio')
|
const hasWorkRatio = hasOwn(row, 'workRatio')
|
||||||
return {
|
return {
|
||||||
id: resolvedMajorId,
|
id: resolvedMajorId,
|
||||||
|
hasCost: isCostMajorById(resolvedMajorId),
|
||||||
|
hasArea: isAreaMajorById(resolvedMajorId),
|
||||||
amount: toFiniteNumberOrNull(row.amount),
|
amount: toFiniteNumberOrNull(row.amount),
|
||||||
landArea: toFiniteNumberOrNull(row.landArea),
|
landArea: toFiniteNumberOrNull(row.landArea),
|
||||||
benchmarkBudgetBasicChecked:
|
benchmarkBudgetBasicChecked:
|
||||||
@ -944,16 +960,49 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
const workloadData = toStoredDetailRowsState(storeWorkloadData) || workloadDataFallback
|
const workloadData = toStoredDetailRowsState(storeWorkloadData) || workloadDataFallback
|
||||||
const hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback
|
const hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback
|
||||||
|
|
||||||
const shouldInitInvest = !Array.isArray(investData?.detailRows) || investData!.detailRows!.length === 0
|
const shouldInitInvest = !Array.isArray(investData?.detailRows)
|
||||||
const shouldInitLand = !Array.isArray(landData?.detailRows) || landData!.detailRows!.length === 0
|
const shouldInitLand = !Array.isArray(landData?.detailRows)
|
||||||
const shouldInitWorkload = !Array.isArray(workloadData?.detailRows) || workloadData!.detailRows!.length === 0
|
const shouldInitWorkload = !Array.isArray(workloadData?.detailRows)
|
||||||
const shouldInitHourly = !Array.isArray(hourlyData?.detailRows) || hourlyData!.detailRows!.length === 0
|
const shouldInitHourly = !Array.isArray(hourlyData?.detailRows)
|
||||||
|
|
||||||
|
console.log('[pricing][ensure-detail-rows][before] ' + JSON.stringify({
|
||||||
|
contractId: params.contractId,
|
||||||
|
serviceId,
|
||||||
|
shouldInit: {
|
||||||
|
invest: shouldInitInvest,
|
||||||
|
land: shouldInitLand,
|
||||||
|
workload: shouldInitWorkload,
|
||||||
|
hourly: shouldInitHourly
|
||||||
|
},
|
||||||
|
existingLengths: {
|
||||||
|
invest: Array.isArray(investData?.detailRows) ? investData.detailRows.length : -1,
|
||||||
|
land: Array.isArray(landData?.detailRows) ? landData.detailRows.length : -1,
|
||||||
|
workload: Array.isArray(workloadData?.detailRows) ? workloadData.detailRows.length : -1,
|
||||||
|
hourly: Array.isArray(hourlyData?.detailRows) ? hourlyData.detailRows.length : -1
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
const writeTasks: Promise<unknown>[] = []
|
const writeTasks: Promise<unknown>[] = []
|
||||||
let defaultRows: PricingMethodDefaultDetailRows | null = null
|
let defaultRows: PricingMethodDefaultDetailRows | null = null
|
||||||
const getDefaultRows = () => {
|
const getDefaultRows = () => {
|
||||||
if (!defaultRows) {
|
if (!defaultRows) {
|
||||||
defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context)
|
defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context)
|
||||||
|
console.log('[pricing][ensure-detail-rows][defaults] ' + JSON.stringify({
|
||||||
|
contractId: params.contractId,
|
||||||
|
serviceId,
|
||||||
|
lengths: {
|
||||||
|
invest: Array.isArray(defaultRows.investScale) ? defaultRows.investScale.length : -1,
|
||||||
|
land: Array.isArray(defaultRows.landScale) ? defaultRows.landScale.length : -1,
|
||||||
|
workload: Array.isArray(defaultRows.workload) ? defaultRows.workload.length : -1,
|
||||||
|
hourly: Array.isArray(defaultRows.hourly) ? defaultRows.hourly.length : -1
|
||||||
|
},
|
||||||
|
sample: {
|
||||||
|
invest: Array.isArray(defaultRows.investScale) ? defaultRows.investScale[0] : null,
|
||||||
|
land: Array.isArray(defaultRows.landScale) ? defaultRows.landScale[0] : null,
|
||||||
|
workload: Array.isArray(defaultRows.workload) ? defaultRows.workload[0] : null,
|
||||||
|
hourly: Array.isArray(defaultRows.hourly) ? defaultRows.hourly[0] : null
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
return defaultRows
|
return defaultRows
|
||||||
}
|
}
|
||||||
@ -997,6 +1046,13 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
if (writeTasks.length > 0) {
|
if (writeTasks.length > 0) {
|
||||||
await Promise.all(writeTasks)
|
await Promise.all(writeTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[pricing][ensure-detail-rows][after] ' + JSON.stringify({
|
||||||
|
contractId: params.contractId,
|
||||||
|
serviceId,
|
||||||
|
wroteAny: writeTasks.length > 0,
|
||||||
|
writeCount: writeTasks.length
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -294,10 +294,10 @@ export const initializeProjectFactorStates = async (
|
|||||||
detailRows: buildFactorRowsFromEntries(majorEntries)
|
detailRows: buildFactorRowsFromEntries(majorEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
// 新项目初始化走 createProjectKvAdapter 时,setItem 是整包读改写,不是原子更新。
|
||||||
kvStore.setItem(consultCategoryFactorKey, consultPayload),
|
// 这里并发写两个 key 会互相覆盖,导致咨询系数或专业系数其中一个丢失。
|
||||||
kvStore.setItem(majorFactorKey, majorPayload)
|
await kvStore.setItem(consultCategoryFactorKey, consultPayload)
|
||||||
])
|
await kvStore.setItem(majorFactorKey, majorPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initializeProjectScaleState = async (
|
export const initializeProjectScaleState = async (
|
||||||
@ -307,3 +307,4 @@ export const initializeProjectScaleState = async (
|
|||||||
) => {
|
) => {
|
||||||
await kvStore.setItem(projectScaleKey, buildDefaultProjectScaleState(industry))
|
await kvStore.setItem(projectScaleKey, buildDefaultProjectScaleState(industry))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { serviceList } from '@/sql'
|
import { getMajorDictById, getMajorIdAliasMap, serviceList } from '@/sql'
|
||||||
import { roundTo, toFiniteNumber, toFiniteNumberOrZero } from '@/lib/decimal'
|
import { roundTo, toFiniteNumber, toFiniteNumberOrZero } from '@/lib/decimal'
|
||||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
export { toFiniteNumber, toFiniteNumberOrZero }
|
export { toFiniteNumber, toFiniteNumberOrZero }
|
||||||
@ -52,6 +52,7 @@ interface ScaleRowLike {
|
|||||||
|
|
||||||
interface WorkloadMethodRowLike {
|
interface WorkloadMethodRowLike {
|
||||||
id: string
|
id: string
|
||||||
|
conversion?: unknown
|
||||||
budgetAdoptedUnitPrice?: unknown
|
budgetAdoptedUnitPrice?: unknown
|
||||||
workload?: unknown
|
workload?: unknown
|
||||||
basicFee?: unknown
|
basicFee?: unknown
|
||||||
@ -246,6 +247,18 @@ export const toScaleMajorId = (row: ScaleMethodRowLike): number | null => {
|
|||||||
return toSafeInteger(parsed.majorPart)
|
return toSafeInteger(parsed.majorPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const majorDictById = getMajorDictById() as Record<string, { hasCost?: unknown; hasArea?: unknown } | undefined>
|
||||||
|
const majorIdAliasMap = getMajorIdAliasMap()
|
||||||
|
|
||||||
|
const resolveMajorCapability = (majorId: number | null) => {
|
||||||
|
if (majorId == null) return null
|
||||||
|
const key = String(majorId)
|
||||||
|
const resolvedKey = Object.prototype.hasOwnProperty.call(majorDictById, key)
|
||||||
|
? key
|
||||||
|
: (majorIdAliasMap.get(key) || key)
|
||||||
|
return majorDictById[resolvedKey] || null
|
||||||
|
}
|
||||||
|
|
||||||
export const toScaleProNum = (row: ScaleMethodRowLike): number => {
|
export const toScaleProNum = (row: ScaleMethodRowLike): number => {
|
||||||
const parsed = parseScaleScopedRowId(row.id)
|
const parsed = parseScaleScopedRowId(row.id)
|
||||||
return parsed.proNum > 0 ? parsed.proNum : 1
|
return parsed.proNum > 0 ? parsed.proNum : 1
|
||||||
@ -276,7 +289,16 @@ const isExportableScaleMethodRow = (
|
|||||||
mode: 'cost' | 'area'
|
mode: 'cost' | 'area'
|
||||||
) => {
|
) => {
|
||||||
if (!isScaleLeafRow(row)) return false
|
if (!isScaleLeafRow(row)) return false
|
||||||
return mode === 'cost' ? row?.hasCost === true : row?.hasArea === true
|
if (mode === 'cost') {
|
||||||
|
if (row?.hasCost === true) return true
|
||||||
|
if (row?.hasCost === false) return false
|
||||||
|
} else {
|
||||||
|
if (row?.hasArea === true) return true
|
||||||
|
if (row?.hasArea === false) return false
|
||||||
|
}
|
||||||
|
const major = row ? resolveMajorCapability(toScaleMajorId(row)) : null
|
||||||
|
if (!major) return false
|
||||||
|
return mode === 'cost' ? major.hasCost !== false : major.hasArea !== false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
export const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
||||||
@ -527,16 +549,34 @@ export const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveWorkloadBasicFee = (row: WorkloadMethodRowLike) => {
|
||||||
|
const basicFee = toFiniteNumber(row.basicFee)
|
||||||
|
if (basicFee != null) return basicFee
|
||||||
|
const price = toFiniteNumber(row.budgetAdoptedUnitPrice)
|
||||||
|
const conversion = toFiniteNumber(row.conversion)
|
||||||
|
const amount = toFiniteNumber(row.workload)
|
||||||
|
if (price == null || conversion == null || amount == null) return null
|
||||||
|
return roundTo(price * conversion * amount, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveWorkloadServiceFee = (row: WorkloadMethodRowLike, basicFee: number | null) => {
|
||||||
|
const fee = toFiniteNumber(row.serviceFee)
|
||||||
|
if (fee != null) return fee
|
||||||
|
const factor = toFiniteNumber(row.consultCategoryFactor)
|
||||||
|
if (basicFee == null || factor == null) return null
|
||||||
|
return roundTo(basicFee * factor, 2)
|
||||||
|
}
|
||||||
|
|
||||||
export const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined) => {
|
export const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined) => {
|
||||||
if (!Array.isArray(rows)) return null
|
if (!Array.isArray(rows)) return null
|
||||||
let hasTotalValue = false
|
let hasTotalValue = false
|
||||||
const det = rows
|
const det = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const task = getTaskIdFromRowId(row.id)
|
const task = getTaskIdFromRowId(row.id)
|
||||||
if (task == null || row.basicFee == null) return null
|
if (task == null) return null
|
||||||
const amount = toFiniteNumber(row.workload)
|
const amount = toFiniteNumber(row.workload)
|
||||||
const basicFee = toFiniteNumber(row.basicFee)
|
const basicFee = resolveWorkloadBasicFee(row)
|
||||||
const fee = toFiniteNumber(row.serviceFee)
|
const fee = resolveWorkloadServiceFee(row, basicFee)
|
||||||
if (fee != null) hasTotalValue = true
|
if (fee != null) hasTotalValue = true
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
const hasValue = amount != null || basicFee != null || fee != null || isNonEmptyString(remark)
|
const hasValue = amount != null || basicFee != null || fee != null || isNonEmptyString(remark)
|
||||||
@ -561,16 +601,26 @@ export const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveHourlyServiceFee = (row: HourlyMethodRowLike) => {
|
||||||
|
const fee = toFiniteNumber(row.serviceBudget)
|
||||||
|
if (fee != null) return fee
|
||||||
|
const price = toFiniteNumber(row.adoptedBudgetUnitPrice)
|
||||||
|
const personNum = toFiniteNumber(row.personnelCount)
|
||||||
|
const workDay = toFiniteNumber(row.workdayCount)
|
||||||
|
if (price == null || personNum == null || workDay == null) return null
|
||||||
|
return roundTo(price * personNum * workDay, 2)
|
||||||
|
}
|
||||||
|
|
||||||
export const buildMethod4 = (rows: HourlyMethodRowLike[] | undefined) => {
|
export const buildMethod4 = (rows: HourlyMethodRowLike[] | undefined) => {
|
||||||
if (!Array.isArray(rows)) return null
|
if (!Array.isArray(rows)) return null
|
||||||
let hasTotalValue = false
|
let hasTotalValue = false
|
||||||
const det = rows
|
const det = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const expert = getExpertIdFromRowId(row.id)
|
const expert = getExpertIdFromRowId(row.id)
|
||||||
if (expert == null || row.serviceBudget == null) return null
|
if (expert == null) return null
|
||||||
const personNum = toFiniteNumber(row.personnelCount)
|
const personNum = toFiniteNumber(row.personnelCount)
|
||||||
const workDay = toFiniteNumber(row.workdayCount)
|
const workDay = toFiniteNumber(row.workdayCount)
|
||||||
const fee = toFiniteNumber(row.serviceBudget)
|
const fee = resolveHourlyServiceFee(row)
|
||||||
if (fee != null) hasTotalValue = true
|
if (fee != null) hasTotalValue = true
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
const hasValue = personNum != null || workDay != null || fee != null || isNonEmptyString(remark)
|
const hasValue = personNum != null || workDay != null || fee != null || isNonEmptyString(remark)
|
||||||
|
|||||||
@ -22,6 +22,15 @@ type FactorDictItem = {
|
|||||||
|
|
||||||
type FactorDict = Record<string, FactorDictItem>
|
type FactorDict = Record<string, FactorDictItem>
|
||||||
|
|
||||||
|
const hasUsableFactorRows = (state: XmFactorState | null | undefined) =>
|
||||||
|
Array.isArray(state?.detailRows) &&
|
||||||
|
state.detailRows.some(row => {
|
||||||
|
const hasFactor =
|
||||||
|
toFiniteNumberOrNull(row?.budgetValue) != null ||
|
||||||
|
toFiniteNumberOrNull(row?.standardFactor) != null
|
||||||
|
return hasFactor || String(row?.id || '').trim() !== ''
|
||||||
|
})
|
||||||
|
|
||||||
const buildStandardFactorMap = (dict: FactorDict): Map<string, number | null> => {
|
const buildStandardFactorMap = (dict: FactorDict): Map<string, number | null> => {
|
||||||
const map = new Map<string, number | null>()
|
const map = new Map<string, number | null>()
|
||||||
for (const [id, item] of Object.entries(dict)) {
|
for (const [id, item] of Object.entries(dict)) {
|
||||||
@ -68,7 +77,12 @@ const loadFactorMap = async (
|
|||||||
const zxFwPricingStore = getZxFwPricingStoreSafely()
|
const zxFwPricingStore = getZxFwPricingStoreSafely()
|
||||||
const kvStore = getKvStoreSafely()
|
const kvStore = getKvStoreSafely()
|
||||||
const piniaData = zxFwPricingStore ? await zxFwPricingStore.loadKeyState<XmFactorState>(storageKey) : null
|
const piniaData = zxFwPricingStore ? await zxFwPricingStore.loadKeyState<XmFactorState>(storageKey) : null
|
||||||
const data = piniaData ?? (kvStore ? await kvStore.getItem<XmFactorState>(storageKey) : null)
|
const kvData = kvStore ? await kvStore.getItem<XmFactorState>(storageKey) : null
|
||||||
|
const data = hasUsableFactorRows(piniaData)
|
||||||
|
? piniaData
|
||||||
|
: hasUsableFactorRows(kvData)
|
||||||
|
? kvData
|
||||||
|
: (piniaData ?? kvData)
|
||||||
const map = buildStandardFactorMap(dict)
|
const map = buildStandardFactorMap(dict)
|
||||||
for (const row of data?.detailRows || []) {
|
for (const row of data?.detailRows || []) {
|
||||||
if (!row?.id) continue
|
if (!row?.id) continue
|
||||||
|
|||||||
@ -1864,6 +1864,8 @@ export function getBasicFeeFromScale(
|
|||||||
* @returns 导出流程完成后的 Promise
|
* @returns 导出流程完成后的 Promise
|
||||||
*/
|
*/
|
||||||
export async function exportFile(fileName: string, data: any | (() => Promise<any>), onSaveConfirmed?: () => void): Promise<string | null> {
|
export async function exportFile(fileName: string, data: any | (() => Promise<any>), onSaveConfirmed?: () => void): Promise<string | null> {
|
||||||
|
|
||||||
|
|
||||||
if (window.showSaveFilePicker) {
|
if (window.showSaveFilePicker) {
|
||||||
const handle = await window.showSaveFilePicker({
|
const handle = await window.showSaveFilePicker({
|
||||||
suggestedName: fileName,
|
suggestedName: fileName,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user