import { describe, expect, it } from "vitest"; import { extractInstructionsFromMail, resolveConfirmInstruction, shouldForceForecastUnit, } from "@/services/parse/resolve-confirm-instruction"; import type { MailInstructionUnit } from "@/services/parse/split-instructions"; function unit( partial: Partial & Pick, ): MailInstructionUnit { return { mailType: "NEW_CONTAINER", label: partial.uiKind, summary: "", keywords: [], source: "body_segment", segmentIndex: 0, isCurrent: false, segmentText: "", ...partial, }; } describe("resolveConfirmInstruction", () => { const forecast = unit({ id: "forecast-body_segment-5-派送单请查收", uiKind: "forecast", isCurrent: false, }); const transfer = unit({ id: "transfer-body_segment-0-新增转仓", uiKind: "transfer", mailType: "TRANSFER", isCurrent: true, }); it("selects the requested instruction even when it is historical forecast", () => { const resolved = resolveConfirmInstruction({ units: [transfer, forecast], instructionId: forecast.id, }); expect(resolved.reason).toBe("ok"); expect(resolved.unit?.id).toBe(forecast.id); expect(resolved.unit?.uiKind).toBe("forecast"); }); it("does not infer from mail main type when instruction_id is missing and multiple units exist", () => { const resolved = resolveConfirmInstruction({ units: [transfer, forecast], }); expect(resolved.reason).toBe("missing_id"); expect(resolved.unit).toBeNull(); expect(resolved.candidates.map((u) => u.uiKind)).toEqual([ "transfer", "forecast", ]); }); it("auto-selects the only pending confirmable unit", () => { const resolved = resolveConfirmInstruction({ units: [forecast], }); expect(resolved.reason).toBe("ok"); expect(resolved.unit?.id).toBe(forecast.id); }); it("keeps an already-imported unit visible as readonly", () => { const resolved = resolveConfirmInstruction({ units: [forecast], instructionId: forecast.id, lineage: { instruction_state: { [forecast.id]: { status: "IMPORTED", importedAt: "2026-08-17" }, }, }, }); expect(resolved.reason).toBe("already_imported"); expect(resolved.unit?.id).toBe(forecast.id); }); }); describe("extractInstructionsFromMail forceForecast parity", () => { it("mail100-style DO + 整柜直送 subject still yields forecast unit", () => { const mail = { subject: "Fw: Fw:回复:柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+不带车架+整柜直送", body_text: "TRHU8524770 DO请查收", attachments: [{ filename: "TRHU8524770-DO-1.pdf" }], parse_result: { container_header: { F_ContainerNo: "TRHU8524770" }, mail_record: { kind: "BL_FORECAST" }, }, }; expect(shouldForceForecastUnit(mail)).toBe(true); const units = extractInstructionsFromMail(mail); expect(units.some((u) => u.uiKind === "do_upload")).toBe(true); const forecast = units.find((u) => u.uiKind === "forecast"); expect(forecast).toBeTruthy(); expect(forecast!.id).toMatch(/^forecast-record-/); const resolved = resolveConfirmInstruction({ units, instructionId: forecast!.id, }); expect(resolved.reason).toBe("ok"); expect(resolved.unit?.uiKind).toBe("forecast"); }); it("forces forecast from 整柜直送 subject even without mail_record", () => { const units = extractInstructionsFromMail({ subject: "回复:柜号:TRHU8524770+提单号:EGLV143661466958+YWZD+不带车架+整柜直送", body_text: "DO请查收", attachments: [{ filename: "TRHU8524770-DO-1.pdf" }], }); expect(units.some((u) => u.uiKind === "forecast")).toBe(true); expect(units.some((u) => u.uiKind === "do_upload")).toBe(true); }); it("does not force forecast for work-order mail that only has container in subject", () => { const mail = { subject: "Fw: 转发:TIIU8073522-90022", body_text: "主题 TIIU8073522-90022\nBAZUS001787430-90022 4PX 预约码: ==CNF26070717", mail_type: "WORK_ORDER", attachments: [] as Array<{ filename: string }>, parse_result: { container_header: { F_ContainerNo: "TIIU8073522" }, mail_record: { kind: "WORK_ORDER" }, }, }; expect(shouldForceForecastUnit(mail)).toBe(false); const units = extractInstructionsFromMail(mail); expect(units.some((u) => u.uiKind === "forecast")).toBe(false); }); });