Files
Public/apps/api/prisma/fix-bid-from-oa.ts
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

126 lines
4.5 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
const prisma = new PrismaClient();
const DUMP = '/opt/import/prod/oa_clean.json';
type Row = Record<string, unknown>;
function str(v: unknown) {
if (v == null) return '';
return String(v).trim();
}
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 entityKey(raw: unknown) {
const s = str(raw);
if (!s) return '';
if (s.includes('鲁兵')) return '山东鲁兵';
if (s.includes('隆创')) return '隆创';
if (s.includes('风影')) return '江苏风影随行科技有限公司';
return s;
}
const ENTITIES: { key: string; name: string; shortName: string }[] = [
{ key: '江苏风影随行科技有限公司', name: '江苏风影随行科技有限公司', shortName: '江苏风影' },
{ key: '山东鲁兵', name: '山东鲁兵', shortName: '山东鲁兵' },
{ key: '隆创', name: '隆创', shortName: '隆创' },
];
async function main() {
const raw = JSON.parse(fs.readFileSync(DUMP, 'utf8')) as Record<string, Row[]>;
const tenders = raw.oa_tender || [];
const entityIds = new Map<string, string>();
for (const e of ENTITIES) {
const existing = await prisma.legalEntity.findFirst({
where: { tenantId: '1', OR: [{ name: e.name }, { shortName: e.shortName }] },
});
if (existing) {
await prisma.legalEntity.update({ where: { id: existing.id }, data: { name: e.name, shortName: e.shortName } });
entityIds.set(e.key, existing.id);
} else {
const created = await prisma.legalEntity.create({
data: { name: e.name, shortName: e.shortName, isDefault: e.shortName === '江苏风影' },
});
entityIds.set(e.key, created.id);
}
}
await prisma.legalEntity.updateMany({
where: { name: { not: '江苏风影随行科技有限公司' } },
data: { isDefault: false },
});
await prisma.legalEntity.updateMany({
where: { name: '江苏风影随行科技有限公司' },
data: { isDefault: true },
});
const users = await prisma.user.findMany();
const userByName = new Map(users.map((u) => [u.displayName, u]));
const bids = await prisma.bidCase.findMany();
const usedNos = new Set<string>();
const matched: { rowId: string; t: Row }[] = [];
for (const t of tenders) {
const name = str(t.project_name);
const sys = str(t.system_number);
const official = str(t.project_number);
const row = bids.find((b) => b.name === name) || bids.find((b) => b.bidNo === sys || b.externalNo === official);
if (!row) continue;
matched.push({ rowId: row.id, t });
}
for (const m of matched) {
await prisma.bidCase.update({
where: { id: m.rowId },
data: { bidNo: `TMP-${m.rowId.replace(/-/g, '').slice(0, 20)}` },
});
}
for (const { rowId, t } of matched) {
const row = bids.find((b) => b.id === rowId)!;
const official = str(t.project_number);
const key = entityKey(t.participating_unit);
let bidNo = official || row.externalNo || row.bidNo;
if (usedNos.has(bidNo)) bidNo = `${bidNo}-${row.id.slice(0, 4)}`;
usedNos.add(bidNo);
const preparer = str(t.bid_prepare_person);
const creator = userByName.get(preparer === '张靖妍' ? '张婧妍' : preparer);
await prisma.bidCase.update({
where: { id: row.id },
data: {
bidNo,
externalNo: official || row.externalNo,
legalEntityId: key ? entityIds.get(key) || null : null,
openAt: when(t.bid_open_date) || row.openAt,
applyEndAt: when(t.file_apply_deadline) || row.applyEndAt,
applyStartAt: when(t.create_time) || row.applyStartAt,
createdById: creator?.id || row.createdById,
createdAt: when(t.create_time) || row.createdAt,
},
});
}
const patched = matched.length;
const loan = await prisma.loanRecord.findFirst({ where: { purpose: { contains: '重庆投标' } } });
const chongqing = await prisma.bidCase.findFirst({ where: { name: { contains: '门诊特病' } } });
if (loan && chongqing) {
await prisma.loanRecord.update({
where: { id: loan.id },
data: { bidCaseId: chongqing.id, costType: 'BID' },
});
}
console.log(JSON.stringify({ patchedBids: patched, entities: ENTITIES.map((e) => e.shortName), linkedChongqingLoan: Boolean(loan && chongqing) }));
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());