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: