import { PrismaClient } from '@prisma/client'; import { readFileSync } from 'fs'; const prisma = new PrismaClient(); type OaExpense = { name: string; amount: number; project: string; date: string; purpose: string; remark: string | null; expenseCategory: string; }; type OaLoan = { name: string; amount: number; purpose: string; project: string }; const NAME_ALIAS: Record = { 张靖妍: '张婧妍', 张婧妍: '张婧妍', }; function normName(v?: string | null) { const s = (v || '').trim(); return NAME_ALIAS[s] || s; } function amtEq(a: unknown, b: unknown) { return Math.abs(Number(a) - Number(b)) < 0.021; } function sameProject(oa: string, locals: string[]) { if (!oa) return locals.length === 0; return locals.some((p) => p === oa || p.includes(oa) || oa.includes(p)); } async function main() { const oaExpenses = JSON.parse(readFileSync('/opt/apps/api/prisma/_oa_reimb_map.json', 'utf8')) as OaExpense[]; const oaLoans = JSON.parse(readFileSync('/opt/apps/api/prisma/_oa_loan_map.json', 'utf8')) as OaLoan[]; const expenses = await prisma.expenseClaim.findMany({ include: { applicant: { select: { displayName: true, employee: { select: { name: true } } } }, project: { select: { name: true } }, bidCase: { select: { name: true } }, }, orderBy: { claimNo: 'asc' }, }); const used = new Set(); let patchedExpenses = 0; const unmatchedExpenses: string[] = []; for (const row of expenses) { const names = [normName(row.applicant?.displayName), normName(row.applicant?.employee?.name)].filter(Boolean); const locals = [row.project?.name, row.bidCase?.name].filter((v): v is string => Boolean(v)); const date = String(row.submittedAt || '').slice(0, 10); const idxs = oaExpenses .map((o, i) => ({ o, i })) .filter(({ o, i }) => !used.has(i) && amtEq(o.amount, row.amount) && names.includes(normName(o.name))); const byProj = idxs.filter(({ o }) => sameProject(o.project, locals)); const byDate = idxs.filter(({ o }) => o.date === date); const hit = (byProj.length === 1 ? byProj[0] : null) || (byDate.length === 1 ? byDate[0] : null) || (idxs.length === 1 ? idxs[0] : byProj[0] || byDate[0] || null); if (!hit) { unmatchedExpenses.push(`${row.claimNo} ${names[0] || '?'} ${row.amount}`); continue; } used.add(hit.i); await prisma.expenseClaim.update({ where: { id: row.id }, data: { purpose: hit.o.purpose || null, remark: hit.o.remark, expenseCategory: hit.o.expenseCategory, }, }); if (hit.o.purpose) { await prisma.approvalTask.updateMany({ where: { bizId: row.id, bizType: 'EXPENSE_APPROVAL' }, data: { title: hit.o.purpose }, }); } patchedExpenses += 1; } const loans = await prisma.loanRecord.findMany({ include: { applicant: { select: { displayName: true, employee: { select: { name: true } } } }, }, orderBy: { loanNo: 'asc' }, }); const usedLoan = new Set(); let patchedLoans = 0; const unmatchedLoans: string[] = []; for (const row of loans) { const names = [normName(row.applicant?.displayName), normName(row.applicant?.employee?.name)].filter(Boolean); const hit = oaLoans .map((o, i) => ({ o, i })) .find(({ o, i }) => !usedLoan.has(i) && amtEq(o.amount, row.amount) && names.includes(normName(o.name)) && (!row.purpose || row.purpose === o.purpose || o.purpose.includes(row.purpose) || row.purpose.includes(o.purpose))); const fallback = oaLoans .map((o, i) => ({ o, i })) .find(({ o, i }) => !usedLoan.has(i) && amtEq(o.amount, row.amount) && names.includes(normName(o.name))); const pick = hit || fallback; if (!pick) { unmatchedLoans.push(`${row.loanNo} ${names[0] || '?'}`); await prisma.loanRecord.update({ where: { id: row.id }, data: { remark: null } }); if (row.purpose) { await prisma.approvalTask.updateMany({ where: { bizId: row.id, bizType: 'LOAN_APPROVAL' }, data: { title: row.purpose }, }); } continue; } usedLoan.add(pick.i); await prisma.loanRecord.update({ where: { id: row.id }, data: { purpose: pick.o.purpose, remark: null }, }); await prisma.approvalTask.updateMany({ where: { bizId: row.id, bizType: 'LOAN_APPROVAL' }, data: { title: pick.o.purpose }, }); patchedLoans += 1; } console.log( JSON.stringify( { patchedExpenses, unmatchedExpenses, patchedLoans, unmatchedLoans }, null, 2, ), ); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(() => prisma.$disconnect());