"""Локальная копия заказов, синхронизированная из 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))