From 14fe89f89978bf098c9551ec327e6cb0e274862a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 10 Jun 2026 12:22:56 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20update=20documentation=20for=20pickup?= =?UTF-8?q?=20(=D1=81=D0=B0=D0=BC=D0=BE=D0=B2=D1=8B=D0=B2=D0=BE=D0=B7)=20f?= =?UTF-8?q?eature=20and=20autodeploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++++++++- deploy.sh | 28 +--------- docs/architecture.md | 47 +++++++++++++++-- docs/n8n-order-group-delivery-flow.md | 13 ++++- docs/product-overview.md | 75 ++++++++++++++++++++------ docs/scenarios.md | 35 ++++++++++-- webhook-listener.py | 76 +++++++++++++++++++++++++++ 7 files changed, 248 insertions(+), 53 deletions(-) create mode 100755 webhook-listener.py diff --git a/README.md b/README.md index 6e3245c..ffac145 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,26 @@ npm install npm run dev ``` +## Деплой + +Приложение разворачивается через Docker (`docker-compose.app.yml`). Сборка и запуск: + +```bash +docker compose -f docker-compose.app.yml up -d --build +``` + +### Автодеплой + +Настроен webhook в Gitea: при пуше в `main` автоматически запускается деплой. + +- **Webhook listener**: systemd-сервис `supersam-webhook` на порту 9000 +- **Gitea hook**: push в `main` → `POST http://10.0.2.1:9000/webhook/supersam` +- **deploy.sh**: `git pull origin main` → `docker compose up -d --build` +- Репозиторий: `https://git.supersamsev.ru/mihail/supersam` + ## Главный документ -- [Обзор системы](/Users/mihailkucer/Documents/super-sam/docs/product-overview.md) — назначение приложения, роли, сценарии, клиентский flow и подготовка к показу. +- [Обзор системы](docs/product-overview.md) — назначение приложения, роли, сценарии, клиентский flow и подготовка к показу. ## Что уже есть @@ -19,15 +36,23 @@ npm run dev - Role-based dashboard для менеджера, логиста и водителя. - Карточка заказа с составом, комментариями и историей. - Публичная страница `/delivery/:token` для выбора даты, половины дня и просмотра состава заказа. +- **Самовывоз** — вкладки Доставка/Самовывоз на клиентской странице, выбор даты и половины дня самовывоза, информация о бесплатном хранении (2 рабочих дня) и платном (300₽/день). - Supabase SQL-схема, таблицы приглашений и Edge Functions для invitation flow. - Документация по продукту, архитектуре и сценариям. ## Структура - `src/` — интерфейс и клиентская логика. +- `src/constants/deliveryWorkflow.js` — статусы доставки, включая самовывоз. +- `src/components/client/DeliverySlotsPicker.jsx` — виджет выбора слотов доставки. +- `src/components/client/PickupSlotsPicker.jsx` — виджет выбора слотов самовывоза. +- `src/components/client/DeliveryChoiceFlow.jsx` — флоу согласования доставки/самовывоза. +- `src/pages/ClientDeliveryPage.jsx` — публичная страница с вкладками Доставка/Самовывоз. +- `src/components/orders/OrderDetailPanel.jsx` — карточка заказа с управлением доставкой и самовывозом. - `supabase/schema.sql` — структура БД, роли, индексы, RLS, триггеры. - `supabase/functions/` — Edge Functions для приглашений, статусов и чат-коммуникаций. - `supabase/seed/stage-1-demo.sql` — набор seed-данных для показа заказчику. - `docs/architecture.md` — архитектура фронтенда и модулей. - `docs/product-overview.md` — общий обзор продукта, ролей и сценариев. - `docs/scenarios.md` — сценарии жизненного цикла заказа. +- `docs/n8n-order-group-delivery-flow.md` — потоки n8n для оркестрации доставки. diff --git a/deploy.sh b/deploy.sh index 4f277ff..bb90c63 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,30 +1,6 @@ #!/bin/bash set -e cd /opt/supersam - -echo "[$(date '+%Y-%m-%d %H:%M:%S')] Deploy starting..." - -# Pull latest from main -git fetch origin main -BEFORE=$(git rev-parse HEAD) -git reset --hard origin/main -AFTER=$(git rev-parse HEAD) - -if [ "$BEFORE" = "$AFTER" ]; then - echo "[$(date '+%Y-%m-%d %H:%M:%S')] No changes, skipping rebuild." - exit 0 -fi - -echo "[$(date '+%Y-%m-%d %H:%M:%S')] Updated: ${BEFORE:0:7} -> ${AFTER:0:7}" - -# Rebuild and restart +git pull origin main docker compose -f docker-compose.app.yml up -d --build - -# Wait for container to be healthy -sleep 3 -if docker ps --format '{{.Names}}' | grep -q 'supersam-app'; then - echo "[$(date '+%Y-%m-%d %H:%M:%S')] Deploy complete. Container running." -else - echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Container not running after deploy!" - exit 1 -fi +echo 'Deploy completed at' $(date) diff --git a/docs/architecture.md b/docs/architecture.md index ca8017d..442aab0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,17 +6,25 @@ - `src/context/ThemeContext.jsx` — управление светлой и тёмной темой через `data-theme`. - `src/hooks/usePwaStatus.js` — клиентское состояние PWA: online/offline, install prompt, standalone и offline readiness. - `src/hooks/useOrders.js` — локальный state заказов, истории, чатов, фильтров, действий и **сгруппированных наборов доставки** (deliverySetBuckets). +- `src/hooks/useOrderGroups.js` — работа с группами заказов: загрузка, обновление статусов, ручное согласование доставки и самовывоза. Метод `saveManualDeliveryChoice` принимает `deliveryType`, `pickupDate`, `pickupTimeSlot`. - `src/services/deliverySetViews.js` — чистые функции группировки импортированных заказов в наборы доставки с buckets: «На подходе», «Готово к запуску», «Ожидает клиента», «Нужна ручная работа», «Согласовано», «Завершено». - `src/services/orderService.js` — чистые функции бизнес-логики заказов, покрытые тестами. - `src/services/supabase/orderRepository.js` — адаптер реальных чтений/записей заказов и чатов в Supabase, включая source-поля 1С и delivery-set поля. +- `src/services/supabase/orderGroupRepository.js` — репозиторий групп заказов. Маппинг полей включает `deliveryType`, `pickupDate`, `pickupTimeSlot`. Метод `updateOrderGroupDeliveryChoice` поддерживает как доставку, так и самовывоз. +- `src/services/deliveryWorkflow.js` — карта статусов доставки и переходов. Включает статус `pickup` (Самовывоз) с переходами в/из других статусов. +- `src/services/orderGroupViews.js` — метки и цвета для статусов, включая `pickup: "Самовывоз"`. +- `src/services/deliveryInvitationApi.js` — API для приглашений доставки. `confirmDeliveryChoice` принимает `deliveryType`, `pickupDate`, `pickupTimeSlot`. - `src/services/driverDeliveries.js` — фильтрация и группировка доставок для рабочей области водителя. - `src/layouts/AppShell.jsx` — общий shell с боковой навигацией, уведомлениями и переключением темы. - `src/components/logistics/LogisticsReadinessBoard.jsx` — интерактивная доска с bucket-ами наборов доставки. - `src/components/logistics/DeliverySetDetailPanel.jsx` — детальная карточка набора доставки: source-поля 1С, production-шаги, слоты, действия. - `src/components/client/DeliverySlotsPicker.jsx` — публичный виджет выбора даты и половины дня доставки. -- `src/components/client/DeliveryChoiceFlow.jsx` — публичный поток согласования доставки по приглашению. +- `src/components/client/PickupSlotsPicker.jsx` — публичный виджет выбора даты и половины дня самовывоза. Отображает доступные даты (сегодня до 12:00, завтра, послезавтра, без выходных), слоты «До обеда»/«После обеда», и информационный блок о стоимости хранения: «Бесплатное хранение — 2 рабочих дня. С 3-го рабочего дня — 300₽/день.». +- `src/components/client/DeliveryChoiceFlow.jsx` — публичный поток согласования доставки или самовывоза по приглашению. Принимает `deliveryType` и показывает динамические лейблы. - `src/components/client/DeliveryStateNotice.jsx` — информационный экран для clients со статусом ссылки. +- `src/pages/ClientDeliveryPage.jsx` — публичная страница согласования доставки с вкладками 🚚 Доставка / 🏪 Самовывоз. Переключение типа получения, отображение соответствующего пикера слотов. - `src/components/orders/*` — фильтры, список заказов, карточка заказа, история статусов и поиск по чату. +- `src/components/orders/OrderDetailPanel.jsx` — карточка заказа с управлением доставкой и самовывозом. Вкладки Доставка/Самовывоз, поля даты самовывоза и половины дня, кнопка статуса «Самовывоз». - `src/components/orders/OrderEditorPanel.jsx` — создание и редактирование заказа менеджером или администратором. - `src/components/dashboard/ProductionQueuePanel.jsx` — отдельный блок производственной очереди. - `src/components/dashboard/RoleWorkspacePanel.jsx` — рабочая панель с delivery-set bucket-ами для логиста. @@ -31,15 +39,42 @@ - **Логист** видит наборы доставки, слоты, сообщения чатбота и ручную обработку исключений. - **Водитель** видит только назначенные доставки и может переводить их через статусы `Загружен`, `В пути`, `Доставлен`, `Проблема доставки`. - **Администратор** видит весь массив заказов, доставок и системные логи. +- **Менеджер** может назначить тип доставки (доставка/самовывоз), дату и половину дня. - Клиент не является авторизованным пользователем приложения. Клиент использует публичную ссылку приглашения. ## Ключевые экраны - `/login` — email + OTP flow. При отсутствии `VITE_SUPABASE_*` включается demo-режим. Подсказка проверять входящие и спам. Неизвестный email: «Email не найден в системе. Обратитесь к администратору.» - `/dashboard` — role-based control center: для логиста — LogisticsReadinessBoard с наборами доставки, для водителя — план маршрута и быстрые действия. -- `/delivery/:token` — публичная страница согласования доставки для клиента. +- `/delivery/:token` — публичная страница согласования доставки для клиента с вкладками Доставка/Самовывоз. - `public/manifest.webmanifest` + `public/service-worker.js` — installable PWA-оболочка и базовое кеширование shell для demo offline. +## Тип получения: Доставка и Самовывоз + +### Переключатель типа + +На клиентской странице (`ClientDeliveryPage`) и в карточке заказа (`OrderDetailPanel`) реализованы вкладки: +- **🚚 Доставка** — стандартный флоу с выбором даты и половины дня. +- **🏪 Самовывоз** — выбор даты самовывоза (сегодня/завтра/послезавтра, без выходных) и половины дня. + +### Статус «Самовывоз» + +В `deliveryWorkflow.js` добавлен статус `pickup` с переходами: +- `pending_confirmation` → `pickup` +- `manual_confirmation_required` → `pickup` +- `pickup` → `assigned_to_driver`, `delivered`, `cancelled` + +### База данных + +Колонки в `order_groups`: +- `delivery_type text DEFAULT 'delivery'` — тип получения +- `pickup_date date` — дата самовывоза +- `pickup_time_slot text` — «До обеда» / «После обеда» + +RPC `confirm_delivery_choice_by_token` обновлён: при `delivery_type = 'pickup'` устанавливает `delivery_status = 'pickup'`, `pickup_date` и `pickup_time_slot`. + +Edge function `confirm-delivery-choice` передаёт `p_delivery_type`, `p_pickup_date`, `p_pickup_time_slot` в RPC. + ## Источник заказов: 1С → Supabase - Заказы импортируются из 1С через XML и сохраняются в `public.orders` с source-полями: `source_order_number`, `source_customer_name`, `source_accept_at`, `source_ship_at` и т.д. @@ -59,4 +94,10 @@ - `src/supabaseClient.js` создаёт клиент Supabase через env-переменные. - `src/services/safeSupabaseCall.js` стандартизирует обработку ошибок. - Данные UI разложены по сущностям, совпадающим с таблицами Supabase: `orders`, `order_history`, `chat_messages`, `delivery_slots`, `delivery_invitations`. -- В `orders` синхронизированы поля `status`, `delivery_agreement_status`, `assigned_driver_id`, а также source-поля 1С и delivery-set данные. \ No newline at end of file +- В `orders` синхронизированы поля `status`, `delivery_agreement_status`, `assigned_driver_id`, а также source-поля 1С и delivery-set данные. + +## Деплой + +- Docker-сборка через `docker-compose.app.yml` (multi-stage: Node.js build → Caddy serve). +- Автодеплой: Gitea webhook → systemd `supersam-webhook` → `deploy.sh` (git pull + docker build). +- Домен: `https://dost.supersamsev.ru/` diff --git a/docs/n8n-order-group-delivery-flow.md b/docs/n8n-order-group-delivery-flow.md index 763e41d..1f9dce6 100644 --- a/docs/n8n-order-group-delivery-flow.md +++ b/docs/n8n-order-group-delivery-flow.md @@ -26,7 +26,9 @@ - `second_sms_sent_at` - время второй SMS - `last_sms_error` - текст последней ошибки провайдера - `next_notification_check_at` - когда `n8n` должен вернуться к записи -- `delivery_date` и `delivery_time` - выбранный слот после подтверждения клиентом +- `delivery_date` и `delivery_time` - выбранный слот доставки после подтверждения клиентом +- `delivery_type` - тип получения: `delivery` (доставка) или `pickup` (самовывоз) +- `pickup_date` и `pickup_time_slot` - выбранный слот самовывоза после подтверждения клиентом ## Окно отправки SMS @@ -66,6 +68,7 @@ public.next_order_group_sms_check_at(start_from timestamptz, delay interval) - `out_for_delivery` - водитель уже в работе - `delivered` - доставка завершена - `cancelled` - группу больше не нужно обрабатывать +- `pickup` - клиент выбрал самовывоз ### `notification_status` @@ -275,6 +278,14 @@ docs/sql/order-groups-auto-delivery-link.sql Никакой логики SMS на фронтенде быть не должно. Никакой генерации ссылок на фронтенде быть не должно. +## Самовывоз + +Когда клиент выбирает вкладку «Самовывоз» на публичной странице: + +1. `confirm-delivery-choice` получает `delivery_type = 'pickup'` вместе с `pickup_date` и `pickup_time_slot`. +2. RPC `confirm_delivery_choice_by_token` устанавливает `delivery_status = 'pickup'`, `delivery_type = 'pickup'`, `pickup_date`, `pickup_time_slot`. +3. SMS-потоки n8n должны игнорировать строки со `delivery_status = 'pickup'` — клиент сам забирает заказ, напоминания о доставке не нужны. + ## Минимальный порядок внедрения 1. Развернуть обновленную схему `Supabase` и `docs/sql/order-groups-auto-delivery-link.sql`. diff --git a/docs/product-overview.md b/docs/product-overview.md index e63612c..1557cb4 100644 --- a/docs/product-overview.md +++ b/docs/product-overview.md @@ -7,7 +7,7 @@ - показать менеджеру единый реестр доставочных заказов с поиском и карточкой заказа; - показать логисту список доставок на сегодня и ближайшие дни с половинами дня; - показать водителю свои доставки, адрес, состав заказа и базовые статусы; -- дать клиенту публичную ссылку, по которой он выбирает дату и половину дня доставки; +- дать клиенту публичную ссылку, по которой он выбирает дату и половину дня доставки **или самовывоза**; - хранить состояние заказов, приглашений и истории изменений в Supabase. ## Роли @@ -17,6 +17,7 @@ - видит список заказов доставки; - ищет по номеру заказа, клиенту и телефону; - открывает карточку заказа и смотрит состав, комментарии и историю; +- может назначить тип доставки (доставка/самовывоз), дату самовывоза и половину дня; - не работает с созданием заказов и внутренними служебными экранами. ### Логист @@ -24,7 +25,8 @@ - видит заказы, готовые к доставке; - смотрит ближайшие даты: сегодня, завтра и послезавтра; - смотрит половину дня и текущий статус доставки; -- открывает карточку заказа, чтобы свериться с деталями. +- открывает карточку заказа, чтобы свериться с деталями; +- может перевести статус заказа в «Самовывоз» и указать дату. ### Водитель @@ -36,9 +38,40 @@ - получает публичную ссылку вида `/delivery/:token`; - видит номер заказа и состав заказа; -- выбирает дату и половину дня: `До обеда` или `После обеда`; +- **выбирает тип получения: Доставка или Самовывоз**; +- при выборе доставки — выбирает дату и половину дня: `До обеда` или `После обеда`; +- при выборе самовывоза — выбирает дату (сегодня/завтра/послезавтра с учётом выходных) и половину дня; +- видит информацию о бесплатном хранении (2 рабочих дня) и платном (300₽/день начиная с 3-го рабочего дня); - подтверждает выбор без входа во внутренний кабинет. +## Самовывоз + +### Клиентский флоу + +1. Клиент открывает ссылку `/delivery/:token`. +2. Видит две вкладки: **🚚 Доставка** и **🏪 Самовывоз**. +3. На вкладке «Самовывоз» видит: + - Доступные даты: сегодня (если до 12:00 текущего дня готовности), завтра, послезавтра (выходные пропускаются). + - Две половины дня: «До обеда» и «После обеда». + - Информационный блок: «Бесплатное хранение — 2 рабочих дня. С 3-го рабочего дня — 300₽/день.» +4. Выбирает дату и половину дня, подтверждает. +5. Статус заказа переходит в `pickup`, в БД сохраняются `pickup_date` и `pickup_time_slot`. + +### Управление в карточке заказа + +- Менеджер, логист и администратор могут переключить тип доставки (Доставка ↔ Самовывоз). +- При самовывозе доступны поля: дата самовывоза и половина дня. +- Статус «Самовывоз» доступен в кнопках смены статуса. + +### База данных + +В таблице `order_groups` добавлены колонки: +- `delivery_type text DEFAULT 'delivery'` — тип получения: `delivery` или `pickup` +- `pickup_date date` — дата самовывоза +- `pickup_time_slot text` — половина дня самовывоза (`До обеда` / `После обеда`) + +Статус `delivery_status` пополнился значением `pickup` — «Самовывоз». + ## Основные сценарии ### Внутренний сценарий @@ -48,7 +81,7 @@ 3. Логист отслеживает готовность и ближайшее окно доставки. 4. Водитель получает свою доставку и доводит её до результата. -### Сценарий клиента +### Сценарий клиента (доставка) Клиентская страница работает по token из таблицы `public.delivery_invitations`. @@ -59,21 +92,27 @@ Эта ссылка показывает: - заказ `CD-240031`; - состав заказа; -- четыре варианта слота; -- две даты; -- две половины дня: `До обеда` и `После обеда`. +- вкладки «Доставка» и «Самовывоз»; +- на вкладке «Доставка» — четыре варианта слота, две даты, две половины дня. -После подтверждения выбора: -- invitation переводится в состояние `agreed`; -- заказ переводится в `Доставка согласована`; -- в `order_history` появляется запись о подтверждении; -- в `delivery_slots` фиксируется подтверждённый слот. +### Сценарий клиента (самовывоз) + +При выборе вкладки «Самовывоз»: +- доступны даты начиная с дня готовности (если до 12:00) или завтра; +- две половины дня: «До обеда» и «После обеда»; +- информационный блок о стоимости хранения; +- подтверждение устанавливает `delivery_type = 'pickup'`, `delivery_status = 'pickup'`. ## Что хранится в Supabase - `public.users` — пользователи и роли; - `public.orders` — заказы и текущие статусы; - `public.order_history` — история изменений; +- `public.order_groups` — группы заказов с полями доставки и самовывоза: + - `delivery_type` — `delivery` или `pickup`; + - `delivery_date`, `delivery_time` — слот доставки; + - `pickup_date`, `pickup_time_slot` — слот самовывоза; + - `delivery_status` — статус согласования (включая `pickup`); - `public.delivery_slots` — возможные и подтверждённые слоты доставки; - `public.delivery_invitations` — публичные invitation token и состояние клиентского flow; - `public.integration_events` — технические и интеграционные события. @@ -95,11 +134,13 @@ - реестр заказов и карточку заказа; - список доставок по датам для логиста; - карточку доставки водителя; -- клиентскую ссылку с выбором даты и половины дня. +- клиентскую ссылку с выбором типа получения (доставка/самовывоз) и датой; +- информационный блок о стоимости хранения при самовывозе. ## Полезные документы -- [README](/Users/mihailkucer/Documents/super-sam/README.md) -- [Архитектура](/Users/mihailkucer/Documents/super-sam/docs/architecture.md) -- [Сценарии](/Users/mihailkucer/Documents/super-sam/docs/scenarios.md) -- [Edge Functions](/Users/mihailkucer/Documents/super-sam/supabase/functions/README.md) +- [README](../README.md) +- [Архитектура](architecture.md) +- [Сценарии](scenarios.md) +- [Поток n8n](n8n-order-group-delivery-flow.md) +- [Edge Functions](../supabase/functions/README.md) diff --git a/docs/scenarios.md b/docs/scenarios.md index e6d5160..2d10a36 100644 --- a/docs/scenarios.md +++ b/docs/scenarios.md @@ -20,15 +20,16 @@ - Перечнем заказов набора, их 1С-номерами и шагами производства (раскрой, склейка, криволинейные, контроль качества, отгрузка). - Телефоном и email клиента, городом, связанными счетами. - Текущим статусом слота. -3. Логист может запустить приглашение, назначить водителя или перейти к ручной обработке. ## 3. Согласование доставки с клиентом 1. Когда набор доставки готов, логист запускает отправку приглашения клиенту. 2. Клиент получает ссылку на `/delivery/:token`. -3. На странице клиент видит **DeliverySlotsPicker** с доступными датами и половинами дня. -4. Клиент выбирает слот и подтверждает. Статус набора переходит в «Ожидает клиента» → «Согласовано». -5. Если клиент не отвечает, система или логист переводит набор в «Нужна ручная работа». +3. На странице клиент видит вкладки **🚚 Доставка** и **🏪 Самовывоз**. +4. На вкладке «Доставка» — **DeliverySlotsPicker** с доступными датами и половинами дня. +5. На вкладке «Самовывоз» — **PickupSlotsPicker** с датами (сегодня до 12:00, завтра, послезавтра, без выходных) и половинами дня, а также информационный блок: «Бесплатное хранение — 2 рабочих дня. С 3-го рабочего дня — 300₽/день.» +6. Клиент выбирает тип получения, слот и подтверждает. Статус набора переходит в «Ожидает клиента» → «Согласовано» или «Самовывоз». +7. Если клиент не отвечает, система или логист переводит набор в «Нужна ручная работа». ## 4. Перенос доставки @@ -55,6 +56,30 @@ 2. После закрытия всех заказов набора он переходит в «Завершено». 3. В истории появляется финальная запись, а чат закрывается для активных действий. +## 8. Самовывоз + +### Клиентский сценарий + +1. Клиент открывает ссылку `/delivery/:token`. +2. Выбирает вкладку **🏪 Самовывоз**. +3. Видит доступные даты: сегодня (если до 12:00 текущего дня готовности), завтра, послезавтра (выходные пропускаются). +4. Выбирает дату и половину дня: «До обеда» или «После обеда». +5. Видит информационный блок: «Бесплатное хранение — 2 рабочих дня. С 3-го рабочего дня — 300₽/день.» +6. Подтверждает выбор. +7. В БД устанавливаются: `delivery_type = 'pickup'`, `delivery_status = 'pickup'`, `pickup_date`, `pickup_time_slot`. + +### Сценарий менеджера/логиста + +1. В карточке заказа (**OrderDetailPanel**) менеджер видит вкладки Доставка/Самовывоз. +2. При выборе «Самовывоз» — поля даты и половины дня для самовывоза. +3. Кнопка статуса «Самовывоз» доступна для менеджера, логиста и администратора. +4. Менеджер может переключить тип доставки в любой момент до передачи водителю. + +### Статусы самовывоза + +- `pickup` — заказ ожидает самовывоза клиентом. +- Переходы: `pending_confirmation` → `pickup`, `manual_confirmation_required` → `pickup`, `pickup` → `assigned_to_driver`, `pickup` → `delivered`, `pickup` → `cancelled`. + ## Сценарий показа заказчику 1. Зайти под логистом. @@ -65,6 +90,6 @@ - Фролова И.Д. — «Нужна ручная работа» (платное хранение). - Орлова Н.С. — «Завершено». 3. Кликнуть по набору Савина — увидеть source-поля, production-шаги, готовность к запуску. -4. Перейти на публичную страницу приглашения — увидеть `DeliverySlotsPicker` с выбором даты и половины дня. +4. Перейти на публичную страницу приглашения — увидеть вкладки «Доставка» и «Самовывоз» с выбором даты и половины дня. 5. Зайти под водителем — увидеть назначенные доставки с адресами и быстрыми действиями. 6. Зайти под несуществующим email — увидеть «Email не найден в системе. Обратитесь к администратору.» diff --git a/webhook-listener.py b/webhook-listener.py new file mode 100755 index 0000000..63530ed --- /dev/null +++ b/webhook-listener.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Simple webhook listener for Gitea push events to trigger supersam deploy.""" +import json +import hashlib +import hmac +import subprocess +import logging +from http.server import HTTPServer, BaseHTTPRequestHandler + +SECRET = '1032ef91aacd726907bb72c023813c6827f56354902cc7b30e72626690c6d51d' + +logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') +logger = logging.getLogger('webhook') + +class WebhookHandler(BaseHTTPRequestHandler): + def verify_signature(self, body): + signature = self.headers.get('X-Gitea-Signature', '') + if not signature: + return False + computed = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest() + return hmac.compare_digest(signature, computed) + + def do_POST(self): + if self.path == '/webhook/supersam': + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length) if content_length else b'' + if not self.verify_signature(body): + self.send_response(403) + self.end_headers() + self.wfile.write(b'{"error": "invalid signature"}') + return + try: + payload = json.loads(body) if body else {} + ref = payload.get('ref', '') + if ref == 'refs/heads/main': + logger.info('Push to main detected, starting deploy...') + result = subprocess.run( + ['/opt/supersam/deploy.sh'], + capture_output=True, text=True, timeout=600 + ) + logger.info('Deploy exit code: %s', result.returncode) + if result.returncode != 0: + logger.error('Deploy stderr: %s', result.stderr[-2000:]) + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'status': 'deployed', 'exit_code': result.returncode}).encode()) + else: + logger.info('Ignoring push to %s', ref) + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write(b'{"status": "ignored"}') + except Exception as e: + logger.error('Error: %s', e) + self.send_response(500) + self.end_headers() + self.wfile.write(json.dumps({'error': str(e)}).encode()) + else: + self.send_response(404) + self.end_headers() + + def do_GET(self): + if self.path == '/health': + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write(b'{"status": "ok"}') + else: + self.send_response(404) + self.end_headers() + +if __name__ == '__main__': + server = HTTPServer(('0.0.0.0', 9000), WebhookHandler) + logger.info('Webhook listener on http://0.0.0.0:9000') + server.serve_forever()