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:
2026-09-02 10:04:03 +00:00
commit 76f266645d
507 changed files with 99891 additions and 0 deletions
+107
View File
@@ -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());