修复大量问题
This commit is contained in:
parent
8e5cb49da5
commit
59bab33e9b
@ -196,7 +196,7 @@ let data1 = {
|
||||
],
|
||||
},
|
||||
tasks: [{ serviceid: 0, text: ['abc', 'efg'] },
|
||||
{ serviceid: 2,text: ['abc', 'efg'] } //tasks不分组的时候传单对象[{text: ['abc', 'efg']}],分组的时候传分组的serviceid
|
||||
{ serviceid: 2,text: ['abc', 'efg'] } //tasks不分组的时候传单对象[{text: ['abc', 'efg']}],分组的时候传分组的服务id[{ serviceid: 0, text: ['abc', 'efg'] },...]
|
||||
],// 工作内容
|
||||
},
|
||||
],
|
||||
|
||||
@ -3,13 +3,14 @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import {
|
||||
useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||
|
||||
interface HtBaseInfoState {
|
||||
interface HtBaseInfoState {
|
||||
quality: string
|
||||
duration:
|
||||
string
|
||||
}
|
||||
|
||||
const DEFAULT_QUALITY = '造价咨询服务的综合评价应达到"较好"或综合评分90分'
|
||||
const DEFAULT_DURATION = ''
|
||||
|
||||
const props =
|
||||
defineProps<{
|
||||
@ -43,13 +44,21 @@ import {
|
||||
const loadForm = async () => {
|
||||
const data = await
|
||||
zxFwPricingStore.loadKeyState<HtBaseInfoState>(storageKey())
|
||||
const hasStoredValue = Boolean(
|
||||
data &&
|
||||
(Object.prototype.hasOwnProperty.call(data, 'quality') || Object.prototype.hasOwnProperty.call(data, 'duration'))
|
||||
)
|
||||
quality.value = typeof data?.quality === 'string' &&
|
||||
data.quality ? data.quality : DEFAULT_QUALITY
|
||||
duration.value = typeof data?.duration === 'string' ? data.duration :
|
||||
''
|
||||
duration.value = typeof data?.duration === 'string'
|
||||
? data.duration
|
||||
: (hasStoredValue ? '' : DEFAULT_DURATION)
|
||||
const payload: HtBaseInfoState = { quality: quality.value, duration: duration.value }
|
||||
|
||||
lastSavedSnapshot.value = JSON.stringify(payload)
|
||||
if (!hasStoredValue) {
|
||||
saveForm(true)
|
||||
}
|
||||
}
|
||||
|
||||
watch([quality, duration], () => { saveForm()
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
:subtitle="`合同段ID:${contractId}`"
|
||||
:meta-text="`合同段预算金额:${formatBudgetAmount(contractBudget)}`"
|
||||
:copy-text="contractId"
|
||||
:storage-key="`project-active-cat-${contractId}`"
|
||||
default-category="info"
|
||||
:storage-key="typeLineStorageKey"
|
||||
default-category="base-info"
|
||||
:categories="xmCategories"
|
||||
/>
|
||||
</template>
|
||||
@ -61,6 +61,7 @@ interface QuantityMethodStateLike {
|
||||
}
|
||||
|
||||
const contractBudget = ref<number | null>(null)
|
||||
const typeLineStorageKey = computed(() => `project-active-cat-${props.contractId}`)
|
||||
let budgetRefreshTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const toFiniteNumber = (value: unknown): number | null => {
|
||||
|
||||
@ -356,7 +356,7 @@ const mergeWithStoredRows = (rowsFromDb: unknown): FeeMethodRow[] => {
|
||||
return fixedNames.value.map((item, index) => {
|
||||
const fromDb = byName.get(item.name)
|
||||
return {
|
||||
id: item?.id || `fee-method-fixed-${index}`,
|
||||
id: String(item?.id || `fee-method-fixed-${index}`),
|
||||
name:item.name,
|
||||
rateFee: fromDb?.rateFee ?? null,
|
||||
hourlyFee: fromDb?.hourlyFee ?? null,
|
||||
@ -424,7 +424,6 @@ const clearRow = async (id: string) => {
|
||||
const editRow = (id: string) => {
|
||||
const row = detailRows.value.find(item => item.id === id)
|
||||
if (!row) return
|
||||
console.log(id)
|
||||
tabStore.openTab({
|
||||
id: `ht-fee-edit-${props.storageKey}-${id}`,
|
||||
title: `费用编辑-${row.name || '未命名'}`,
|
||||
@ -449,16 +448,16 @@ const ActionCellRenderer = defineComponent({
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => {
|
||||
if (isSummaryRow(props.params.data as FeeMethodRow | undefined)) return null
|
||||
const onActionClick = (action: 'edit' | 'clear') => (event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const rowId = props.params.data?.id
|
||||
if (!rowId) return
|
||||
if (action === 'edit') {
|
||||
props.params.context?.onActionEdit?.(rowId)
|
||||
return
|
||||
return () => {
|
||||
if (isSummaryRow(props.params.data as FeeMethodRow | undefined)) return null
|
||||
const onActionClick = (action: 'edit' | 'clear') => (event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const rowId = String(props.params.data?.id || '').trim()
|
||||
if (!rowId) return
|
||||
if (action === 'edit') {
|
||||
props.params.context?.onActionEdit?.(rowId)
|
||||
return
|
||||
}
|
||||
void props.params.context?.onActionRequestClear?.(rowId, String(props.params.data?.name || ''))
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ interface WorkContentRow {
|
||||
content: string
|
||||
type: WorkType
|
||||
serviceGroup?: string
|
||||
serviceid?: number | null
|
||||
remark: string
|
||||
checked: boolean
|
||||
custom: boolean
|
||||
@ -93,6 +94,12 @@ const syncGroupedRowsRender = async () => {
|
||||
}, 16)
|
||||
}
|
||||
|
||||
const toServiceId = (value: unknown): number | null => {
|
||||
const parsed = Number(value)
|
||||
if (!Number.isSafeInteger(parsed)) return null
|
||||
return parsed
|
||||
}
|
||||
|
||||
|
||||
|
||||
const loadProjectIndustryId = async () => {
|
||||
@ -193,6 +200,7 @@ const buildDefaultRowsFromDict = async (): Promise<WorkContentRow[]> => {
|
||||
content,
|
||||
type: typeLabel,
|
||||
serviceGroup,
|
||||
serviceid: toServiceId(entry.serviceid),
|
||||
remark: '',
|
||||
checked: false,
|
||||
custom: false,
|
||||
@ -247,9 +255,27 @@ const loadFromStore = async () => {
|
||||
const persistedRows = state.detailRows.map(item => ({
|
||||
...item,
|
||||
type: item.custom ? '自定义' : (item.type || '基本工作'),
|
||||
serviceid: toServiceId(item.serviceid),
|
||||
path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名']
|
||||
})) as WorkContentRow[]
|
||||
|
||||
const defaultGroupServiceIdMap = new Map<string, number>()
|
||||
for (const row of defaultRows) {
|
||||
const groupName = String(row.serviceGroup || '').trim()
|
||||
const serviceid = toServiceId(row.serviceid)
|
||||
if (!groupName || serviceid == null) continue
|
||||
defaultGroupServiceIdMap.set(groupName, serviceid)
|
||||
}
|
||||
for (const row of persistedRows) {
|
||||
if (row.serviceid != null) continue
|
||||
const groupName = String(row.serviceGroup || '').trim()
|
||||
if (!groupName) continue
|
||||
const fallbackServiceId = defaultGroupServiceIdMap.get(groupName)
|
||||
if (fallbackServiceId != null) {
|
||||
row.serviceid = fallbackServiceId
|
||||
}
|
||||
}
|
||||
|
||||
// 按最新词典规则重建默认行,再合并历史勾选/备注,保证分组规则变更后立即生效。
|
||||
if (defaultRows.length > 0) {
|
||||
const persistedCustomRows = persistedRows.filter(item => item.custom)
|
||||
@ -462,6 +488,7 @@ const createAddTriggerRow = (groupName?: string): WorkContentRow => {
|
||||
content: '点击添加自定义内容',
|
||||
type: '自定义' as WorkType,
|
||||
serviceGroup: groupName || '',
|
||||
serviceid: null,
|
||||
remark: '',
|
||||
checked: false,
|
||||
custom: false,
|
||||
@ -513,11 +540,19 @@ const addCustomRow = (groupName?: string) => {
|
||||
const finalGroupName = isWholeProcessGroupedMode.value
|
||||
? String(groupName || groupedServiceGroups.value[0] || '').trim()
|
||||
: ''
|
||||
const finalServiceId = isWholeProcessGroupedMode.value
|
||||
? (() => {
|
||||
const pureRows = getPersistableRows(rowData.value)
|
||||
const hit = pureRows.find(item => String(item.serviceGroup || '').trim() === finalGroupName && item.serviceid != null)
|
||||
return hit?.serviceid ?? null
|
||||
})()
|
||||
: null
|
||||
const nextRow: WorkContentRow = {
|
||||
id: `custom-${ts}`,
|
||||
content: '',
|
||||
type: '自定义' as WorkType,
|
||||
serviceGroup: finalGroupName,
|
||||
serviceid: finalServiceId,
|
||||
remark: '',
|
||||
checked: false,
|
||||
custom: true,
|
||||
|
||||
@ -26,19 +26,20 @@ interface TypeLineCategoryItem {
|
||||
const props = defineProps<{
|
||||
sourceTitle?: string
|
||||
storageKey: string
|
||||
rowId: string
|
||||
rowId: string | number
|
||||
rowName?: string
|
||||
contractId?: string
|
||||
contractName?: string
|
||||
}>()
|
||||
const sourceTitleText = computed(() => props.sourceTitle || '费用明细')
|
||||
const rowNameText = computed(() => props.rowName || '未命名')
|
||||
const rowIdText = computed(() => String(props.rowId || '').trim())
|
||||
const contractIdText = computed(() => String(props.contractId || '').trim())
|
||||
const contractNameText = computed(() => String(props.contractName || '').trim() || contractIdText.value || '-')
|
||||
const titleText = computed(() => `合同段:${contractNameText.value} · ${rowNameText.value || sourceTitleText.value}`)
|
||||
const activeTypeStorageKey = computed(() => `ht-fee-type-active-cat-${props.storageKey}-${props.rowId}`)
|
||||
const activeTypeStorageKey = computed(() => `ht-fee-type-active-cat-${props.storageKey}-${rowIdText.value}`)
|
||||
const buildMethodStorageKey = (method: 'rate-fee' | 'hourly-fee' | 'quantity-unit-price-fee') =>
|
||||
`${props.storageKey}-${props.rowId}-${method}`
|
||||
`${props.storageKey}-${rowIdText.value}-${method}`
|
||||
|
||||
const quantityUnitPricePane = markRaw(
|
||||
defineComponent({
|
||||
@ -50,7 +51,7 @@ const quantityUnitPricePane = markRaw(
|
||||
title: '数量单价',
|
||||
storageKey: quantityStorageKey.value,
|
||||
htMainStorageKey: props.storageKey,
|
||||
htRowId: props.rowId,
|
||||
htRowId: rowIdText.value,
|
||||
htMethodType: 'quantity-unit-price-fee'
|
||||
})
|
||||
}
|
||||
@ -67,7 +68,7 @@ const rateFeePane = markRaw(
|
||||
storageKey: rateStorageKey.value,
|
||||
contractId: props.contractId,
|
||||
htMainStorageKey: props.storageKey,
|
||||
htRowId: props.rowId,
|
||||
htRowId: rowIdText.value,
|
||||
htMethodType: 'rate-fee'
|
||||
})
|
||||
}
|
||||
@ -84,7 +85,7 @@ const hourlyFeePane = markRaw(
|
||||
title: '工时法明细',
|
||||
storageKey: hourlyStorageKey.value,
|
||||
htMainStorageKey: props.storageKey,
|
||||
htRowId: props.rowId,
|
||||
htRowId: rowIdText.value,
|
||||
htMethodType: 'hourly-fee'
|
||||
})
|
||||
}
|
||||
@ -109,7 +110,7 @@ const workContentPane = markRaw(
|
||||
})
|
||||
return () => h(AsyncWorkContentGrid, {
|
||||
title: '工作内容',
|
||||
storageKey: `work-content-${props.storageKey}-${props.rowId}`,
|
||||
storageKey: `work-content-${props.storageKey}-${rowIdText.value}`,
|
||||
dictMode: 'additional'
|
||||
})
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
|
||||
import { PROJECT_TAB_ID, QUICK_TAB_ID, readWorkspaceMode, writeWorkspaceMode } from '@/lib/workspace'
|
||||
import { addNumbers, roundTo } from '@/lib/decimal'
|
||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||
import { exportFile, serviceList } from '@/sql'
|
||||
|
||||
interface DataEntry {
|
||||
@ -110,6 +111,7 @@ interface WorkContentRowLike {
|
||||
checked?: unknown
|
||||
custom?: unknown
|
||||
serviceGroup?: unknown
|
||||
serviceid?: unknown
|
||||
isAddTrigger?: unknown
|
||||
}
|
||||
|
||||
@ -544,14 +546,11 @@ const finishReportExportProgress = (success: boolean, text: string, blobUrl?: st
|
||||
reportExportStatus.value = success ? 'success' : 'error'
|
||||
reportExportProgress.value = 100
|
||||
reportExportText.value = text
|
||||
console.log(blobUrl)
|
||||
reportExportBlobUrl.value = success && blobUrl ? blobUrl : null
|
||||
reportExportToastOpen.value = true
|
||||
if (!success || !blobUrl) {
|
||||
reportExportToastTimer = setTimeout(() => {
|
||||
reportExportToastOpen.value = false
|
||||
}, success ? 1200 : 1800)
|
||||
}
|
||||
reportExportToastTimer = setTimeout(() => {
|
||||
reportExportToastOpen.value = false
|
||||
}, success ? 2000 : 1800)
|
||||
}
|
||||
|
||||
const openExportedReport = () => {
|
||||
@ -1037,6 +1036,7 @@ const sumNumbers = (values: Array<number | null | undefined>): number =>
|
||||
(sum, value) => sum + (typeof value === 'number' && Number.isFinite(value) ? value : 0),
|
||||
0
|
||||
)
|
||||
const toMoney = (value: unknown): number => roundTo(toFiniteNumber(value) ?? 0, 2)
|
||||
|
||||
const isNonEmptyString = (value: unknown): value is string =>
|
||||
typeof value === 'string' && value.trim().length > 0
|
||||
@ -1100,10 +1100,42 @@ const toScaleProNum = (row: ScaleMethodRowLike): number => {
|
||||
}
|
||||
|
||||
const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
||||
const resolveTaskRowServiceId = (row: WorkContentRowLike): number | null =>
|
||||
toSafeInteger((row as { serviceid?: unknown })?.serviceid)
|
||||
const resolveScaleMethodFee = (row: ScaleMethodRowLike, mode: 'cost' | 'area') => {
|
||||
const scaleValue = mode === 'cost' ? toFiniteNumber(row.amount) : toFiniteNumber(row.landArea)
|
||||
const benchmarkSplit = getBenchmarkBudgetSplitByScale(scaleValue, mode)
|
||||
const computedSplit = benchmarkSplit
|
||||
? getScaleBudgetFeeSplit({
|
||||
benchmarkBudgetBasic: benchmarkSplit.basic,
|
||||
benchmarkBudgetOptional: benchmarkSplit.optional,
|
||||
majorFactor: row.majorFactor,
|
||||
consultCategoryFactor: row.consultCategoryFactor,
|
||||
workStageFactor: row.workStageFactor,
|
||||
workRatio: row.workRatio
|
||||
})
|
||||
: null
|
||||
const basicFee = toFiniteNumber(row.budgetFee) ?? computedSplit?.total ?? null
|
||||
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic) ?? computedSplit?.basic ?? null
|
||||
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional) ?? computedSplit?.optional ?? null
|
||||
const basicFormula = typeof row.basicFormula === 'string' && row.basicFormula.trim()
|
||||
? row.basicFormula
|
||||
: (benchmarkSplit?.basicFormula ?? '')
|
||||
const optionalFormula = typeof row.optionalFormula === 'string' && row.optionalFormula.trim()
|
||||
? row.optionalFormula
|
||||
: (benchmarkSplit?.optionalFormula ?? '')
|
||||
return {
|
||||
basicFee,
|
||||
basicFeeBasic,
|
||||
basicFeeOptional,
|
||||
basicFormula,
|
||||
optionalFormula
|
||||
}
|
||||
}
|
||||
|
||||
const groupWorkContentTasks = (
|
||||
rows: WorkContentRowLike[] | undefined,
|
||||
options?: { forceUngroup?: boolean; serviceLabelToId?: Map<string, number> }
|
||||
options?: { forceUngroup?: boolean }
|
||||
): ExportTaskGroup[] => {
|
||||
const source = Array.isArray(rows) ? rows : []
|
||||
const selected = source.filter(item => {
|
||||
@ -1114,7 +1146,7 @@ const groupWorkContentTasks = (
|
||||
})
|
||||
if (selected.length === 0) return []
|
||||
|
||||
const hasGroup = !options?.forceUngroup && selected.some(item => normalizeTaskText(item?.serviceGroup).length > 0)
|
||||
const hasGroup = !options?.forceUngroup && selected.some(item => resolveTaskRowServiceId(item) != null)
|
||||
if (!hasGroup) {
|
||||
const text = selected
|
||||
.map(item => normalizeTaskText(item?.content))
|
||||
@ -1122,31 +1154,35 @@ const groupWorkContentTasks = (
|
||||
return text.length > 0 ? [{ text }] : []
|
||||
}
|
||||
|
||||
const grouped = new Map<string, string[]>()
|
||||
const orderedGroupKeys: string[] = []
|
||||
const grouped = new Map<number, string[]>()
|
||||
const orderedServiceIds: number[] = []
|
||||
const ungroupedText: string[] = []
|
||||
for (const item of selected) {
|
||||
const groupName = normalizeTaskText(item?.serviceGroup)
|
||||
const key = groupName || '__ungrouped__'
|
||||
if (!grouped.has(key)) {
|
||||
grouped.set(key, [])
|
||||
orderedGroupKeys.push(key)
|
||||
}
|
||||
const content = normalizeTaskText(item?.content)
|
||||
if (!content) continue
|
||||
grouped.get(key)?.push(content)
|
||||
const serviceid = resolveTaskRowServiceId(item)
|
||||
if (serviceid == null) {
|
||||
ungroupedText.push(content)
|
||||
continue
|
||||
}
|
||||
if (!grouped.has(serviceid)) {
|
||||
grouped.set(serviceid, [])
|
||||
orderedServiceIds.push(serviceid)
|
||||
}
|
||||
grouped.get(serviceid)?.push(content)
|
||||
}
|
||||
|
||||
const byLabel = options?.serviceLabelToId || new Map<string, number>()
|
||||
return orderedGroupKeys
|
||||
.map(groupName => {
|
||||
const text = grouped.get(groupName) || []
|
||||
if (text.length === 0) return null
|
||||
const entry: ExportTaskGroup = { text }
|
||||
const resolvedServiceId = byLabel.get(groupName)
|
||||
if (resolvedServiceId != null) entry.serviceid = resolvedServiceId
|
||||
return entry
|
||||
})
|
||||
.filter((item): item is ExportTaskGroup => Boolean(item))
|
||||
const groupedTasks: ExportTaskGroup[] = []
|
||||
for (const serviceid of orderedServiceIds) {
|
||||
const text = grouped.get(serviceid) || []
|
||||
if (text.length === 0) continue
|
||||
groupedTasks.push({ serviceid, text })
|
||||
}
|
||||
|
||||
if (ungroupedText.length > 0) {
|
||||
groupedTasks.push({ text: ungroupedText })
|
||||
}
|
||||
return groupedTasks
|
||||
}
|
||||
|
||||
const buildProjectServiceCoes = (rows: FactorRowLike[] | undefined): ExportServiceCoe[] => {
|
||||
@ -1205,14 +1241,15 @@ const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | n
|
||||
const det = rows
|
||||
.map(row => {
|
||||
const major = toScaleMajorId(row)
|
||||
if (major == null || row.budgetFee == null) return null
|
||||
if (major == null) return null
|
||||
const proNum = toScaleProNum(row)
|
||||
proSet.add(proNum)
|
||||
const cost = toFiniteNumber(row.amount)
|
||||
const basicFee = toFiniteNumber(row.budgetFee)
|
||||
const feeResolved = resolveScaleMethodFee(row, 'cost')
|
||||
const basicFee = feeResolved.basicFee
|
||||
if (basicFee != null) hasTotalValue = true
|
||||
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic)
|
||||
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional)
|
||||
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||
const hasValue =
|
||||
cost != null ||
|
||||
@ -1225,16 +1262,16 @@ const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | n
|
||||
proNum,
|
||||
major,
|
||||
cost: cost ?? 0,
|
||||
basicFee: basicFee ?? 0,
|
||||
basicFormula: typeof row.basicFormula === 'string' ? row.basicFormula : '',
|
||||
basicFee_basic: basicFeeBasic ?? 0,
|
||||
optionalFormula: typeof row.optionalFormula === 'string' ? row.optionalFormula : '',
|
||||
basicFee_optional: basicFeeOptional ?? 0,
|
||||
basicFee: toMoney(basicFee),
|
||||
basicFormula: feeResolved.basicFormula,
|
||||
basicFee_basic: toMoney(basicFeeBasic),
|
||||
optionalFormula: feeResolved.optionalFormula,
|
||||
basicFee_optional: toMoney(basicFeeOptional),
|
||||
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||
fee: basicFee ?? 0,
|
||||
fee: toMoney(basicFee),
|
||||
remark
|
||||
}
|
||||
})
|
||||
@ -1244,10 +1281,10 @@ const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | n
|
||||
return {
|
||||
proAmount: proSet.size > 0 ? proSet.size : 1,
|
||||
cost: sumNumbers(det.map(item => item.cost)),
|
||||
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
||||
basicFee_basic: sumNumbers(det.map(item => item.basicFee_basic)),
|
||||
basicFee_optional: sumNumbers(det.map(item => item.basicFee_optional)),
|
||||
fee: sumNumbers(det.map(item => item.fee)),
|
||||
basicFee: toMoney(sumNumbers(det.map(item => item.basicFee))),
|
||||
basicFee_basic: toMoney(sumNumbers(det.map(item => item.basicFee_basic))),
|
||||
basicFee_optional: toMoney(sumNumbers(det.map(item => item.basicFee_optional))),
|
||||
fee: toMoney(sumNumbers(det.map(item => item.fee))),
|
||||
det
|
||||
}
|
||||
}
|
||||
@ -1259,14 +1296,15 @@ const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | n
|
||||
const det = rows
|
||||
.map(row => {
|
||||
const major = toScaleMajorId(row)
|
||||
if (major == null || row.budgetFee == null) return null
|
||||
if (major == null) return null
|
||||
const proNum = toScaleProNum(row)
|
||||
proSet.add(proNum)
|
||||
const area = toFiniteNumber(row.landArea)
|
||||
const basicFee = toFiniteNumber(row.budgetFee)
|
||||
const feeResolved = resolveScaleMethodFee(row, 'area')
|
||||
const basicFee = feeResolved.basicFee
|
||||
if (basicFee != null) hasTotalValue = true
|
||||
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic)
|
||||
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional)
|
||||
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||
const hasValue =
|
||||
area != null ||
|
||||
@ -1279,16 +1317,16 @@ const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | n
|
||||
proNum,
|
||||
major,
|
||||
area: area ?? 0,
|
||||
basicFee: basicFee ?? 0,
|
||||
basicFormula: typeof row.basicFormula === 'string' ? row.basicFormula : '',
|
||||
basicFee_basic: basicFeeBasic ?? 0,
|
||||
optionalFormula: typeof row.optionalFormula === 'string' ? row.optionalFormula : '',
|
||||
basicFee_optional: basicFeeOptional ?? 0,
|
||||
basicFee: toMoney(basicFee),
|
||||
basicFormula: feeResolved.basicFormula,
|
||||
basicFee_basic: toMoney(basicFeeBasic),
|
||||
optionalFormula: feeResolved.optionalFormula,
|
||||
basicFee_optional: toMoney(basicFeeOptional),
|
||||
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||
fee: basicFee ?? 0,
|
||||
fee: toMoney(basicFee),
|
||||
remark
|
||||
}
|
||||
})
|
||||
@ -1298,10 +1336,10 @@ const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | n
|
||||
return {
|
||||
proAmount: proSet.size > 0 ? proSet.size : 1,
|
||||
area: sumNumbers(det.map(item => item.area)),
|
||||
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
||||
basicFee_basic: sumNumbers(det.map(item => item.basicFee_basic)),
|
||||
basicFee_optional: sumNumbers(det.map(item => item.basicFee_optional)),
|
||||
fee: sumNumbers(det.map(item => item.fee)),
|
||||
basicFee: toMoney(sumNumbers(det.map(item => item.basicFee))),
|
||||
basicFee_basic: toMoney(sumNumbers(det.map(item => item.basicFee_basic))),
|
||||
basicFee_optional: toMoney(sumNumbers(det.map(item => item.basicFee_optional))),
|
||||
fee: toMoney(sumNumbers(det.map(item => item.fee))),
|
||||
det
|
||||
}
|
||||
}
|
||||
@ -1480,27 +1518,12 @@ const loadHtFeeMethodsByRow = async (mainStorageKey: string, rowId: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const buildServiceGroupLabelToIdMap = (serviceIds: string[]): Map<string, number> => {
|
||||
const map = new Map<string, number>()
|
||||
for (const serviceId of serviceIds) {
|
||||
const item = (serviceList as Record<string, any>)[serviceId]
|
||||
if (!item) continue
|
||||
const id = toSafeInteger(serviceId)
|
||||
if (id == null) continue
|
||||
const label = `${String(item.code || '').trim()} ${String(item.name || '').trim()}`.trim()
|
||||
if (!label) continue
|
||||
map.set(label, id)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
const buildServiceTasks = async (
|
||||
contractId: string,
|
||||
serviceId: string,
|
||||
serviceLabelToId: Map<string, number>
|
||||
serviceId: string
|
||||
): Promise<ExportTaskGroup[]> => {
|
||||
const taskState = await zxFwPricingStore.loadKeyState<WorkContentStateLike>(`work-content-${contractId}-${serviceId}`)
|
||||
return groupWorkContentTasks(taskState?.detailRows, { serviceLabelToId })
|
||||
return groupWorkContentTasks(taskState?.detailRows)
|
||||
}
|
||||
|
||||
const buildAdditionalRowTasks = async (contractId: string, rowId: string): Promise<ExportTaskGroup[]> => {
|
||||
@ -1631,7 +1654,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
kvStore.getItem<ZxFwStorageLike>(`zxFW-${contractId}`),
|
||||
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(`ht-consult-category-factor-v1-${contractId}`),
|
||||
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(`ht-major-factor-v1-${contractId}`),
|
||||
kvStore.getItem<HtBaseInfoLike>(`ht-base-info-${contractId}`)
|
||||
zxFwPricingStore.loadKeyState<HtBaseInfoLike>(`ht-base-info-${contractId}`)
|
||||
])
|
||||
|
||||
const contractState = zxFwPricingStore.getContractState(contractId)
|
||||
@ -1673,8 +1696,6 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
const serviceIdTexts = sortServiceIdsByDict(
|
||||
(selectedIds.length > 0 ? selectedIds : fallbackServiceIds).filter(hasServiceId)
|
||||
)
|
||||
const serviceLabelToId = buildServiceGroupLabelToIdMap(serviceIdTexts)
|
||||
|
||||
const services = (
|
||||
await Promise.all(
|
||||
serviceIdTexts.map(async serviceIdText => {
|
||||
@ -1699,7 +1720,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
const method4 = buildMethod4(method4Raw?.detailRows)
|
||||
const fee = buildServiceFee(sourceRow, method1, method2, method3, method4)
|
||||
const finalFee = buildServiceFinalFee(sourceRow, method1, method2, method3, method4)
|
||||
const tasks = await buildServiceTasks(contractId, serviceIdText, serviceLabelToId)
|
||||
const tasks = await buildServiceTasks(contractId, serviceIdText)
|
||||
const process = Number(sourceRow?.process) === 1 ? 1 : 0
|
||||
const service: ExportService = {
|
||||
id: serviceId,
|
||||
@ -1717,6 +1738,8 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
)
|
||||
).filter((item): item is ExportService => Boolean(item))
|
||||
|
||||
const fixedFinalFee = toFiniteNumber(fixedRow?.finalFee)
|
||||
const serviceFinalFeeSum = sumNumbers(services.map(item => item.finalFee))
|
||||
const fixedSubtotal = toFiniteNumber(fixedRow?.subtotal)
|
||||
const serviceFeeSum = sumNumbers(services.map(item => item.fee))
|
||||
const fixedMethodSum = sumNumbers([
|
||||
@ -1725,7 +1748,9 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
toFiniteNumber(fixedRow?.workload),
|
||||
toFiniteNumber(fixedRow?.hourly)
|
||||
])
|
||||
const serviceFee = fixedSubtotal ?? (serviceFeeSum !== 0 ? serviceFeeSum : fixedMethodSum)
|
||||
const serviceFee =
|
||||
fixedFinalFee ??
|
||||
(services.length > 0 ? serviceFinalFeeSum : (fixedSubtotal ?? (serviceFeeSum !== 0 ? serviceFeeSum : fixedMethodSum)))
|
||||
const [addtional, reserve] = await Promise.all([
|
||||
buildAdditionalExport(contractId),
|
||||
buildReserveExport(contractId)
|
||||
@ -1741,7 +1766,6 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
const contractServiceCoesRaw = buildProjectServiceCoes(htConsultCategoryFactorRaw?.detailRows)
|
||||
const contractMajorCoesRaw = buildProjectMajorCoes(htMajorFactorRaw?.detailRows)
|
||||
|
||||
|
||||
contracts.push({
|
||||
name: isNonEmptyString(contract.name) ? contract.name : `合同段-${index + 1}`,
|
||||
serviceFee,
|
||||
@ -2291,9 +2315,19 @@ watch(
|
||||
class="pointer-events-auto rounded-xl border border-border bg-card px-4 py-3 text-foreground shadow-lg"
|
||||
@update:open="(val) => { if (!val) dismissReportToast() }"
|
||||
>
|
||||
<ToastTitle class="text-sm font-semibold text-foreground">
|
||||
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
|
||||
</ToastTitle>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<ToastTitle class="text-sm font-semibold text-foreground">
|
||||
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
|
||||
</ToastTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
|
||||
@click="dismissReportToast"
|
||||
>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<ToastDescription class="mt-1 text-xs text-muted-foreground">{{ reportExportText }}</ToastDescription>
|
||||
<!-- <div v-if="reportExportStatus === 'success' && reportExportBlobUrl" class="mt-2 flex items-center gap-2">
|
||||
<Button size="sm" class="h-7 rounded-md px-3 text-xs" @click="openExportedReport">
|
||||
|
||||
@ -30,6 +30,7 @@ const props = withDefaults(
|
||||
categories: TypeLineCategory[]
|
||||
storageKey?: string
|
||||
defaultCategory?: string
|
||||
persistActiveCategory?: boolean
|
||||
}>(),
|
||||
{
|
||||
scene: 'default',
|
||||
@ -38,15 +39,28 @@ const props = withDefaults(
|
||||
metaText: '',
|
||||
copyText: '',
|
||||
storageKey: '',
|
||||
defaultCategory: ''
|
||||
defaultCategory: '',
|
||||
persistActiveCategory: true
|
||||
}
|
||||
)
|
||||
|
||||
const cacheKey = computed(() => props.storageKey || `type-line-active-cat-${props.scene}`)
|
||||
|
||||
const readStoredCategory = (key: string) => {
|
||||
const sessionValue = sessionStorage.getItem(key)
|
||||
if (sessionValue) return sessionValue
|
||||
return localStorage.getItem(key)
|
||||
}
|
||||
|
||||
const writeStoredCategory = (key: string, value: string) => {
|
||||
sessionStorage.setItem(key, value)
|
||||
localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
const resolveInitialCategory = () => {
|
||||
const defaultKey = props.defaultCategory || props.categories[0]?.key || ''
|
||||
const savedKey = sessionStorage.getItem(cacheKey.value)
|
||||
if (!props.persistActiveCategory) return defaultKey
|
||||
const savedKey = readStoredCategory(cacheKey.value)
|
||||
const validSavedKey = props.categories.some(item => item.key === savedKey)
|
||||
return validSavedKey ? (savedKey as string) : defaultKey
|
||||
}
|
||||
@ -56,6 +70,8 @@ const activeCategory = ref(resolveInitialCategory())
|
||||
watch(
|
||||
() => [props.categories, props.defaultCategory, cacheKey.value],
|
||||
() => {
|
||||
const isCurrentValid = props.categories.some(item => item.key === activeCategory.value)
|
||||
if (isCurrentValid) return
|
||||
activeCategory.value = resolveInitialCategory()
|
||||
},
|
||||
{ deep: true }
|
||||
@ -65,7 +81,8 @@ watch(
|
||||
|
||||
const switchCategory = (cat: string) => {
|
||||
activeCategory.value = cat
|
||||
sessionStorage.setItem(cacheKey.value, cat)
|
||||
if (!props.persistActiveCategory) return
|
||||
writeStoredCategory(cacheKey.value, cat)
|
||||
}
|
||||
|
||||
const activeComponent = computed(() => {
|
||||
|
||||
@ -189,6 +189,20 @@ const toRowMap = <TRow extends { id: string }>(rows?: TRow[]) => {
|
||||
return map
|
||||
}
|
||||
|
||||
const parseScopedMajorId = (value: unknown) => {
|
||||
const raw = String(value || '').trim()
|
||||
const scoped = /^\d+::(.+)$/.exec(raw)
|
||||
return (scoped ? String(scoped[1] || '').trim() : raw) || raw
|
||||
}
|
||||
|
||||
const hasScopedScaleRows = (rows?: Array<Record<string, unknown>>) =>
|
||||
(rows || []).some(row => {
|
||||
const id = String(row?.id || '')
|
||||
if (/^\d+::/.test(id)) return true
|
||||
const projectIndex = Number((row as { projectIndex?: unknown })?.projectIndex)
|
||||
return Number.isFinite(projectIndex) && projectIndex > 1
|
||||
})
|
||||
|
||||
const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
||||
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||
return toFiniteNumberOrNull(service?.defCoe)
|
||||
@ -614,6 +628,44 @@ const resolveScaleRows = (
|
||||
return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap)
|
||||
}
|
||||
|
||||
const normalizeScopedScaleRows = (
|
||||
serviceId: string,
|
||||
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
||||
consultCategoryFactorMap?: Map<string, number | null>,
|
||||
majorFactorMap?: Map<string, number | null>
|
||||
): ScaleRow[] => {
|
||||
const rows = stripGroupScaleRows(rowsFromDb) as Array<Record<string, unknown>>
|
||||
if (rows.length === 0) return []
|
||||
const defaultConsultCategoryFactor =
|
||||
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
||||
|
||||
return rows.map(row => {
|
||||
const parsedMajorId = parseScopedMajorId(row.id)
|
||||
const resolvedMajorId = majorById.has(parsedMajorId) ? parsedMajorId : (majorIdAliasMap.get(parsedMajorId) || parsedMajorId)
|
||||
const hasConsultCategoryFactor = hasOwn(row, 'consultCategoryFactor')
|
||||
const hasMajorFactor = hasOwn(row, 'majorFactor')
|
||||
const hasWorkStageFactor = hasOwn(row, 'workStageFactor')
|
||||
const hasWorkRatio = hasOwn(row, 'workRatio')
|
||||
return {
|
||||
id: resolvedMajorId,
|
||||
amount: toFiniteNumberOrNull(row.amount),
|
||||
landArea: toFiniteNumberOrNull(row.landArea),
|
||||
consultCategoryFactor:
|
||||
toFiniteNumberOrNull(row.consultCategoryFactor) ??
|
||||
(hasConsultCategoryFactor ? null : defaultConsultCategoryFactor),
|
||||
majorFactor:
|
||||
toFiniteNumberOrNull(row.majorFactor) ??
|
||||
(hasMajorFactor ? null : (majorFactorMap?.get(resolvedMajorId) ?? getDefaultMajorFactorById(resolvedMajorId))),
|
||||
workStageFactor:
|
||||
toFiniteNumberOrNull(row.workStageFactor) ??
|
||||
(hasWorkStageFactor ? null : 1),
|
||||
workRatio:
|
||||
toFiniteNumberOrNull(row.workRatio) ??
|
||||
(hasWorkRatio ? null : 100)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 统一生成某合同下某个咨询服务四种计费方式的存储键。
|
||||
// 优先复用 Pinia store 当前约定的 key,避免与旧版 fallback key 脱节。
|
||||
export const getPricingMethodDetailDbKeys = (
|
||||
@ -778,6 +830,14 @@ export const getPricingMethodTotalsForService = async (params: {
|
||||
|
||||
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
||||
const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true
|
||||
const investScaleRowsSource = stripGroupScaleRows(investData?.detailRows as Array<Record<string, unknown>> | undefined)
|
||||
const landScaleRowsSource = stripGroupScaleRows(landData?.detailRows as Array<Record<string, unknown>> | undefined)
|
||||
const scopedInvestRows = hasScopedScaleRows(investScaleRowsSource)
|
||||
? normalizeScopedScaleRows(serviceId, investScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
||||
: null
|
||||
const scopedLandRows = hasScopedScaleRows(landScaleRowsSource)
|
||||
? normalizeScopedScaleRows(serviceId, landScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
||||
: null
|
||||
const investScale = onlyCostScale
|
||||
? getOnlyCostScaleBudgetFee(
|
||||
serviceId,
|
||||
@ -788,7 +848,7 @@ export const getPricingMethodTotalsForService = async (params: {
|
||||
industryId
|
||||
)
|
||||
: (() => {
|
||||
const investRows = resolveScaleRows(
|
||||
const investRows = scopedInvestRows || resolveScaleRows(
|
||||
serviceId,
|
||||
investData,
|
||||
htData,
|
||||
@ -802,7 +862,7 @@ export const getPricingMethodTotalsForService = async (params: {
|
||||
})
|
||||
})()
|
||||
|
||||
const landRows = resolveScaleRows(
|
||||
const landRows = scopedLandRows || resolveScaleRows(
|
||||
serviceId,
|
||||
landData,
|
||||
htData,
|
||||
|
||||
@ -857,7 +857,7 @@ export async function exportFile(fileName: string, data: any | (() => Promise<an
|
||||
|
||||
// 按模板生成最终工作簿:填充封面、目录、各分表及汇总数据。
|
||||
async function generateTemplate(data) {
|
||||
// const downTextTmp = { richText: [{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: '常规' }, { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: '下标' }] };
|
||||
console.log(data)
|
||||
// 编制说明 → 工作内容的前后默认项
|
||||
let prefixIDs = [6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27];
|
||||
let suffixIDs = [6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user