168 lines
5.0 KiB
JavaScript
168 lines
5.0 KiB
JavaScript
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: "Завершено",
|
||
}; |