Владелец: «нужно видеть, согласование прошло через пуш/смс/звонок/вручную через
менеджера» и «в колонке с именем клиента пишется просто «Согласовано» — непонятно
как и кем». Плюс в карточке «завтра/послезавтра» читалось как дата заказа.
Источник правды — order_groups.delivery_date_source: manual (логист), autocall
(робот), и теперь push/sms/messenger/client от клиентской страницы. Раньше клиентское
подтверждение канал не писало вовсе — такие записи разбираются по confirmed_at
приглашения (канал «link» → «По ссылке»).
- миграция 20260929_confirmation_channel.sql: confirm_delivery_choice_by_token
принимает p_src и сохраняет канал в delivery_date_source + action_logs.details;
- клиентская страница читает ?src= из ссылки и прокидывает в RPC (SMS-ссылка уже
содержит src=sms);
- orderGroupViews: resolveConfirmChannel/confirmChannelMeta — единый источник подписей
канала, плюс invitationConfirmedAt в репозитории (и в списке, и в карточке);
- реестр доставок/самовывозов: под статусом чип «как согласовано» (📞 звонок робота,
✉️ SMS-ссылка, 🔔 push, 🧑💼 логист, 🔗 по ссылке);
- карточка заказа: рядом с «Согласовано» тот же чип;
- AutocallStatusCard: «робот предлагал: завтра или послезавтра» вместо голого
«завтра/послезавтра» (читалось как дата заказа);
- Аналитика: в «Пути заказа» блок «Как согласовали дату» — полосы по каналам
(за 30 дней: логист 135, по ссылке 20, звонок робота 7, push 0).
Тесты: orderGroupViews +4, OrderPathFunnelBlock +1 (37 зелёных в этих файлах). SW v128.
Владелец: «а путь заказа можно сдать какой-то красивой воронкой?»
В admin_comms_funnel добавлен блок order_path: этапы (групп в работе → первое SMS →
дата согласована → выдано по цепочке), честная плашка «мимо цепочки» (выдано без даты,
платное хранение, отмены), источники даты (менеджер/робот/без даты) и разбивка
самовывоз/доставка. Считается так, что водитель без даты не едет: последний этап —
это выданные С датой, поэтому воронка монотонная.
OrderPathFunnelBlock рисует воронку штатным recharts FunnelChart (градиент по этапам,
подписи «значение · %», тултип с конверсией шага), под ней — легенда теми же цифрами
(на телефоне воронку читать тяжело), чипы конверсий между ступенями, плашка «мимо
цепочки» и чипы источников/типа заказа. Блок стоит над воронкой коммуникаций.
За 30 дней: 396 → первое SMS 340 (85.9%) → дата 161 (47.4% от SMS) → выдано по цепочке
134 (83.2%); мимо цепочки 204; дата от менеджера 135, от робота 6; самовывоз 235,
доставка 161. Тест: OrderPathFunnelBlock.test.jsx (6 проверок). SW v127.
Владелец: «нужно видеть, как всё происходит: push отправили — реакции не было, потом SMS,
потом звонок; с какого раза человек ответил на звонок, кто вообще не поднимает трубку».
Один RPC admin_comms_funnel(p_days, p_date_from, p_date_to) — период считается как у
admin_automation_economics (30 = группы, созданные за 30 дней; 0 = всё время) — отдаёт:
этапы цепочки по когорте (группы → push → первое SMS → открыли ссылку → позвонили →
взяли трубку → согласовано → в ручную → выдано), разбивку звонков по попыткам
(всего/взяли/согласовали/не взяли + % ответов), каналы подтверждения (робот / менеджер /
ссылка), список «не поднимают трубку» и «открыли ссылку, но не подтвердили», и «silent» —
кого не достали ни ссылкой, ни звонком.
Блок CommsFunnelBlock на «Аналитике» под KPI-карточками, те же табы периода, mobile-first,
время в МСК. За 30 дней: 396 групп, первое SMS 340, открыли ссылку 1, звонили 26,
взяли трубку 14, согласовано 128, в ручную 22, выдано 357; 265 (66.9%) не достали никак.
С попыток: 1-я — 42 ответа из 64, 2-я — 2 из 6, 3-я — 4 из 4.
Тест: src/components/admin/analytics/CommsFunnelBlock.test.jsx (7 проверок, фикстура по
реальному ответу RPC). SW: v123 -> v124.
В тестовом режиме робот звонит на тестовый номер, но нажатия уходили в заказ
клиента: заказ получал статус «согласовано роботом», дату, комментарий и
счётчик попытки — хотя клиенту не звонили. Теперь при was_test_mode:
- строка журнала пишется без привязки к заказу (order_group_id = null),
- заказ не обновляется вообще (ранний выход до всех update).
Симптом на проде 28.09: заказ ТП-010796 (9788324113) получил
«Согласовано роботом: сегодня, вторая половина дня» после тестового звонка.
Клиентская страница согласования:
- приглашение теперь отдаёт deliveryType и pickupCode (public view) — до этого
страница не знала, что заказ самовывозный, и говорила «Доставка заказа /
Согласование доставки» даже для самовывоза; в шапке и hero-тексте выбор по типу
- в блоке с кодом: «Ваш номер доставки 010796» + «Ваш код выдачи 7598»
(номер берём из состава заказа, «ТП\-010796» → «010796»)
- ссылка на заказ теперь «Доставка ТП-010796» вместо «Счет ТП\-010796»,
экранированный дефис из обмена с 1С больше не показывается
SMS: в шаблоны добавились переменные {order}/{номер}/{номер_доставки} (номер
доставки) и {code}/{код}/{код_выдачи} (код выдачи) — работают и в шаблоне
кампании, и в тексте профиля города. Дефолтные тексты самовывоза (и значения в
БД, см. supabase/sms_pickup_code_placeholders.sql) теперь содержат номер и код;
в админке у полей появилась памятка по переменным и предпросмотр с подстановкой.
service-worker v118 → v119
- supabase/log_order_group_change.sql, supabase/order_history_rls.sql: триггер
логирования изменений order_groups и его RLS существовали только на сервере
(в БД работают с 2026-07-29), в git исходников не было — восстановить было нечем
- scripts/__pycache__/*.pyc и DriverDeliveryPlanner.jsx.bak сняты с отслеживания
(артефакты и бэкап, в .gitignore уже были __pycache__/ и *.bak — добавлен *.bak-*)
Раньше текст был зашит в кампании и всегда говорил про доставку; настраиваемый
в админке sms_text_template не читался скриптами вообще. Сначала выяснилось,
что всем 909 pickup-группам уходило «Согласуйте дату доставки по ссылке».
- sms_campaign_settings.sms_text_template_pickup (новая колонка) + значения
sms_text_template = «…дату доставки…», pickup = «…дату самовывоза…»
(supabase/sms_text_templates_pickup.sql = миграция с фактическими текстами)
- sms_first_campaign / sms_second_campaign / sms_paid_storage_campaign:
build_sms_text(group, settings) — шаблон выбирается по og.delivery_type,
{link} подставляется ссылкой; приоритет профиль города (режим «Уведомление»)
→ шаблон кампании → дефолт скрипта
- вторая SMS и платное хранение тоже получили pickup-вариант (кампании были
выключены, поэтому в текстах жил только «доставка»)
- SmsCampaignPanel: два поля ввода (📦 Доставка / 🏪 Самовывоз) + длины и
предпросмотр с подставленной ссылкой + пояснение приоритета
- service-worker v117 (обязательный бамп при деплое фронта)
Решение владельца: у самовывоза дата живёт в pickup_date + pickup_time_slot,
delivery_date/delivery_time робот не трогает (раньше дублировал в оба поля,
и дата самовывоза всплывала в полях доставки). Маркер «согласовано роботом»
общий — delivery_date_source='autocall': по нему работают карточка, фильтры и
SQL-статус autocall_status(og); отдельного pickup_date_source в схеме нет.
Проверено на боевой функции временными группами с метками в датах:
самовывоз — pickup_date обновился, delivery_date остался нетронутым;
доставка — delivery_date обновился, pickup_date нетронут. Временные группы удалены.
Исторический дубль (единственный: ТП-010747) очищен — дата осталась в pickup_date.
Проверка цепочки «варианты в меню → цифра → дата в заказе» нашла два дефекта:
1. edge-функция: если дата в call-файл не попала (старый файл/сбой), дата считалась
как «сегодня + цифра − 1» для ОБОИХ типов. Для доставки это сдвигало день на
сутки назад: абонент нажимает «день 1» (робот говорит «завтра»), а в заказ
уезжает сегодня. Теперь fallback повторяет ОЗВУЧЕННУЮ семантику: самовывоз
день 1 = сегодня (сегодня/завтра/послезавтра), доставка день 1 = завтра,
день 2 = послезавтра. Проверено живыми POST на функцию (5 случаев, включая
приоритет chosen_date) — временная группа создана и удалена.
2. планировщик: при пустом «Графике работы» окна подменяют пустой список
дефолтом Пн–Пт, то есть робот предлагал бы дни, когда компания не работает.
Теперь при пустом графике звонок пропускается с понятным предупреждением.
Цепочка «варианты → цифра → дата» проверена на живых окнах: пятница 17:41 → доставка
пн/вт, самовывоз после отсечки 15:30 — только пн (2 варианта), при готовности сегодня
до 15:30 — 3 варианта (пт/пн/вт), цифры 1/2/3 → PICKUP_DAY1/2/3 и DELIVERY_DAY1/2
уходят в диалог и возвращаются как chosen_date.
Full edge-fn sync clobbered a live hotfix: repo request-otp only wrote
otp_code_hash, but login_otps.otp_code is NOT NULL, so every request
failed with 'Failed to generate OTP' and no PIN email was sent. Restore
writing otp_code (n8n send_pin trigger emails it; verify still uses hash).
confirm-delivery-choice blocked any order_groups with delivery_status
other than 'pending_confirmation', so orders in manual handling
(manual_confirmation_required) showed the client a form but rejected the
save. Allow booking for manual_confirmation_required so clients can pick
date+time until the order is assigned/paid. Surface 'paid_storage' and
'driver_assigned' states to the client view instead of a dead form.
Edge function confirm-delivery-choice:
- currentGroup использовался до объявления — перемещён вверх
- pickup не очищал assigned_driver_id — добавлена очистка
- выборка currentGroup теперь включает delivery_address, customer_address
Edge function update-order-group-delivery-choice:
- добавлена поддержка deliveryType=pickup (ранее всегда ставил 'agreed')
- pickup очищает assigned_driver_id, устанавливает pickup_date/time_slot
- дата доставки не обязательна для pickup
Фронтенд:
- assignDriverToOrderGroup: null → 'agreed' (NOT NULL constraint)
- isDeliveryAgreed включает статус 'pickup'
- orderGroupViews: warning-тон для групп с проблемами отгрузки
- OrdersTable: колонка Отгрузка с визуализацией проблем
Bug 1: deliveryType='delivery' перезаписывался на 'pickup' маппером из-за адреса/источника. Теперь если БД явно содержит delivery_type='delivery', маппер это уважает.
Bug 2: водитель не мог сохранить частичную отгрузку — добавлена кнопка 'Сохранить отгрузку' в DriverShipmentPanel + saveShipmentData() в репозитории.
Bug 3: при смене типа на самовывоз assigned_driver_id не очищался — добавлено assigned_driver_id: null в updatePayload.
- Extract OrderCompositionPanel as standalone component
- Support nested order items with sub-positions from source_orders
- Enrich order items from Edge Function when RPC only has invoice refs
- Update RPC SQL to extract product items from source_orders
- Update Edge Function to include source_orders items in invitation
- Add detail rows (account, customer) when no order items available
- orderGroupViews: unify notification manual_required + pending delivery_status into single "Требуется ручное управление" status
- LogisticsReadinessBoard: use status:manual_required in funnel instead of delivery:manual_confirmation_required
- DriverDeliveryPlanner: show "Назначено вам" for driver_assigned instead of "Назначен водитель"
- Add sync-manual-status.sql trigger patch for backend
- Replaced window.confirm with inline confirmation panel in app style
- Shows confirmation block with 'Yes, transfer' / 'Cancel' buttons
- After transfer: shows 'Transferred to paid storage' with date and yellow indicator
- Added cancel button to revert from paid_storage back to pending_confirmation
Add a Supabase trigger that creates delivery invitations and writes delivery_link on order_groups changes. n8n can now consume link_ready rows without calling the edge function first.
- Fix bug in transfer-to-logistics: body.note was incorrectly used for delivery_agreement_status
- Add quick actions section in OrderDetailPanel for status changes (visible to role)
- Add driver assignment buttons for logistician/admin
- Add demo login buttons for all roles (works with real Supabase config)
- Add loginAsDemoUser function to AuthContext for quick demo login