Add Checkbox ETTN receipts for Nova Poshta COD waybills

Cashier creates an ETTN receipt in Checkbox bound to the TTN with payment
control; Checkbox fiscalizes it itself when the parcel is paid for.

- cash_registers (Fernet-encrypted license key / PIN) and receipts tables
- Checkbox HTTP client + stub (ETTN does not work on test registers)
- two-phase create via ARQ job, timeout reconciliation, cron status polling
- /receipts and /cash-registers API, audit records
- dashboard: per-order and bulk create, prepayment, cancel; cash registers page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 23:39:00 +03:00
co-authored by Claude Opus 5.5
parent d13e7ce2b3
commit 518a99197f
44 changed files with 2931 additions and 20 deletions
+32
View File
@@ -0,0 +1,32 @@
"""Схемы Checkbox API для ЕТТН-чеков (`/api/v1/ettn`).
Тело запроса собирается как обычный dict в `app/services/receipts.py` —
его снимок сохраняется в `receipts.request_body` без преобразований.
Здесь — только разбор ответа (`BaseEttnResponse`).
"""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field
class EttnStatus:
"""Статусы ЕТТН-чека в Checkbox (`OrderStatus`)."""
CREATED = "CREATED"
CANCELLED = "CANCELLED"
RECEIPT_ERROR = "RECEIPT_ERROR"
DONE = "DONE"
DONE_WITHOUT_SMS = "DONE_WITHOUT_SMS"
RETURNED = "RETURNED"
class EttnOut(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: str
status: str
ettn_number: str = Field(alias="ettnNumber")
total_sum: int | None = Field(default=None, alias="totalSum")
receipt_id: str | None = Field(default=None, alias="receiptId")
raw_error: str | None = Field(default=None, alias="rawError")