76f266645d
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { Prisma, PrismaClient } from '@prisma/client';
|
|
import { mapExpenseLineKind, parseOaJson } from './oa-json';
|
|
|
|
const prisma = new PrismaClient();
|
|
const FLOW = '/opt/import/prod/oa_flow_finance.json';
|
|
|
|
type Row = Record<string, unknown>;
|
|
|
|
function str(v: unknown) {
|
|
if (v == null) return '';
|
|
return String(v).trim();
|
|
}
|
|
|
|
function num(v: unknown) {
|
|
if (v == null || v === '') return 0;
|
|
const n = Number(String(v).replace(/,/g, ''));
|
|
return Number.isFinite(n) ? n : 0;
|
|
}
|
|
|
|
function when(v: unknown) {
|
|
const s = str(v);
|
|
if (!s || s.startsWith('0000')) return null;
|
|
const d = new Date(s.includes('T') ? s : s.replace(' ', 'T'));
|
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
}
|
|
|
|
function clip(v: unknown, n = 200) {
|
|
const s = str(v);
|
|
return s.length > n ? s.slice(0, n) : s;
|
|
}
|
|
|
|
async function main() {
|
|
const flow = parseOaJson<Record<string, Row[]>>(FLOW);
|
|
const itemsByReimb = new Map<string, Row[]>();
|
|
for (const item of flow.reimbursement_item || []) {
|
|
const key = str(item.reimbursement_id);
|
|
if (!key) continue;
|
|
const list = itemsByReimb.get(key) || [];
|
|
list.push(item);
|
|
itemsByReimb.set(key, list);
|
|
}
|
|
|
|
let claims = 0;
|
|
let lines = 0;
|
|
let skipped = 0;
|
|
for (const r of flow.reimbursement || []) {
|
|
const claimNo = str(r.reimburse_no);
|
|
if (!claimNo) continue;
|
|
const row = await prisma.expenseClaim.findFirst({ where: { tenantId: '1', claimNo } });
|
|
if (!row) {
|
|
skipped += 1;
|
|
continue;
|
|
}
|
|
const incoming = itemsByReimb.get(str(r.id)) || [];
|
|
await prisma.$transaction(async (tx) => {
|
|
await tx.expenseLine.deleteMany({ where: { claimId: row.id } });
|
|
for (const [i, line] of incoming.entries()) {
|
|
await tx.expenseLine.create({
|
|
data: {
|
|
claimId: row.id,
|
|
kind: mapExpenseLineKind(str(line.item_type), str(line.remark)),
|
|
amount: new Prisma.Decimal(num(line.amount)),
|
|
invoiceNo: clip(line.invoice_no, 80) || undefined,
|
|
occurAt: when(line.expense_date) || undefined,
|
|
remark: clip(line.remark || line.item_type, 200) || undefined,
|
|
sortNo: num(line.sort_order) || i,
|
|
},
|
|
});
|
|
lines += 1;
|
|
}
|
|
});
|
|
claims += 1;
|
|
}
|
|
|
|
const sample = await prisma.expenseClaim.findFirst({
|
|
where: { claimNo: 'BX-20260828-002' },
|
|
include: { lines: { orderBy: { sortNo: 'asc' } } },
|
|
});
|
|
const sum = sample?.lines.reduce((s, l) => s + Number(l.amount), 0);
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
claims,
|
|
lines,
|
|
skipped,
|
|
sample: sample
|
|
? {
|
|
claimNo: sample.claimNo,
|
|
header: Number(sample.amount),
|
|
lineCount: sample.lines.length,
|
|
lineSum: sum,
|
|
kinds: sample.lines.map((l) => `${l.kind}:${l.amount}:${l.remark}`),
|
|
}
|
|
: null,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(() => prisma.$disconnect());
|