From a7cba7616ad6347a2f3043f956936c17b55c3e2b Mon Sep 17 00:00:00 2001 From: Liutenko Oleksandr Date: Fri, 25 Sep 2026 12:25:12 +0300 Subject: [PATCH] Poll ETTN statuses in batches and commit per receipt The cron poll committed only at the end; with ~100 created receipts and a 1s pause between Checkbox requests it hit arq's 300s timeout every run, so all status updates were rolled back and fiscalized receipts stayed "created". Now each run polls at most 30 least-recently-checked receipts and commits after each one. Co-Authored-By: Claude Opus 5.5 --- backend/app/services/receipts.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/backend/app/services/receipts.py b/backend/app/services/receipts.py index 2f3dd66..3b96a2f 100644 --- a/backend/app/services/receipts.py +++ b/backend/app/services/receipts.py @@ -68,6 +68,11 @@ _CHECKBOX_TO_STATUS = { # или Checkbox был недоступен) и повторить его из cron'а. _PENDING_RETRY_AFTER = timedelta(minutes=1) +# Сколько `created`-чеков опрашивать за один прогон cron'а (раз в минуту): +# при паузе ~1 с между запросами к Checkbox прогон укладывается в минуту и +# не наслаивается на следующий. +_ETTN_POLL_BATCH = 30 + # Статус заказа в CRM, когда Checkbox принял ЕТТН-чек: заказ можно собирать. CRM_STATUS_AFTER_RECEIPT = "PACKED" # Чеки, при которых заказу нужен этот статус в CRM (Checkbox чек принял). @@ -495,9 +500,20 @@ async def cancel_receipt( async def sync_ettn_statuses(session: AsyncSession, client: CheckboxClient) -> None: - """Опрашивает Checkbox по чекам, ожидающим оплаты посылки (`created`).""" + """Опрашивает Checkbox по чекам, ожидающим оплаты посылки (`created`). + + За прогон — не больше `_ETTN_POLL_BATCH` давно не проверенных чеков, с + коммитом после каждого: запросы к Checkbox идут с паузой, и опрос всех + чеков разом не укладывался в таймаут cron-задачи, а откат по таймауту + терял весь прогон — статусы не обновлялись вовсе. + """ receipts = list( - await session.scalars(select(Receipt).where(Receipt.status == ReceiptStatus.CREATED)) + await session.scalars( + select(Receipt) + .where(Receipt.status == ReceiptStatus.CREATED) + .order_by(Receipt.last_checked_at.asc().nulls_first()) + .limit(_ETTN_POLL_BATCH) + ) ) registers: dict[uuid.UUID, CheckboxCredentials] = {} for receipt in receipts: @@ -521,8 +537,7 @@ async def sync_ettn_statuses(session: AsyncSession, client: CheckboxClient) -> N _apply_ettn(receipt, ettn) if receipt.status in CLOSED_STATUSES: await _release_order(session, receipt.order_id) - - await session.commit() + await session.commit() async def sync_crm_statuses(session: AsyncSession, crm: CrmClient) -> None: -- 2.54.0