import uuid from datetime import datetime from decimal import Decimal from sqlalchemy import ( DateTime, ForeignKey, Numeric, Text, func, text, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base from app.models.catalog import _timestamptz, _uuid_pk class ProjectTransfer(Base): """project_transfers — Inter-project (inter-site) material transfer ledger. Records the movement of materials from a source project warehouse to a destination project. Stock is deducted from the source warehouse immediately upon creation (status = 'in_transit') via out_transaction_id. When the destination project PM confirms receipt in 'Arrivals', stock is added to the destination warehouse (status = 'received'). While 'in_transit', the transfer can be edited or cancelled (restoring source stock). Once 'received', the transfer is permanently locked from edit or cancellation. """ __tablename__ = "project_transfers" __mapper_args__ = {"eager_defaults": True} id: Mapped[uuid.UUID] = _uuid_pk() from_project_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("projects.id", ondelete="RESTRICT"), nullable=False ) from_warehouse_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("warehouses.id", ondelete="RESTRICT"), nullable=False ) to_project_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("projects.id", ondelete="RESTRICT"), nullable=False ) material_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("materials.id", ondelete="RESTRICT"), nullable=False ) brand_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("brands.id", ondelete="SET NULL") ) unit_id: Mapped[int] = mapped_column( ForeignKey("units.id", ondelete="RESTRICT"), nullable=False ) quantity: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False) specs: Mapped[dict] = mapped_column(JSONB, nullable=False, server_default=text("'{}'::jsonb")) specs_hash: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("'default'")) note: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("'in_transit'")) out_transaction_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("inventory_transactions.id", ondelete="SET NULL") ) transfer_date: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) transferred_by: Mapped[str | None] = mapped_column(Text) received_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) received_by: Mapped[str | None] = mapped_column(Text) to_warehouse_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("warehouses.id", ondelete="SET NULL") ) receipt_allocations: Mapped[list | None] = mapped_column(JSONB) created_at: Mapped[datetime] = _timestamptz() updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) from_project: Mapped["Project"] = relationship("Project", foreign_keys=[from_project_id], lazy="raise") to_project: Mapped["Project"] = relationship("Project", foreign_keys=[to_project_id], lazy="raise") from_warehouse: Mapped["Warehouse"] = relationship("Warehouse", foreign_keys=[from_warehouse_id], lazy="raise") to_warehouse: Mapped["Warehouse | None"] = relationship("Warehouse", foreign_keys=[to_warehouse_id], lazy="raise") material: Mapped["Material"] = relationship("Material", foreign_keys=[material_id], lazy="raise") brand: Mapped["Brand | None"] = relationship("Brand", foreign_keys=[brand_id], lazy="raise") unit: Mapped["Unit"] = relationship("Unit", foreign_keys=[unit_id], lazy="raise") out_transaction: Mapped["InventoryTransaction | None"] = relationship( "InventoryTransaction", foreign_keys=[out_transaction_id], lazy="raise" )