import { DELIVERY_SET_STATUSES } from "../constants/deliveryWorkflow"; const NORMALIZE_PHONE_RE = /[\s\-()]/g; const normalizePhone = (phone) => (phone || "").replace(NORMALIZE_PHONE_RE, "").toLowerCase(); const normalizeName = (name) => (name || "").trim().toLowerCase(); const computeFallbackSetKey = (order) => { if (order.deliverySetKey) { return order.deliverySetKey; } const phone = normalizePhone( order.sourceCustomerPhone || order.customer?.phone || "", ); const name = normalizeName( order.sourceCustomerName || order.customer?.name || "", ); if (!phone && !name) { return `singleton-${order.id || order.orderNumber}`; } return `${phone}::${name}`; }; export const computeDeliverySetStatus = (orders) => { if (!orders.length) { return "approaching"; } const allAccepted = orders.every( (order) => order.sourceAcceptAt != null && order.sourceShipAt == null, ); const allShipped = orders.every( (order) => order.sourceShipAt != null, ); if (allShipped) { return "completed"; } if (allAccepted) { return "ready_to_launch"; } return "approaching"; }; export const computeDeliverySetReadyAt = (orders) => { if (!orders.length) { return null; } if (!orders.every((order) => order.sourceAcceptAt != null)) { return null; } const timestamps = orders .map((order) => order.sourceAcceptAt) .filter(Boolean) .sort(); return timestamps.length ? timestamps[timestamps.length - 1] : null; }; export const computeDeliveryReadyReason = (orders) => { if (!orders.length) { return "empty_set"; } const allAccepted = orders.every( (order) => order.sourceAcceptAt != null && order.sourceShipAt == null, ); if (allAccepted) { return "all_accepted"; } return "waiting_for_acceptance"; }; const getSourceFieldSummary = (order) => ({ sourceOrderNumber: order.sourceOrderNumber || null, sourceOrderDate: order.sourceOrderDate || null, sourceCustomerName: order.sourceCustomerName || order.customer?.name || null, sourceCustomerPhone: order.sourceCustomerPhone || order.customer?.phone || null, sourceCustomerEmail: order.sourceCustomerEmail || null, sourceCustomerCity: order.sourceCustomerCity || order.customer?.city || null, sourceTotalSum: order.sourceTotalSum ?? null, sourcePaidAt: order.sourcePaidAt || null, sourceGateway: order.sourceGateway || null, sourceAssociatedBillsText: order.sourceAssociatedBillsText || null, sourceProductionAt: order.sourceProductionAt || null, sourceSawAt: order.sourceSawAt || null, sourceGlueAt: order.sourceGlueAt || null, sourceHGlueAt: order.sourceHGlueAt || null, sourceCurveAt: order.sourceCurveAt || null, sourceAcceptAt: order.sourceAcceptAt || null, sourceShipAt: order.sourceShipAt || null, sourceSmsLegacyAt: order.sourceSmsLegacyAt || null, }); export const groupOrdersIntoDeliverySets = (orders) => { const setMap = new Map(); for (const order of orders) { const key = computeFallbackSetKey(order); if (!setMap.has(key)) { setMap.set(key, []); } setMap.get(key).push(order); } return Array.from(setMap.entries()).map(([key, setOrders]) => { const firstName = setOrders[0]; const computedStatus = computeDeliverySetStatus(setOrders); const computedReadyAt = computeDeliverySetReadyAt(setOrders); const computedReason = computeDeliveryReadyReason(setOrders); return { key, name: firstName.deliverySetName || firstName.sourceCustomerName || firstName.customer?.name || key, status: firstName.deliverySetStatus || computedStatus, readyAt: firstName.deliverySetReadyAt || computedReadyAt, readyReason: firstName.deliveryReadyReason || computedReason, sourceCustomerName: firstName.sourceCustomerName || firstName.customer?.name || null, sourceCustomerPhone: firstName.sourceCustomerPhone || firstName.customer?.phone || null, sourceCustomerCity: firstName.sourceCustomerCity || firstName.customer?.city || null, orders: setOrders.map((order) => ({ ...order, sourceFieldSummary: getSourceFieldSummary(order), })), orderCount: setOrders.length, linkedBillTexts: setOrders .map((o) => o.sourceAssociatedBillsText) .filter(Boolean) .join("; ") || null, }; }).sort((a, b) => { const statusPriority = DELIVERY_SET_STATUSES.indexOf(a.status) - DELIVERY_SET_STATUSES.indexOf(b.status); if (statusPriority !== 0) { return statusPriority; } return (b.readyAt || "").localeCompare(a.readyAt || ""); }); }; export const getDeliverySetBucket = (status) => { if (DELIVERY_SET_STATUSES.includes(status)) { return status; } return "approaching"; }; export const DELIVERY_SET_BUCKET_LABELS = { approaching: "На подходе", ready_to_launch: "Готово к запуску", awaiting_client: "Ожидает клиента", manual_work: "Нужна ручная работа", agreed: "Согласовано", completed: "Завершено", };