import { PrismaClient } from '@prisma/client'; import * as fs from 'fs'; import { parseOaJson } from './oa-json'; const prisma = new PrismaClient(); function str(v: unknown) { if (v == null) return ''; return String(v).trim(); } function compactName(name: string) { return name.replace(/[\s()()【】\[\]《》·,,。.\-_/\\]/g, '').toLowerCase(); } function namesOverlap(a: string, b: string) { const na = compactName(a); const nb = compactName(b); if (!na || !nb) return false; if (na === nb) return true; const shorter = na.length <= nb.length ? na : nb; const longer = na.length <= nb.length ? nb : na; return shorter.length >= 8 && longer.includes(shorter); } async function linkContracts() { const bids = await prisma.bidCase.findMany({ select: { id: true, bidNo: true, externalNo: true, name: true }, }); const contracts = await prisma.contract.findMany({ where: { bidCaseId: null }, select: { id: true, name: true, contractNo: true }, }); let linked = 0; for (const c of contracts) { const no = c.contractNo || ''; const tb = no.match(/TB-\d+-\d+/); const hits = bids.filter((b) => { if (tb && (b.bidNo === tb[0] || b.bidNo.includes(tb[0]) || (b.externalNo && b.externalNo.includes(tb[0])))) { return true; } if (no && (b.bidNo && no.includes(b.bidNo))) return true; return namesOverlap(c.name, b.name); }); const unique = [...new Map(hits.map((h) => [h.id, h])).values()]; if (unique.length !== 1) continue; await prisma.contract.update({ where: { id: c.id }, data: { bidCaseId: unique[0].id } }); linked += 1; } return { scanned: contracts.length, linked }; } async function backfillSealApplicants() { const dumps = ['/opt/import/prod/oa_fresh.json', '/opt/import/prod/oa_core.json', '/opt/import/prod/oa_clean.json']; const dump = dumps.find((p) => fs.existsSync(p)); if (!dump) return { updated: 0, skipped: 'no dump' }; const raw = parseOaJson>(dump); const hrPath = '/opt/import/prod/oa_hr.json'; const hr = fs.existsSync(hrPath) ? parseOaJson<{ users?: Record[] }>(hrPath) : { users: [] }; const users = await prisma.user.findMany(); const byUsername = new Map(users.map((u) => [u.username, u])); const byName = new Map(users.map((u) => [u.displayName, u])); const admin = users.find((u) => u.displayName === '系统管理员'); const oaUser = new Map((hr.users || []).map((u) => [String(u.id), str(u.userName || u.username)])); const userFromOaId = (id: unknown) => { const uname = oaUser.get(str(id)); return uname ? byUsername.get(uname) : undefined; }; const applies = (raw.oa_seal_apply || raw.oa_seal || []) as Record[]; const rows = await prisma.sealRequest.findMany({ include: { applicant: { select: { displayName: true } } }, }); let updated = 0; for (const r of rows) { if (r.applicant?.displayName && r.applicant.displayName !== '系统管理员') continue; const reason = r.reason || ''; const created = r.createdAt ? r.createdAt.getTime() : 0; const hit = applies.find((a) => { const content = str(a.content || a.apply_subject || a.reason); if (content && reason && (content === reason || reason.includes(content) || content.includes(reason))) return true; const t = str(a.create_time); if (t && created) { const d = new Date(t.includes('T') ? t : t.replace(' ', 'T')); return Math.abs(d.getTime() - created) < 2000 && str(a.seal_type || '') === r.sealType; } return false; }); if (!hit) continue; const who = byName.get(str(hit.applicant_name) === '张靖妍' ? '张婧妍' : str(hit.applicant_name)) || userFromOaId(hit.applicant_id) || userFromOaId(hit.create_by) || userFromOaId(hit.creator_id) || userFromOaId(hit.apply_user); if (!who || who.id === admin?.id) continue; await prisma.sealRequest.update({ where: { id: r.id }, data: { applicantId: who.id } }); updated += 1; } return { updated, scanned: rows.length }; } async function main() { const contracts = await linkContracts(); const seals = await backfillSealApplicants(); const [tender, incremental] = await Promise.all([ prisma.contract.count({ where: { bidCaseId: { not: null } } }), prisma.contract.count({ where: { bidCaseId: null } }), ]); console.log(JSON.stringify({ contracts, seals, tender, incremental }, null, 2)); } main() .then(async () => prisma.$disconnect()) .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });