Each cash register stores its own encrypted NP API key. Status polling uses register keys and binds an order to the register whose key sees the TTN as its own (PhoneSender present); ETTN receipts are created from that register. Migration 0008 moves the old NOVA_POSHTA_API_KEY into the default register. Closes #3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
3.4 KiB
Python
62 lines
3.4 KiB
Python
"""Схема статуса ТТН Nova Poshta (`TrackingDocument.getStatusDocuments`).
|
|
|
|
Поля валидируются из "сырых" PascalCase-ключей ответа NP через `Field(alias=...)`,
|
|
как и `OrderOut` для CRM. Суммы остаются строками "как есть" от NP — в
|
|
integer-копейки переводится только на границе с персистентной моделью.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
|
|
|
class TrackingStatusOut(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
number: str = Field(alias="Number")
|
|
status: str = Field(alias="Status")
|
|
status_code: str = Field(alias="StatusCode")
|
|
payment_method: str | None = Field(default=None, alias="PaymentMethod")
|
|
scheduled_delivery_date: str | None = Field(default=None, alias="ScheduledDeliveryDate")
|
|
actual_delivery_date: str | None = Field(default=None, alias="ActualDeliveryDate")
|
|
|
|
# Чистая сумма к перечислению продавцу за товар — поле `AfterpaymentOnGoodsCost`
|
|
# ("Контроль оплати" в кабинете NP). В отличие от `AmountToPay`/
|
|
# `ExpressWaybillAmountToPay` (сколько получатель должен заплатить НП прямо
|
|
# сейчас — включает стоимость доставки и комиссию НП и обнуляется после
|
|
# оплаты), это поле — зафиксированная при создании ТТН сумма за товар без
|
|
# доставки и комиссии, и не меняется по ходу доставки. `AmountToPay`/
|
|
# `ExpressWaybillAmountToPay` — запасной вариант на случай, если NP для
|
|
# какого-то типа накладной не возвращает `AfterpaymentOnGoodsCost`.
|
|
cod_amount: str | None = Field(default=None)
|
|
|
|
# Телефон отправителя NP отдаёт только кабинету отправителя (или получателя):
|
|
# по чужому ключу без телефона ответ урезан — нет ни отправителя, ни
|
|
# `AfterpaymentOnGoodsCost`. По этому полю определяется, чья это посылка.
|
|
phone_sender: str | None = Field(default=None, alias="PhoneSender")
|
|
|
|
# Статус оплаты аналогично раздвоен на `PaymentStatus`/`ExpressWaybillPaymentStatus`.
|
|
payment_status: str | None = Field(default=None)
|
|
|
|
@property
|
|
def is_own(self) -> bool:
|
|
"""ТТН видна ключу как своя — отправлена из кабинета этого ключа."""
|
|
return bool(self.phone_sender)
|
|
|
|
@model_validator(mode="before")
|
|
@classmethod
|
|
def _fold_express_fields(cls, data: Any) -> Any:
|
|
if not isinstance(data, dict):
|
|
return data
|
|
data = dict(data)
|
|
afterpayment = data.get("AfterpaymentOnGoodsCost")
|
|
fallback = data.get("AmountToPay") or data.get("ExpressWaybillAmountToPay") or None
|
|
data.setdefault("cod_amount", str(afterpayment) if afterpayment else fallback)
|
|
data.setdefault(
|
|
"payment_status",
|
|
data.get("PaymentStatus") or data.get("ExpressWaybillPaymentStatus") or None,
|
|
)
|
|
return data
|