Files
Public/apps/api/prisma/fix-approval-fields.ts
T
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

142 lines
4.6 KiB
TypeScript

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<string, string> = {
: '张婧妍',
: '张婧妍',
};
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<number>();
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<number>();
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());