Commit Graph

57 Commits

Author SHA1 Message Date
Hermes c3df1fc0e1 feat(analytics): «Путь заказа» — воронка по ступеням обработки
Владелец: «а путь заказа можно сдать какой-то красивой воронкой?»

В 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.
2026-09-29 11:03:24 +02:00
Hermes 188db318e3 feat(analytics): воронка коммуникаций на странице «Аналитика»
Владелец: «нужно видеть, как всё происходит: 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.
2026-09-29 10:23:01 +02:00
Hermes 6d8992dfad fix(autocall): результат тестового звонка больше не пишется в боевой заказ
В тестовом режиме робот звонит на тестовый номер, но нажатия уходили в заказ
клиента: заказ получал статус «согласовано роботом», дату, комментарий и
счётчик попытки — хотя клиенту не звонили. Теперь при was_test_mode:
- строка журнала пишется без привязки к заказу (order_group_id = null),
- заказ не обновляется вообще (ранний выход до всех update).

Симптом на проде 28.09: заказ ТП-010796 (9788324113) получил
«Согласовано роботом: сегодня, вторая половина дня» после тестового звонка.
2026-09-28 13:11:56 +02:00
Hermes d98c44c9fd feat(client): номер доставки и код выдачи + «Доставка» вместо «Счета»; в SMS — {order} и {code}
Клиентская страница согласования:
- приглашение теперь отдаёт 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
2026-09-28 12:21:51 +02:00
Hermes b69e5172d6 chore: сохранить SQL аудита истории в репозитории + убрать мусор из индекса
- 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-*)
2026-09-28 11:34:45 +02:00
Hermes cf5bf31008 feat(sms): текст SMS по типу отгрузки — свои шаблоны для доставки и самовывоза
Раньше текст был зашит в кампании и всегда говорил про доставку; настраиваемый
в админке 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 (обязательный бамп при деплое фронта)
2026-09-28 10:50:32 +02:00
Hermes ece302d512 fix(autocall): самовывоз пишет только pickup_date
Решение владельца: у самовывоза дата живёт в 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.
2026-09-25 17:30:29 +02:00
Hermes 7e719e6b43 fix(autocall): день робота → дата без сдвига; окно не считаем при пустом графике
Проверка цепочки «варианты в меню → цифра → дата в заказе» нашла два дефекта:

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.
2026-09-25 16:56:21 +02:00
Hermes 3a4cceb9a4 feat(autocall-log): пишем дату согласованного слота (chosen_date) в журнал звонков
AGI уже передаёт дату (chosen_date) — теперь она сохраняется в autocall_log,
поэтому карточка и журнал показывают конкретный день, а не «день 1/2».
2026-09-25 16:16:43 +02:00
Hermes aeb4bbf63f fix(autocall): убраны сценарии «0» и «9» из диалога и из презентации; пауза между звонками 1 час; граница половины дня 13:00; в презентации готовность по обмену с 1С, без готовности в субботу 2026-09-16 10:36:01 +02:00
Hermes 44f58ce9e7 fix(autocall): доставка предлагает реальные рабочие дни графика (пт → пн/вт), эскалация по настройкам админки, WAIT FOR DIGIT и «0 — менеджер» 2026-09-15 09:40:43 +02:00
Hermes 3706f64ca7 feat(orders): блок про SMS смотрит на баланс счёта — гасится после пополнения, закрывается крестиком, предупреждает о низком балансе (эдж-функция sms-balance) 2026-09-14 15:58:30 +02:00
Hermes f62a6b525b feat(autocall): окно самовывоза 3 дня (сегодня/завтра/послезавтра), даты из диалога, гео-исключения как у SMS 2026-09-11 11:29:49 +02:00
Hermes c4e2c4a181 feat(autocall): самовывоз — тип звонка pkp/dlv из карточки заказа, выключатель самовывоза, тип в логе и очереди 2026-09-11 10:53:53 +02:00
Hermes 5dbeae84f4 feat(autocall): исключения автодозвона — таблица, UI в настройках, проверка в планировщике, бейдж в реестре/карточке 2026-09-08 11:16:16 +02:00
Hermes 0d8e0f9d40 feat(autocall): тестовый звонок из UI для мегаадмина — autocall_queue nullable + is_test_call, TestCallPanel с окном результата 2026-09-02 11:10:06 +02:00
Hermes 4ed7f64a37 feat(autocall): добавить autocall_queue (ручной звонок через планировщик). Миграция 20260902 + чтение очереди в scheduler 2026-09-02 10:56:02 +02:00
Hermes 75476c9d70 fix(auth): добавить dev.mkn8n.ru в ALLOWED_ORIGINS (CORS Origin not allowed) 2026-09-01 22:52:12 +02:00
Hermes a08612ef0d fix(autocall): edge fn обновляет по uniqueid вместо дубликата (клиент бросает трубку после выбора) 2026-09-01 22:50:39 +02:00
Hermes 6435679161 feat(autocall): раздел «Коммуникации» с вкладками SMS/Автодозвон/Аналитика, бейджи, карточка заказа, edge fn autocall-result, миграция БД 2026-09-01 21:34:39 +02:00
Hermes 2fda27a329 fix(login): request-otp must insert otp_code (NOT NULL) for send_pin email
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).
2026-08-21 16:35:04 +02:00
Hermes dc0f7f51c5 fix(client): allow self-booking in manual-confirmation status
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.
2026-08-21 14:25:16 +02:00
Hermes c0ebe7f59f fix(manage-users): detach FK references before deleting user (RESTRICT FKs in order_history/action_logs/suggestions/order_groups blocked auth.deleteUser with 'Database error deleting user') 2026-08-14 15:20:40 +02:00
Hermes 7654413d8b fix(push): test notification targets the device's own subscription (by endpoint), not localStorage phone 2026-08-13 15:02:26 +02:00
Hermes 15132f56e8 feat(client): PWA install — client route, manifest start_url=/client, SW app-shell, invitation links → /client/:token 2026-08-11 13:37:29 +02:00
root 1922778e0f feat(push): prepare client subscription test contour 2026-08-06 08:56:12 +00:00
root d397d0cb24 feat: configurable business schedule (delivery/pickup/SMS days)
- New table business_schedule_settings (delivery_days, pickup_days, sms_days)
- RPC get_business_schedule() (public) + update_business_schedule() (admin)
- Admin UI: BusinessSchedulePanel with day pills for each category
- ClientDeliveryPage: fetches schedule, replaces hardcoded Sunday/weekend checks
- PickupSlotsPicker: uses pickupDays prop instead of hardcoded isWeekend
- Edge function confirm-delivery-choice: reads delivery_days from DB
- delivery-invitations.ts: buildDefaultDatedAvailableSlots reads delivery_days
- RPC confirm_delivery_choice_by_token: checks delivery_days instead of extract(dow)=0
- SMS scripts: get_sms_days() reads sms_days from business_schedule_settings
  and overrides per-campaign work_days setting
