1
This commit is contained in:
1 parent
62546bc937
commit
33913c29d2
9 files changed
+1153
-239
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import type { ColDef } from 'ag-grid-community'
|
||||
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||
import localforage from 'localforage'
|
||||
import { majorList } from '@/sql'
|
||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||
@@ -11,7 +11,7 @@ import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPrici
|
||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
AlertDialogAction,
|
||||
@@ -49,9 +49,15 @@ interface DetailRow {
|
||||
majorName: string
|
||||
amount: number | null
|
||||
benchmarkBudget: number | null
|
||||
benchmarkBudgetBasic: number | null
|
||||
benchmarkBudgetOptional: number | null
|
||||
basicFormula: string
|
||||
optionalFormula: string
|
||||
consultCategoryFactor: number | null
|
||||
majorFactor: number | null
|
||||
budgetFee: number | null
|
||||
budgetFeeBasic: number | null
|
||||
budgetFeeOptional: number | null
|
||||
remark: string
|
||||
path: string[]
|
||||
}
|
||||
@@ -197,9 +203,15 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
majorName: child.name,
|
||||
amount: null,
|
||||
benchmarkBudget: null,
|
||||
benchmarkBudgetBasic: null,
|
||||
benchmarkBudgetOptional: null,
|
||||
basicFormula: '',
|
||||
optionalFormula: '',
|
||||
consultCategoryFactor: null,
|
||||
majorFactor: null,
|
||||
budgetFee: null,
|
||||
budgetFeeBasic: null,
|
||||
budgetFeeOptional: null,
|
||||
remark: '',
|
||||
path: [group.id, child.id]
|
||||
})
|
||||
@@ -208,7 +220,24 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
return rows
|
||||
}
|
||||
|
||||
type SourceRow = Pick<DetailRow, 'id'> & Partial<Pick<DetailRow, 'amount' | 'benchmarkBudget' | 'consultCategoryFactor' | 'majorFactor' | 'budgetFee' | 'remark'>>
|
||||
type SourceRow = Pick<DetailRow, 'id'> &
|
||||
Partial<
|
||||
Pick<
|
||||
DetailRow,
|
||||
| 'amount'
|
||||
| 'benchmarkBudget'
|
||||
| 'benchmarkBudgetBasic'
|
||||
| 'benchmarkBudgetOptional'
|
||||
| 'basicFormula'
|
||||
| 'optionalFormula'
|
||||
| 'consultCategoryFactor'
|
||||
| 'majorFactor'
|
||||
| 'budgetFee'
|
||||
| 'budgetFeeBasic'
|
||||
| 'budgetFeeOptional'
|
||||
| 'remark'
|
||||
>
|
||||
>
|
||||
const mergeWithDictRows = (
|
||||
rowsFromDb: SourceRow[] | undefined,
|
||||
options?: { includeAmount?: boolean; includeFactorValues?: boolean }
|
||||
@@ -230,6 +259,10 @@ const mergeWithDictRows = (
|
||||
...row,
|
||||
amount: includeAmount && typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
||||
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
|
||||
benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null,
|
||||
benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null,
|
||||
basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '',
|
||||
optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '',
|
||||
consultCategoryFactor:
|
||||
!includeFactorValues
|
||||
? null
|
||||
@@ -247,6 +280,8 @@ const mergeWithDictRows = (
|
||||
? null
|
||||
: getDefaultMajorFactorById(row.id),
|
||||
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
|
||||
budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null,
|
||||
budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null,
|
||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
||||
}
|
||||
})
|
||||
@@ -254,7 +289,7 @@ const mergeWithDictRows = (
|
||||
|
||||
const formatEditableNumber = (params: any) => {
|
||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||
return '点击输入'
|
||||
return '请输入'
|
||||
}
|
||||
if (params.value == null) return ''
|
||||
return Number(params.value).toFixed(2)
|
||||
@@ -281,18 +316,34 @@ const formatReadonlyMoney = (params: any) => {
|
||||
return formatThousands(roundTo(params.value, 2))
|
||||
}
|
||||
|
||||
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) =>
|
||||
getBenchmarkBudgetByScale(row?.amount, 'cost')
|
||||
const getBenchmarkBudgetSplitByAmount = (row?: Pick<DetailRow, 'amount'>) =>
|
||||
getBenchmarkBudgetSplitByScale(row?.amount, 'cost')
|
||||
|
||||
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||
return getScaleBudgetFee({
|
||||
benchmarkBudget: getBenchmarkBudgetByAmount(row),
|
||||
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
|
||||
if (!benchmarkBudgetSplit) return null
|
||||
|
||||
const splitBudgetFee = getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||
majorFactor: row?.majorFactor,
|
||||
consultCategoryFactor: row?.consultCategoryFactor
|
||||
})
|
||||
return splitBudgetFee ? splitBudgetFee.total : null
|
||||
}
|
||||
|
||||
const getBudgetFeeSplit = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
|
||||
if (!benchmarkBudgetSplit) return null
|
||||
return getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||
majorFactor: row?.majorFactor,
|
||||
consultCategoryFactor: row?.consultCategoryFactor
|
||||
})
|
||||
}
|
||||
|
||||
const columnDefs: ColDef<DetailRow>[] = [
|
||||
const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
|
||||
{
|
||||
headerName: '造价金额(万元)',
|
||||
field: 'amount',
|
||||
@@ -310,18 +361,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatEditableMoney
|
||||
},
|
||||
{
|
||||
headerName: '基准预算(元)',
|
||||
field: 'benchmarkBudget',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 100,
|
||||
flex: 2,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => getBenchmarkBudgetByAmount(params.data),
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
|
||||
{
|
||||
headerName: '咨询分类系数',
|
||||
field: 'consultCategoryFactor',
|
||||
@@ -352,17 +392,89 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatMajorFactor
|
||||
},
|
||||
{
|
||||
headerName: '基准预算(元)',
|
||||
marryChildren: true,
|
||||
children: [
|
||||
{
|
||||
headerName: '基本工作',
|
||||
field: 'benchmarkBudgetBasic',
|
||||
colId: 'benchmarkBudgetBasic',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 130,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.benchmarkBudgetBasic ?? null
|
||||
: getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
{
|
||||
headerName: '可选工作',
|
||||
field: 'benchmarkBudgetOptional',
|
||||
colId: 'benchmarkBudgetOptional',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 130,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.benchmarkBudgetOptional ?? null
|
||||
: getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: '预算费用',
|
||||
field: 'budgetFee',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 100,
|
||||
flex:2,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatReadonlyMoney
|
||||
marryChildren: true,
|
||||
children: [
|
||||
{
|
||||
headerName: '基本工作',
|
||||
field: 'budgetFeeBasic',
|
||||
colId: 'budgetFeeBasic',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 120,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.budgetFeeBasic ?? null
|
||||
: getBudgetFeeSplit(params.data)?.basic ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
{
|
||||
headerName: '可选工作',
|
||||
field: 'budgetFeeOptional',
|
||||
colId: 'budgetFeeOptional',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 120,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.budgetFeeOptional ?? null
|
||||
: getBudgetFeeSplit(params.data)?.optional ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
{
|
||||
headerName: '合计',
|
||||
field: 'budgetFee',
|
||||
colId: 'budgetFeeTotal',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 130,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
||||
valueFormatter: formatReadonlyMoney
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: '说明',
|
||||
@@ -416,8 +528,12 @@ const autoGroupColumnDef: ColDef = {
|
||||
|
||||
const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))
|
||||
|
||||
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetByAmount(row)))
|
||||
const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.basic))
|
||||
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional))
|
||||
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.total))
|
||||
|
||||
const totalBudgetFeeBasic = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.basic))
|
||||
const totalBudgetFeeOptional = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.optional))
|
||||
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
|
||||
const pinnedTopRowData = computed(() => [
|
||||
{
|
||||
@@ -428,21 +544,50 @@ const pinnedTopRowData = computed(() => [
|
||||
majorName: '',
|
||||
amount: totalAmount.value,
|
||||
benchmarkBudget: totalBenchmarkBudget.value,
|
||||
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value,
|
||||
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value,
|
||||
basicFormula: '',
|
||||
optionalFormula: '',
|
||||
consultCategoryFactor: null,
|
||||
majorFactor: null,
|
||||
budgetFee: totalBudgetFee.value,
|
||||
budgetFeeBasic: totalBudgetFeeBasic.value,
|
||||
budgetFeeOptional: totalBudgetFeeOptional.value,
|
||||
remark: '',
|
||||
path: ['TOTAL']
|
||||
}
|
||||
])
|
||||
|
||||
const buildPersistDetailRows = () =>
|
||||
detailRows.value.map(row => {
|
||||
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
|
||||
const budgetFeeSplit = benchmarkBudgetSplit
|
||||
? getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||
majorFactor: row.majorFactor,
|
||||
consultCategoryFactor: row.consultCategoryFactor
|
||||
})
|
||||
: null
|
||||
|
||||
return {
|
||||
...row,
|
||||
benchmarkBudget: benchmarkBudgetSplit?.total ?? null,
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null,
|
||||
basicFormula: benchmarkBudgetSplit?.basicFormula ?? '',
|
||||
optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '',
|
||||
budgetFee: budgetFeeSplit?.total ?? null,
|
||||
budgetFeeBasic: budgetFeeSplit?.basic ?? null,
|
||||
budgetFeeOptional: budgetFeeSplit?.optional ?? null
|
||||
}
|
||||
})
|
||||
|
||||
const saveToIndexedDB = async () => {
|
||||
if (shouldSkipPersist()) return
|
||||
try {
|
||||
const payload = {
|
||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
||||
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||
}
|
||||
console.log('Saving to IndexedDB:', payload)
|
||||
await localforage.setItem(DB_KEY.value, payload)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import type { ColDef } from 'ag-grid-community'
|
||||
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||
import localforage from 'localforage'
|
||||
import { majorList } from '@/sql'
|
||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||
@@ -11,7 +11,7 @@ import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPrici
|
||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
AlertDialogAction,
|
||||
@@ -50,9 +50,15 @@ interface DetailRow {
|
||||
amount: number | null
|
||||
landArea: number | null
|
||||
benchmarkBudget: number | null
|
||||
benchmarkBudgetBasic: number | null
|
||||
benchmarkBudgetOptional: number | null
|
||||
basicFormula: string
|
||||
optionalFormula: string
|
||||
consultCategoryFactor: number | null
|
||||
majorFactor: number | null
|
||||
budgetFee: number | null
|
||||
budgetFeeBasic: number | null
|
||||
budgetFeeOptional: number | null
|
||||
remark: string
|
||||
path: string[]
|
||||
}
|
||||
@@ -199,9 +205,15 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
amount: null,
|
||||
landArea: null,
|
||||
benchmarkBudget: null,
|
||||
benchmarkBudgetBasic: null,
|
||||
benchmarkBudgetOptional: null,
|
||||
basicFormula: '',
|
||||
optionalFormula: '',
|
||||
consultCategoryFactor: null,
|
||||
majorFactor: null,
|
||||
budgetFee: null,
|
||||
budgetFeeBasic: null,
|
||||
budgetFeeOptional: null,
|
||||
remark: '',
|
||||
path: [group.id, child.id]
|
||||
})
|
||||
@@ -210,7 +222,25 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
return rows
|
||||
}
|
||||
|
||||
type SourceRow = Pick<DetailRow, 'id'> & Partial<Pick<DetailRow, 'amount' | 'landArea' | 'benchmarkBudget' | 'consultCategoryFactor' | 'majorFactor' | 'budgetFee' | 'remark'>>
|
||||
type SourceRow = Pick<DetailRow, 'id'> &
|
||||
Partial<
|
||||
Pick<
|
||||
DetailRow,
|
||||
| 'amount'
|
||||
| 'landArea'
|
||||
| 'benchmarkBudget'
|
||||
| 'benchmarkBudgetBasic'
|
||||
| 'benchmarkBudgetOptional'
|
||||
| 'basicFormula'
|
||||
| 'optionalFormula'
|
||||
| 'consultCategoryFactor'
|
||||
| 'majorFactor'
|
||||
| 'budgetFee'
|
||||
| 'budgetFeeBasic'
|
||||
| 'budgetFeeOptional'
|
||||
| 'remark'
|
||||
>
|
||||
>
|
||||
const mergeWithDictRows = (
|
||||
rowsFromDb: SourceRow[] | undefined,
|
||||
options?: { includeScaleValues?: boolean; includeFactorValues?: boolean }
|
||||
@@ -233,6 +263,10 @@ const mergeWithDictRows = (
|
||||
amount: includeScaleValues && typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
||||
landArea: includeScaleValues && typeof fromDb.landArea === 'number' ? fromDb.landArea : null,
|
||||
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
|
||||
benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null,
|
||||
benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null,
|
||||
basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '',
|
||||
optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '',
|
||||
consultCategoryFactor:
|
||||
!includeFactorValues
|
||||
? null
|
||||
@@ -250,6 +284,8 @@ const mergeWithDictRows = (
|
||||
? null
|
||||
: getDefaultMajorFactorById(row.id),
|
||||
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
|
||||
budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null,
|
||||
budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null,
|
||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
||||
}
|
||||
})
|
||||
@@ -257,7 +293,7 @@ const mergeWithDictRows = (
|
||||
|
||||
const formatEditableNumber = (params: any) => {
|
||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||
return '点击输入'
|
||||
return '请输入'
|
||||
}
|
||||
if (params.value == null) return ''
|
||||
return Number(params.value).toFixed(2)
|
||||
@@ -276,12 +312,28 @@ const formatReadonlyMoney = (params: any) => {
|
||||
return formatThousands(roundTo(params.value, 2))
|
||||
}
|
||||
|
||||
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
|
||||
getBenchmarkBudgetByScale(row?.landArea, 'area')
|
||||
const getBenchmarkBudgetSplitByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
|
||||
getBenchmarkBudgetSplitByScale(row?.landArea, 'area')
|
||||
|
||||
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||
return getScaleBudgetFee({
|
||||
benchmarkBudget: getBenchmarkBudgetByLandArea(row),
|
||||
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row)
|
||||
if (!benchmarkBudgetSplit) return null
|
||||
|
||||
const splitBudgetFee = getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||
majorFactor: row?.majorFactor,
|
||||
consultCategoryFactor: row?.consultCategoryFactor
|
||||
})
|
||||
return splitBudgetFee ? splitBudgetFee.total : null
|
||||
}
|
||||
|
||||
const getBudgetFeeSplit = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row)
|
||||
if (!benchmarkBudgetSplit) return null
|
||||
return getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||
majorFactor: row?.majorFactor,
|
||||
consultCategoryFactor: row?.consultCategoryFactor
|
||||
})
|
||||
@@ -295,7 +347,7 @@ const formatEditableFlexibleNumber = (params: any) => {
|
||||
return String(Number(params.value))
|
||||
}
|
||||
|
||||
const columnDefs: ColDef<DetailRow>[] = [
|
||||
const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
|
||||
{
|
||||
headerName: '用地面积(亩)',
|
||||
field: 'landArea',
|
||||
@@ -312,18 +364,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatEditableFlexibleNumber
|
||||
},
|
||||
{
|
||||
headerName: '基准预算(元)',
|
||||
field: 'benchmarkBudget',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 170,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => getBenchmarkBudgetByLandArea(params.data),
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
|
||||
{
|
||||
headerName: '咨询分类系数',
|
||||
field: 'consultCategoryFactor',
|
||||
@@ -354,17 +395,89 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatMajorFactor
|
||||
},
|
||||
{
|
||||
headerName: '基准预算(元)',
|
||||
marryChildren: true,
|
||||
children: [
|
||||
{
|
||||
headerName: '基本工作',
|
||||
field: 'benchmarkBudgetBasic',
|
||||
colId: 'benchmarkBudgetBasic',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 140,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.benchmarkBudgetBasic ?? null
|
||||
: getBenchmarkBudgetSplitByLandArea(params.data)?.basic ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
{
|
||||
headerName: '可选工作',
|
||||
field: 'benchmarkBudgetOptional',
|
||||
colId: 'benchmarkBudgetOptional',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 140,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.benchmarkBudgetOptional ?? null
|
||||
: getBenchmarkBudgetSplitByLandArea(params.data)?.optional ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: '预算费用',
|
||||
field: 'budgetFee',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 150,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueFormatter: formatReadonlyMoney
|
||||
marryChildren: true,
|
||||
children: [
|
||||
{
|
||||
headerName: '基本工作',
|
||||
field: 'budgetFeeBasic',
|
||||
colId: 'budgetFeeBasic',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 130,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.budgetFeeBasic ?? null
|
||||
: getBudgetFeeSplit(params.data)?.basic ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
{
|
||||
headerName: '可选工作',
|
||||
field: 'budgetFeeOptional',
|
||||
colId: 'budgetFeeOptional',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 130,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params =>
|
||||
params.node?.rowPinned
|
||||
? params.data?.budgetFeeOptional ?? null
|
||||
: getBudgetFeeSplit(params.data)?.optional ?? null,
|
||||
valueFormatter: formatReadonlyMoney
|
||||
},
|
||||
{
|
||||
headerName: '合计',
|
||||
field: 'budgetFee',
|
||||
colId: 'budgetFeeTotal',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 140,
|
||||
flex: 1,
|
||||
cellClass: 'ag-right-aligned-cell',
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
||||
valueFormatter: formatReadonlyMoney
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: '说明',
|
||||
@@ -418,8 +531,12 @@ const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amou
|
||||
|
||||
const totalLandArea = computed(() => sumByNumber(detailRows.value, row => row.landArea))
|
||||
|
||||
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetByLandArea(row)))
|
||||
const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByLandArea(row)?.basic))
|
||||
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByLandArea(row)?.optional))
|
||||
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByLandArea(row)?.total))
|
||||
|
||||
const totalBudgetFeeBasic = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.basic))
|
||||
const totalBudgetFeeOptional = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.optional))
|
||||
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
|
||||
const pinnedTopRowData = computed(() => [
|
||||
{
|
||||
@@ -431,21 +548,50 @@ const pinnedTopRowData = computed(() => [
|
||||
amount: totalAmount.value,
|
||||
landArea: totalLandArea.value,
|
||||
benchmarkBudget: totalBenchmarkBudget.value,
|
||||
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value,
|
||||
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value,
|
||||
basicFormula: '',
|
||||
optionalFormula: '',
|
||||
consultCategoryFactor: null,
|
||||
majorFactor: null,
|
||||
budgetFee: totalBudgetFee.value,
|
||||
budgetFeeBasic: totalBudgetFeeBasic.value,
|
||||
budgetFeeOptional: totalBudgetFeeOptional.value,
|
||||
remark: '',
|
||||
path: ['TOTAL']
|
||||
}
|
||||
])
|
||||
|
||||
const buildPersistDetailRows = () =>
|
||||
detailRows.value.map(row => {
|
||||
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row)
|
||||
const budgetFeeSplit = benchmarkBudgetSplit
|
||||
? getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||
majorFactor: row.majorFactor,
|
||||
consultCategoryFactor: row.consultCategoryFactor
|
||||
})
|
||||
: null
|
||||
|
||||
return {
|
||||
...row,
|
||||
benchmarkBudget: benchmarkBudgetSplit?.total ?? null,
|
||||
benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null,
|
||||
benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null,
|
||||
basicFormula: benchmarkBudgetSplit?.basicFormula ?? '',
|
||||
optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '',
|
||||
budgetFee: budgetFeeSplit?.total ?? null,
|
||||
budgetFeeBasic: budgetFeeSplit?.basic ?? null,
|
||||
budgetFeeOptional: budgetFeeSplit?.optional ?? null
|
||||
}
|
||||
})
|
||||
|
||||
const saveToIndexedDB = async () => {
|
||||
if (shouldSkipPersist()) return
|
||||
try {
|
||||
const payload = {
|
||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
||||
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||
}
|
||||
console.log('Saving to IndexedDB:', payload)
|
||||
await localforage.setItem(DB_KEY.value, payload)
|
||||
|
||||
@@ -21,6 +21,7 @@ interface DetailRow {
|
||||
unit: string
|
||||
conversion: number | null
|
||||
workload: number | null
|
||||
basicFee: number | null
|
||||
budgetBase: string
|
||||
budgetReferenceUnitPrice: string
|
||||
budgetAdoptedUnitPrice: number | null
|
||||
@@ -142,6 +143,7 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
unit: task.unit || '',
|
||||
conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
|
||||
workload: null,
|
||||
basicFee: null,
|
||||
budgetBase: task.basicParam || '',
|
||||
budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
|
||||
budgetAdoptedUnitPrice:
|
||||
@@ -171,6 +173,7 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
||||
return {
|
||||
...row,
|
||||
workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
|
||||
basicFee: typeof fromDb.basicFee === 'number' ? fromDb.basicFee : null,
|
||||
budgetAdoptedUnitPrice:
|
||||
typeof fromDb.budgetAdoptedUnitPrice === 'number' ? fromDb.budgetAdoptedUnitPrice : null,
|
||||
consultCategoryFactor:
|
||||
@@ -184,25 +187,36 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
||||
const parseSanitizedNumberOrNull = (value: unknown) =>
|
||||
parseNumberOrNull(value, { sanitize: true })
|
||||
|
||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||
const calcBasicFee = (row: DetailRow | undefined) => {
|
||||
if (!row || isNoTaskRow(row)) return null
|
||||
const price = row.budgetAdoptedUnitPrice
|
||||
const conversion = row.conversion
|
||||
const workload = row.workload
|
||||
const factor = row.consultCategoryFactor
|
||||
if (
|
||||
typeof price !== 'number' ||
|
||||
!Number.isFinite(price) ||
|
||||
typeof conversion !== 'number' ||
|
||||
!Number.isFinite(conversion) ||
|
||||
typeof workload !== 'number' ||
|
||||
!Number.isFinite(workload) ||
|
||||
!Number.isFinite(workload)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
|
||||
}
|
||||
|
||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||
if (!row || isNoTaskRow(row)) return null
|
||||
const factor = row.consultCategoryFactor
|
||||
const basicFee = calcBasicFee(row)
|
||||
if (
|
||||
basicFee == null ||
|
||||
typeof factor !== 'number' ||
|
||||
!Number.isFinite(factor)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return roundTo(toDecimal(price).mul(conversion).mul(workload).mul(factor), 2)
|
||||
return roundTo(toDecimal(basicFee).mul(factor), 2)
|
||||
}
|
||||
|
||||
const formatEditableNumber = (params: any) => {
|
||||
@@ -368,6 +382,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
]
|
||||
|
||||
const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
|
||||
const totalBasicFee = computed(() => sumByNumber(detailRows.value, row => calcBasicFee(row)))
|
||||
|
||||
const totalServiceFee = computed(() => sumByNumber(detailRows.value, row => calcServiceFee(row)))
|
||||
const pinnedTopRowData = computed(() => [
|
||||
@@ -378,6 +393,7 @@ const pinnedTopRowData = computed(() => [
|
||||
unit: '',
|
||||
conversion: null,
|
||||
workload: totalWorkload.value,
|
||||
basicFee: totalBasicFee.value,
|
||||
budgetBase: '',
|
||||
budgetReferenceUnitPrice: '',
|
||||
budgetAdoptedUnitPrice: null,
|
||||
@@ -390,12 +406,18 @@ const pinnedTopRowData = computed(() => [
|
||||
|
||||
|
||||
|
||||
const buildPersistDetailRows = () =>
|
||||
detailRows.value.map(row => ({
|
||||
...row,
|
||||
basicFee: calcBasicFee(row),
|
||||
serviceFee: calcServiceFee(row)
|
||||
}))
|
||||
const saveToIndexedDB = async () => {
|
||||
if (!isWorkloadMethodApplicable.value) return
|
||||
if (shouldSkipPersist()) return
|
||||
try {
|
||||
const payload = {
|
||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
||||
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||
}
|
||||
console.log('Saving to IndexedDB:', payload)
|
||||
await localforage.setItem(DB_KEY.value, payload)
|
||||
@@ -537,3 +559,4 @@ const mydiyTheme = myTheme.withParams({
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in new issue
Block a user