This commit is contained in:
wintsa 2026-03-07 16:09:06 +08:00
parent 043e1fc879
commit 303f54bb71
3 changed files with 170 additions and 56 deletions

View File

@ -3,7 +3,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'v
import { AgGridVue } from 'ag-grid-vue3' import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, ColGroupDef } from 'ag-grid-community' import type { ColDef, ColGroupDef } from 'ag-grid-community'
import localforage from 'localforage' import localforage from 'localforage'
import { getMajorDictEntries, getServiceDictItemById, isMajorIdInIndustryScope } from '@/sql' import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal' import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
import { formatThousands } from '@/lib/numberFormat' import { formatThousands } from '@/lib/numberFormat'
@ -100,6 +100,12 @@ const majorFactorMap = ref<Map<string, number | null>>(new Map())
let factorDefaultsLoaded = false let factorDefaultsLoaded = false
const paneInstanceCreatedAt = Date.now() const paneInstanceCreatedAt = Date.now()
const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__' 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 = () => const getDefaultConsultCategoryFactor = () =>
consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null
@ -109,6 +115,10 @@ const isOnlyCostScaleService = computed(() => {
const service = getServiceDictItemById(props.serviceId) as ServiceLite | undefined const service = getServiceDictItemById(props.serviceId) as ServiceLite | undefined
return service?.onlyCostScale === true return service?.onlyCostScale === true
}) })
const totalLabel = computed(() => {
const industryName = industryNameMap.get(activeIndustryCode.value.trim()) || ''
return industryName ? `${industryName}总投资` : '总投资'
})
const loadFactorDefaults = async () => { const loadFactorDefaults = async () => {
const [consultMap, majorMap] = await Promise.all([ const [consultMap, majorMap] = await Promise.all([
@ -158,7 +168,14 @@ const shouldForceDefaultLoad = () => {
} }
const detailRows = ref<DetailRow[]>([]) const detailRows = ref<DetailRow[]>([])
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 serviceEntries = getMajorDictEntries().map(({ id, item }) => [id, item] as [string, majorLite])
const majorIdAliasMap = new Map(getMajorDictEntries().map(({ rawId, id }) => [rawId, id])) const majorIdAliasMap = new Map(getMajorDictEntries().map(({ rawId, id }) => [rawId, id]))
@ -258,6 +275,21 @@ const buildDefaultRows = (): DetailRow[] => {
const calcOnlyCostScaleAmountFromRows = (rows?: Array<{ amount?: unknown }>) => const calcOnlyCostScaleAmountFromRows = (rows?: Array<{ amount?: unknown }>) =>
sumByNumber(rows || [], row => (typeof row?.amount === 'number' ? row.amount : null)) 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 = ( const buildOnlyCostScaleRow = (
amount: number | null, amount: number | null,
fromDb?: Partial<Pick<DetailRow, 'consultCategoryFactor' | 'majorFactor' | 'workStageFactor' | 'workRatio' | 'remark'>> fromDb?: Partial<Pick<DetailRow, 'consultCategoryFactor' | 'majorFactor' | 'workStageFactor' | 'workRatio' | 'remark'>>
@ -279,7 +311,7 @@ const buildOnlyCostScaleRow = (
optionalFormula: '', optionalFormula: '',
consultCategoryFactor: consultCategoryFactor:
typeof fromDb?.consultCategoryFactor === 'number' ? fromDb.consultCategoryFactor : getDefaultConsultCategoryFactor(), 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, workStageFactor: typeof fromDb?.workStageFactor === 'number' ? fromDb.workStageFactor : 1,
workRatio: typeof fromDb?.workRatio === 'number' ? fromDb.workRatio : 100, workRatio: typeof fromDb?.workRatio === 'number' ? fromDb.workRatio : 100,
budgetFee: null, budgetFee: null,
@ -509,7 +541,6 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
? params.value == null || params.value === '' ? params.value == null || params.value === ''
: !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost) && (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 }), valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatEditableMoney valueFormatter: formatEditableMoney
}, },
@ -525,10 +556,9 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
minWidth: 130, minWidth: 130,
flex: 1, flex: 1,
cellClass: 'ag-right-aligned-cell', cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params => valueGetter: params =>
params.node?.rowPinned params.node?.rowPinned
? params.data?.benchmarkBudgetBasic ?? null ? null
: getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null, : getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null,
cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'), cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'),
valueFormatter: formatReadonlyMoney valueFormatter: formatReadonlyMoney
@ -541,10 +571,9 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
minWidth: 130, minWidth: 130,
flex: 1, flex: 1,
cellClass: 'ag-right-aligned-cell', cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params => valueGetter: params =>
params.node?.rowPinned params.node?.rowPinned
? params.data?.benchmarkBudgetOptional ?? null ? null
: getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null, : getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null,
cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'), cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'),
valueFormatter: formatReadonlyMoney valueFormatter: formatReadonlyMoney
@ -557,10 +586,9 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
minWidth: 100, minWidth: 100,
flex: 1, flex: 1,
cellClass: 'ag-right-aligned-cell', cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params => valueGetter: params =>
params.node?.rowPinned params.node?.rowPinned
? params.data?.benchmarkBudget ?? null ? null
: getBenchmarkBudgetSplitByAmount(params.data)?.total ?? null, : getBenchmarkBudgetSplitByAmount(params.data)?.total ?? null,
valueFormatter: formatReadonlyMoney valueFormatter: formatReadonlyMoney
} }
@ -576,11 +604,21 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
colId: 'consultCategoryFactor', colId: 'consultCategoryFactor',
minWidth: 80, minWidth: 80,
flex: 1, flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned, editable: params =>
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), 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: { cellClassRules: {
'editable-cell-empty': params => '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 }), valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatConsultCategoryFactor valueFormatter: formatConsultCategoryFactor
@ -591,11 +629,21 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
colId: 'majorFactor', colId: 'majorFactor',
minWidth: 80, minWidth: 80,
flex: 1, flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned, editable: params =>
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), 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: { cellClassRules: {
'editable-cell-empty': params => '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 }), valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatMajorFactor valueFormatter: formatMajorFactor
@ -606,11 +654,21 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
colId: 'workStageFactor', colId: 'workStageFactor',
minWidth: 80, minWidth: 80,
flex: 1, flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned, editable: params =>
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), 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: { cellClassRules: {
'editable-cell-empty': params => '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 }), valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatEditableNumber valueFormatter: formatEditableNumber
@ -621,11 +679,21 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
colId: 'workRatio', colId: 'workRatio',
minWidth: 80, minWidth: 80,
flex: 1, flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned, editable: params =>
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), 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: { cellClassRules: {
'editable-cell-empty': params => '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 }), valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatEditableNumber valueFormatter: formatEditableNumber
@ -681,13 +749,13 @@ const autoGroupColumnDef: ColDef = {
}, },
valueFormatter: params => { valueFormatter: params => {
if (params.node?.rowPinned) { if (params.node?.rowPinned) {
return isOnlyCostScaleService.value ? '总投资' : '总合计' return totalLabel.value
} }
const nodeId = String(params.value || '') const nodeId = String(params.value || '')
return idLabelMap.get(nodeId) || nodeId return idLabelMap.get(nodeId) || nodeId
}, },
tooltipValueGetter: params => { tooltipValueGetter: params => {
if (params.node?.rowPinned) return isOnlyCostScaleService.value ? '总投资' : '总合计' if (params.node?.rowPinned) return totalLabel.value
const nodeId = String(params.value || '') const nodeId = String(params.value || '')
return idLabelMap.get(nodeId) || nodeId return idLabelMap.get(nodeId) || nodeId
} }
@ -696,6 +764,7 @@ const autoGroupColumnDef: ColDef = {
const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount)) const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))
const visibleDetailRows = computed(() => (isOnlyCostScaleService.value ? [] : detailRows.value)) 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 totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.basic))
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional)) const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional))
@ -713,18 +782,18 @@ const pinnedTopRowData = computed(() => [
majorName: '', majorName: '',
hasCost: false, hasCost: false,
hasArea: false, hasArea: false,
amount: totalAmount.value, amount: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.amount : null,
benchmarkBudget: totalBenchmarkBudget.value, benchmarkBudget: null,
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value, benchmarkBudgetBasic: null,
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value, benchmarkBudgetOptional: null,
benchmarkBudgetBasicChecked: true, benchmarkBudgetBasicChecked: true,
benchmarkBudgetOptionalChecked: true, benchmarkBudgetOptionalChecked: true,
basicFormula: '', basicFormula: '',
optionalFormula: '', optionalFormula: '',
consultCategoryFactor: null, consultCategoryFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.consultCategoryFactor : null,
majorFactor: null, majorFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.majorFactor : null,
workStageFactor: null, workStageFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.workStageFactor : null,
workRatio: null, workRatio: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.workRatio : null,
budgetFee: totalBudgetFee.value, budgetFee: totalBudgetFee.value,
budgetFeeBasic: totalBudgetFeeBasic.value, budgetFeeBasic: totalBudgetFeeBasic.value,
budgetFeeOptional: totalBudgetFeeOptional.value, budgetFeeOptional: totalBudgetFeeOptional.value,
@ -881,19 +950,28 @@ let persistTimer: ReturnType<typeof setTimeout> | null = null
let gridPersistTimer: ReturnType<typeof setTimeout> | null = null let gridPersistTimer: ReturnType<typeof setTimeout> | null = null
const applyOnlyCostScalePinnedAmount = (rawValue: unknown) => { const applyOnlyCostScalePinnedValue = (field: string, rawValue: unknown) => {
const amount = parseNumberOrNull(rawValue, { precision: 2 }) const parsedValue = parseNumberOrNull(rawValue, { precision: 2 })
const current = detailRows.value[0] const current = detailRows.value[0]
if (!current) { if (!current) {
detailRows.value = [buildOnlyCostScaleRow(amount)] detailRows.value = [buildOnlyCostScaleRow(field === 'amount' ? parsedValue : null)]
return 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) => { const handleCellValueChanged = (event?: any) => {
if (isOnlyCostScaleService.value && event?.node?.rowPinned && event.colDef?.field === 'amount') { if (isOnlyCostScaleService.value && event?.node?.rowPinned && typeof event.colDef?.field === 'string') {
applyOnlyCostScalePinnedAmount(event.newValue) applyOnlyCostScalePinnedValue(event.colDef.field, event.newValue)
} }
syncComputedValuesToDetailRows() syncComputedValuesToDetailRows()
if (gridPersistTimer) clearTimeout(gridPersistTimer) if (gridPersistTimer) clearTimeout(gridPersistTimer)

View File

@ -3,7 +3,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'v
import { AgGridVue } from 'ag-grid-vue3' import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, ColGroupDef } from 'ag-grid-community' import type { ColDef, ColGroupDef } from 'ag-grid-community'
import localforage from 'localforage' import localforage from 'localforage'
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql' import { getMajorDictEntries, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal' import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
import { formatThousands } from '@/lib/numberFormat' import { formatThousands } from '@/lib/numberFormat'
@ -96,6 +96,16 @@ const consultCategoryFactorMap = ref<Map<string, number | null>>(new Map())
const majorFactorMap = ref<Map<string, number | null>>(new Map()) const majorFactorMap = ref<Map<string, number | null>>(new Map())
let factorDefaultsLoaded = false let factorDefaultsLoaded = false
const paneInstanceCreatedAt = Date.now() 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<DetailRow[]>([]) const detailRows = ref<DetailRow[]>([])
const getDefaultConsultCategoryFactor = () => const getDefaultConsultCategoryFactor = () =>
@ -463,10 +473,9 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
minWidth: 130, minWidth: 130,
flex: 1, flex: 1,
cellClass: 'ag-right-aligned-cell', cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params => valueGetter: params =>
params.node?.rowPinned params.node?.rowPinned
? params.data?.benchmarkBudgetBasic ?? null ? null
: getBenchmarkBudgetSplitByLandArea(params.data)?.basic ?? null, : getBenchmarkBudgetSplitByLandArea(params.data)?.basic ?? null,
cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'), cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'),
valueFormatter: formatReadonlyMoney valueFormatter: formatReadonlyMoney
@ -479,10 +488,9 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
minWidth: 130, minWidth: 130,
flex: 1, flex: 1,
cellClass: 'ag-right-aligned-cell', cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params => valueGetter: params =>
params.node?.rowPinned params.node?.rowPinned
? params.data?.benchmarkBudgetOptional ?? null ? null
: getBenchmarkBudgetSplitByLandArea(params.data)?.optional ?? null, : getBenchmarkBudgetSplitByLandArea(params.data)?.optional ?? null,
cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'), cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'),
valueFormatter: formatReadonlyMoney valueFormatter: formatReadonlyMoney
@ -495,10 +503,9 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
minWidth: 100, minWidth: 100,
flex: 1, flex: 1,
cellClass: 'ag-right-aligned-cell', cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params => valueGetter: params =>
params.node?.rowPinned params.node?.rowPinned
? params.data?.benchmarkBudget ?? null ? null
: getBenchmarkBudgetSplitByLandArea(params.data)?.total ?? null, : getBenchmarkBudgetSplitByLandArea(params.data)?.total ?? null,
valueFormatter: formatReadonlyMoney valueFormatter: formatReadonlyMoney
} }
@ -616,13 +623,13 @@ const autoGroupColumnDef: ColDef = {
}, },
valueFormatter: params => { valueFormatter: params => {
if (params.node?.rowPinned) { if (params.node?.rowPinned) {
return '总合计' return totalLabel.value
} }
const nodeId = String(params.value || '') const nodeId = String(params.value || '')
return idLabelMap.get(nodeId) || nodeId return idLabelMap.get(nodeId) || nodeId
}, },
tooltipValueGetter: params => { tooltipValueGetter: params => {
if (params.node?.rowPinned) return '总合计' if (params.node?.rowPinned) return totalLabel.value
const nodeId = String(params.value || '') const nodeId = String(params.value || '')
return idLabelMap.get(nodeId) || nodeId return idLabelMap.get(nodeId) || nodeId
} }
@ -648,11 +655,11 @@ const pinnedTopRowData = computed(() => [
majorName: '', majorName: '',
hasCost: false, hasCost: false,
hasArea: false, hasArea: false,
amount: totalAmount.value, amount: null,
landArea: null, landArea: null,
benchmarkBudget: totalBenchmarkBudget.value, benchmarkBudget: null,
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value, benchmarkBudgetBasic: null,
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value, benchmarkBudgetOptional: null,
benchmarkBudgetBasicChecked: true, benchmarkBudgetBasicChecked: true,
benchmarkBudgetOptionalChecked: true, benchmarkBudgetOptionalChecked: true,
basicFormula: '', basicFormula: '',

View File

@ -55,6 +55,7 @@ interface MajorLite {
defCoe: number | null defCoe: number | null
hasCost?: boolean hasCost?: boolean
hasArea?: boolean hasArea?: boolean
industryId?: string | number | null
} }
interface ServiceLite { interface ServiceLite {
@ -73,6 +74,10 @@ interface ExpertLite {
manageCoe: number | null manageCoe: number | null
} }
interface XmBaseInfoState {
projectIndustry?: string
}
export interface PricingMethodTotals { export interface PricingMethodTotals {
investScale: number | null investScale: number | null
landScale: number | null landScale: number | null
@ -125,6 +130,18 @@ const isDualScaleMajorById = (id: string) => {
return hasCost && hasArea 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 = ( const resolveFactorValue = (
row: { budgetValue?: number | null; standardFactor?: number | null } | undefined, row: { budgetValue?: number | null; standardFactor?: number | null } | undefined,
fallback: number | null fallback: number | null
@ -253,7 +270,9 @@ const getInvestmentBudgetFee = (row: ScaleRow) => {
const getOnlyCostScaleBudgetFee = ( const getOnlyCostScaleBudgetFee = (
serviceId: string, serviceId: string,
rowsFromDb: Array<Record<string, unknown>> | undefined, rowsFromDb: Array<Record<string, unknown>> | undefined,
consultCategoryFactorMap?: Map<string, number | null> consultCategoryFactorMap?: Map<string, number | null>,
majorFactorMap?: Map<string, number | null>,
industryId?: string | null
) => { ) => {
const totalAmount = sumByNumber(rowsFromDb || [], row => const totalAmount = sumByNumber(rowsFromDb || [], row =>
typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null
@ -263,7 +282,12 @@ const getOnlyCostScaleBudgetFee = (
toFiniteNumberOrNull(onlyRow?.consultCategoryFactor) ?? toFiniteNumberOrNull(onlyRow?.consultCategoryFactor) ??
consultCategoryFactorMap?.get(String(serviceId)) ?? consultCategoryFactorMap?.get(String(serviceId)) ??
getDefaultConsultCategoryFactor(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 workStageFactor = toFiniteNumberOrNull(onlyRow?.workStageFactor) ?? 1
const workRatio = toFiniteNumberOrNull(onlyRow?.workRatio) ?? 100 const workRatio = toFiniteNumberOrNull(onlyRow?.workRatio) ?? 100
return getScaleBudgetFee({ return getScaleBudgetFee({
@ -427,24 +451,27 @@ export const getPricingMethodTotalsForService = async (params: {
const htDbKey = `ht-info-v3-${params.contractId}` const htDbKey = `ht-info-v3-${params.contractId}`
const consultFactorDbKey = `ht-consult-category-factor-v1-${params.contractId}` const consultFactorDbKey = `ht-consult-category-factor-v1-${params.contractId}`
const majorFactorDbKey = `ht-major-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 investDbKey = `tzGMF-${params.contractId}-${serviceId}`
const landDbKey = `ydGMF-${params.contractId}-${serviceId}` const landDbKey = `ydGMF-${params.contractId}-${serviceId}`
const workloadDbKey = `gzlF-${params.contractId}-${serviceId}` const workloadDbKey = `gzlF-${params.contractId}-${serviceId}`
const hourlyDbKey = `hourlyPricing-${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<StoredDetailRowsState>(investDbKey), localforage.getItem<StoredDetailRowsState>(investDbKey),
localforage.getItem<StoredDetailRowsState>(landDbKey), localforage.getItem<StoredDetailRowsState>(landDbKey),
localforage.getItem<StoredDetailRowsState>(workloadDbKey), localforage.getItem<StoredDetailRowsState>(workloadDbKey),
localforage.getItem<StoredDetailRowsState>(hourlyDbKey), localforage.getItem<StoredDetailRowsState>(hourlyDbKey),
localforage.getItem<StoredDetailRowsState>(htDbKey), localforage.getItem<StoredDetailRowsState>(htDbKey),
localforage.getItem<StoredFactorState>(consultFactorDbKey), localforage.getItem<StoredFactorState>(consultFactorDbKey),
localforage.getItem<StoredFactorState>(majorFactorDbKey) localforage.getItem<StoredFactorState>(majorFactorDbKey),
localforage.getItem<XmBaseInfoState>(baseInfoDbKey)
]) ])
const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData) const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData)
const majorFactorMap = buildMajorFactorMap(majorFactorData) const majorFactorMap = buildMajorFactorMap(majorFactorData)
const onlyCostScale = isOnlyCostScaleService(serviceId) const onlyCostScale = isOnlyCostScaleService(serviceId)
const industryId = typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。 // 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true
@ -453,7 +480,9 @@ export const getPricingMethodTotalsForService = async (params: {
serviceId, serviceId,
(investData?.detailRows as Array<Record<string, unknown>> | undefined) || (investData?.detailRows as Array<Record<string, unknown>> | undefined) ||
(htData?.detailRows as Array<Record<string, unknown>> | undefined), (htData?.detailRows as Array<Record<string, unknown>> | undefined),
consultCategoryFactorMap consultCategoryFactorMap,
majorFactorMap,
industryId
) )
: (() => { : (() => {
const investRows = resolveScaleRows( const investRows = resolveScaleRows(