const parseTicketTypeLetter = (ticketNo?: string | null): string | undefined => { const parts = (ticketNo ?? "").trim().split("-"); return parts[1]?.toUpperCase(); }; export const isTiETicketNo = (ticketNo?: string | null): boolean => { const tn = (ticketNo ?? "").trim().toUpperCase(); return parseTicketTypeLetter(tn) === "E" || tn.startsWith("TI-E-"); }; export const isTiMTicketNo = (ticketNo?: string | null): boolean => (ticketNo ?? "").trim().toUpperCase().startsWith("TI-M-"); const isExtraReleaseType = (releaseType?: string | null): boolean => { const rt = (releaseType ?? "").trim().toLowerCase(); return rt === "isextra" || rt === "isextrabatch" || rt === "isextrasingle"; }; export type TraceDoOutboundExtraSource = { isExtra?: boolean; ticketNo?: string | null; consoCode?: string | null; releaseType?: string | null; deliveryOrderIsExtra?: boolean; deliveryOrderPickOrderId?: number | null; relationshipId?: number | null; }; /** * Trace 加單 chip rules (aligned with DeliveryOrderService.isExtraDeliveryTicket): * - TI-E / conso TI-E- → extra * - TI-M → per DO isExtra, conso TI-E-, or relationshipId lineage (≠ own dop id) * - Other → releaseType / API isExtra */ export const resolveTraceDoOutboundIsExtra = ( source: TraceDoOutboundExtraSource, ): boolean => { if (source.isExtra === true) return true; const ticket = source.ticketNo?.trim() || ""; const conso = source.consoCode?.trim() || ""; const consoIsTiE = isTiETicketNo(conso); const deliveryOrderIsExtra = source.deliveryOrderIsExtra === true; if (isTiMTicketNo(ticket)) { if (deliveryOrderIsExtra) return true; if (consoIsTiE) return true; const dopId = source.deliveryOrderPickOrderId ?? null; const relationshipId = source.relationshipId ?? null; if (dopId != null && relationshipId != null && relationshipId !== dopId) return true; return false; } if (isTiETicketNo(ticket) || consoIsTiE) return true; if (isExtraReleaseType(source.releaseType) && !isTiMTicketNo(ticket)) return true; return deliveryOrderIsExtra; };