Перейти к основному содержимому

0028. Отправка ответов автоответчика (статусы, авто-режим, идемпотентность)

Копия ADR из репозитория: backend/docs/adr/0028-autoreply-answer-send.md. Канонический текст — в репозитории; портал обновляется командой npm run sync:adr.

  • Статус: accepted
  • Дата: 2026-06-26
  • Связанные задачи: OP-74 (5.5 · Автоответы на отзывы и вопросы)

Контекст и проблема​

ТЗ 5.5: ответы на отзывы/вопросы публикуются на Wildberries в ручном и автоматическом режимах, с фиксацией статуса, ретраями и аудитом. Публикация ответа на WB — необратимое публичное действие (повторная отправка создаёт дубль/конфликт). Вопросы:

  1. Где хранить статус отправки и как им управлять?
  2. Как реализовать авто-режим, не создавая двойных публикаций?
  3. Как обеспечить ретраи и идемпотентность при at-least-once доставке джоб?

Решение​

Статусная модель — на черновике​

Жизненный цикл хранится на autoreply_drafts (status: draft → pending → sent/error), а не в отдельной сущности: черновик и его отправка — один объект (1:1 с обращением). Регенерация (OP-73 upsert) сбрасывает статус в draft и очищает sent_at/send_error/send_attempts — новый текст считается неотправленным.

Очередь + ретраи​

Отправка — через BullMQ-очередь autoreply-answer-send (attempts=3, exponential backoff). Воркер постит ответ (отзыв — POST /feedbacks/answer, вопрос — PATCH /questions), при ошибке пробрасывает исключение для ретрая. Статус error ставится только на финальной попытке (finalAttempt из job.attemptsMade), между попытками — pending (чтобы UI не показывал ложный финал).

Идемпотентность (против двойной публикации)​

  • enqueue: sent → 409 Conflict (ручной путь) / пропуск (авто); pending → без дублирующей джобы.
  • sendDraft (воркер): если статус уже sent — выходит без вызова WB (защита от повторной доставки джобы).

Авто-режим​

Режим берётся из сценария (ReplyMode). При auto генерация (OP-73) после сохранения черновика ставит его в очередь отправки; при manual — ждёт явного POST /autoreply/send.

Последствия​

  • Один текущий черновик/ответ на обращение; история отправок — отдельная задача (OP-76).
  • Авто-публикация необратима: идемпотентные гарды критичны и покрыты тестами.
  • Аудит (F6) пишется на каждую успешную/неуспешную отправку.
  • error между ретраями не финализируется — потребитель отличает «упало, будет ретрай» (pending) от «упало окончательно» (error).