Adds NpTrackingClient (Protocol + real/stub impls) and an ARQ worker that polls Nova Poshta every minute for orders without a receipt, writing status, net COD amount (Контроль оплати), and payment status onto the order. Surfaced in the orders table and detail modal. Marks plan stages 3-4 done in README. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
53 lines
2.9 KiB
Python
53 lines
2.9 KiB
Python
"""Локальная копия заказов, синхронизированная из CRM.
|
|
|
|
Живёт отдельно от live-проксирования (`app/services/crm/`), чтобы кассир мог
|
|
скрыть ненужный заказ (soft-delete) и он не появился снова при следующей
|
|
синхронизации, и чтобы отличать заказы без чека от уже пробитых.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from sqlalchemy import BigInteger, Boolean, DateTime, String, Text
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base, TimestampMixin
|
|
|
|
|
|
class Order(TimestampMixin, Base):
|
|
__tablename__ = "orders"
|
|
|
|
# PK — собственный ID заказа в CRM, а не сгенерированный UUID: синхронизация
|
|
# делает upsert по нему.
|
|
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
|
|
create_date_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
recipient_name: Mapped[str | None] = mapped_column(String(255))
|
|
recipient_phone: Mapped[str | None] = mapped_column(String(32))
|
|
recipient_email: Mapped[str | None] = mapped_column(String(320))
|
|
waybill_number: Mapped[str | None] = mapped_column(String(64))
|
|
notes: Mapped[str | None] = mapped_column(Text)
|
|
|
|
# Деньги — integer копейки (конвенция проекта), а не строка/float.
|
|
total_amount_kopecks: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
|
|
# Товарные позиции — снимок из CRM для отображения в модалке. Построчно в
|
|
# копейки не переводится: не участвует в денежных расчётах, только в показе.
|
|
goods: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, nullable=False, default=list)
|
|
|
|
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, index=True)
|
|
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
# Заполняется будущей интеграцией с Checkbox — сейчас всегда NULL.
|
|
receipt_created_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
|
|
|
# Статус ТТН Nova Poshta — опрашивается ARQ worker'ом раз в минуту для
|
|
# заказов без чека, у которых есть номер ТТН (см. app/worker.py).
|
|
np_status: Mapped[str | None] = mapped_column(String(255))
|
|
np_status_code: Mapped[str | None] = mapped_column(String(16))
|
|
np_cod_amount_kopecks: Mapped[int | None] = mapped_column(BigInteger)
|
|
np_payment_status: Mapped[str | None] = mapped_column(String(32))
|