supersam/src/services/orderGroupViews.js

446 lines
13 KiB
JavaScript
Raw 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.

const normalizeDate = (value) => (value ? String(value) : "");
const getDeliveryDate = (group) => normalizeDate(group.deliveryDate || group.customerDate || "");
export const DELIVERY_GROUP_STATUS_LABELS = {
pending_confirmation: "Ожидает согласования",
manual_confirmation_required: "Требуется ручное подтверждение",
agreed: "Согласовано",
driver_assigned: "Назначен водитель",
loaded: "Загружено",
on_route: "В пути",
delivered: "Доставлено",
problem: "Проблема",
cancelled: "Отменено",
};
export const NOTIFICATION_STATUS_LABELS = {
not_started: "",
link_ready: "Ссылка готова",
first_sms_sent: "1-е приглашение отправлено",
second_sms_sent: "2-е приглашение отправлено",
send_failed: "Ошибка отправки",
confirmed: "Согласовано",
manual_required: "Переведено в ручное",
};
export const DRIVER_VISIBLE_DELIVERY_STATUSES = [
"agreed",
"driver_assigned",
"loaded",
"on_route",
"problem",
"delivered",
];
export const DRIVER_ACTIVE_DELIVERY_STATUSES = ["agreed", "driver_assigned", "loaded", "on_route", "problem"];
const HALF_DAY_LABELS = {
morning: "Первая половина дня",
afternoon: "Вторая половина дня",
};
const normalizeDeliveryHalfDayLabel = (value) => {
const normalized = normalizeDate(value).trim();
if (!normalized) {
return "";
}
const lower = normalized.toLowerCase();
if (lower.includes("до обеда") || lower.includes("первая половина дня") || lower.includes("утро")) {
return HALF_DAY_LABELS.morning;
}
if (lower.includes("после обеда") || lower.includes("вторая половина дня") || lower.includes("вечер")) {
return HALF_DAY_LABELS.afternoon;
}
return "";
};
const parseJsonIfNeeded = (value) => {
if (typeof value !== "string") {
return value;
}
try {
return JSON.parse(value);
} catch {
return value;
}
};
const findDeliveryHalfDayInValue = (value) => {
const parsedValue = parseJsonIfNeeded(value);
if (Array.isArray(parsedValue)) {
for (const item of parsedValue) {
const match = findDeliveryHalfDayInValue(item);
if (match) {
return match;
}
}
return "";
}
if (parsedValue && typeof parsedValue === "object") {
const candidates = [
parsedValue.deliveryTime,
parsedValue.delivery_time,
parsedValue.time,
parsedValue.deliveryHalfDay,
parsedValue.delivery_half_day,
parsedValue.window,
parsedValue.deliveryWindow,
parsedValue.delivery_window,
parsedValue.slot?.time,
parsedValue.deliverySlot?.time,
];
for (const candidate of candidates) {
const match = normalizeDeliveryHalfDayLabel(candidate);
if (match) {
return match;
}
}
for (const nestedValue of Object.values(parsedValue)) {
const match = findDeliveryHalfDayInValue(nestedValue);
if (match) {
return match;
}
}
}
return normalizeDeliveryHalfDayLabel(parsedValue);
};
export const getOrderGroupDeliveryHalfDay = (group) =>
normalizeDeliveryHalfDayLabel(
group?.deliveryHalfDay ||
group?.deliveryTime ||
group?.deliveryWindow ||
findDeliveryHalfDayInValue(group?.sourceOrders),
);
export const isOrderGroupAgreedForDelivery = (group) => {
if (!group) {
return false;
}
return isOrderGroupVisibleToDriver(group);
};
export const getOrderGroupDeliveryStatusLabel = (status) =>
DELIVERY_GROUP_STATUS_LABELS[status] || status || "Неизвестно";
export const getOrderGroupDisplayStatusLabel = (group) => {
const deliveryStatus = group?.deliveryStatus || group?.delivery_status;
if (deliveryStatus && deliveryStatus !== "pending_confirmation") {
return getOrderGroupDeliveryStatusLabel(deliveryStatus);
}
const notificationStatus = group?.notificationStatus || group?.notification_status;
const notificationLabel = NOTIFICATION_STATUS_LABELS[notificationStatus];
if (notificationLabel && notificationStatus !== "link_ready" && notificationStatus !== "not_started") {
return notificationLabel;
}
return getOrderGroupStatusLabel(group?.status);
};
export const getOrderGroupDisplayStatusValue = (group) => {
const deliveryStatus = group?.deliveryStatus || group?.delivery_status;
if (deliveryStatus) {
return `delivery:${deliveryStatus}`;
}
return `status:${group?.status || "unknown"}`;
};
export const isOrderGroupVisibleToDriver = (group) => {
const deliveryStatus = group?.deliveryStatus || group?.delivery_status || "pending_confirmation";
return DRIVER_VISIBLE_DELIVERY_STATUSES.includes(deliveryStatus);
};
const parseGroupDate = (value) => {
const normalized = normalizeDate(value);
if (!normalized) {
return null;
}
const isoDateMatch = normalized.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (isoDateMatch) {
return new Date(normalized);
}
const shortDateMatch = normalized.match(/^(\d{2})\.(\d{2})\.(\d{2})$/);
if (shortDateMatch) {
const [, day, month, year] = shortDateMatch;
return new Date(Date.UTC(Number(`20${year}`), Number(month) - 1, Number(day)));
}
const parsed = new Date(normalized);
return Number.isNaN(parsed.getTime()) ? null : parsed;
};
export const filterOrderGroups = (groups, filters = {}) => {
const query = normalizeDate(filters.query).trim().toLowerCase();
const status = filters.status || "all";
const displayStatus = normalizeDate(filters.displayStatus || "all");
const deliveryStatus = normalizeDate(filters.deliveryStatus || "all");
const dateFrom = normalizeDate(filters.dateFrom);
const dateTo = normalizeDate(filters.dateTo);
const deliveryHalfDay = normalizeDate(filters.deliveryHalfDay || filters.timeSlot || "all");
const isWithinDateRange = (group) => {
const deliveryDate = parseGroupDate(getDeliveryDate(group));
if (!deliveryDate) {
return !dateFrom && !dateTo;
}
if (dateFrom) {
const fromDate = parseGroupDate(dateFrom);
if (fromDate && deliveryDate < fromDate) {
return false;
}
}
if (dateTo) {
const toDate = parseGroupDate(dateTo);
if (toDate && deliveryDate > toDate) {
return false;
}
}
return true;
};
const getSearchHaystack = (group) =>
(group.searchText ||
[
group.groupKey,
group.displayTitle,
group.customerName,
group.customerPhone,
group.customerDate,
Array.isArray(group.orderNumbers) ? group.orderNumbers.join(" ") : "",
group.status,
getOrderGroupStatusLabel(group.status),
group.deliveryStatus,
getOrderGroupDeliveryStatusLabel(group.deliveryStatus),
]
.filter(Boolean)
.join(" "))
.toLowerCase();
return (groups || []).filter((group) => {
if (status !== "all" && group.status !== status) {
return false;
}
if (displayStatus !== "all" && getOrderGroupDisplayStatusValue(group) !== displayStatus) {
return false;
}
if (deliveryStatus !== "all") {
const groupDeliveryStatus = group.deliveryStatus || group.delivery_status || "pending_confirmation";
if (groupDeliveryStatus !== deliveryStatus) {
return false;
}
}
if (!isWithinDateRange(group)) {
return false;
}
if (deliveryHalfDay !== "all") {
const groupDeliveryHalfDay = getOrderGroupDeliveryHalfDay(group);
if (deliveryHalfDay === "unknown") {
if (groupDeliveryHalfDay) {
return false;
}
} else if (groupDeliveryHalfDay !== HALF_DAY_LABELS[deliveryHalfDay]) {
return false;
}
}
if (!query) {
return true;
}
return getSearchHaystack(group).includes(query);
});
};
export const ORDER_GROUP_STATUS_LABELS = {
ready_for_notification: "Готово к уведомлению",
sms_sent: "SMS отправлены",
manual_work: "Нужна ручная работа",
ready_to_launch: "Готово к запуску",
};
export const ORDER_GROUP_DISPLAY_STATUS_OPTIONS = [
{ value: "all", label: "Все статусы" },
{ value: "delivery:pending_confirmation", label: DELIVERY_GROUP_STATUS_LABELS.pending_confirmation },
{ value: "delivery:manual_confirmation_required", label: DELIVERY_GROUP_STATUS_LABELS.manual_confirmation_required },
{ value: "delivery:agreed", label: DELIVERY_GROUP_STATUS_LABELS.agreed },
{ value: "delivery:driver_assigned", label: DELIVERY_GROUP_STATUS_LABELS.driver_assigned },
{ value: "delivery:loaded", label: DELIVERY_GROUP_STATUS_LABELS.loaded },
{ value: "delivery:on_route", label: DELIVERY_GROUP_STATUS_LABELS.on_route },
{ value: "delivery:delivered", label: DELIVERY_GROUP_STATUS_LABELS.delivered },
{ value: "delivery:problem", label: DELIVERY_GROUP_STATUS_LABELS.problem },
{ value: "delivery:cancelled", label: DELIVERY_GROUP_STATUS_LABELS.cancelled },
];
export const getOrderGroupStatusLabel = (status) =>
ORDER_GROUP_STATUS_LABELS[status] || status || "Неизвестно";
export const getOrderGroupDeliveryStatusTone = (status) => {
switch (status) {
case "pending_confirmation":
return "neutral";
case "manual_confirmation_required":
return "warning";
case "agreed":
return "accent";
case "driver_assigned":
return "info";
case "loaded":
return "info";
case "on_route":
return "accent";
case "delivered":
return "accent";
case "problem":
return "danger";
case "cancelled":
return "danger";
default:
return "neutral";
}
};
export const groupOrderGroupsByDate = (groups) => {
const buckets = (groups || []).reduce((accumulator, group) => {
const date = getDeliveryDate(group) || "Без даты";
accumulator[date] = accumulator[date] || [];
accumulator[date].push(group);
return accumulator;
}, {});
return Object.entries(buckets)
.sort(([leftDate], [rightDate]) => {
const leftTime = parseGroupDate(leftDate)?.getTime();
const rightTime = parseGroupDate(rightDate)?.getTime();
if (leftTime != null && rightTime != null && leftTime !== rightTime) {
return leftTime - rightTime;
}
return leftDate.localeCompare(rightDate);
})
.map(([date, items]) => ({
date,
items: [...items].sort((left, right) => {
const leftCount = Number(left.ordersCount || 0);
const rightCount = Number(right.ordersCount || 0);
if (leftCount !== rightCount) {
return rightCount - leftCount;
}
return (right.updatedAt || "").localeCompare(left.updatedAt || "");
}),
}));
};
const getBucketKey = (group) => {
const notificationStatus = group?.notificationStatus || group?.notification_status;
if (notificationStatus === "manual_required") {
return "manual_work";
}
if (group.smsSentAt) {
return "sms_sent";
}
if ((group.readyCount || 0) > 0 && (group.notReadyCount || 0) > 0) {
return "manual_work";
}
if ((group.notReadyCount || 0) > 0) {
return "manual_work";
}
if (group.status === "ready_for_notification" || (group.readyCount || 0) >= (group.ordersCount || 0)) {
return "ready_to_launch";
}
return "manual_work";
};
export const ORDER_GROUP_BUCKET_LABELS = {
ready_to_launch: "Готовы к уведомлению",
sms_sent: "Уведомления отправлены",
manual_work: "Нужна ручная работа",
};
export const ORDER_GROUP_DELIVERY_HALF_DAY_OPTIONS = [
{ value: "all", label: "Все интервалы" },
{ value: "morning", label: HALF_DAY_LABELS.morning },
{ value: "afternoon", label: HALF_DAY_LABELS.afternoon },
{ value: "unknown", label: "Без времени" },
];
export const buildOrderGroupBuckets = (groups) => {
const buckets = {
ready_to_launch: [],
sms_sent: [],
manual_work: [],
};
for (const group of groups || []) {
const bucketKey = getBucketKey(group);
buckets[bucketKey].push(group);
}
return buckets;
};
export const getOrderGroupStatusTone = (group) => {
const deliveryStatus = group?.deliveryStatus || group?.delivery_status;
if (deliveryStatus && deliveryStatus !== "pending_confirmation") {
return getOrderGroupDeliveryStatusTone(deliveryStatus);
}
const notificationStatus = group?.notificationStatus || group?.notification_status;
if (notificationStatus === "send_failed" || notificationStatus === "manual_required") {
return "warning";
}
if (notificationStatus === "first_sms_sent" || notificationStatus === "second_sms_sent") {
return "accent";
}
if (group.smsSentAt) {
return "accent";
}
if ((group.notReadyCount || 0) > 0) {
return "warning";
}
return "neutral";
};