295 lines
12 KiB
Markdown
295 lines
12 KiB
Markdown
# Поток n8n для `order_groups`
|
||
|
||
## Цель
|
||
|
||
`Supabase` хранит состояние доставки, генерирует ссылку для клиента и
|
||
сохраняет все временные отметки.
|
||
`n8n` отвечает за отправку SMS, повторные попытки и перевод зависших
|
||
групп в ручную обработку.
|
||
|
||
Короткая ссылка с нашей стороны не нужна. Мы храним полную `delivery_link`,
|
||
а сервис SMS сам сокращает ссылку при отправке.
|
||
|
||
## Источник истины
|
||
|
||
Основная запись находится в `public.order_groups`.
|
||
|
||
Важные поля:
|
||
|
||
- `status` - бизнес-готовность группы
|
||
- `delivery_status` - состояние согласования доставки для клиента и логиста
|
||
- `delivery_link` - полная публичная ссылка на `/delivery/:token`
|
||
- `delivery_invitation_id` - связанная запись приглашения
|
||
- `notification_status` - состояние SMS-оркестрации для `n8n`
|
||
- `sms_attempts` - сколько было попыток отправки SMS
|
||
- `first_sms_sent_at` - время первой SMS
|
||
- `second_sms_sent_at` - время второй SMS
|
||
- `last_sms_error` - текст последней ошибки провайдера
|
||
- `next_notification_check_at` - когда `n8n` должен вернуться к записи
|
||
- `delivery_date` и `delivery_time` - выбранный слот после подтверждения клиентом
|
||
|
||
## Окно отправки SMS
|
||
|
||
Чтобы не тревожить клиентов ночью, SMS отправляются только в местное окно:
|
||
|
||
```text
|
||
09:00-20:00 Europe/Simferopol
|
||
```
|
||
|
||
Если запись стала готова к отправке вне этого окна, SMS не отправляется сразу.
|
||
`Supabase` должен поставить ближайшее разрешенное время в
|
||
`next_notification_check_at`:
|
||
|
||
- до `09:00` - сегодня в `09:00`
|
||
- с `09:00` до `20:00` - сразу
|
||
- после `20:00` - завтра в `09:00`
|
||
|
||
Для этого в SQL есть helper:
|
||
|
||
```sql
|
||
public.next_order_group_sms_check_at(start_from timestamptz, delay interval)
|
||
```
|
||
|
||
`n8n` не должен сам решать, можно ли сейчас тревожить клиента. Он просто выбирает
|
||
записи, где `next_notification_check_at <= now()`.
|
||
|
||
## Рекомендуемая модель статусов
|
||
|
||
### `delivery_status`
|
||
|
||
- `pending_confirmation` - клиент еще не выбрал слот
|
||
- `agreed` - клиент выбрал слот доставки
|
||
- `manual_confirmation_required` - автоматический поток не сработал,
|
||
менеджер/логист должен продолжить вручную
|
||
- `no_contact` - менеджер/логист пытался связаться вручную, но связи с клиентом нет
|
||
- `assigned_to_driver` - доставка согласована и передана в планирование водителю
|
||
- `out_for_delivery` - водитель уже в работе
|
||
- `delivered` - доставка завершена
|
||
- `cancelled` - группу больше не нужно обрабатывать
|
||
|
||
### `notification_status`
|
||
|
||
- `not_started` - ссылка еще не подготовлена
|
||
- `link_ready` - `Supabase` создал `delivery_link`, `n8n` может отправлять первую SMS
|
||
- `first_sms_sent` - первая SMS принята провайдером
|
||
- `second_sms_sent` - отправлено напоминание
|
||
- `confirmed` - клиент выбрал слот
|
||
- `manual_required` - после повторов подтверждения нет
|
||
- `send_failed` - ошибка провайдера/API, можно повторить
|
||
|
||
## Ответственность Supabase
|
||
|
||
### 1. Подготовка ссылки
|
||
|
||
Когда `order_group` попадает в поток согласования доставки:
|
||
|
||
- `status = 'ready_for_notification'`
|
||
- `delivery_status = 'pending_confirmation'`
|
||
|
||
Триггер Supabase `order_groups_ensure_delivery_link` должен:
|
||
|
||
- создать запись `delivery_invitations` с `order_group_id`
|
||
- сгенерировать token и полную публичную ссылку
|
||
- подготовить слоты только на завтра и послезавтра
|
||
- записать `delivery_link` в `order_groups`
|
||
- установить `delivery_invitation_id`
|
||
- установить `notification_status = 'link_ready'`
|
||
- установить `next_notification_check_at` через `public.next_order_group_sms_check_at()`
|
||
|
||
SQL для этого триггера лежит здесь:
|
||
|
||
```text
|
||
docs/sql/order-groups-auto-delivery-link.sql
|
||
```
|
||
|
||
`n8n` больше не должен вызывать `create-delivery-invitation` для `order_groups`.
|
||
Ему нужно ждать, пока в строке уже будут
|
||
`notification_status = 'link_ready'` и `delivery_link is not null`.
|
||
|
||
### 2. Прием выбора клиента
|
||
|
||
Публичная страница использует token.
|
||
Когда клиент подтверждает слот, `confirm-delivery-choice` должен:
|
||
|
||
- сохранить `delivery_date` и `delivery_time`
|
||
- установить `delivery_status = 'agreed'`
|
||
- установить `notification_status = 'confirmed'`
|
||
|
||
Это становится стоп-сигналом для всех напоминаний в `n8n`.
|
||
|
||
## Потоки n8n
|
||
|
||
### Поток 1. Отправка первой SMS
|
||
|
||
Триггер:
|
||
|
||
- Cron каждые 5-10 минут
|
||
- Опционально запасной webhook-триггер, если потом захочешь push-старт
|
||
|
||
Запрос:
|
||
|
||
- `status = 'ready_for_notification'`
|
||
- `delivery_status = 'pending_confirmation'`
|
||
- `notification_status = 'link_ready'`
|
||
- `delivery_link is not null`
|
||
- `next_notification_check_at <= now()`
|
||
|
||
Действие:
|
||
|
||
- отправить SMS с `delivery_link`
|
||
|
||
При успехе обновить `order_groups`:
|
||
|
||
- `notification_status = 'first_sms_sent'`
|
||
- `sms_attempts = 1`
|
||
- `first_sms_sent_at = now()`
|
||
- `sms_sent_at = now()`
|
||
- `last_sms_error = null`
|
||
- `next_notification_check_at = public.next_order_group_sms_check_at(now(), interval '3 hours')`
|
||
|
||
Важно: если первая SMS ушла, например, в `18:30`, проверка через 3 часа попала бы
|
||
на `21:30`. Helper перенесет следующую проверку на завтра `09:00`.
|
||
|
||
При ошибке обновить `order_groups`:
|
||
|
||
- `notification_status = 'send_failed'`
|
||
- `last_sms_error = <ошибка провайдера>`
|
||
- `next_notification_check_at = public.next_order_group_sms_check_at(now(), interval '10 minutes')`
|
||
|
||
### Поток 2. Наблюдатель доставки
|
||
|
||
Триггер:
|
||
|
||
- Cron каждые 10 минут
|
||
|
||
Назначение:
|
||
|
||
- найти записи, где первый поток не завершился корректно
|
||
- повторить неудачные первые отправки
|
||
|
||
Кандидаты для выбора:
|
||
|
||
- `notification_status = 'send_failed'`
|
||
- `delivery_status = 'pending_confirmation'`
|
||
- `next_notification_check_at <= now()`
|
||
|
||
Поведение:
|
||
|
||
- повторить первую SMS
|
||
- если успех, перевести в `first_sms_sent`
|
||
- если повторные ошибки превысили выбранный порог, перевести в `manual_required`
|
||
- все новые значения `next_notification_check_at` считать через
|
||
`public.next_order_group_sms_check_at(...)`
|
||
|
||
### Поток 3. Напоминание по SMS
|
||
|
||
Триггер:
|
||
|
||
- Cron каждые 10 минут
|
||
|
||
Запрос:
|
||
|
||
- `delivery_status = 'pending_confirmation'`
|
||
- `notification_status = 'first_sms_sent'`
|
||
- `next_notification_check_at <= now()`
|
||
|
||
Действие:
|
||
|
||
- отправить вторую SMS-напоминалку с той же `delivery_link`
|
||
|
||
При успехе обновить:
|
||
|
||
- `notification_status = 'second_sms_sent'`
|
||
- `sms_attempts = 2`
|
||
- `second_sms_sent_at = now()`
|
||
- `last_sms_error = null`
|
||
- `next_notification_check_at = public.next_order_group_sms_check_at(now(), interval '3 hours')`
|
||
|
||
При ошибке обновить:
|
||
|
||
- `notification_status = 'send_failed'`
|
||
- `last_sms_error = <ошибка провайдера>`
|
||
- `next_notification_check_at = public.next_order_group_sms_check_at(now(), interval '30 minutes')`
|
||
|
||
### Поток 4. Передача в ручную обработку
|
||
|
||
Триггер:
|
||
|
||
- Cron каждые 10 минут
|
||
|
||
Запрос:
|
||
|
||
- `delivery_status = 'pending_confirmation'`
|
||
- `notification_status = 'second_sms_sent'`
|
||
- `next_notification_check_at <= now()`
|
||
|
||
Действие:
|
||
|
||
- остановить автоматические напоминания
|
||
- перевести группу в ручную обработку
|
||
|
||
Обновление:
|
||
|
||
- `delivery_status = 'manual_confirmation_required'`
|
||
- `notification_status = 'manual_required'`
|
||
|
||
После этого автоматические SMS больше не отправляются. В ЛК менеджер/логист
|
||
должен вручную выбрать дату доставки или поставить результат `no_contact`, если
|
||
связаться с клиентом не удалось.
|
||
|
||
### Поток 5. Условия остановки
|
||
|
||
Каждый поток должен игнорировать строки, где:
|
||
|
||
- `delivery_status in ('agreed', 'no_contact', 'assigned_to_driver', 'out_for_delivery', 'delivered', 'cancelled')`
|
||
- `notification_status in ('confirmed', 'manual_required')`
|
||
|
||
Это не дает слать дублирующие SMS после ответа клиента или передачи кейса человеку.
|
||
|
||
## Рекомендуемый текст SMS
|
||
|
||
Пример:
|
||
|
||
```text
|
||
Ваш заказ готов к согласованию доставки.
|
||
Выберите удобные дату и время по ссылке:
|
||
{{delivery_link}}
|
||
```
|
||
|
||
Напоминание:
|
||
|
||
```text
|
||
Напоминаем: нужно выбрать дату и время доставки вашего заказа.
|
||
Ссылка:
|
||
{{delivery_link}}
|
||
```
|
||
|
||
## Что нужно фронтенду
|
||
|
||
Публичной странице нужны только:
|
||
|
||
- token из URL
|
||
- `get-delivery-invitation`
|
||
- `confirm-delivery-choice`
|
||
|
||
Никакой логики SMS на фронтенде быть не должно.
|
||
Никакой генерации ссылок на фронтенде быть не должно.
|
||
|
||
## Минимальный порядок внедрения
|
||
|
||
1. Развернуть обновленную схему `Supabase` и `docs/sql/order-groups-auto-delivery-link.sql`.
|
||
2. Проверить, что insert/update в `order_groups` пишет `delivery_link` и `notification_status = 'link_ready'`.
|
||
3. Собрать поток `n8n` для первой SMS.
|
||
4. Собрать поток `n8n` для напоминаний.
|
||
5. Собрать поток `n8n` для ручной передачи.
|
||
6. Проверить полный цикл на одной реальной записи `order_group`.
|
||
|
||
## Сценарий проверки
|
||
|
||
1. Пометить одну `order_group` как готовую к клиентскому согласованию доставки.
|
||
2. Убедиться, что `delivery_link` появился в `order_groups` автоматически.
|
||
3. Дать `n8n` отправить первую SMS.
|
||
4. Открыть ссылку и подтвердить слот на клиентской странице.
|
||
5. Убедиться, что `delivery_status = 'agreed'` и `notification_status = 'confirmed'`.
|
||
6. Убедиться, что потоки напоминаний больше не трогают эту группу.
|