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