/** * 入库「邮�? 完整线程�? 新辰泽卡派模板(历史预报货件表)�? * 最新指令是换标/贴标工单;主题链路上的新增预报仅作历史只读�? * 用法: pnpm exec tsx scripts/ingest-forecast-ui.ts */ import { createHash } from "crypto"; import fs from "fs/promises"; import path from "path"; import { prisma } from "@/services/db"; import { ParsePipeline } from "@/services/parse/pipeline"; import { sha256 } from "@/utils/hash"; import { loadMailboxPdfBody } from "./lib/load-mailbox-pdf-body"; const SUBJECT = "Fw: \u8f6c\u53d1\uff1a\u65b0\u589e\u9884\u62a5136\uff1a\u65b0\u8fb0\u6cfd+WHLC027G597465+WHSU8127240+\u6d1b\u6749\u77f6+40HQ+EDT2026.04-25 ETA2026.05-15\u8239\u540d\u822a\u6b21HMM EMERALD 013E+\u63d0\u62c6\u6d3e\u7ec4\u5408\u67dc-\u4e0d\u5e26\u6258\u67b6"; const FALLBACK_BODY = [ "Dear,", "\u539f\u7bb1\u53f7YT2604021091=FBA199R49LD6-MDW2=76\u4ef6\u64cd\u4f5c\u6307\u4ee4", "\u6362\u6807\u540e\u5355\u53f7\uff1aFBA19HW52S0L-2G1MCB6X-HIA1=76\u4ef6", "1\uff1a\u8986\u76d6\u8d34FBA\u6807\u7b7e\uff0c\u4e00\u7bb1\u8d34\u4e24\u5f20", "3\uff1a\u8d34\u597d\u62cd\u7167\u56de\u4f20", ].join("\n"); const MESSAGE_ID = ""; const MAIL4_DIR = path.join(process.cwd(), "docx", "\u90ae\u4ef6", "\u90ae\u4ef64"); const TEMPLATE = path.join( process.cwd(), "docx", "\u90ae\u4ef6", "\u6a21\u677f", "\u6570\u636e\u6a21\u7248-\u65b0\u8fb0\u6cfd-WHSU8127240.xlsx", ); async function attachTemplate(mailId: bigint): Promise { const buf = await fs.readFile(TEMPLATE); const hash = sha256(buf); const filename = "\u65b0\u8fb0\u6cfd-\u5361\u6d3e\u8d44\u6599-WHSU8127240.xlsx"; const dir = path.join(process.cwd(), "data", "mails", String(mailId)); await fs.mkdir(dir, { recursive: true }); const dest = path.join(dir, filename); await fs.writeFile(dest, buf); const rel = path.relative(process.cwd(), dest).replace(/\\/g, "/"); await prisma.mailAttachment.create({ data: { mailId, filename, contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", sha256: hash, path: rel, size: buf.length, rejected: false, }, }); } async function main() { const pdfBody = await loadMailboxPdfBody(MAIL4_DIR); const bodyText = pdfBody && pdfBody.length > FALLBACK_BODY.length ? pdfBody : FALLBACK_BODY; console.log(`body chars=${bodyText.length} (pdf=${Boolean(pdfBody)})`); const rawHash = createHash("sha256") .update(`forecast-ui|${MESSAGE_ID}|${SUBJECT}|${bodyText}|tpl-v5-full`) .digest("hex"); const existing = await prisma.mailMessage.findFirst({ where: { OR: [{ messageId: MESSAGE_ID }, { rawHash }] }, }); let mailId: bigint; if (existing) { await prisma.importCompensation.deleteMany({ where: { import: { mailId: existing.id } }, }); await prisma.containerImport.deleteMany({ where: { mailId: existing.id } }); await prisma.parseResult.deleteMany({ where: { mailId: existing.id } }); await prisma.mailAttachment.deleteMany({ where: { mailId: existing.id } }); await prisma.mailMessage.update({ where: { id: existing.id }, data: { subject: SUBJECT.slice(0, 512), fromAddr: "forecast-ui@local.test", bodyText, status: "FETCHED", mailType: "UNKNOWN", lastError: null, rawHash, receivedAt: new Date("2026-04-20T08:00:00.000Z"), typeEvidence: { note: "forecast_formorder_ui_full_thread" }, version: { increment: 1 }, }, }); mailId = existing.id; } else { // also refresh by messageId if hash changed const byMid = await prisma.mailMessage.findFirst({ where: { messageId: MESSAGE_ID }, }); if (byMid) { await prisma.importCompensation.deleteMany({ where: { import: { mailId: byMid.id } }, }); await prisma.containerImport.deleteMany({ where: { mailId: byMid.id } }); await prisma.parseResult.deleteMany({ where: { mailId: byMid.id } }); await prisma.mailAttachment.deleteMany({ where: { mailId: byMid.id } }); await prisma.mailMessage.update({ where: { id: byMid.id }, data: { subject: SUBJECT.slice(0, 512), bodyText, status: "FETCHED", mailType: "UNKNOWN", lastError: null, rawHash, typeEvidence: { note: "forecast_formorder_ui_full_thread" }, version: { increment: 1 }, }, }); mailId = byMid.id; } else { const created = await prisma.mailMessage.create({ data: { messageId: MESSAGE_ID, subject: SUBJECT.slice(0, 512), fromAddr: "forecast-ui@local.test", bodyText, status: "FETCHED", mailType: "UNKNOWN", rawHash, folder: "INBOX", receivedAt: new Date("2026-04-20T08:00:00.000Z"), isThreadRoot: true, typeEvidence: { note: "forecast_formorder_ui_full_thread" }, }, }); mailId = created.id; } } await attachTemplate(mailId); await ParsePipeline.run(mailId); const updated = await prisma.mailMessage.findUniqueOrThrow({ where: { id: mailId }, include: { parseResult: true, attachments: true }, }); console.log( JSON.stringify( { id: String(updated.id), mail_type: updated.mailType, status: updated.status, body_chars: (updated.bodyText || "").length, has_label: /原箱号|换标/.test(updated.bodyText || ""), shipments: ((updated.parseResult?.shipments as unknown[]) || []).length, url: `http://localhost:3100/mails/${updated.id}`, }, null, 2, ), ); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });