From 303f54bb71d2b429f9ae674e2416fac0b8aaa975 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Sat, 7 Mar 2026 16:09:06 +0800 Subject: [PATCH] if --- .../InvestmentScalePricingPane.vue | 154 +++++++++++++----- .../pricingView/LandScalePricingPane.vue | 33 ++-- src/lib/pricingMethodTotals.ts | 39 ++++- 3 files changed, 170 insertions(+), 56 deletions(-) diff --git a/src/components/views/pricingView/InvestmentScalePricingPane.vue b/src/components/views/pricingView/InvestmentScalePricingPane.vue index ba826f8..0202479 100644 --- a/src/components/views/pricingView/InvestmentScalePricingPane.vue +++ b/src/components/views/pricingView/InvestmentScalePricingPane.vue @@ -3,7 +3,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'v import { AgGridVue } from 'ag-grid-vue3' import type { ColDef, ColGroupDef } from 'ag-grid-community' import localforage from 'localforage' -import { getMajorDictEntries, getServiceDictItemById, isMajorIdInIndustryScope } from '@/sql' +import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal' import { formatThousands } from '@/lib/numberFormat' @@ -100,6 +100,12 @@ const majorFactorMap = ref>(new Map()) let factorDefaultsLoaded = false const paneInstanceCreatedAt = Date.now() const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__' +const industryNameMap = new Map( + industryTypeList.flatMap(item => [ + [String(item.id).trim(), item.name], + [String(item.type).trim(), item.name] + ]) +) const getDefaultConsultCategoryFactor = () => consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null @@ -109,6 +115,10 @@ const isOnlyCostScaleService = computed(() => { const service = getServiceDictItemById(props.serviceId) as ServiceLite | undefined return service?.onlyCostScale === true }) +const totalLabel = computed(() => { + const industryName = industryNameMap.get(activeIndustryCode.value.trim()) || '' + return industryName ? `${industryName}总投资` : '总投资' +}) const loadFactorDefaults = async () => { const [consultMap, majorMap] = await Promise.all([ @@ -158,7 +168,14 @@ const shouldForceDefaultLoad = () => { } const detailRows = ref([]) -type majorLite = { code: string; name: string; defCoe: number | null; hasCost?: boolean; hasArea?: boolean } +type majorLite = { + code: string + name: string + defCoe: number | null + hasCost?: boolean + hasArea?: boolean + industryId?: string | number | null +} const serviceEntries = getMajorDictEntries().map(({ id, item }) => [id, item] as [string, majorLite]) const majorIdAliasMap = new Map(getMajorDictEntries().map(({ rawId, id }) => [rawId, id])) @@ -258,6 +275,21 @@ const buildDefaultRows = (): DetailRow[] => { const calcOnlyCostScaleAmountFromRows = (rows?: Array<{ amount?: unknown }>) => sumByNumber(rows || [], row => (typeof row?.amount === 'number' ? row.amount : null)) +const getOnlyCostScaleMajorFactorDefault = () => { + const industryId = String(activeIndustryCode.value || '').trim() + if (!industryId) return 1 + const industryMajor = serviceEntries.find(([, item]) => { + const majorIndustryId = String(item?.industryId ?? '').trim() + return majorIndustryId === industryId && !String(item?.code || '').includes('-') + }) + if (!industryMajor) return 1 + const [majorId, majorItem] = industryMajor + const fromMap = majorFactorMap.value.get(String(majorId)) + if (typeof fromMap === 'number' && Number.isFinite(fromMap)) return fromMap + if (typeof majorItem?.defCoe === 'number' && Number.isFinite(majorItem.defCoe)) return majorItem.defCoe + return 1 +} + const buildOnlyCostScaleRow = ( amount: number | null, fromDb?: Partial> @@ -279,7 +311,7 @@ const buildOnlyCostScaleRow = ( optionalFormula: '', consultCategoryFactor: typeof fromDb?.consultCategoryFactor === 'number' ? fromDb.consultCategoryFactor : getDefaultConsultCategoryFactor(), - majorFactor: typeof fromDb?.majorFactor === 'number' ? fromDb.majorFactor : 1, + majorFactor: typeof fromDb?.majorFactor === 'number' ? fromDb.majorFactor : getOnlyCostScaleMajorFactorDefault(), workStageFactor: typeof fromDb?.workStageFactor === 'number' ? fromDb.workStageFactor : 1, workRatio: typeof fromDb?.workRatio === 'number' ? fromDb.workRatio : 100, budgetFee: null, @@ -509,7 +541,6 @@ const columnDefs: Array | ColGroupDef> = [ ? params.value == null || params.value === '' : !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost) && (params.value == null || params.value === '') }, - aggFunc: decimalAggSum, valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), valueFormatter: formatEditableMoney }, @@ -525,10 +556,9 @@ const columnDefs: Array | ColGroupDef> = [ minWidth: 130, flex: 1, cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, valueGetter: params => params.node?.rowPinned - ? params.data?.benchmarkBudgetBasic ?? null + ? null : getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null, cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'), valueFormatter: formatReadonlyMoney @@ -541,10 +571,9 @@ const columnDefs: Array | ColGroupDef> = [ minWidth: 130, flex: 1, cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, valueGetter: params => params.node?.rowPinned - ? params.data?.benchmarkBudgetOptional ?? null + ? null : getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null, cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'), valueFormatter: formatReadonlyMoney @@ -557,10 +586,9 @@ const columnDefs: Array | ColGroupDef> = [ minWidth: 100, flex: 1, cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, valueGetter: params => params.node?.rowPinned - ? params.data?.benchmarkBudget ?? null + ? null : getBenchmarkBudgetSplitByAmount(params.data)?.total ?? null, valueFormatter: formatReadonlyMoney } @@ -576,11 +604,21 @@ const columnDefs: Array | ColGroupDef> = [ colId: 'consultCategoryFactor', minWidth: 80, flex: 1, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + editable: params => + isOnlyCostScaleService.value + ? Boolean(params.node?.rowPinned) + : !params.node?.group && !params.node?.rowPinned, + cellClass: params => + isOnlyCostScaleService.value && params.node?.rowPinned + ? 'editable-cell-line' + : !params.node?.group && !params.node?.rowPinned + ? 'editable-cell-line' + : '', cellClassRules: { 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + isOnlyCostScaleService.value && params.node?.rowPinned + ? params.value == null || params.value === '' + : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') }, valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), valueFormatter: formatConsultCategoryFactor @@ -591,11 +629,21 @@ const columnDefs: Array | ColGroupDef> = [ colId: 'majorFactor', minWidth: 80, flex: 1, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + editable: params => + isOnlyCostScaleService.value + ? Boolean(params.node?.rowPinned) + : !params.node?.group && !params.node?.rowPinned, + cellClass: params => + isOnlyCostScaleService.value && params.node?.rowPinned + ? 'editable-cell-line' + : !params.node?.group && !params.node?.rowPinned + ? 'editable-cell-line' + : '', cellClassRules: { 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + isOnlyCostScaleService.value && params.node?.rowPinned + ? params.value == null || params.value === '' + : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') }, valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), valueFormatter: formatMajorFactor @@ -606,11 +654,21 @@ const columnDefs: Array | ColGroupDef> = [ colId: 'workStageFactor', minWidth: 80, flex: 1, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + editable: params => + isOnlyCostScaleService.value + ? Boolean(params.node?.rowPinned) + : !params.node?.group && !params.node?.rowPinned, + cellClass: params => + isOnlyCostScaleService.value && params.node?.rowPinned + ? 'editable-cell-line' + : !params.node?.group && !params.node?.rowPinned + ? 'editable-cell-line' + : '', cellClassRules: { 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + isOnlyCostScaleService.value && params.node?.rowPinned + ? params.value == null || params.value === '' + : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') }, valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), valueFormatter: formatEditableNumber @@ -621,11 +679,21 @@ const columnDefs: Array | ColGroupDef> = [ colId: 'workRatio', minWidth: 80, flex: 1, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + editable: params => + isOnlyCostScaleService.value + ? Boolean(params.node?.rowPinned) + : !params.node?.group && !params.node?.rowPinned, + cellClass: params => + isOnlyCostScaleService.value && params.node?.rowPinned + ? 'editable-cell-line' + : !params.node?.group && !params.node?.rowPinned + ? 'editable-cell-line' + : '', cellClassRules: { 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + isOnlyCostScaleService.value && params.node?.rowPinned + ? params.value == null || params.value === '' + : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') }, valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), valueFormatter: formatEditableNumber @@ -681,13 +749,13 @@ const autoGroupColumnDef: ColDef = { }, valueFormatter: params => { if (params.node?.rowPinned) { - return isOnlyCostScaleService.value ? '总投资' : '总合计' + return totalLabel.value } const nodeId = String(params.value || '') return idLabelMap.get(nodeId) || nodeId }, tooltipValueGetter: params => { - if (params.node?.rowPinned) return isOnlyCostScaleService.value ? '总投资' : '总合计' + if (params.node?.rowPinned) return totalLabel.value const nodeId = String(params.value || '') return idLabelMap.get(nodeId) || nodeId } @@ -696,6 +764,7 @@ const autoGroupColumnDef: ColDef = { const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount)) const visibleDetailRows = computed(() => (isOnlyCostScaleService.value ? [] : detailRows.value)) +const onlyCostScaleSourceRow = computed(() => detailRows.value[0] ?? buildOnlyCostScaleRow(null)) const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.basic)) const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional)) @@ -713,18 +782,18 @@ const pinnedTopRowData = computed(() => [ majorName: '', hasCost: false, hasArea: false, - amount: totalAmount.value, - benchmarkBudget: totalBenchmarkBudget.value, - benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value, - benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value, + amount: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.amount : null, + benchmarkBudget: null, + benchmarkBudgetBasic: null, + benchmarkBudgetOptional: null, benchmarkBudgetBasicChecked: true, benchmarkBudgetOptionalChecked: true, basicFormula: '', optionalFormula: '', - consultCategoryFactor: null, - majorFactor: null, - workStageFactor: null, - workRatio: null, + consultCategoryFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.consultCategoryFactor : null, + majorFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.majorFactor : null, + workStageFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.workStageFactor : null, + workRatio: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.workRatio : null, budgetFee: totalBudgetFee.value, budgetFeeBasic: totalBudgetFeeBasic.value, budgetFeeOptional: totalBudgetFeeOptional.value, @@ -881,19 +950,28 @@ let persistTimer: ReturnType | null = null let gridPersistTimer: ReturnType | null = null -const applyOnlyCostScalePinnedAmount = (rawValue: unknown) => { - const amount = parseNumberOrNull(rawValue, { precision: 2 }) +const applyOnlyCostScalePinnedValue = (field: string, rawValue: unknown) => { + const parsedValue = parseNumberOrNull(rawValue, { precision: 2 }) const current = detailRows.value[0] if (!current) { - detailRows.value = [buildOnlyCostScaleRow(amount)] + detailRows.value = [buildOnlyCostScaleRow(field === 'amount' ? parsedValue : null)] return } - detailRows.value = [{ ...current, amount }] + if ( + field !== 'amount' && + field !== 'consultCategoryFactor' && + field !== 'majorFactor' && + field !== 'workStageFactor' && + field !== 'workRatio' + ) { + return + } + detailRows.value = [{ ...current, [field]: parsedValue }] } const handleCellValueChanged = (event?: any) => { - if (isOnlyCostScaleService.value && event?.node?.rowPinned && event.colDef?.field === 'amount') { - applyOnlyCostScalePinnedAmount(event.newValue) + if (isOnlyCostScaleService.value && event?.node?.rowPinned && typeof event.colDef?.field === 'string') { + applyOnlyCostScalePinnedValue(event.colDef.field, event.newValue) } syncComputedValuesToDetailRows() if (gridPersistTimer) clearTimeout(gridPersistTimer) diff --git a/src/components/views/pricingView/LandScalePricingPane.vue b/src/components/views/pricingView/LandScalePricingPane.vue index 29f744d..a0d15db 100644 --- a/src/components/views/pricingView/LandScalePricingPane.vue +++ b/src/components/views/pricingView/LandScalePricingPane.vue @@ -3,7 +3,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'v import { AgGridVue } from 'ag-grid-vue3' import type { ColDef, ColGroupDef } from 'ag-grid-community' import localforage from 'localforage' -import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql' +import { getMajorDictEntries, industryTypeList, isMajorIdInIndustryScope } from '@/sql' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal' import { formatThousands } from '@/lib/numberFormat' @@ -96,6 +96,16 @@ const consultCategoryFactorMap = ref>(new Map()) const majorFactorMap = ref>(new Map()) let factorDefaultsLoaded = false const paneInstanceCreatedAt = Date.now() +const industryNameMap = new Map( + industryTypeList.flatMap(item => [ + [String(item.id).trim(), item.name], + [String(item.type).trim(), item.name] + ]) +) +const totalLabel = computed(() => { + const industryName = industryNameMap.get(activeIndustryCode.value.trim()) || '' + return industryName ? `${industryName}总投资` : '总投资' +}) const detailRows = ref([]) const getDefaultConsultCategoryFactor = () => @@ -463,10 +473,9 @@ const columnDefs: Array | ColGroupDef> = [ minWidth: 130, flex: 1, cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, valueGetter: params => params.node?.rowPinned - ? params.data?.benchmarkBudgetBasic ?? null + ? null : getBenchmarkBudgetSplitByLandArea(params.data)?.basic ?? null, cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'), valueFormatter: formatReadonlyMoney @@ -479,10 +488,9 @@ const columnDefs: Array | ColGroupDef> = [ minWidth: 130, flex: 1, cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, valueGetter: params => params.node?.rowPinned - ? params.data?.benchmarkBudgetOptional ?? null + ? null : getBenchmarkBudgetSplitByLandArea(params.data)?.optional ?? null, cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'), valueFormatter: formatReadonlyMoney @@ -495,10 +503,9 @@ const columnDefs: Array | ColGroupDef> = [ minWidth: 100, flex: 1, cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, valueGetter: params => params.node?.rowPinned - ? params.data?.benchmarkBudget ?? null + ? null : getBenchmarkBudgetSplitByLandArea(params.data)?.total ?? null, valueFormatter: formatReadonlyMoney } @@ -616,13 +623,13 @@ const autoGroupColumnDef: ColDef = { }, valueFormatter: params => { if (params.node?.rowPinned) { - return '总合计' + return totalLabel.value } const nodeId = String(params.value || '') return idLabelMap.get(nodeId) || nodeId }, tooltipValueGetter: params => { - if (params.node?.rowPinned) return '总合计' + if (params.node?.rowPinned) return totalLabel.value const nodeId = String(params.value || '') return idLabelMap.get(nodeId) || nodeId } @@ -648,11 +655,11 @@ const pinnedTopRowData = computed(() => [ majorName: '', hasCost: false, hasArea: false, - amount: totalAmount.value, + amount: null, landArea: null, - benchmarkBudget: totalBenchmarkBudget.value, - benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value, - benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value, + benchmarkBudget: null, + benchmarkBudgetBasic: null, + benchmarkBudgetOptional: null, benchmarkBudgetBasicChecked: true, benchmarkBudgetOptionalChecked: true, basicFormula: '', diff --git a/src/lib/pricingMethodTotals.ts b/src/lib/pricingMethodTotals.ts index e64f0ae..5b68507 100644 --- a/src/lib/pricingMethodTotals.ts +++ b/src/lib/pricingMethodTotals.ts @@ -55,6 +55,7 @@ interface MajorLite { defCoe: number | null hasCost?: boolean hasArea?: boolean + industryId?: string | number | null } interface ServiceLite { @@ -73,6 +74,10 @@ interface ExpertLite { manageCoe: number | null } +interface XmBaseInfoState { + projectIndustry?: string +} + export interface PricingMethodTotals { investScale: number | null landScale: number | null @@ -125,6 +130,18 @@ const isDualScaleMajorById = (id: string) => { 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 @@ -253,7 +270,9 @@ const getInvestmentBudgetFee = (row: ScaleRow) => { const getOnlyCostScaleBudgetFee = ( serviceId: string, rowsFromDb: Array> | undefined, - consultCategoryFactorMap?: Map + consultCategoryFactorMap?: Map, + majorFactorMap?: Map, + industryId?: string | null ) => { const totalAmount = sumByNumber(rowsFromDb || [], row => typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null @@ -263,7 +282,12 @@ const getOnlyCostScaleBudgetFee = ( toFiniteNumberOrNull(onlyRow?.consultCategoryFactor) ?? consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) - const majorFactor = toFiniteNumberOrNull(onlyRow?.majorFactor) ?? 1 + const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId) + 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 getScaleBudgetFee({ @@ -427,24 +451,27 @@ export const getPricingMethodTotalsForService = async (params: { 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] = await Promise.all([ + 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(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 @@ -453,7 +480,9 @@ export const getPricingMethodTotalsForService = async (params: { serviceId, (investData?.detailRows as Array> | undefined) || (htData?.detailRows as Array> | undefined), - consultCategoryFactorMap + consultCategoryFactorMap, + majorFactorMap, + industryId ) : (() => { const investRows = resolveScaleRows(