6.4 KiB
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, dataclassesCDEKOrderInfo,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.py—WaybillPollerConfig.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.py—CDEKClient.get_orderиget_waybill: 200/4xx/5xx, ретраи, парсинг.tests/repositories/order/test_repository.py—list_orders_pending_waybill,record_order_poll,record_waybill_poll(фильтр, упорядочивание, защита waybill_uuid/waybill_url от перезаписи).tests/services/test_waybill_poller.py—poll_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 -qpoetry run alembic upgrade headpoetry run python -m app.workers.waybill_pollerpython3 spec/gen_spec_index.py --check