/** * 后端 VO ↔ 新前端页面展示模型映射(不新增页面字段,只做值转换) */ import type { AlertItem, DiskFailPerson, DiskRecord, Person, ProcessLogItem, SpecialBiz, SubsidyStandard, VillageBatch, VillageMonthlyBatch, } from '@/types/biz' import { regionName } from './regionStore' /** 后端批次 status → 前端 VillageBatch.stage(页面流程文案) */ const BATCH_STATUS_TO_STAGE: Record = { DRAFT: '人员新增录入', PENDING_TOWN_APPROVE: '镇经发中心初审', PENDING_INSURANCE_MATCH: '居保状态匹配', PENDING_STATUS_MAINTAIN: '比对结果已生成', PENDING_LEADER_APPROVE: '分管领导审批', READY_EXPORT: '待线下发放', EXPORTED: '回盘信息确认', RETURNED: '已回盘确认', ARCHIVED: '已归档', REJECTED: '退回修改', } /** 后端 status → 月补贴清单展示 status */ const BATCH_STATUS_TO_MONTHLY: Record = { PENDING_STATUS_MAINTAIN: '待提交', PENDING_LEADER_APPROVE: '待审核', READY_EXPORT: '已审核', EXPORTED: '已出盘', RETURNED: '已回盘', ARCHIVED: '已回盘', } const PERSON_STATUS_LABEL: Record = { DRAFT: '草稿', PENDING_TOWN_APPROVE: '待镇审批', PENDING_INSURANCE_MATCH: '待居保匹配', NORMAL: '正常在库', PAUSED: '暂停', STOPPED: '停止', FUNERAL: '死亡丧葬', ABNORMAL: '异常待处理', ARCHIVED: '已归档', } const INSURANCE_LABEL: Record = { PENDING: '未匹配', SUCCESS: '匹配通过', LOSE_ELIGIBILITY: '丧失资格', FAIL: '匹配失败', MANUAL_PASS: '匹配通过', } const ALERT_STATUS_LABEL: Record = { PENDING: '待处理', PROCESSING: '处理中', CLOSED: '已关闭', } const ALERT_LEVEL_LABEL: Record = { HIGH: '高', MEDIUM: '中', LOW: '低', } const SPECIAL_TYPE_LABEL: Record = { FUNERAL: '丧葬补贴', PAUSE: '暂停发放', STOP: '停止发放', RESUME: '恢复发放', STANDARD_ADJUST: '调标差额补发', SUPPLEMENT: '纳入下月补发', REFUND: '退款', HEIR_UPDATE: '继承人信息变更', } const SPECIAL_TYPE_CODE: Record = { 丧葬补贴: 'FUNERAL', 丧葬: 'FUNERAL', 暂停发放: 'PAUSE', 暂停: 'PAUSE', 停止发放: 'STOP', 停止: 'STOP', 恢复发放: 'RESUME', 恢复: 'RESUME', 调标差额补发: 'STANDARD_ADJUST', 调标补发: 'STANDARD_ADJUST', 纳入下月补发: 'SUPPLEMENT', 补发: 'SUPPLEMENT', } /** 发放年月形态:20 开头 6 位(含调标月键如 202699) */ export function isPayMonthLike(value?: string): boolean { const s = String(value || '').replace(/-/g, '') return /^20\d{4}$/.test(s) } export function payMonthToMonth(payMonth?: string): string { if (!payMonth) return '' const s = String(payMonth).replace(/-/g, '') if (s.length === 6) return `${s.slice(0, 4)}-${s.slice(4)}` return payMonth } export function monthToPayMonth(month?: string): string { if (!month) return '' return String(month).replace(/-/g, '') } /** * 统一批次号展示:优先发放年月 payMonth(如 202608) * 历史库里可能把业务号前 6 位(如 214808)误填进 batchNo,需避开 */ export function resolveUnifiedBatchNo(raw: Record): string { const pay = String(raw.payMonth || '').replace(/-/g, '') const no = String(raw.batchNo || '').replace(/-/g, '') if (isPayMonthLike(pay)) return pay if (isPayMonthLike(no)) return no // 都不是合法发放月:仍回传原值,便于排查脏数据 return String(raw.batchNo || raw.payMonth || raw.id || '') } export function fmtTime(ts?: number | string | null): string { if (ts == null || ts === '') return '' const n = Number(ts) if (!Number.isFinite(n) || n <= 0) return String(ts) const d = new Date(n) if (Number.isNaN(d.getTime())) return String(ts) const p = (x: number) => String(x).padStart(2, '0') return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}` } export function mapPerson(raw: Record, batchNoHint?: string): Person { const biz = String(raw.bizStatus || '') const ins = String(raw.insuranceMatchResult || raw.insuranceStatus || '') let status = PERSON_STATUS_LABEL[biz] || biz || '草稿' // 居保失败待人工:页面用「待人工确认」 if (ins === 'FAIL' || status === '异常待处理') status = '待人工确认' // 资格备注 const remarks = String(raw.remarks || '') let retireQualifyType: Person['retireQualifyType'] if (remarks.includes('无土地')) retireQualifyType = '无土地有资格' else if (remarks.includes('有土地') || remarks.includes('资格已核验')) retireQualifyType = '有土地退养' return { id: String(raw.id || '') as unknown as number, batchNo: batchNoHint || '', name: String(raw.name || ''), idCard: String(raw.idNumber || ''), town: regionName(String(raw.townId || '')), village: regionName(String(raw.villageId || '')), bankCard: String(raw.bankCardNumber || ''), bankName: String(raw.bankName || ''), socialInsuranceCode: raw.socialInsuranceCode ? String(raw.socialInsuranceCode) : undefined, // 月补只认街镇标准表关联结果;无标准时勿回退 0/陈旧冗余值 standard: raw.monthlyStandard == null || raw.monthlyStandard === '' ? undefined : Number(raw.monthlyStandard), startMonth: payMonthToMonth(String(raw.enjoyStartMonth || '')), status, source: String(raw.source || ''), insuranceStatus: INSURANCE_LABEL[ins] || String(raw.insuranceStatus || '未匹配'), remark: remarks || undefined, retireQualifyType, } } const FLOW_STAGE_LABEL: Record = { STAGE1_PERSONNEL_INPUT: '人员新增录入', STAGE2_TOWN_PRECHECK: '镇经发中心初审', STAGE3_INSURANCE_MATCH: '居保状态匹配', STAGE4_MONTHLY_LIST: '生成月补贴清单', STAGE5_LEADER_ONLINE_APPROVE: '分管领导审批', STAGE6_COMPARE_FILE: '生成对照文件', STAGE7_OFFLINE_SIGN_PAY: '待线下发放', STAGE8_RETURN_CONFIRM: '回盘信息确认', } export function flowStageLabel(code?: string): string { if (!code) return '' return FLOW_STAGE_LABEL[code] || BATCH_STATUS_TO_STAGE[code] || code } function resolveSubmittedAt(raw: Record): string | undefined { const fromField = fmtTime(raw.submitTime as number | string) || fmtTime(raw.updateTime as number | string) if (fromField) return fromField const remarks = String(raw.remarks || '') const m = remarks.match(/SUBMIT_TIME\|(\d+)/) if (m?.[1]) return fmtTime(m[1]) return undefined } export function mapVillageBatch(raw: Record): VillageBatch { const status = String(raw.status || '') const stage = BATCH_STATUS_TO_STAGE[status] || flowStageLabel(String(raw.currentStage || '')) || status const batchNo = resolveUnifiedBatchNo(raw) const payMonth = isPayMonthLike(String(raw.payMonth || '')) ? String(raw.payMonth).replace(/-/g, '') : batchNo return { id: String(raw.id || ''), batchNo, month: payMonthToMonth(payMonth), town: regionName(String(raw.townId || '')), village: regionName(String(raw.villageId || '')), peopleCount: Number(raw.peopleCount || 0), totalAmount: Number(raw.totalAmount || 0), stage, submitter: String(raw.submitter || ''), submittedAt: resolveSubmittedAt(raw), statusCode: status || undefined, exportStatus: raw.exportStatus ? String(raw.exportStatus) : undefined, returnStatus: raw.returnStatus ? String(raw.returnStatus) : undefined, exportDiskNo: raw.exportDiskNo ? String(raw.exportDiskNo) : undefined, exportFileName: raw.exportFileName ? String(raw.exportFileName) : undefined, townOpinion: raw.townOpinion ? String(raw.townOpinion) : undefined, remarks: raw.remarks ? String(raw.remarks) : undefined, normalAmount: Number(raw.normalAmount || 0) || undefined, supplementAmount: Number(raw.supplementAmount || 0) || undefined, deductAmount: Number(raw.deductAmount || 0) || undefined, } } export function mapProcessLog(raw: Record): ProcessLogItem { return { id: String(raw.id || ''), batchId: raw.batchId ? String(raw.batchId) : undefined, batchNo: raw.batchNo ? String(raw.batchNo) : undefined, stage: flowStageLabel(String(raw.stage || '')), action: String(raw.action || raw.approveNode || '办理'), approveNode: raw.approveNode ? String(raw.approveNode) : undefined, operator: String(raw.operator || raw.operatorRole || ''), operatorRole: raw.operatorRole ? String(raw.operatorRole) : undefined, operatorOrg: raw.operatorOrg ? String(raw.operatorOrg) : undefined, opinion: raw.opinion ? String(raw.opinion) : undefined, result: String(raw.result || raw.status || ''), status: raw.status ? String(raw.status) : undefined, operateTime: fmtTime(raw.operateTime as number), fileUrls: raw.fileUrls ? String(raw.fileUrls) : undefined, } } export function mapVillageMonthly(raw: Record): VillageMonthlyBatch { const b = mapVillageBatch(raw) const status = String(raw.status || '') return { id: b.id, batchNo: b.batchNo, month: b.month, town: b.town, village: b.village, peopleCount: b.peopleCount, normalAmount: Number(raw.normalAmount || 0), extraAmount: Number(raw.supplementAmount || 0), deductAmount: Number(raw.deductAmount || 0), totalAmount: Number(raw.totalAmount || b.totalAmount || 0), status: BATCH_STATUS_TO_MONTHLY[status] || b.stage, stage: b.stage, submitter: b.submitter, submittedAt: b.submittedAt, } } export function mapDisk(raw: Record): DiskRecord { const status = String(raw.status || '') const returnStatusRaw = String(raw.returnStatus || '未回盘') let diskStatus = '待回盘' let returnStatus = '待确认' if (status === 'RETURNED' || status === 'ARCHIVED') { diskStatus = '已回盘' if (returnStatusRaw.includes('部分') || returnStatusRaw.includes('异常')) returnStatus = '部分失败' else if (returnStatusRaw.includes('成功') || returnStatusRaw === '全部成功') returnStatus = '成功' else returnStatus = '成功' } else if (status === 'EXPORTED') { diskStatus = '待回盘' returnStatus = '待确认' } return { id: String(raw.id || ''), batchNo: String(raw.batchNo || ''), town: regionName(String(raw.townId || '')), fileName: String(raw.exportFileName || `${raw.batchNo || ''}-月补贴发放文件.xlsx`), peopleCount: Number(raw.peopleCount || 0), amount: Number(raw.totalAmount || 0), status: diskStatus, returnStatus, failCount: 0, returnFileName: undefined, } } export function mapReturnFail(raw: Record, diskId: string): DiskFailPerson { return { id: String(raw.id || raw.paymentDetailId || ''), diskId, batchNo: String(raw.batchId || ''), town: regionName(String(raw.townId || '')), village: regionName(String(raw.villageId || '')), personName: String(raw.name || ''), idCard: String(raw.idNumber || ''), bankCard: undefined, failReason: String(raw.failReasonDesc || raw.failReasonCode || ''), amount: Number(raw.amount ?? raw.actualAmount ?? 0), payStatus: raw.success === false ? '待确认' : '已确认发放', } } function alertBatchLabel(raw: Record): string { const no = String(raw.batchNo || '').trim() if (no) return no const id = String(raw.batchId || '').trim() if (id) return id // 兼容历史:正文里「批次 VB…」 const content = String(raw.content || '') const m = content.match(/批次\s*([A-Za-z0-9_-]+)/) return m?.[1] || '' } export function mapAlert(raw: Record): AlertItem { const hs = String(raw.handleStatus || '') const level = String(raw.level || '') return { id: String(raw.id || ''), batchNo: alertBatchLabel(raw), personName: String(raw.personnelName || ''), town: regionName(String(raw.townId || '')), village: regionName(String(raw.villageId || '')), type: String(raw.type || ''), level: ALERT_LEVEL_LABEL[level] || level, content: String(raw.content || ''), status: ALERT_STATUS_LABEL[hs] || hs || '待处理', result: raw.handleResult ? String(raw.handleResult) : undefined, createdAt: fmtTime(raw.createTime as number), failReason: raw.failReasonCode ? String(raw.failReasonCode) : undefined, } } export function mapSpecial(raw: Record): SpecialBiz { const bizType = String(raw.bizType || '') return { id: String(raw.id || ''), batchNo: String(raw.batchNo || ''), town: regionName(String(raw.townId || '')), village: '', type: SPECIAL_TYPE_LABEL[bizType] || bizType, personName: String(raw.name || ''), reason: String(raw.reason || ''), amount: Number(raw.amount || 0), status: String(raw.approveStatus || ''), heirName: raw.heirName ? String(raw.heirName) : undefined, createdAt: fmtTime(raw.createTime as number), } } export function specialTypeToCode(type: string): string { return SPECIAL_TYPE_CODE[type] || type } export function mapStandard(raw: Record): SubsidyStandard { // effectiveTime 可能是毫秒时间戳或 yyyy-MM-dd let date = '' const effRaw = raw.effectiveTime if (typeof effRaw === 'number' && Number.isFinite(effRaw)) { const d = new Date(effRaw) date = Number.isNaN(d.getTime()) ? '' : `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` } else { const eff = String(effRaw || '') date = eff.length >= 10 ? eff.slice(0, 10) : eff } const year = date ? Number(date.slice(0, 4)) : new Date().getFullYear() // id 可能是字符串,页面用 number 比较「是否最新」——用 hash const idStr = String(raw.id || '') let idNum = Number(idStr) if (!Number.isFinite(idNum)) { idNum = [...idStr].reduce((s, c) => s + c.charCodeAt(0), 0) } return { id: idNum, town: String(raw.townName || regionName(String(raw.townId || ''))), year: Number.isFinite(year) ? year : new Date().getFullYear(), monthly: Number(raw.amount || 0), funeral: Number(raw.funeralAmount || 0), date, reason: String(raw.adjustReason || raw.remarks || ''), } } /** 审批单合成 ID:B:batchId / S:specialId */ export function approvalIdBatch(batchId: string) { return `B:${batchId}` } export function approvalIdSpecial(specialId: string) { return `S:${specialId}` } export function parseApprovalId(id: string): { kind: 'batch' | 'special'; bizId: string } | null { if (id.startsWith('B:')) return { kind: 'batch', bizId: id.slice(2) } if (id.startsWith('S:')) return { kind: 'special', bizId: id.slice(2) } // 兼容直接传批次/特殊单 ID if (id.startsWith('VB') || id.startsWith('MB')) return { kind: 'batch', bizId: id } return { kind: 'special', bizId: id } } export function personIdStr(id: number | string): string { return String(id) }