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
+13 -1
View File
@@ -16,6 +16,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
if TYPE_CHECKING:
from app.db.models.order import Order
from app.db.models.receipt import Receipt
class OrderGoodOut(BaseModel):
@@ -82,9 +83,14 @@ class OrderRowOut(BaseModel):
np_status_code: str | None
np_cod_amount: str | None
np_payment_status: str | None
# Последний ЕТТН-чек по заказу (в т.ч. отменённый/неудачный — для показа причины).
receipt_id: str | None = None
receipt_status: str | None = None
receipt_error: str | None = None
receipt_prepayment: str | None = None
@classmethod
def from_order(cls, order: Order) -> OrderRowOut:
def from_order(cls, order: Order, receipt: Receipt | None = None) -> OrderRowOut:
return cls(
id=order.id,
create_date_time=order.create_date_time,
@@ -104,4 +110,10 @@ class OrderRowOut(BaseModel):
else None
),
np_payment_status=order.np_payment_status,
receipt_id=str(receipt.id) if receipt else None,
receipt_status=receipt.status.value if receipt else None,
receipt_error=receipt.error if receipt else None,
receipt_prepayment=(
f"{receipt.prepayment_kopecks / 100:.2f}" if receipt else None
),
)