""" 合同域 ORM 模型 映射: erp_contracts / erp_contract_items / erp_contract_attachments """ from __future__ import annotations import uuid from datetime import date, datetime from sqlalchemy import ( Boolean, Date, DateTime, ForeignKey, Numeric, String, Text, func, ) from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base # ── 付款条件枚举 ───────────────────────────────────────── PAYMENT_TERMS = [ "预付全款订货", "预付30%订货,到货前付清", "预付50%订货,到货前付清", "货到付全款", "开具发票后30天内付款", "开具发票45天付款", "开具发票60天付款", "开具发票90天付款", ] # ── 运费条款枚举 ───────────────────────────────────────── SHIPPING_TERMS = [ "买方自提", "卖方免费送达天津指定地点", "卖方免费送达指定地点", "物流发货,运费买方承担", ] class ErpContract(Base): """合同主表 —— B2B 交易防线核心""" __tablename__ = "erp_contracts" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) contract_no: Mapped[str] = mapped_column(String(30), unique=True, nullable=False) buyer_customer_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("crm_customers.id"), nullable=False, comment="买方(CRM 客户)" ) seller_company_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("sys_companies.id"), nullable=False, comment="卖方(当前操作公司)" ) company_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("sys_companies.id"), nullable=False, index=True, comment="多租户隔离" ) total_amount_excl_tax: Mapped[float] = mapped_column(Numeric(14, 2), default=0) total_amount_incl_tax: Mapped[float] = mapped_column(Numeric(14, 2), default=0) total_amount_cn: Mapped[str | None] = mapped_column( String(100), nullable=True, comment="大写合计金额" ) payment_terms: Mapped[str] = mapped_column( String(50), nullable=False, default="货到付全款" ) shipping_terms: Mapped[str] = mapped_column( String(50), nullable=False, default="买方自提" ) status: Mapped[str] = mapped_column( String(20), nullable=False, default="draft", comment="draft→active→completed→cancelled" ) is_signed: Mapped[bool] = mapped_column(Boolean, default=False) signed_file_url: Mapped[str | None] = mapped_column(String(500), nullable=True) linked_order_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("erp_orders.id"), nullable=True, comment="一键推单后回填" ) salesperson_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("sys_users.id"), nullable=True ) sign_date: Mapped[date | None] = mapped_column(Date, nullable=True) remark: Mapped[str | None] = mapped_column(Text, nullable=True) delivery_terms: Mapped[str | None] = mapped_column( String(200), nullable=True, comment="货期(手动输入)" ) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) # 关系 buyer_customer: Mapped["CrmCustomer"] = relationship( # noqa: F821 "CrmCustomer", lazy="selectin" ) seller_company: Mapped["SysCompany"] = relationship( # noqa: F821 "SysCompany", foreign_keys=[seller_company_id], lazy="selectin" ) salesperson: Mapped["SysUser | None"] = relationship("SysUser", foreign_keys=[salesperson_id], lazy="selectin") # noqa: F821 linked_order: Mapped["ErpOrder | None"] = relationship("ErpOrder", foreign_keys=[linked_order_id], lazy="selectin") # noqa: F821 items: Mapped[list["ErpContractItem"]] = relationship( "ErpContractItem", back_populates="contract", lazy="selectin" ) attachments: Mapped[list["ErpContractAttachment"]] = relationship( "ErpContractAttachment", back_populates="contract", lazy="selectin" ) class ErpContractItem(Base): """合同明细行""" __tablename__ = "erp_contract_items" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) contract_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("erp_contracts.id"), nullable=False ) sku_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("erp_product_skus.id"), nullable=False ) qty: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) unit_price: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) sub_total: Mapped[float] = mapped_column(Numeric(14, 2), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) # 关系 contract: Mapped[ErpContract] = relationship("ErpContract", back_populates="items") sku: Mapped["ProductSku"] = relationship("ProductSku", lazy="selectin") # noqa: F821 class ErpContractAttachment(Base): """合同附件(双签盖章版等)""" __tablename__ = "erp_contract_attachments" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) contract_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("erp_contracts.id"), nullable=False ) file_name: Mapped[str] = mapped_column(String(200), nullable=False) file_url: Mapped[str] = mapped_column(String(500), nullable=False) file_type: Mapped[str] = mapped_column( String(30), nullable=False, default="signed_copy", comment="signed_copy / supplement / other" ) uploader_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("sys_users.id"), nullable=True ) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) # 关系 contract: Mapped[ErpContract] = relationship("ErpContract", back_populates="attachments") uploader: Mapped["SysUser | None"] = relationship("SysUser", lazy="selectin") # noqa: F821