- Nav item Расписание for mega_admin and admin roles
- Default: Mon-Fri (1,2,3,4,5) for all three categories
2026-07-03 12:45:02 +00:00
root 93d3ce4ca8 fix: manage-users — remove manual public.users insert, trigger handle_new_user does it via user_metadata 2026-06-30 07:04:24 +00:00
root cadcceef7e feat: manage-users edge function — admin user CRUD (create/delete) 2026-06-30 06:55:55 +00:00
root b74add32ef fix: skip Sunday in delivery date selection (client flow + backend validation)
- buildDefaultDatedAvailableSlots(): skip Sunday via getNextWorkday()
- ClientDeliveryPage.jsx: getAllowedDeliveryDateKeys() + fallback slots skip Sunday
- confirm-delivery-choice edge function: reject Sunday for delivery
- confirm_delivery_choice_by_token SQL RPC: extract(dow) = 0 → RAISE EXCEPTION
- Tests: Friday→Sat+Mon, Saturday→Mon+Tue (skip Sunday)
- Skill updated with pitfall
2026-06-23 04:56:49 +00:00
root d04901d296 security: fix 9 vulnerabilities + OrdersTable 14px font + colored status badges
Security fixes:
- Remove VITE_SUPABASE_SERVICE_ROLE_KEY from Docker build args
- Add missing security.ts functions (verifyInternalRequest, requireSameOrigin, etc)
- Store only OTP hash, remove plaintext otp_code
- Fix CORS origin check: startsWith → exact match
- Use atomic SQL check_rate_limit RPC instead of JS rate limiter
- Mask PII in get_delivery_invitation_by_token single-order path
- Add RequireAuth wrapper for protected routes
- Revoke anon execute on confirm_delivery_choice_by_token
- Add HSTS header to Caddyfile

UI:
- OrdersTable desktop: all cells text-sm (14px)
- Status column: use colored Badge with tone from getOrderGroupStatusTone
- Header row: text-sm (14px)
2026-06-19 12:14:19 +00:00
root 37ea7d3a19 fix: убрать генерацию pickup_code — приходит из 1С, показывать только при самовывозе 2026-06-16 07:36:47 +00:00
root 9408b98f06 fix: pickup_code — 6-значное число вместо букв 2026-06-16 06:57:57 +00:00
root 5867272873 feat: pickup_code для самовывоза + упрощение статуса отгрузки
- pickup_code: автогенерация 6-значного кода при выборе самовывоза
- Колонка pickup_code в order_groups + SELECT
- Отображение кода выдачи в карточках (логист, клиент)
- Упрощение: любая отгрузка → delivered (проблемы в has_delivery_problem)
- RPC: добавлены deliveryType, pickupCode, pickupDate, pickupTimeSlot
2026-06-16 06:53:12 +00:00
root 41272bdbbf fix: аудит доставки — критические и средние баги
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: колонка Отгрузка с визуализацией проблем
2026-06-15 15:38:42 +00:00
root ae1e0a2e0e fix: 3 бага доставки — самовывоз/водитель/отгрузка
Bug 1: deliveryType='delivery' перезаписывался на 'pickup' маппером из-за адреса/источника. Теперь если БД явно содержит delivery_type='delivery', маппер это уважает.

