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,142 @@
|
||||
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();
|
||||
}
|
||||
|
||||
const CANON = [
|
||||
{ name: '江苏风影随行科技有限公司', shortName: '随行科技', aliases: ['江苏风影随行科技有限公司', '江苏风影随行', '随行科技'] },
|
||||
{ name: '江苏风影软件有限公司', shortName: '风影软件', aliases: ['江苏风影软件有限公司', '风影软件'] },
|
||||
{ name: '江苏风影工程科技有限公司', shortName: '风影工程', aliases: ['江苏风影工程科技有限公司', '风影工程'] },
|
||||
{ name: '山东鲁兵', shortName: '山东鲁兵', aliases: ['山东鲁兵', '鲁兵'] },
|
||||
{ name: '隆创', shortName: '隆创', aliases: ['隆创'] },
|
||||
] as const;
|
||||
|
||||
function canonOf(raw: string) {
|
||||
const s = str(raw);
|
||||
if (!s) return null;
|
||||
if (/软件/.test(s) && /风影/.test(s)) return CANON[1];
|
||||
if (/工程/.test(s) && /风影/.test(s)) return CANON[2];
|
||||
if (/随行/.test(s) || s === '江苏风影' || (s.includes('风影') && !/软件|工程/.test(s))) return CANON[0];
|
||||
if (s.includes('鲁兵')) return CANON[3];
|
||||
if (s.includes('隆创')) return CANON[4];
|
||||
for (const c of CANON) {
|
||||
if (c.aliases.some((a) => s === a || s.includes(a))) return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const ids = new Map<string, string>();
|
||||
for (const c of CANON) {
|
||||
let row =
|
||||
(await prisma.legalEntity.findFirst({ where: { tenantId: '1', name: c.name } })) ||
|
||||
(await prisma.legalEntity.findFirst({
|
||||
where: { tenantId: '1', OR: c.aliases.map((a) => ({ name: a })) },
|
||||
}));
|
||||
if (row) {
|
||||
row = await prisma.legalEntity.update({
|
||||
where: { id: row.id },
|
||||
data: { name: c.name, shortName: c.shortName, isDefault: c.name === '江苏风影随行科技有限公司' },
|
||||
});
|
||||
} else {
|
||||
row = await prisma.legalEntity.create({
|
||||
data: {
|
||||
name: c.name,
|
||||
shortName: c.shortName,
|
||||
isDefault: c.name === '江苏风影随行科技有限公司',
|
||||
},
|
||||
});
|
||||
}
|
||||
ids.set(c.name, row.id);
|
||||
}
|
||||
|
||||
const all = await prisma.legalEntity.findMany({ where: { tenantId: '1' } });
|
||||
const keep = new Set(ids.values());
|
||||
let merged = 0;
|
||||
for (const extra of all) {
|
||||
if (keep.has(extra.id)) continue;
|
||||
const hit = canonOf(extra.name) || canonOf(extra.shortName);
|
||||
const target = hit ? ids.get(hit.name) : ids.get('江苏风影随行科技有限公司');
|
||||
if (!target || target === extra.id) continue;
|
||||
const n = await prisma.bidCase.updateMany({ where: { legalEntityId: extra.id }, data: { legalEntityId: target } });
|
||||
await prisma.contract.updateMany({ where: { legalEntityId: extra.id }, data: { legalEntityId: target } }).catch(() => null);
|
||||
await prisma.sealItem.updateMany({ where: { legalEntityId: extra.id }, data: { legalEntityId: target } }).catch(() => null);
|
||||
await prisma.registeredAsset.updateMany({ where: { legalEntityId: extra.id }, data: { legalEntityId: target } }).catch(() => null);
|
||||
await prisma.legalEntity.delete({ where: { id: extra.id } }).catch(() => null);
|
||||
merged += 1;
|
||||
console.log('merged', extra.name, '->', hit?.name, 'bids', n.count);
|
||||
}
|
||||
|
||||
await prisma.legalEntity.updateMany({
|
||||
where: { name: { not: '江苏风影随行科技有限公司' } },
|
||||
data: { isDefault: false },
|
||||
});
|
||||
await prisma.legalEntity.updateMany({
|
||||
where: { name: '江苏风影随行科技有限公司' },
|
||||
data: { isDefault: true },
|
||||
});
|
||||
|
||||
let remapped = 0;
|
||||
if (fs.existsSync(DUMP)) {
|
||||
const raw = JSON.parse(fs.readFileSync(DUMP, 'utf8')) as Record<string, Row[]>;
|
||||
const tenders = raw.oa_tender || [];
|
||||
const bids = await prisma.bidCase.findMany({ select: { id: true, name: true, bidNo: true, externalNo: true } });
|
||||
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 || b.bidNo === official);
|
||||
const hit = canonOf(str(t.participating_unit));
|
||||
const legalEntityId = hit ? ids.get(hit.name) : undefined;
|
||||
const winner = str(t.winning_unit);
|
||||
const amtRaw = str(t.winning_amount);
|
||||
const status = str(t.winning_status);
|
||||
if (!row) continue;
|
||||
const data: { legalEntityId?: string; winnerName?: string; winnerAmount?: number } = {};
|
||||
if (legalEntityId) data.legalEntityId = legalEntityId;
|
||||
if (winner && status && !/中标/.test(status)) {
|
||||
data.winnerName = winner;
|
||||
if (amtRaw && !Number.isNaN(Number(amtRaw))) data.winnerAmount = Number(amtRaw);
|
||||
}
|
||||
if (!Object.keys(data).length) continue;
|
||||
await prisma.bidCase.update({ where: { id: row.id }, data });
|
||||
remapped += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const leftover = await prisma.legalEntity.findMany({ where: { tenantId: '1' }, orderBy: { name: 'asc' } });
|
||||
const grouped = await prisma.bidCase.groupBy({ by: ['legalEntityId'], _count: true });
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
entities: leftover.map((e) => ({ name: e.name, shortName: e.shortName, isDefault: e.isDefault })),
|
||||
bids: grouped.map((g) => ({
|
||||
id: g.legalEntityId,
|
||||
count: g._count,
|
||||
name: leftover.find((e) => e.id === g.legalEntityId)?.name || '(空)',
|
||||
})),
|
||||
merged,
|
||||
remapped,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
Reference in New Issue
Block a user