From 794c3ce5e3e81c908e5419621584399ad3508063 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 11 Sep 2026 15:53:03 +0200 Subject: [PATCH] =?UTF-8?q?fix(autocall,client):=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=87=D0=B8=D0=B5=20=D0=B4=D0=BD=D0=B8=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0=20=D0=B0=D0=B4=D0=BC?= =?UTF-8?q?=D0=B8=D0=BD=D0=BA=D0=B8=20=E2=80=94=20=D1=81=D0=B0=D0=BC=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B2=D0=BE=D0=B7=20=D0=BF=D0=BD-=D0=BF=D1=82=20?= =?UTF-8?q?=D1=83=20=D1=80=D0=BE=D0=B1=D0=BE=D1=82=D0=B0=20=D0=B8=20=D1=81?= =?UTF-8?q?=D1=81=D1=8B=D0=BB=D0=BA=D0=B8,=20=D0=B4=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B0=20=D0=B2=20=D1=81=D1=81=D1=8B=D0=BB?= =?UTF-8?q?=D0=BA=D0=B5=20=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D1=82=20=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D1=84=D0=B8=D0=BA;=20=D1=82=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=82=D1=8B=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=C2=AB?= =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D0=B5=20=D0=B4=D0=BD=D0=B8?= =?UTF-8?q?=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/BusinessSchedulePanel.jsx | 8 ++- src/pages/ClientDeliveryPage.jsx | 61 ++++++++++++++----- src/services/deliveryInvitationApi.js | 44 ++++++++----- 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/components/admin/BusinessSchedulePanel.jsx b/src/components/admin/BusinessSchedulePanel.jsx index 76bc703..da36a7f 100644 --- a/src/components/admin/BusinessSchedulePanel.jsx +++ b/src/components/admin/BusinessSchedulePanel.jsx @@ -153,6 +153,8 @@ export const BusinessSchedulePanel = () => {

Рабочие дни

Настройте, в какие дни доступны доставка, самовывоз и отправка SMS-приглашений. + Дни самовывоза — единый источник правды: их же использует страница клиента по ссылке + и робот-автодозвон, в другие дни самовывоз не предлагается.

{savedAt && !hasChanges && ( @@ -169,7 +171,8 @@ export const BusinessSchedulePanel = () => { Доставка

- Дни, в которые клиент может выбрать доставку. В остальные дни доставка не предлагается. + Дни, в которые клиент может выбрать доставку по ссылке. В остальные дни доставка + не предлагается.

@@ -191,7 +194,8 @@ export const BusinessSchedulePanel = () => { Самовывоз

- Дни, в которые клиент может выбрать самовывоз. В остальные дни самовывоз не предлагается. + Дни, в которые склад выдаёт самовывоз. В остальные дни самовывоз не предлагается — + ни в ссылке клиента, ни в звонке робота.

diff --git a/src/pages/ClientDeliveryPage.jsx b/src/pages/ClientDeliveryPage.jsx index 6dce74b..9f38332 100644 --- a/src/pages/ClientDeliveryPage.jsx +++ b/src/pages/ClientDeliveryPage.jsx @@ -15,6 +15,7 @@ import { formatDeliveryDate } from "../components/client/deliveryDateFormatting" import { confirmDeliveryChoice, fetchDeliveryInvitation, + fetchDeliveryWorkDays, fetchPickupWorkDays, } from "../services/deliveryInvitationApi"; @@ -128,30 +129,52 @@ const isSundayKey = (dateKey) => { return d.getUTCDay() === 0; }; -const getNextWorkdayKey = (dateKey) => { +// Рабочие дни доставки из «Графика работы» (1=пн … 7=вс). +// Дефолт — прежнее поведение страницы: все дни, кроме воскресенья. +const DEFAULT_DELIVERY_DAYS = [1, 2, 3, 4, 5, 6]; + +const isAllowedDeliveryDayKey = (dateKey, workDays = DEFAULT_DELIVERY_DAYS) => { + if (!dateKey) return false; + const d = new Date(`${dateKey}T12:00:00Z`); + if (Number.isNaN(d.getTime())) return false; + const weekday = d.getUTCDay() === 0 ? 7 : d.getUTCDay(); + return workDays.includes(weekday); +}; + +const getNextWorkdayKey = (dateKey, workDays = DEFAULT_DELIVERY_DAYS) => { let next = addDaysToDateKey(dateKey, 1); - while (isSundayKey(next)) { + let guard = 0; + while (!isAllowedDeliveryDayKey(next, workDays) && guard < 14) { next = addDaysToDateKey(next, 1); + guard += 1; } return next; }; -const getAllowedDeliveryDateKeys = (referenceDate = new Date()) => { +const getAllowedDeliveryDateKeys = (referenceDate = new Date(), workDays = DEFAULT_DELIVERY_DAYS) => { const todayKey = getBusinessTodayKey(referenceDate); - const firstWorkday = getNextWorkdayKey(todayKey); - const secondWorkday = getNextWorkdayKey(firstWorkday); + const firstWorkday = getNextWorkdayKey(todayKey, workDays); + const secondWorkday = getNextWorkdayKey(firstWorkday, workDays); return new Set([firstWorkday, secondWorkday].filter(Boolean)); }; -const isAllowedDeliverySlotDate = (dateKey, referenceDate = new Date()) => { +const isAllowedDeliverySlotDate = ( + dateKey, + referenceDate = new Date(), + workDays = DEFAULT_DELIVERY_DAYS, +) => { if (!dateKey) { return false; } - return getAllowedDeliveryDateKeys(referenceDate).has(dateKey); + return getAllowedDeliveryDateKeys(referenceDate, workDays).has(dateKey); }; -export const groupSlotsFromInvitation = (invitation, referenceDate = new Date()) => { +export const groupSlotsFromInvitation = ( + invitation, + referenceDate = new Date(), + workDays = DEFAULT_DELIVERY_DAYS, +) => { if (!invitation) { return []; } @@ -161,10 +184,10 @@ export const groupSlotsFromInvitation = (invitation, referenceDate = new Date()) const deliveryTime = invitation.deliveryTime; if (!rawSlots.length && !deliveryDate) { - // Fallback: generate default delivery slots (next 2 workdays, both halves, skip Sunday) + // Fallback: generate default delivery slots (next 2 workdays, both halves) const todayKey = getBusinessTodayKey(referenceDate); - const firstWorkday = getNextWorkdayKey(todayKey); - const secondWorkday = getNextWorkdayKey(firstWorkday); + const firstWorkday = getNextWorkdayKey(todayKey, workDays); + const secondWorkday = getNextWorkdayKey(firstWorkday, workDays); return [ { id: `slot-${firstWorkday}-first`, date: firstWorkday, time: "Первая половина дня" }, { id: `slot-${firstWorkday}-second`, date: firstWorkday, time: "Вторая половина дня" }, @@ -194,7 +217,7 @@ export const groupSlotsFromInvitation = (invitation, referenceDate = new Date()) || deliveryDate || ""; - if (!isAllowedDeliverySlotDate(parsedDate, referenceDate)) { + if (!isAllowedDeliverySlotDate(parsedDate, referenceDate, workDays)) { return null; } @@ -218,7 +241,7 @@ export const groupSlotsFromInvitation = (invitation, referenceDate = new Date()) return null; } - if (!isAllowedDeliverySlotDate(slotDate, referenceDate)) { + if (!isAllowedDeliverySlotDate(slotDate, referenceDate, workDays)) { return null; } @@ -306,10 +329,16 @@ export const ClientDeliveryPage = () => { [token, invitation?.smsSentAt], ); - // Рабочие дни самовывоза — из графика компании, те же, что называет робот. + // Рабочие дни — из графика компании, те же, что называет робот. const [pickupWorkDays, setPickupWorkDays] = React.useState([1, 2, 3, 4, 5]); + const [deliveryWorkDays, setDeliveryWorkDays] = React.useState(DEFAULT_DELIVERY_DAYS); React.useEffect(() => { let cancelled = false; + fetchDeliveryWorkDays() + .then((days) => { + if (!cancelled) setDeliveryWorkDays(days); + }) + .catch(() => {}); fetchPickupWorkDays() .then((days) => { if (!cancelled) setPickupWorkDays(days); @@ -365,7 +394,7 @@ export const ClientDeliveryPage = () => { }; }, [token]); - const slots = groupSlotsFromInvitation(invitation, referenceDate); + const slots = groupSlotsFromInvitation(invitation, referenceDate, deliveryWorkDays); const invitationState = invitation?.state || "awaiting_choice"; const isActiveState = ["awaiting_choice", "opened", "reminder_sent"].includes(invitationState); @@ -412,7 +441,7 @@ export const ClientDeliveryPage = () => { setSelectedSlot( buildSelectedSlotFromInvitation( loadedInvitation, - groupSlotsFromInvitation(loadedInvitation, referenceDate), + groupSlotsFromInvitation(loadedInvitation, referenceDate, deliveryWorkDays), ) || effectiveSelectedSlot, ); setChoiceSaved(true); diff --git a/src/services/deliveryInvitationApi.js b/src/services/deliveryInvitationApi.js index 00dc0c0..fa9245f 100644 --- a/src/services/deliveryInvitationApi.js +++ b/src/services/deliveryInvitationApi.js @@ -184,30 +184,46 @@ export const __resetLocalDeliveryInvitationCache = () => { }; /** - * Рабочие дни самовывоза из графика компании («График работы»). - * Робот и страница клиента обязаны предлагать одни и те же дни, иначе - * клиент выберет день, который склад не работает (например, воскресенье). - * Номера дней: 1=пн … 7=вс. + * Рабочие дни из графика компании («График работы» в админке). + * Админка — единственный источник правды: и страница клиента, и робот-автодозвон + * обязаны предлагать одни и те же дни, иначе клиент выберет день, когда склад + * или доставка не работают. Номера дней: 1=пн … 7=вс. */ -export const fetchPickupWorkDays = async () => { - const fallback = [1, 2, 3, 4, 5]; +const SCHEDULE_FALLBACK = { + deliveryDays: [1, 2, 3, 4, 5, 6], + pickupDays: [1, 2, 3, 4, 5], + smsDays: [1, 2, 3, 4, 5], +}; + +const normalizeDays = (days, fallback) => { + const parsed = (Array.isArray(days) ? days : []) + .map((d) => Number(d)) + .filter((d) => Number.isInteger(d) && d >= 1 && d <= 7); + return parsed.length ? parsed : fallback; +}; + +export const fetchBusinessSchedule = async () => { if (!hasSupabaseConfig || !supabase?.rpc) { - return fallback; + return { ...SCHEDULE_FALLBACK }; } try { const { data, error } = await supabase.rpc("get_business_schedule"); if (error) throw error; - const days = Array.isArray(data?.pickupDays) ? data.pickupDays : []; - const parsed = days - .map((d) => Number(d)) - .filter((d) => Number.isInteger(d) && d >= 1 && d <= 7); - return parsed.length ? parsed : fallback; + return { + deliveryDays: normalizeDays(data?.deliveryDays, SCHEDULE_FALLBACK.deliveryDays), + pickupDays: normalizeDays(data?.pickupDays, SCHEDULE_FALLBACK.pickupDays), + smsDays: normalizeDays(data?.smsDays, SCHEDULE_FALLBACK.smsDays), + }; } catch (e) { - console.warn("Не удалось получить график самовывоза, беру пн-пт", e); - return fallback; + console.warn("Не удалось получить график работы, беру значения по умолчанию", e); + return { ...SCHEDULE_FALLBACK }; } }; +export const fetchPickupWorkDays = async () => (await fetchBusinessSchedule()).pickupDays; + +export const fetchDeliveryWorkDays = async () => (await fetchBusinessSchedule()).deliveryDays; + export const fetchDeliveryInvitation = async (token) => { if (!token) { throw new Error("Token is required");