fix(registry): дата и время самовывоза в реестре групп
Владелец: «в таблице всех доставок и самовывоза, если согласована доставка, дата и время указаны, а если самовывоз — нет; нужно и самовывоз выводить». Причина: колонка «Дата» (LogisticsReadinessBoard) читала только delivery_date/ delivery_time, а у самовывоза дата и половина дня лежат в pickup_date/ pickup_time_slot — поэтому строка самовывоза показывала «—», хотя слот согласован. Тот же пропуск был в фильтре по периоду и группировке по датам: getDeliveryDate() смотрела только на delivery_date, и при выбранном периоде заказы самовывоза пропадали из реестра (падали в дату заказа). Введены общие хелперы в orderGroupViews: isPickupGroup, getGroupSlotDate, getGroupSlotTime (камелкеис и snake_case). Реестр показывает дату и время слота по типу заказа, сортировка — по этой же показанной дате, фильтр по периоду и группировка — тоже по ней. Подпись колонки «Доставка» → «Дата» (в колонке теперь и дата выдачи). Попутно поправлено устаревшее ожидание в тесте (getOrderGroupDisplayStatusLabel для черновика ждало «draft», подпись локализована в «Черновик») — тест падал и до этой правки. SW: v121 -> v122.
This commit is contained in:
parent
6944386b3b
commit
3b90b79f9f
|
|
@ -1,7 +1,7 @@
|
||||||
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
||||||
|
|
||||||
if (!isLocalhost) {
|
if (!isLocalhost) {
|
||||||
const STATIC_CACHE = "construction-delivery-static-v121";
|
const STATIC_CACHE = "construction-delivery-static-v122";
|
||||||
const RUNTIME_CACHE = "construction-delivery-runtime-v119";
|
const RUNTIME_CACHE = "construction-delivery-runtime-v119";
|
||||||
const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import {
|
||||||
import { CSS } from "@dnd-kit/utilities";
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
import {
|
import {
|
||||||
filterOrderGroups,
|
filterOrderGroups,
|
||||||
|
getGroupSlotDate,
|
||||||
|
getGroupSlotTime,
|
||||||
getOrderGroupDisplayStatusLabel,
|
getOrderGroupDisplayStatusLabel,
|
||||||
getOrderGroupDisplayStatusValue,
|
getOrderGroupDisplayStatusValue,
|
||||||
getOrderGroupStatusTone,
|
getOrderGroupStatusTone,
|
||||||
|
|
@ -109,7 +111,7 @@ const TABLE_COLUMNS = [
|
||||||
{ key: "customer", label: "Клиент" },
|
{ key: "customer", label: "Клиент" },
|
||||||
{ key: "city", label: "Город" },
|
{ key: "city", label: "Город" },
|
||||||
{ key: "deliveryType", label: "Тип" },
|
{ key: "deliveryType", label: "Тип" },
|
||||||
{ key: "deliveryDate", label: "Доставка" },
|
{ key: "deliveryDate", label: "Дата" },
|
||||||
{ key: "driver", label: "Водитель" },
|
{ key: "driver", label: "Водитель" },
|
||||||
{ key: "status", label: "Статус" },
|
{ key: "status", label: "Статус" },
|
||||||
{ key: "updatedAt", label: "Обновлён" },
|
{ key: "updatedAt", label: "Обновлён" },
|
||||||
|
|
@ -189,6 +191,9 @@ const renderRow = (g, onSelectSet) => {
|
||||||
const smsTone = getSmsProblemTone(g);
|
const smsTone = getSmsProblemTone(g);
|
||||||
const smsTag = smsTone !== "ok" ? SMS_TAG[smsTone] : null;
|
const smsTag = smsTone !== "ok" ? SMS_TAG[smsTone] : null;
|
||||||
const notAgreedHighlight = getNotAgreedHighlight(g);
|
const notAgreedHighlight = getNotAgreedHighlight(g);
|
||||||
|
// Дата и время слота: у самовывоза — выдача, у доставки — доставка (владелец: показывать оба типа).
|
||||||
|
const slotDate = getGroupSlotDate(g);
|
||||||
|
const slotTime = getGroupSlotTime(g);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -228,8 +233,8 @@ const renderRow = (g, onSelectSet) => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-2.5 text-sm text-[var(--color-text-muted)]">
|
<div className="px-3 py-2.5 text-sm text-[var(--color-text-muted)]">
|
||||||
{g.deliveryDate ? fmtDate(g.deliveryDate) : "—"}
|
{slotDate ? fmtDate(slotDate) : "—"}
|
||||||
{g.deliveryTime ? <span className="ml-1 text-xs text-[var(--color-text-muted)]">{g.deliveryTime}</span> : null}
|
{slotTime ? <span className="ml-1 text-xs text-[var(--color-text-muted)]">{slotTime}</span> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-2.5 text-sm text-[var(--color-text-muted)]">{g.assignedDriverName || "—"}</div>
|
<div className="px-3 py-2.5 text-sm text-[var(--color-text-muted)]">{g.assignedDriverName || "—"}</div>
|
||||||
<div className="px-3 py-2.5">
|
<div className="px-3 py-2.5">
|
||||||
|
|
@ -382,7 +387,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], totalGroups: totalGr
|
||||||
case "customer": return text(group.customerName || group.groupKey);
|
case "customer": return text(group.customerName || group.groupKey);
|
||||||
case "city": return text(group.city);
|
case "city": return text(group.city);
|
||||||
case "deliveryType": return text(group.deliveryType === "pickup" ? "Самовывоз" : "Доставка");
|
case "deliveryType": return text(group.deliveryType === "pickup" ? "Самовывоз" : "Доставка");
|
||||||
case "deliveryDate": return time(group.deliveryDate);
|
case "deliveryDate": return time(getGroupSlotDate(group));
|
||||||
case "driver": return text(group.assignedDriverName);
|
case "driver": return text(group.assignedDriverName);
|
||||||
case "status": return text(getOrderGroupDisplayStatusLabel(group));
|
case "status": return text(getOrderGroupDisplayStatusLabel(group));
|
||||||
case "updatedAt": return time(group.updatedAt);
|
case "updatedAt": return time(group.updatedAt);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,26 @@
|
||||||
const normalizeDate = (value) => (value ? String(value) : "");
|
const normalizeDate = (value) => (value ? String(value) : "");
|
||||||
|
|
||||||
const getDeliveryDate = (group) => normalizeDate(group.deliveryDate || group.customerDate || "");
|
// Слот заказа: у самовывоза дата и время выдачи лежат в pickup_*, у доставки — в delivery_*.
|
||||||
|
// Панели и фильтры раньше смотрели только на delivery_*, поэтому у самовывоза колонка даты
|
||||||
|
// пустовала, а фильтр по периоду выкидывал такие заказы.
|
||||||
|
export const isPickupGroup = (group) =>
|
||||||
|
(group?.deliveryType || group?.delivery_type) === "pickup" ||
|
||||||
|
(group?.deliveryStatus || group?.delivery_status) === "pickup";
|
||||||
|
|
||||||
|
export const getGroupSlotDate = (group) =>
|
||||||
|
normalizeDate(
|
||||||
|
isPickupGroup(group)
|
||||||
|
? group?.pickupDate || group?.pickup_date || ""
|
||||||
|
: group?.deliveryDate || group?.delivery_date || "",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getGroupSlotTime = (group) =>
|
||||||
|
(isPickupGroup(group)
|
||||||
|
? group?.pickupTimeSlot || group?.pickup_time_slot || ""
|
||||||
|
: group?.deliveryTime || group?.delivery_time || "") || "";
|
||||||
|
|
||||||
|
// Дата, по которой группируют и фильтруют реестр: согласованный слот, иначе дата заказа.
|
||||||
|
const getDeliveryDate = (group) => normalizeDate(getGroupSlotDate(group) || group.customerDate || "");
|
||||||
|
|
||||||
export const DELIVERY_GROUP_STATUS_LABELS = {
|
export const DELIVERY_GROUP_STATUS_LABELS = {
|
||||||
pending_confirmation: "Ожидает согласования",
|
pending_confirmation: "Ожидает согласования",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
buildOrderGroupBuckets,
|
buildOrderGroupBuckets,
|
||||||
filterOrderGroups,
|
filterOrderGroups,
|
||||||
|
getGroupSlotDate,
|
||||||
|
getGroupSlotTime,
|
||||||
|
isPickupGroup,
|
||||||
getOrderGroupDisplayStatusLabel,
|
getOrderGroupDisplayStatusLabel,
|
||||||
getOrderGroupDisplayStatusValue,
|
getOrderGroupDisplayStatusValue,
|
||||||
getOrderGroupDeliveryStatusLabel,
|
getOrderGroupDeliveryStatusLabel,
|
||||||
|
|
@ -104,7 +107,7 @@ describe("orderGroupViews", () => {
|
||||||
expect(getOrderGroupDisplayStatusLabel(groups[0])).toBe("Согласовано");
|
expect(getOrderGroupDisplayStatusLabel(groups[0])).toBe("Согласовано");
|
||||||
expect(getOrderGroupDisplayStatusValue(groups[0])).toBe("delivery:agreed");
|
expect(getOrderGroupDisplayStatusValue(groups[0])).toBe("delivery:agreed");
|
||||||
expect(getOrderGroupStatusTone(groups[0])).toBe("accent");
|
expect(getOrderGroupStatusTone(groups[0])).toBe("accent");
|
||||||
expect(getOrderGroupDisplayStatusLabel(groups[2])).toBe("draft");
|
expect(getOrderGroupDisplayStatusLabel(groups[2])).toBe("Черновик"); // подпись локализована
|
||||||
});
|
});
|
||||||
|
|
||||||
it("filters by the visible card status", () => {
|
it("filters by the visible card status", () => {
|
||||||
|
|
@ -115,4 +118,62 @@ describe("orderGroupViews", () => {
|
||||||
expect(agreedGroups.map((group) => group.id)).toEqual(["g-1"]);
|
expect(agreedGroups.map((group) => group.id)).toEqual(["g-1"]);
|
||||||
expect(deliveredGroups.map((group) => group.id)).toEqual(["g-2"]);
|
expect(deliveredGroups.map((group) => group.id)).toEqual(["g-2"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("берёт дату и время самовывоза из pickup_*, а не из delivery_*", () => {
|
||||||
|
const pickupGroup = {
|
||||||
|
id: "p-1",
|
||||||
|
deliveryType: "pickup",
|
||||||
|
deliveryStatus: "agreed",
|
||||||
|
deliveryDate: null,
|
||||||
|
deliveryTime: null,
|
||||||
|
pickupDate: "2026-09-30",
|
||||||
|
pickupTimeSlot: "Вторая половина дня",
|
||||||
|
customerDate: "28.09.26",
|
||||||
|
orderNumbers: ["P-1"],
|
||||||
|
status: "ready_for_notification",
|
||||||
|
smsSentAt: null,
|
||||||
|
ordersCount: 1,
|
||||||
|
readyCount: 1,
|
||||||
|
notReadyCount: 0,
|
||||||
|
createdAt: "2026-09-28T07:00:00Z",
|
||||||
|
updatedAt: "2026-09-28T07:00:00Z",
|
||||||
|
};
|
||||||
|
const deliveryGroup = { ...groups[0], deliveryType: "delivery", deliveryDate: "2026-09-30", deliveryTime: "Первая половина дня" };
|
||||||
|
|
||||||
|
expect(isPickupGroup(pickupGroup)).toBe(true);
|
||||||
|
expect(getGroupSlotDate(pickupGroup)).toBe("2026-09-30");
|
||||||
|
expect(getGroupSlotTime(pickupGroup)).toBe("Вторая половина дня");
|
||||||
|
expect(getGroupSlotDate(deliveryGroup)).toBe("2026-09-30");
|
||||||
|
expect(getGroupSlotTime(deliveryGroup)).toBe("Первая половина дня");
|
||||||
|
|
||||||
|
// snake_case из сырой строки БД тоже поддержан
|
||||||
|
expect(getGroupSlotDate({ delivery_type: "pickup", pickup_date: "2026-10-01" })).toBe("2026-10-01");
|
||||||
|
expect(getGroupSlotTime({ delivery_type: "pickup", pickup_time_slot: "Первая половина дня" })).toBe("Первая половина дня");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("фильтр по периоду ловит самовывоз по дате выдачи", () => {
|
||||||
|
const pickupGroup = {
|
||||||
|
id: "p-2",
|
||||||
|
deliveryType: "pickup",
|
||||||
|
deliveryStatus: "agreed",
|
||||||
|
deliveryDate: null,
|
||||||
|
pickupDate: "2026-09-30",
|
||||||
|
pickupTimeSlot: "Вторая половина дня",
|
||||||
|
customerDate: "28.09.26",
|
||||||
|
orderNumbers: ["P-2"],
|
||||||
|
status: "ready_for_notification",
|
||||||
|
smsSentAt: null,
|
||||||
|
ordersCount: 1,
|
||||||
|
readyCount: 1,
|
||||||
|
notReadyCount: 0,
|
||||||
|
createdAt: "2026-09-28T07:00:00Z",
|
||||||
|
updatedAt: "2026-09-28T07:00:00Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(filterOrderGroups([pickupGroup], { dateFrom: "2026-09-30", dateTo: "2026-09-30" })).toHaveLength(1);
|
||||||
|
expect(filterOrderGroups([pickupGroup], { dateFrom: "2026-10-01", dateTo: "2026-10-05" })).toHaveLength(0);
|
||||||
|
|
||||||
|
const grouped = groupOrderGroupsByDate([pickupGroup]);
|
||||||
|
expect(grouped[0].date).toBe("2026-09-30");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue