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
+131
View File
@@ -0,0 +1,131 @@
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
const prisma = new PrismaClient();
const DUMP = '/opt/import/prod/oa_core.json';
type Row = Record<string, unknown>;
function str(v: unknown) {
if (v == null) return '';
return String(v).trim();
}
function officialNo(raw: unknown) {
const n = str(raw).replace(/[)]+$/, '').trim();
if (!n || n === '无' || n === '-' || n === '—') return '';
return n;
}
function mapQual(raw: unknown) {
const s = str(raw);
if (!s) return '';
try {
const arr = JSON.parse(s) as { item?: string }[];
const item = str(arr?.[0]?.item);
if (!item || item === '无') return '无需资质';
if (item.includes('军')) return '军二';
if (item.includes('涉')) return '涉乙';
return item.slice(0, 20);
} catch {
if (s.includes('军')) return '军二';
if (s.includes('涉')) return '涉乙';
return '';
}
}
function mapStatus(row: Row) {
const st = str(row.project_status);
const win = str(row.winning_status);
if (st === '中标' || (st === '' && win.includes('中标'))) return 'WON';
if (st === '未中' || (st === '' && win.includes('未中'))) return 'LOST';
if (st === '流标') return 'FAILED';
if (st === '弃标' || win.includes('弃标')) return 'TERMINATED';
return '';
}
function isRealTender(row: Row) {
return /^TB-\d/.test(str(row.system_number)) && Boolean(str(row.project_name));
}
async function main() {
const raw = JSON.parse(fs.readFileSync(DUMP, 'utf8')) as Record<string, Row[]>;
const tenders = (raw.oa_tender || []).filter(isRealTender);
const bids = await prisma.bidCase.findMany();
const users = await prisma.user.findMany({ select: { id: true, displayName: true } });
const userByName = new Map(users.map((u) => [u.displayName, u.id]));
userByName.set('张靖妍', userByName.get('张婧妍') || '');
const testNos = ['SP-TEST-1788068192', 'SP-FIX-1788068273'];
const tests = bids.filter((b) => testNos.includes(b.bidNo) || testNos.includes(b.externalNo || ''));
const testIds = tests.map((b) => b.id);
if (testIds.length) {
await prisma.todoTask.deleteMany({ where: { bizId: { in: testIds } } });
await prisma.approvalTask.deleteMany({ where: { bizId: { in: testIds } } });
await prisma.bidCase.deleteMany({ where: { id: { in: testIds } } });
}
const used = new Set<string>();
const remaining = await prisma.bidCase.findMany();
let patched = 0;
const moves: { name: string; from: string; to: string; no: string }[] = [];
for (const t of tenders) {
const name = str(t.project_name);
const sys = str(t.system_number);
const official = officialNo(t.project_number);
const row =
remaining.find((b) => b.name === name) ||
remaining.find((b) => b.bidNo === sys || b.externalNo === official || b.externalNo === str(t.project_number));
if (!row) continue;
const status = mapStatus(t) || row.status;
const creatorName = str(t.bid_prepare_person) === '张靖妍' ? '张婧妍' : str(t.bid_prepare_person);
let bidNo = official || sys;
if (used.has(bidNo)) bidNo = `${official || sys}-${sys.slice(-3)}`;
used.add(bidNo);
if (row.status !== status) {
moves.push({ name: name.slice(0, 24), from: row.status, to: status, no: official || sys });
}
const qual = mapQual(t.bid_doc_requirement);
await prisma.bidCase.update({
where: { id: row.id },
data: {
bidNo: `TMP-${row.id.replace(/-/g, '').slice(0, 18)}`,
},
});
await prisma.bidCase.update({
where: { id: row.id },
data: {
bidNo,
externalNo: official || null,
status,
sourceType: 'FLOW',
qualificationNeed: qual || row.qualificationNeed,
tenderMethod: str(t.tender_method) || row.tenderMethod,
applyMethod: row.applyMethod || (str(t.announcement_file) ? '网上申领' : row.applyMethod),
delivererName: str(t.bid_delivery_person) || row.delivererName,
createdById: userByName.get(creatorName) || row.createdById,
},
});
patched += 1;
}
const after = await prisma.bidCase.groupBy({ by: ['status'], _count: true });
console.log(
JSON.stringify(
{ deletedTests: testIds.length, patched, moves, counts: after },
null,
2,
),
);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());