fix: handle risk explanation standard adjustment

This commit is contained in:
caoxiaozhu
2026-06-03 17:31:40 +08:00
parent 67b81a1bd8
commit 8e2477587f
19 changed files with 976 additions and 61 deletions

View File

@@ -32,11 +32,24 @@ export const LONG_DISTANCE_TRAVEL_EXPENSE_TYPES = new Set(['train_ticket', 'flig
export const ROUTE_DESCRIPTION_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket', 'ship_ticket', 'ferry_ticket', 'ride_ticket'])
export const HOTEL_DESCRIPTION_EXPENSE_TYPES = new Set(['hotel_ticket'])
export const ROUTE_DESCRIPTION_PATTERN = /^[A-Za-z0-9\u4e00-\u9fa5()·]{2,40}\s*-\s*[A-Za-z0-9\u4e00-\u9fa5()·]{2,40}$/
export const STANDARD_ADJUSTMENT_RISK_SOURCE = 'reimbursement_standard_adjustment'
export function parseCurrency(value) {
return Number.parseFloat(String(value).replace(/[^\d.]/g, '')) || 0
}
function parseOptionalCurrency(value) {
if (value === null || value === undefined || String(value).trim() === '') {
return null
}
const normalized = String(value).replace(/[^\d.]/g, '')
if (!normalized) {
return null
}
const amount = Number.parseFloat(normalized)
return Number.isFinite(amount) && amount >= 0 ? amount : null
}
export function formatCurrency(value) {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
@@ -395,6 +408,60 @@ export function resolveExpenseTimeLabel({ id, itemType, isSystemGenerated, reque
return requestModel?.detailVariant === 'travel' ? '出行时间' : '业务发生时间'
}
export function buildStandardAdjustmentMap(requestModel = {}) {
const flags = Array.isArray(requestModel?.riskFlags)
? requestModel.riskFlags
: Array.isArray(requestModel?.risk_flags_json)
? requestModel.risk_flags_json
: []
const adjustmentMap = new Map()
flags.forEach((flag) => {
if (!flag || typeof flag !== 'object') {
return
}
if (String(flag.source || '').trim() !== STANDARD_ADJUSTMENT_RISK_SOURCE) {
return
}
const itemId = String(flag.item_id || flag.itemId || '').trim()
if (!itemId) {
return
}
const originalAmount = parseOptionalCurrency(flag.original_amount ?? flag.originalAmount)
const reimbursableAmount = parseOptionalCurrency(flag.reimbursable_amount ?? flag.reimbursableAmount)
if (reimbursableAmount === null) {
return
}
const employeeAbsorbedAmount = parseOptionalCurrency(flag.employee_absorbed_amount ?? flag.employeeAbsorbedAmount) || 0
adjustmentMap.set(itemId, {
originalAmount,
reimbursableAmount,
employeeAbsorbedAmount,
message: String(flag.message || flag.summary || '').trim()
})
})
return adjustmentMap
}
function resolveSourceStandardAdjustment(source, id, requestModel) {
const requestAdjustment = buildStandardAdjustmentMap(requestModel).get(id)
if (requestAdjustment) {
return requestAdjustment
}
const reimbursableAmount = parseOptionalCurrency(source?.reimbursableAmount ?? source?.reimbursable_amount)
if (reimbursableAmount === null) {
return null
}
return {
originalAmount: parseOptionalCurrency(source?.originalItemAmount ?? source?.original_item_amount ?? source?.originalAmount ?? source?.original_amount),
reimbursableAmount,
employeeAbsorbedAmount: parseOptionalCurrency(source?.employeeAbsorbedAmount ?? source?.employee_absorbed_amount) || 0,
message: String(source?.standardAdjustmentMessage || source?.standard_adjustment_message || '').trim()
}
}
export function buildExpenseItemViewModel(source, index, requestModel, travelTimeLabelMap = new Map()) {
const itemType = normalizeExpenseType(source?.itemType || source?.item_type || requestModel?.typeCode || 'other')
const isSystemGenerated = isSystemGeneratedExpenseItemSource({ ...source, itemType })
@@ -407,7 +474,13 @@ export function buildExpenseItemViewModel(source, index, requestModel, travelTim
const invoiceId = String(source?.invoiceId ?? source?.invoice_id ?? '').trim()
const attachmentName = String(source?.attachmentName || source?.attachment_name || extractAttachmentDisplayName(invoiceId)).trim()
const attachments = invoiceId ? [attachmentName || invoiceId] : []
const standardAdjustment = resolveSourceStandardAdjustment(source, id, requestModel)
const originalItemAmount = standardAdjustment?.originalAmount ?? itemAmount
const reimbursableAmount = standardAdjustment?.reimbursableAmount ?? itemAmount
const employeeAbsorbedAmount = standardAdjustment?.employeeAbsorbedAmount || Math.max(originalItemAmount - reimbursableAmount, 0)
const hasStandardAdjustment = reimbursableAmount >= 0 && reimbursableAmount < originalItemAmount
const amountDisplay = itemAmount > 0 ? formatCurrency(itemAmount) : '待补充'
const reimbursableAmountDisplay = reimbursableAmount > 0 ? formatCurrency(reimbursableAmount) : '待补充'
const riskText = String(source?.riskText || '').trim()
const filledAt = formatExpenseFilledTime(
source?.filledAt
@@ -424,6 +497,15 @@ export function buildExpenseItemViewModel(source, index, requestModel, travelTim
itemLocation,
itemNote,
itemAmount,
originalItemAmount,
originalAmountDisplay: originalItemAmount > 0 ? formatCurrency(originalItemAmount) : amountDisplay,
reimbursableAmount,
reimbursableAmountDisplay,
employeeAbsorbedAmount,
employeeAbsorbedAmountDisplay: employeeAbsorbedAmount > 0 ? formatCurrency(employeeAbsorbedAmount) : '',
hasStandardAdjustment,
standardAdjustmentAccepted: Boolean(standardAdjustment),
standardAdjustmentMessage: standardAdjustment?.message || '',
invoiceId,
isSystemGenerated,
time: itemDate || '待补充',