Files
g2s-aggregator/spec/tasks/033_add_waybill_polling_worker.md
T
2026-05-23 20:24:23 +03:00

6.4 KiB
Raw Blame History

id, title, status, created
id title status created
033 Add CDEK waybill polling worker DONE 2026-05-23

Context

POST /v2/orders в CDEK асинхронный. Когда entity.related_entities[type=waybill].uuid синхронно приходит в ответе, поле url практически всегда пустое — PDF генерится позже и его url доступен только при GET /v2/print/orders/{waybill_uuid} после перехода накладной в READY. Бывают и случаи, когда waybill_uuid тоже не приходит синхронно, или заказ уходит в терминальный INVALID без генерации накладной. Ранее _save_cdek_order_uuid (app/services/aggregator.py) писал waybill-поля из синхронного ответа POST, из-за чего запись прыгала между двумя источниками и требовала покрытия граничных случаев в двух местах.

Goal

Перевести запись waybill-данных под единоличную ответственность отдельного фонового worker-а. При регистрации заказа сохранять только cdek_order_uuid, а waybill_uuid / waybill_url пополнять поллингом до тех пор, пока заказ не дойдёт до waybill_url IS NOT NULL либо до терминального статуса.

Constraints

  • Запуск — отдельный процесс python -m app.workers.waybill_poller, отдельный сервис в docker-compose.yml, одна реплика.
  • Никаких записей waybill-полей при регистрации заказа: _save_cdek_order_uuid должен сохранять только cdek_order_uuid.
  • Поллер — единственный writer полей cdek_order_status, cdek_waybill_uuid, cdek_waybill_url, cdek_polled_at.
  • На терминальных статусах заказа (INVALID, DELIVERED, NOT_DELIVERED, CANCELLED) опросы по этому заказу прекращаются.
  • Бизнес-логика терминальности — pure функция в app/domain/cdek_polling.py, без зависимостей на репозиторий/адаптер.
  • Соблюсти слои AGENTS.md: новые HTTP-вызовы CDEK живут только в адаптере, работа с БД — только в репозитории, оркестрация — в сервисе.

Acceptance criteria

  • При успешной регистрации заказа в БД заполняется только cdek_order_uuid; cdek_waybill_uuid и cdek_waybill_url остаются NULL.
  • Worker раз в waybill_poller.interval_seconds секунд выбирает заказы с cdek_order_uuid IS NOT NULL AND cdek_waybill_url IS NULL и нетерминальным cdek_order_status (с упорядочиванием cdek_polled_at NULLS FIRST и лимитом waybill_poller.batch_size).
  • Если у заказа cdek_waybill_uuid IS NULL, worker делает GET /v2/orders/{uuid} и сохраняет последний статус заказа + waybill_uuid из related_entities.
  • Если cdek_waybill_uuid IS NOT NULL и url пуст, worker делает GET /v2/print/orders/{waybill_uuid} и сохраняет entity.url.
  • Ошибка CDEK по одному заказу логируется и не валит весь батч.
  • Worker корректно завершает работу по SIGTERM/SIGINT: закрывает HTTP-клиент и async engine.

Definition of Done

  • Миграция 20260524_034_add_cdek_polling_columns.py добавляет cdek_order_status и cdek_polled_at; модель синхронизирована.
  • _save_cdek_order_uuid и OrderRepository.mark_cdek_order_registered больше не принимают waybill-поля.
  • CDEKClient.get_order / get_waybill и соответствующие мапперы (map_cdek_order_info_response, map_cdek_waybill_info_response, dataclasses CDEKOrderInfo, CDEKWaybillInfo) реализованы; CDEKProvider предоставляет обёртки.
  • app/domain/cdek_polling.py содержит TERMINAL_ORDER_STATUSES и is_terminal_order_status.
  • OrderRepository.list_orders_pending_waybill, record_order_poll, record_waybill_poll реализованы.
  • WaybillPollerService.poll_once и run_forever реализованы.
  • app/workers/waybill_poller.py поднимает зависимости, поддерживает graceful shutdown по сигналу.
  • config.example.yaml содержит секцию waybill_poller, app/config.pyWaybillPollerConfig.
  • docker-compose.yml содержит сервис waybill-poller.

Tests

  • tests/domain/test_cdek_polling.py — терминальные/нетерминальные статусы.
  • tests/adapters/delivery_providers/cdek/test_order_mapper.py — мапперы ответов GET /v2/orders/{uuid} и GET /v2/print/orders/{uuid}.
  • tests/adapters/delivery_providers/cdek/test_order_info_client.pyCDEKClient.get_order и get_waybill: 200/4xx/5xx, ретраи, парсинг.
  • tests/repositories/order/test_repository.pylist_orders_pending_waybill, record_order_poll, record_waybill_poll (фильтр, упорядочивание, защита waybill_uuid/waybill_url от перезаписи).
  • tests/services/test_waybill_poller.pypoll_once на стабах (два пути, терминальный статус, изоляция ошибок), run_forever завершается по stop_event.
  • tests/services/test_tbank_notifications.py — регистрация заказа больше не сохраняет waybill-поля.
  • tests/workers/test_waybill_poller_main.py_run собирает зависимости и завершается по stop_event.

Commands

  • poetry run pytest -q
  • poetry run alembic upgrade head
  • poetry run python -m app.workers.waybill_poller
  • python3 spec/gen_spec_index.py --check