Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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());
|
||||
Reference in New Issue
Block a user