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,107 @@
|
||||
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());
|
||||
Reference in New Issue
Block a user