feat: pickup_code для самовывоза + упрощение статуса отгрузки
- pickup_code: автогенерация 6-значного кода при выборе самовывоза - Колонка pickup_code в order_groups + SELECT - Отображение кода выдачи в карточках (логист, клиент) - Упрощение: любая отгрузка → delivered (проблемы в has_delivery_problem) - RPC: добавлены deliveryType, pickupCode, pickupDate, pickupTimeSlot
This commit is contained in:
parent
6184f5aeed
commit
5867272873
|
|
@ -863,6 +863,12 @@ export const OrderDetailPanel = ({
|
|||
<p className="text-xs text-[var(--color-text-muted)]">{isPickupOrder ? "Статус самовывоза" : "Статус доставки"}</p>
|
||||
<p className="font-medium !text-[var(--color-text)]">{getOrderGroupDeliveryStatusLabel(order.deliveryStatus || order.delivery_status)}</p>
|
||||
</div>
|
||||
{(order.pickupCode || order.pickup_code) ? (
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Код выдачи</p>
|
||||
<p className="text-2xl font-bold tracking-[0.3em] text-[var(--color-accent)]">{order.pickupCode || order.pickup_code}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
|
|
@ -1087,14 +1093,11 @@ export const OrderDetailPanel = ({
|
|||
|
||||
return statusOptions.map((statusOption) => {
|
||||
const isSelected = pendingStatus?.value === statusOption.value;
|
||||
const isDeliveredBtn = statusOption.value === "delivered";
|
||||
const deliveryBlocked = isDeliveredBtn && shipmentState && !shipmentState.canMarkDelivered;
|
||||
return (
|
||||
<Button
|
||||
key={statusOption.value}
|
||||
variant={isSelected ? "primary" : "secondary"}
|
||||
disabled={deliveryBlocked}
|
||||
title={deliveryBlocked ? "Сначала отметьте все позиции как отгруженные" : undefined}
|
||||
disabled={isSavingStatusChange}
|
||||
onClick={() => {
|
||||
if (statusOption.value === "problem") {
|
||||
setProblemReason("selecting");
|
||||
|
|
@ -1104,11 +1107,6 @@ export const OrderDetailPanel = ({
|
|||
}}
|
||||
>
|
||||
{statusOption.label}
|
||||
{isDeliveredBtn && shipmentState && !shipmentState.canMarkDelivered ? (
|
||||
<span className="ml-1.5 text-xs text-[var(--color-text-muted)]">
|
||||
({shipmentState.shipped}/{shipmentState.total})
|
||||
</span>
|
||||
) : null}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
|
@ -1120,7 +1118,6 @@ export const OrderDetailPanel = ({
|
|||
variant="primary"
|
||||
disabled={isSavingStatusChange}
|
||||
onClick={() => {
|
||||
if (pendingStatus.value === "delivered" && shipmentState && !shipmentState.canMarkDelivered) return;
|
||||
onChangeDeliveryStatus({
|
||||
orderGroupId: order.id,
|
||||
status: pendingStatus.value,
|
||||
|
|
|
|||
|
|
@ -378,6 +378,13 @@ export const ClientDeliveryPage = () => {
|
|||
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
|
||||
Статус: {invitation?.deliveryType === "pickup" ? "самовывоз" : "доставка"} уже согласован. При повторном открытии этой ссылки будет показан тот же выбор.
|
||||
</p>
|
||||
{(invitation?.pickupCode || invitation?.pickup_code) ? (
|
||||
<div className="mt-3 rounded-2xl border-2 border-[var(--color-accent)] bg-[var(--color-accent-soft)] p-4 text-center">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-[var(--color-text-muted)]">Код выдачи</p>
|
||||
<p className="mt-1 text-3xl font-bold tracking-[0.4em] text-[var(--color-accent)]">{invitation.pickupCode || invitation.pickup_code}</p>
|
||||
<p className="mt-2 text-xs text-[var(--color-text-muted)]">Покажите этот код при получении заказа</p>
|
||||
</div>
|
||||
) : null}
|
||||
</Panel>
|
||||
) : null}
|
||||
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
driverShipmentData: row.driver_shipment_data || null,
|
||||
hasDeliveryProblem: row.has_delivery_problem || false,
|
||||
deliveryProblemNote: row.delivery_problem_note || null,
|
||||
pickupCode: row.pickup_code || null,
|
||||
syncedTo1cAt: row.synced_to_1c_at || null,
|
||||
deliveryHalfDay: getOrderGroupDeliveryHalfDay({
|
||||
deliveryHalfDay: rawDeliveryHalfDay,
|
||||
|
|
@ -264,7 +265,7 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
};
|
||||
};
|
||||
|
||||
const ORDER_GROUP_SELECT_FIELDS = `id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, customer_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name), driver_shipment_data, delivery_type, pickup_date, pickup_time_slot, has_delivery_problem, delivery_problem_note, synced_to_1c_at`;
|
||||
const ORDER_GROUP_SELECT_FIELDS = `id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, customer_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name), driver_shipment_data, delivery_type, pickup_date, pickup_time_slot, has_delivery_problem, delivery_problem_note, pickup_code, synced_to_1c_at`;
|
||||
|
||||
export const updateOrderGroupDeliveryChoice = async ({
|
||||
orderGroupId,
|
||||
|
|
@ -292,6 +293,16 @@ export const updateOrderGroupDeliveryChoice = async ({
|
|||
updatePayload.pickup_time_slot = pickupTimeSlot || null;
|
||||
// Pickup orders don't need a driver — clear assignment
|
||||
updatePayload.assigned_driver_id = null;
|
||||
// Generate pickup code if not already set
|
||||
const { data: existing } = await client
|
||||
.from("order_groups")
|
||||
.select("pickup_code")
|
||||
.eq("id", orderGroupId)
|
||||
.single();
|
||||
if (!existing?.pickup_code) {
|
||||
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
updatePayload.pickup_code = code;
|
||||
}
|
||||
} else {
|
||||
updatePayload.pickup_date = null;
|
||||
updatePayload.pickup_time_slot = null;
|
||||
|
|
@ -393,22 +404,14 @@ export const assignDriverToOrderGroup = async ({
|
|||
export const saveShipmentData = async ({ orderGroupId, shipmentData }) => {
|
||||
return safeSupabaseCall(async () => {
|
||||
const client = requireSupabase();
|
||||
const unshipped = shipmentData.filter((i) => !i.shipped);
|
||||
const allShipped = unshipped.length === 0 && shipmentData.length > 0;
|
||||
const unshippedWithoutComment = unshipped.filter((i) => !i.comment?.trim()).length;
|
||||
const canMarkDelivered = allShipped || unshippedWithoutComment === 0;
|
||||
const hasProblem = unshipped.length > 0;
|
||||
const hasAnyShipped = shipmentData.some((i) => i.shipped);
|
||||
const hasProblem = shipmentData.some((i) => !i.shipped);
|
||||
const problemNote = hasProblem
|
||||
? unshipped.map((i) => `${i.name}${i.quantity ? ` (${i.quantity}${i.unit ? ` ${i.unit}` : ""})` : ""}${i.comment ? ` — ${i.comment}` : ""}`).join("; ")
|
||||
? shipmentData.filter((i) => !i.shipped).map((i) => `${i.name}${i.quantity ? ` (${i.quantity}${i.unit ? ` ${i.unit}` : ""})` : ""}${i.comment ? ` — ${i.comment}` : ""}`).join("; ")
|
||||
: null;
|
||||
|
||||
// Auto-update delivery status based on shipment
|
||||
let newDeliveryStatus = undefined;
|
||||
if (canMarkDelivered) {
|
||||
newDeliveryStatus = "delivered";
|
||||
} else if (hasProblem) {
|
||||
newDeliveryStatus = "problem";
|
||||
}
|
||||
// Any shipment data saved = delivered status. Problems go into has_delivery_problem/delivery_problem_note columns.
|
||||
const newDeliveryStatus = hasAnyShipped ? "delivered" : undefined;
|
||||
|
||||
const updatePayload = {
|
||||
driver_shipment_data: shipmentData,
|
||||
|
|
|
|||
|
|
@ -210,6 +210,16 @@ Deno.serve(async (request) => {
|
|||
groupUpdateData.pickup_time_slot = body.pickupTimeSlot || requestedSlot.deliveryTime || null;
|
||||
// Pickup orders don't need a driver — clear assignment
|
||||
groupUpdateData.assigned_driver_id = null;
|
||||
// Generate pickup code if not already set
|
||||
const { data: existingGroup } = await supabase
|
||||
.from("order_groups")
|
||||
.select("pickup_code")
|
||||
.eq("id", invitation.order_group_id)
|
||||
.single();
|
||||
if (!existingGroup?.pickup_code) {
|
||||
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
groupUpdateData.pickup_code = code;
|
||||
}
|
||||
}
|
||||
|
||||
const { error: groupUpdateError } = await supabase
|
||||
|
|
@ -258,6 +268,7 @@ Deno.serve(async (request) => {
|
|||
ok: true,
|
||||
orderGroupId: invitation.order_group_id,
|
||||
deliveryStatus: effectiveDeliveryStatus,
|
||||
pickupCode: groupUpdateData.pickup_code || null,
|
||||
},
|
||||
200,
|
||||
corsHeaders,
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ Deno.serve(async (request) => {
|
|||
|
||||
const { data: currentGroup, error: currentGroupError } = await supabase
|
||||
.from("order_groups")
|
||||
.select("id, delivery_status, delivery_invitation_id")
|
||||
.select("id, delivery_status, delivery_invitation_id, pickup_code")
|
||||
.eq("id", orderGroupId)
|
||||
.single();
|
||||
|
||||
|
|
@ -175,6 +175,11 @@ Deno.serve(async (request) => {
|
|||
updateData.pickup_date = body.pickupDate || deliveryDate || null;
|
||||
updateData.pickup_time_slot = body.pickupTimeSlot || deliveryTime || null;
|
||||
updateData.assigned_driver_id = null;
|
||||
// Generate pickup code if not already set
|
||||
if (!currentGroup.pickup_code) {
|
||||
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
updateData.pickup_code = code;
|
||||
}
|
||||
} else {
|
||||
updateData.pickup_date = null;
|
||||
updateData.pickup_time_slot = null;
|
||||
|
|
|
|||
Loading…
Reference in New Issue