feat: delivery/pickup switching in admin card + address field

- Added delivery address input field in manual agreement form (visible when delivery type)
- Pickup switching clears delivery_address in DB, delivery switching preserves/updates it
- useOrderGroups passes deliveryType, deliveryAddress, pickupDate, pickupTimeSlot to API
- orderGroupRepository: updateOrderGroupDeliveryChoice now handles delivery_type and address
- Pickup orders: address cleared, pickup_date/pickup_time_slot saved
- Delivery orders: address saved, pickup fields cleared
- Updated tests to match new payload structure
This commit is contained in:
root 2026-06-12 08:23:41 +00:00
parent 9aef4d49c0
commit 498faca24d
4 changed files with 50 additions and 16 deletions

View File

@ -637,8 +637,8 @@ export const OrderDetailPanel = ({
deliveryDate: deliveryType === "pickup" ? pickupDate : deliveryDate, deliveryDate: deliveryType === "pickup" ? pickupDate : deliveryDate,
deliveryTime: deliveryType === "pickup" ? pickupTimeSlot : deliveryTime, deliveryTime: deliveryType === "pickup" ? pickupTimeSlot : deliveryTime,
deliveryType, deliveryType,
...(deliveryType === "pickup" ? { pickupDate, pickupTimeSlot } : {}), ...(deliveryType === "pickup" ? { pickupDate, pickupTimeSlot, deliveryAddress: "" } : {}),
...(deliveryType === "delivery" && deliveryAddress.trim() ? { deliveryAddress: deliveryAddress.trim() } : {}), ...(deliveryType === "delivery" ? { deliveryAddress: deliveryAddress.trim() } : {}),
}); });
if (result?.success) { if (result?.success) {
@ -866,6 +866,20 @@ export const OrderDetailPanel = ({
<p>Начиная с 3-го рабочего дня <strong>300 /день</strong> платного хранения.</p> <p>Начиная с 3-го рабочего дня <strong>300 /день</strong> платного хранения.</p>
</div> </div>
)} )}
{deliveryType === "delivery" && (
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
Адрес доставки
</label>
<input
type="text"
value={deliveryAddress}
onChange={(e) => setDeliveryAddress(e.target.value)}
placeholder="Введите адрес доставки"
className="w-full rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-3 text-sm !text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-accent)] focus:outline-none"
/>
</div>
)}
{isDeliveryAgreed && !isEditingDate ? ( {isDeliveryAgreed && !isEditingDate ? (
<div className="space-y-3"> <div className="space-y-3">
<div className="rounded-[24px] border border-[rgba(18,128,92,0.35)] bg-[var(--color-accent-soft)] p-4 !text-[var(--color-text)]"> <div className="rounded-[24px] border border-[rgba(18,128,92,0.35)] bg-[var(--color-accent-soft)] p-4 !text-[var(--color-text)]">

View File

@ -115,6 +115,10 @@ export const useOrderGroups = () => {
orderGroupId, orderGroupId,
deliveryDate, deliveryDate,
deliveryTime, deliveryTime,
deliveryType,
deliveryAddress,
pickupDate,
pickupTimeSlot,
}) => { }) => {
setIsSavingDeliveryChoice(true); setIsSavingDeliveryChoice(true);
@ -125,6 +129,10 @@ export const useOrderGroups = () => {
orderGroupId, orderGroupId,
deliveryDate, deliveryDate,
deliveryTime, deliveryTime,
deliveryType,
deliveryAddress,
pickupDate,
pickupTimeSlot,
}); });
if (result.error) { if (result.error) {

View File

@ -258,25 +258,36 @@ export const updateOrderGroupDeliveryChoice = async ({
deliveryDate, deliveryDate,
deliveryTime, deliveryTime,
deliveryType, deliveryType,
deliveryAddress,
pickupDate, pickupDate,
pickupTimeSlot, pickupTimeSlot,
}) => { }) => {
return safeSupabaseCall(async () => { return safeSupabaseCall(async () => {
const client = requireSupabase(); const client = requireSupabase();
const effectiveDeliveryStatus = deliveryType === "pickup" ? "pickup" : "agreed"; const effectiveDeliveryStatus = deliveryType === "pickup" ? "pickup" : "agreed";
const updatePayload = {
delivery_status: effectiveDeliveryStatus,
delivery_date: deliveryDate,
delivery_time: deliveryTime,
delivery_type: deliveryType || "delivery",
delivery_date_source: "manual",
notification_status: "confirmed",
updated_at: new Date().toISOString(),
};
if (deliveryType === "pickup") {
updatePayload.pickup_date = pickupDate || null;
updatePayload.pickup_time_slot = pickupTimeSlot || null;
updatePayload.delivery_address = "";
} else {
updatePayload.pickup_date = null;
updatePayload.pickup_time_slot = null;
if (deliveryAddress !== undefined) {
updatePayload.delivery_address = deliveryAddress;
}
}
const updateResult = await client const updateResult = await client
.from("order_groups") .from("order_groups")
.update({ .update(updatePayload)
delivery_status: effectiveDeliveryStatus,
delivery_date: deliveryDate,
delivery_time: deliveryTime,
delivery_type: deliveryType || "delivery",
pickup_date: deliveryType === "pickup" ? pickupDate : null,
pickup_time_slot: deliveryType === "pickup" ? pickupTimeSlot : null,
delivery_date_source: "manual",
notification_status: "confirmed",
updated_at: new Date().toISOString(),
})
.eq("id", orderGroupId); .eq("id", orderGroupId);
if (updateResult.error) { if (updateResult.error) {

View File

@ -158,15 +158,16 @@ describe("updateOrderGroupDeliveryChoice", () => {
}); });
expect(fromMock).toHaveBeenCalledWith("order_groups"); expect(fromMock).toHaveBeenCalledWith("order_groups");
expect(updateMock).toHaveBeenCalledWith({ expect(updateMock).toHaveBeenCalledWith(expect.objectContaining({
delivery_status: "agreed", delivery_status: "agreed",
delivery_date: "2026-05-13", delivery_date: "2026-05-13",
delivery_time: "Первая половина дня", delivery_time: "Первая половина дня",
delivery_type: "delivery",
notification_status: "confirmed", notification_status: "confirmed",
updated_at: expect.any(String), updated_at: expect.any(String),
}); }));
expect(eqMock).toHaveBeenCalledWith("id", "group-id"); expect(eqMock).toHaveBeenCalledWith("id", "group-id");
expect(selectMock).toHaveBeenCalledWith("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, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)"); expect(selectMock).toHaveBeenCalledWith(expect.stringContaining("delivery_type, pickup_date, pickup_time_slot"));
expect(singleMock).toHaveBeenCalledTimes(1); expect(singleMock).toHaveBeenCalledTimes(1);
}); });
}); });