Bug 2: водитель не мог сохранить частичную отгрузку — добавлена кнопка 'Сохранить отгрузку' в DriverShipmentPanel + saveShipmentData() в репозитории.

Bug 3: при смене типа на самовывоз assigned_driver_id не очищался — добавлено assigned_driver_id: null в updatePayload.
2026-06-15 15:10:30 +00:00
root 7e43f9e990 feat: requires_address status + address input for pickup-to-delivery switch
- New status: requires_address (Требуется адрес) in deliveryWorkflow
- ClientDeliveryPage: address input shown when delivery chosen without address on file
- OrderDetailPanel: requires_address status badge + address input for manager
- confirm_delivery_choice_by_token RPC: p_delivery_address param, sets requires_address status
- Edge function: delivery_address passed to RPC
- deliveryInvitationApi: deliveryAddress param
- CHECK constraint: requires_address, address_required added
- PickupSlotsPicker: styled storage conditions info block
2026-06-10 14:52:17 +00:00
root e05613ac1d feat: add pickup (самовывоз) delivery type
- New status pickup in delivery workflow
- DB: delivery_type, pickup_date, pickup_time_slot columns
- Client page: tabs Доставка/Самовывоз with PickupSlotsPicker
- PickupSlotsPicker: today/tomorrow/day-after with half-day slots
- Storage notice: free 2 workdays, then 300₽/day
- OrderDetailPanel: delivery type tabs, pickup date/time, status button
- Edge function: delivery_type/pickup fields in confirm-delivery-choice
- RPC: confirm_delivery_choice_by_token updated for pickup
- orderGroupRepository: full pickup field mapping
2026-06-10 12:02:46 +00:00
root dac8450586 feat: action_logs — журнал действий сотрудников с фильтрами 2026-05-27 15:12:46 +00:00
Codex e6dc1972fb feat: show full order composition on delivery page
- 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
2026-05-21 11:52:46 +03:00
Codex 6244749545 feat: unify manual_required status and driver label
- 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
2026-05-20 16:25:06 +03:00
Codex 838c4cb7ae feat(paid-storage): bypass stale RPC for paid_storage transitions
- OrderDetailPanel: paid_storage bypasses update_delivery_status RPC
- Repository: direct table update for paid_storage entry/exit
- LogisticsBoard: collapsible sections ordered by funnel
- SQL: updated paid_storage schema patch
- LoginPage: hide dev quick-login in production
2026-05-20 16:25:06 +03:00
Codex c8fbe95bd1 feat(paid-storage): inline confirmation + cancel action + status display
- 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
2026-05-20 16:25:06 +03:00
Codex b9c6bb2810 fix: RLS recursion, driver UI, auth errors, status counts
- Fix infinite RLS recursion in users policies via current_role_name()
- Add is_admin() helper and simplify users select policies
- Fix order_groups insert for service_role (n8n integration)
- Fix status option counts in dropdown (LogisticsReadinessBoard)
- Add driver delivery status buttons (loaded, on_route, delivered, problem, cancelled)
- Replace driver date range filters with single date picker + date pills
- Hide SMS/internal data from driver view in OrderDetailPanel
- Fix Edge Function error messages in OTP flow (read error.context body)
- Add update_delivery_status RPC for driver workflow
- Remove customer column from order_groups fetch (DB schema mismatch)
- UI: swap filter/search positions, limit modal width, status label cleanup
2026-05-20 16:25:05 +03:00
Codex fc74241378 fix(delivery): show invoice refs in client flow 2026-05-14 12:51:25 +03:00
Codex eeb2620547 fix(delivery): switch public flow to rpc 2026-05-13 18:59:30 +03:00
Codex 633973142d fix(delivery): load links and schedule SMS 2026-05-13 18:59:30 +03:00
Codex b79de7afba feat(delivery): auto-create invitation links
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.
2026-05-13 18:59:30 +03:00
Codex f2230f3277 Add logistics UI actions and demo login buttons
- 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
2026-05-13 18:59:30 +03:00
Codex 219670583b Add security hardening SQL patch 2026-05-13 18:59:30 +03:00