supersam/src/services/deliverySetViews.js

168 lines
5.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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