fix: pickup display — detect from source_orders.ship, show correct labels, hide placeholder address
- orderGroupRepository: detect pickup from source_orders.ship='САМОВЫВОЗ' and address='САМОВЫВОЗ' - orderGroupRepository: set effectiveDeliveryType='pickup' when source data indicates pickup even if DB says 'delivery' - orderGroupRepository: clear deliveryAddress when it's just 'САМОВЫВОЗ' placeholder - OrderDetailPanel: dynamic header 'Карточка группы самовывоза' vs 'Карточка группы доставки' - OrderDetailPanel: subtitle now includes orderNumbers for visibility - OrderDetailPanel: label changed from 'Номер счёта' to 'Заказ' with '+N сч.' for sub-bills - GroupDetailPage: neutral 'Группа не найдена' instead of 'Группа доставки не найдена' - Added pickup-specific test case
This commit is contained in:
parent
ec9b28fa6f
commit
9aef4d49c0
|
|
@ -642,7 +642,7 @@ export const OrderDetailPanel = ({
|
|||
});
|
||||
|
||||
if (result?.success) {
|
||||
setFormMessage("Доставка согласована вручную.");
|
||||
setFormMessage(deliveryType === "pickup" ? "Самовывоз согласован вручную." : "Доставка согласована вручную.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -682,36 +682,57 @@ export const OrderDetailPanel = ({
|
|||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-sm uppercase tracking-[0.2em] text-[var(--color-text-muted)]">
|
||||
Карточка группы доставки
|
||||
{(order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup") ? "Карточка группы самовывоза" : "Карточка группы доставки"}
|
||||
</p>
|
||||
<h2 className="mt-2 text-2xl font-semibold">
|
||||
{order.displayTitle || order.customerName || order.groupKey}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||||
{order.displaySubtitle || [order.customerPhone, order.customerDate].filter(Boolean).join(" · ") || "Не указано"}
|
||||
{(() => {
|
||||
const parts = [];
|
||||
if (order.orderNumbers && order.orderNumbers.length > 0) parts.push(order.orderNumbers.join(", "));
|
||||
const sub = order.displaySubtitle || [order.customerPhone, order.customerDate].filter(Boolean).join(" · ");
|
||||
if (sub) parts.push(sub);
|
||||
return parts.join(" · ") || "Не указано";
|
||||
})()}
|
||||
</p>
|
||||
</div>
|
||||
<Badge tone={getOrderGroupStatusTone(order)}>{getOrderGroupDisplayStatusLabel(order)}</Badge>
|
||||
</div>
|
||||
|
||||
{(() => {
|
||||
const isPickup = order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup";
|
||||
const deliveryTypeLabel = isPickup
|
||||
? "Самовывоз"
|
||||
: (order.deliveryStatus === "requires_address" || order.delivery_status === "requires_address")
|
||||
? "Доставка (требуется адрес)"
|
||||
: "Доставка";
|
||||
const dateLabel = isPickup ? "Дата самовывоза" : "Дата доставки";
|
||||
const timeLabel = isPickup ? "Время самовывоза" : "Время доставки";
|
||||
const addressLabel = isPickup ? "Адрес самовывоза" : "Адрес доставки";
|
||||
// For pickup orders, hide the delivery address if it's just a placeholder like "Самовывоз"
|
||||
const effectiveAddress = isPickup
|
||||
? (order.deliveryAddress && order.deliveryAddress !== "Самовывоз" && order.deliveryAddress !== "самовывоз" ? order.deliveryAddress : "")
|
||||
: order.deliveryAddress;
|
||||
return (
|
||||
<div className="grid gap-3 rounded-[24px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 md:grid-cols-4">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
Дата доставки
|
||||
{dateLabel}
|
||||
</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{formatDeliveryDateDisplay(order.deliveryDate)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
Время доставки
|
||||
{timeLabel}
|
||||
</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{renderValue(order.deliveryTime || order.deliveryHalfDay)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
Тип доставки
|
||||
Тип
|
||||
</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{order.deliveryType === "pickup" ? "Самовывоз" : order.deliveryStatus === "requires_address" || order.delivery_status === "requires_address" ? "Доставка (требуется адрес)" : "Доставка"}</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{deliveryTypeLabel}</p>
|
||||
{(order.deliveryStatus === "requires_address" || order.delivery_status === "requires_address") && (
|
||||
<div className="mt-2 flex items-start gap-2 rounded-xl border border-[rgba(239,68,68,0.3)] bg-[rgba(239,68,68,0.08)] p-3">
|
||||
<span className="text-lg">📍</span>
|
||||
|
|
@ -726,7 +747,7 @@ export const OrderDetailPanel = ({
|
|||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
Водитель
|
||||
</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{order.assignedDriverId ? renderValue(order.assignedDriverName) : "Не назначен"}</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{order.assignedDriverId ? renderValue(order.assignedDriverName) : (isPickup ? "Не нужен" : "Не назначен")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
|
|
@ -739,18 +760,38 @@ export const OrderDetailPanel = ({
|
|||
{renderValue(order.customerPhone)}
|
||||
</a>
|
||||
</div>
|
||||
<div className="">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
Адрес доставки
|
||||
</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{renderValue(order.deliveryAddress)}</p>
|
||||
</div>
|
||||
{!isPickup || effectiveAddress ? (
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
{addressLabel}
|
||||
</p>
|
||||
<p className="mt-1 text-base font-medium !text-[var(--color-text)]">{renderValue(effectiveAddress)}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
<div className="grid gap-x-4 gap-y-2 grid-cols-2 md:grid-cols-4">
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Номер счёта</p>
|
||||
<p className="font-medium !text-[var(--color-text)]">{renderValue(order.orderNumberSummary)}</p>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Заказ</p>
|
||||
<p className="font-medium !text-[var(--color-text)]">{(() => {
|
||||
const mainNumbers = order.orderNumbers || [];
|
||||
const allNumbers = order.allBillNumbers || [];
|
||||
const mainSet = new Set(mainNumbers.map(String));
|
||||
const extraNumbers = allNumbers.filter((n) => !mainSet.has(String(n)));
|
||||
if (mainNumbers.length > 0) {
|
||||
return (
|
||||
<span>
|
||||
<span className="font-bold">{mainNumbers.join(", ")}</span>
|
||||
{extraNumbers.length > 0 && (
|
||||
<span className="text-[var(--color-text-muted)]"> +{extraNumbers.length} сч.</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return renderValue(order.orderNumberSummary);
|
||||
})()}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Клиент</p>
|
||||
|
|
@ -777,7 +818,7 @@ export const OrderDetailPanel = ({
|
|||
<p className="font-medium !text-[var(--color-text)]">{formatDateTime(order.updatedAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Статус доставки</p>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">{(order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup") ? "Статус самовывоза" : "Статус доставки"}</p>
|
||||
<p className="font-medium !text-[var(--color-text)]">{getOrderGroupDeliveryStatusLabel(order.deliveryStatus || order.delivery_status)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -786,11 +827,11 @@ export const OrderDetailPanel = ({
|
|||
{canManageDelivery ? (
|
||||
<Panel className="space-y-4 p-5">
|
||||
<div>
|
||||
<strong>Ручное согласование доставки</strong>
|
||||
<strong>Ручное согласование</strong>
|
||||
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||||
{isDeliveryAgreed
|
||||
? "Дата и половина дня доставки уже зафиксированы."
|
||||
: "Если клиент согласовал доставку по телефону, сохраните дату и половину дня здесь."}
|
||||
? "Дата и время уже зафиксированы."
|
||||
: "Если клиент согласовал доставку или самовывоз по телефону, сохраните дату и время здесь."}
|
||||
</p>
|
||||
</div>
|
||||
{/* Delivery type tabs */}
|
||||
|
|
@ -831,7 +872,7 @@ export const OrderDetailPanel = ({
|
|||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-accent)]">
|
||||
Доставка согласована
|
||||
{(order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup") ? "Самовывоз согласован" : "Доставка согласована"}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-semibold">
|
||||
{agreedDeliveryLabel || "Дата и время сохранены"}
|
||||
|
|
@ -847,7 +888,7 @@ export const OrderDetailPanel = ({
|
|||
disabled={isSavingDeliveryChoice}
|
||||
className="text-sm"
|
||||
>
|
||||
Изменить дату доставки
|
||||
Изменить {(order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup") ? "дату самовывоза" : "дату доставки"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -1292,7 +1333,39 @@ export const OrderDetailPanel = ({
|
|||
|
||||
<Panel className="space-y-4 p-5">
|
||||
<strong>Счета</strong>
|
||||
{renderList(getAllBillNumbers(order))}
|
||||
{(() => {
|
||||
const mainNumbers = order.orderNumbers || [];
|
||||
const allNumbers = getAllBillNumbers(order);
|
||||
const mainSet = new Set(mainNumbers.map(String));
|
||||
const extraNumbers = allNumbers.filter((n) => !mainSet.has(String(n)));
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{mainNumbers.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Основной счёт</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{mainNumbers.map((num, idx) => (
|
||||
<span key={idx} className="rounded-full bg-[var(--color-accent-soft)] px-3 py-1 text-sm font-semibold text-[var(--color-accent)]">{num}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{extraNumbers.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">{mainNumbers.length > 0 ? "Составные заказы" : "Все счета"}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{extraNumbers.map((num, idx) => (
|
||||
<span key={idx} className="rounded-full bg-[var(--color-surface)] px-3 py-1 text-xs text-[var(--color-text-muted)]">{num}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{mainNumbers.length === 0 && extraNumbers.length === 0 && (
|
||||
<p className="text-sm text-[var(--color-text-muted)]">Нет данных</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</Panel>
|
||||
|
||||
<Panel className="space-y-4 p-5">
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ describe("OrderDetailPanel", () => {
|
|||
<OrderDetailPanel order={order} />,
|
||||
);
|
||||
|
||||
expect(markup).toContain("Карточка группы доставки");
|
||||
expect(markup).toContain("Карточка группы");
|
||||
expect(markup).toContain("Мария Волкова");
|
||||
expect(markup).toContain("Адрес доставки");
|
||||
expect(markup).toContain("Симферополь, ул. Ленина, 10");
|
||||
|
|
@ -109,4 +109,22 @@ describe("OrderDetailPanel", () => {
|
|||
it("skips weekends when selecting the default manual delivery date", () => {
|
||||
expect(getNextSelectableDateKey(new Date("2026-05-15T12:00:00Z"))).toBe("2026-05-18");
|
||||
});
|
||||
|
||||
it("shows pickup labels for pickup orders", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<OrderDetailPanel
|
||||
order={{
|
||||
...order,
|
||||
deliveryType: "pickup",
|
||||
deliveryStatus: "pickup",
|
||||
deliveryAddress: "",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(markup).toContain("Карточка группы самовывоза");
|
||||
expect(markup).toContain("Самовывоз");
|
||||
expect(markup).toContain("Дата самовывоза");
|
||||
expect(markup).toContain("Статус самовывоза");
|
||||
expect(markup).not.toContain("Адрес доставки");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export const GroupDetailPage = () => {
|
|||
/>
|
||||
) : (
|
||||
<Panel className="p-6 text-sm text-[var(--color-text-muted)]">
|
||||
Группа доставки не найдена.
|
||||
Группа не найдена.
|
||||
</Panel>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,24 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
};
|
||||
|
||||
const deliveryAddress = normalizeText(row.delivery_address) || extractAddressFromSourceOrders(row.source_orders);
|
||||
|
||||
// Detect pickup from source_orders ship field (1C sends "САМОВЫВОЗ")
|
||||
const isPickupFromSource = Array.isArray(row.source_orders) && row.source_orders.length > 0
|
||||
&& normalizeText(row.source_orders[0].ship || "").toUpperCase() === "САМОВЫВОЗ";
|
||||
// Also treat address equal to "САМОВЫВОЗ" as pickup indicator
|
||||
const isPickupAddress = deliveryAddress.toUpperCase() === "САМОВЫВОЗ";
|
||||
|
||||
// Resolve effective delivery type: DB field takes precedence, but if it says "delivery"
|
||||
// while source data clearly indicates pickup, treat as pickup
|
||||
const effectiveDeliveryType = (row.delivery_type === "pickup" || deliveryStatus === "pickup" || isPickupFromSource || isPickupAddress)
|
||||
? "pickup"
|
||||
: (row.delivery_type || "delivery");
|
||||
|
||||
// Clear placeholder pickup address
|
||||
const resolvedDeliveryAddress = (effectiveDeliveryType === "pickup" && (deliveryAddress.toUpperCase() === "САМОВЫВОЗ" || !deliveryAddress))
|
||||
? ""
|
||||
: deliveryAddress;
|
||||
|
||||
const customerAddress = normalizeText(row.customer_address) || "";
|
||||
|
||||
const extractCity = (addr) => {
|
||||
|
|
@ -151,7 +169,7 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
customerPhone,
|
||||
customerPhoneNormalized: parsedKey.phone || normalizePhone(customerPhone),
|
||||
customerDate,
|
||||
deliveryAddress,
|
||||
deliveryAddress: resolvedDeliveryAddress,
|
||||
customerAddress,
|
||||
city,
|
||||
assignedDriverId: row.assigned_driver_id || null,
|
||||
|
|
@ -189,7 +207,7 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
deliveryDate,
|
||||
deliveryTime,
|
||||
deliveryDateSource: row.delivery_date_source || null,
|
||||
deliveryType: row.delivery_type || "delivery",
|
||||
deliveryType: effectiveDeliveryType,
|
||||
pickupDate: row.pickup_date || null,
|
||||
pickupTimeSlot: row.pickup_time_slot || null,
|
||||
driverShipmentData: row.driver_shipment_data || null,
|
||||
|
|
@ -205,7 +223,7 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
customerName,
|
||||
customerPhone,
|
||||
customerDate,
|
||||
deliveryAddress,
|
||||
resolvedDeliveryAddress,
|
||||
customerAddress,
|
||||
city,
|
||||
rawDeliveryHalfDay,
|
||||
|
|
|
|||
Loading…
Reference in New Issue