Уведомление проходит по цепочке:
доменное событие / Celery Beat / chat-service
-> PushNotificationDispatcher
-> UserNotification в PostgreSQL
-> Celery send_push_to_user_task
-> Firebase Cloud Messaging
-> mobile notification catalog
-> popup и экран уведомлений
Backend хранит только стабильный код, группу, уровень и структурированные данные.
Пользовательские тексты и кнопки находятся во frontend-каталоге
mobile_around/src/notifications/catalog.ts.
Справочник типов уведомлений:
| Поле | Значение |
|---|---|
code |
Стабильный уникальный код для frontend-каталога |
name |
Группа: new_order, status, change, message, reminder, deadline |
level |
info, success, warning, error |
users |
M2M к пользователям через UserNotification |
Through-модель пользовательского уведомления:
| Поле | Значение |
|---|---|
user |
Получатель |
notification |
FK на NotificationType |
data |
Контекст события без пользовательского текста |
read_at |
Время прочтения или null |
dedupe_key |
Уникальный ключ защиты от повторной отправки |
Хранит FCM-токен, платформу, locale, версию приложения, активность и последнюю
ошибку Firebase. Невалидные FCM-токены автоматически деактивируются.
Типичный data payload:
{
"type": "status",
"event_type": "event_order_paid",
"action": "open_order",
"sheet_type": "job",
"sheet_id": 14,
"sheet_title": "Маникюр",
"event_id": 81,
"client_id": 10,
"issuer_id": 20,
"scheduled_at": "2026-07-20T15:00:00+05:00"
}
Перед отправкой push dispatcher добавляет notification_id, code, name и
level. Для переносов payload также может содержать changed_fields, для чата —
chat_id, message_id, sender_id, для привоза — bringing_date, closes_at.
| Код | Получатель | Где и когда срабатывает |
|---|---|---|
event_order_created |
Исполнитель | Создание заказа в BaseOrderEventService.create |
event_order_status |
Клиент | Подтверждение или принятие заказа |
event_contract_payment_required |
Клиент | Исполнитель подтвердил контракт |
event_order_paid |
Исполнитель | Успешный payment webhook |
event_payment_error |
Клиент | Платёж перешёл в error |
event_refund_completed |
Клиент | Подтверждён возврат |
event_time_change_suggested |
Вторая сторона | Запрошен перенос |
event_time_changed |
Участники / вторая сторона | Время изменено |
event_time_change_accepted |
Инициатор | Вторая сторона приняла перенос |
event_order_canceled |
Вторая сторона | Заказ отменён |
event_bringing_canceled |
Участники | Привоз отменён |
event_delivery_started |
Клиент | Исполнитель начал привоз |
event_confirm_completion_required |
Клиент | Требуется подтвердить завершение |
event_completed_by_client |
Исполнитель | Клиент подтвердил выполнение |
event_receipt_confirmed |
Клиент | QR выдачи успешно отсканирован |
event_goods_transferred |
Исполнитель | Передача заказа подтверждена |
event_order_problem |
Исполнитель | Заказ перешёл в controversy |
hot_ad_activated |
Владелец | Объявление активировано |
chat_message_received |
Собеседник | Chat-service сохранил новое сообщение |
event_starts_in_2_hours |
Оба участника | До job/storage события осталось 2 часа |
event_starts_in_30_minutes |
Оба участника | Осталось 30 минут |
bringing_tomorrow |
Оба участника | Привоз назначен на завтра |
event_deadline_tomorrow |
Оба участника | Контракт заканчивается завтра |
event_deadline_expired |
Оба участника | Срок контракта прошёл |
bringing_closes_in_two_hours |
Уникальные клиенты привоза | До закрытия осталось 90–120 минут |
bringing_closes_in_thirty_minutes |
Уникальные клиенты привоза | До закрытия осталось 20–30 минут |
Все расписания создаются миграциями и редактируются в Django Admin в разделе
Periodic Tasks.
| Periodic task | Celery task | Начальное расписание |
|---|---|---|
Notifications: two hours before |
notifications.scan_two_hour_reminders |
Каждые 5 минут |
Notifications: thirty minutes before |
notifications.scan_thirty_minute_reminders |
Каждые 5 минут |
Notifications: date deadlines |
notifications.scan_date_reminders |
Ежедневно |
Notifications: bringings closing in two hours |
notifications.scan_bringings_closing_in_two_hours |
Каждые 15 минут |
Notifications: bringings closing in thirty minutes |
notifications.scan_bringings_closing_in_thirty_minutes |
Каждые 5 минут |
При применении календарного шаблона к дате CalendarDay.calendar_template сохраняет
ссылку на конкретный шаблон. Задачи выбирают только дни с шаблоном типа bringing.
Время закрытия:
CalendarDay.date
+ CalendarTemplate.working_end_time
- CalendarTemplate.order_closing_minutes
Заказы группируются по (bringing_sheet, planning_bringing_time). Уведомление
отправляется каждому уникальному клиенту группы. День без назначенного bringing-шаблона
или шаблон без order_closing_minutes пропускается.
Создание чата уведомление не отправляет. После успешного CreateMessage Go chat-service
вызывает Django callback только при created=true. Получатель — PeerID(sender).
Повтор одного client_message_id не отправляет push. Callback защищён заголовком
X-Chat-Notification-Token, а Django дедуплицирует событие по message_id + recipient_id.
Пользовательские endpoints требуют Bearer token:
GET /api/v1/notifications;GET /api/v1/notifications?read=true|false;POST /api/v1/notifications/{id}/read;POST /api/v1/notifications/read-all;GET /api/v1/push/devices;POST /api/v1/push/devices/register;POST /api/v1/push/devices/unregister.Внутренний endpoint:
POST /api/v1/internal/chat/message-sent с X-Chat-Notification-Token.| Переменная | Назначение |
|---|---|
FIREBASE_ENABLED |
Включает отправку FCM |
FIREBASE_CREDENTIALS_FILE |
Service account JSON |
FIREBASE_PROJECT_ID |
Firebase project |
PUSH_DRY_RUN |
Firebase dry-run |
CHAT_NOTIFICATION_TOKEN |
Общий секрет Django и chat-service |
DJANGO_API_URL |
Django API URL для chat callback |
HotAd требуется поле expires_at.CalendarDay, созданные до добавления calendar_template, не участвуют в