"""业务核心 ORM 模型。 定义订单管理系统的所有业务数据表,包括客户、产品、供应商、 订单、物流、提醒、文件附件、定价规则等。 """ from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Integer, Numeric, String, Text, func from sqlalchemy.orm import Mapped, mapped_column from backend.app.db import Base from backend.app.models.base import AuditMixin, TimestampMixin class Customer(TimestampMixin, AuditMixin, Base): """客户表(customer)。 存储客户基本信息、结算方式、所属业务员、信用额度等。 支持软删除(AuditMixin.deleted)。 被调用方: customer_repository、customer_service、order_service """ __tablename__ = "customer" id: Mapped[int] = mapped_column(primary_key=True) customer_name: Mapped[str] = mapped_column(String(64), index=True) mobile: Mapped[str] = mapped_column(String(32), index=True) address: Mapped[str | None] = mapped_column(String(255), nullable=True) settlement_type: Mapped[str | None] = mapped_column(String(32), nullable=True) settlement_days: Mapped[int] = mapped_column(default=0) settlement_day_of_month: Mapped[int | None] = mapped_column(Integer, nullable=True) # 每月几号结算 reminder_day_of_month: Mapped[int | None] = mapped_column(Integer, nullable=True) # 每月几号提醒 customer_type: Mapped[str | None] = mapped_column(String(32), nullable=True) price_tier: Mapped[str | None] = mapped_column(String(32), nullable=True, default="default") salesman_id: Mapped[int | None] = mapped_column(nullable=True) credit_limit: Mapped[float] = mapped_column(Numeric(18, 2), default=0) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) class ProductCategory(TimestampMixin, AuditMixin, Base): """产品分类表(product_category)。 管理产品的一级分类,如"铝板"、"钢材"等。 category_code 唯一标识,sort_no 控制前端排序。 """ __tablename__ = "product_category" id: Mapped[int] = mapped_column(primary_key=True) category_name: Mapped[str] = mapped_column(String(64)) category_code: Mapped[str] = mapped_column(String(64), unique=True, index=True) sort_no: Mapped[int] = mapped_column(default=0) status: Mapped[int] = mapped_column(default=1) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) class Product(TimestampMixin, AuditMixin, Base): """产品表(product)。 存储产品名称、规格、单位、成本价、售价等信息。 category_id 关联 product_category 表,is_default 标记默认规格。 被调用方: product_repository、product_service、pricing_engine """ __tablename__ = "product" id: Mapped[int] = mapped_column(primary_key=True) product_name: Mapped[str] = mapped_column(String(64), index=True) specification: Mapped[str] = mapped_column(String(64), default="") unit: Mapped[str] = mapped_column(String(32), default="") category: Mapped[str | None] = mapped_column(String(64), nullable=True) category_id: Mapped[int | None] = mapped_column(ForeignKey("product_category.id"), nullable=True) cost_price: Mapped[float] = mapped_column(Numeric(18, 2), default=0) sale_price: Mapped[float] = mapped_column(Numeric(18, 2), default=0) pricing_type: Mapped[str | None] = mapped_column(String(32), nullable=True) pricing_unit: Mapped[str | None] = mapped_column(String(16), nullable=True, default="㎡") thickness: Mapped[str | None] = mapped_column(String(32), nullable=True) weight_gsm: Mapped[int | None] = mapped_column(Integer, nullable=True) default_width_m: Mapped[float | None] = mapped_column(Numeric(10, 4), nullable=True) status: Mapped[int] = mapped_column(default=1) is_default: Mapped[int] = mapped_column(default=0) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) class Supplier(TimestampMixin, AuditMixin, Base): """供应商表(supplier)。 存储供应商/工厂基本信息,supplier_type 区分"factory"等类型。 template_type 用于生成发厂文本时选择对应的模板。 """ __tablename__ = "supplier" id: Mapped[int] = mapped_column(primary_key=True) supplier_name: Mapped[str] = mapped_column(String(64), index=True) supplier_type: Mapped[str] = mapped_column(String(32), default="factory") contact_name: Mapped[str | None] = mapped_column(String(64), nullable=True) contact_mobile: Mapped[str | None] = mapped_column(String(32), nullable=True) address: Mapped[str | None] = mapped_column(String(255), nullable=True) template_type: Mapped[str | None] = mapped_column(String(64), nullable=True) status: Mapped[int] = mapped_column(default=1) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) class SalesOrder(TimestampMixin, AuditMixin, Base): """销售订单主表(sales_order)。 订单全流程的核心表,包含客户信息快照、金额汇总、状态流转、 取消审批、发厂确认等字段。order_status 驱动整个业务流程。 状态流转(有司机): draft → pending_approve → approved → pending_driver → accepted → picked_up → pending_logistics → (快递100) → delivered → completed → settled 状态流转(无司机): draft → pending_approve → approved → pending_logistics → (快递100) → delivered → completed → settled 被调用方: order_repository、order_service、reminder_service """ __tablename__ = "sales_order" id: Mapped[int] = mapped_column(primary_key=True) order_no: Mapped[str] = mapped_column(String(64), unique=True, index=True) customer_id: Mapped[int | None] = mapped_column(ForeignKey("customer.id"), nullable=True) customer_name: Mapped[str] = mapped_column(String(64)) customer_mobile: Mapped[str] = mapped_column(String(32)) customer_address: Mapped[str | None] = mapped_column(String(255), nullable=True) salesman_id: Mapped[int | None] = mapped_column(nullable=True) order_status: Mapped[str] = mapped_column(String(32), index=True, default="draft") order_source: Mapped[str | None] = mapped_column(String(64), nullable=True) delivery_type: Mapped[str | None] = mapped_column(String(64), nullable=True) factory_id: Mapped[int | None] = mapped_column(ForeignKey("supplier.id"), nullable=True) contract_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) sale_price_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) cost_price_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) rebate_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) freight_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) tax_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) other_fee_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) profit_total: Mapped[float] = mapped_column(Numeric(18, 2), default=0) profit_rate: Mapped[float] = mapped_column(Numeric(18, 2), default=0) commission_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) cancel_requested_by: Mapped[int | None] = mapped_column(nullable=True) cancel_requested_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True) cancel_reason: Mapped[str | None] = mapped_column(String(255), nullable=True) cancel_opinion: Mapped[str | None] = mapped_column(Text, nullable=True) cancel_previous_status: Mapped[str | None] = mapped_column(String(32), nullable=True) supplier_text_confirmed_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True) supplier_text_confirmed_by: Mapped[int | None] = mapped_column(nullable=True) payment_method: Mapped[str | None] = mapped_column(String(64), nullable=True) tax_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) need_invoice: Mapped[int] = mapped_column(Integer, default=0) order_type: Mapped[str | None] = mapped_column(String(32), nullable=True) # industry/daily self_delivery: Mapped[int] = mapped_column(Integer, default=0) tracking_number: Mapped[str | None] = mapped_column(String(128), nullable=True) express_company: Mapped[str | None] = mapped_column(String(64), nullable=True) customer_demand: Mapped[str | None] = mapped_column(Text, nullable=True) remark: Mapped[str | None] = mapped_column(Text, nullable=True) class SalesOrderItem(TimestampMixin, AuditMixin, Base): """订单明细行表(sales_order_item)。 每个订单包含多条明细,记录产品、数量、单价、面积、附加费用等。 pricing_type 区分按面积/按重量等计价方式。 被调用方: order_repository、order_service """ __tablename__ = "sales_order_item" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) product_id: Mapped[int | None] = mapped_column(nullable=True) product_name: Mapped[str] = mapped_column(String(64)) specification: Mapped[str] = mapped_column(String(64), default="") unit: Mapped[str] = mapped_column(String(32), default="") quantity: Mapped[float] = mapped_column(Numeric(18, 4), default=0) sale_price: Mapped[float] = mapped_column(Numeric(18, 2), default=0) cost_price: Mapped[float] = mapped_column(Numeric(18, 2), default=0) rebate_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) freight_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) tax_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) other_fee_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) pricing_type: Mapped[str | None] = mapped_column(String(32), nullable=True) length_m: Mapped[float | None] = mapped_column(Numeric(10, 4), nullable=True) width_m: Mapped[float | None] = mapped_column(Numeric(10, 4), nullable=True) area_sqm: Mapped[float | None] = mapped_column(Numeric(18, 4), nullable=True) surcharge_detail: Mapped[str | None] = mapped_column(Text, nullable=True) processing_detail: Mapped[str | None] = mapped_column(Text, nullable=True) supplier_id: Mapped[int | None] = mapped_column(ForeignKey("supplier.id"), nullable=True) supplier_model: Mapped[str | None] = mapped_column(String(64), nullable=True) price_tier: Mapped[str | None] = mapped_column(String(32), nullable=True) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) demand_specification: Mapped[str | None] = mapped_column(String(128), nullable=True) class SalesOrderApproveLog(TimestampMixin, AuditMixin, Base): """订单审批日志表(sales_order_approve_log)。 记录每次审批操作的结果(通过/驳回)、审批前后状态、审批意见。 approve_type 区分"order"(订单审批)和"cancel_order"(取消审批)。 被调用方: order_service """ __tablename__ = "sales_order_approve_log" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) approve_type: Mapped[str] = mapped_column(String(32), default="order") approve_result: Mapped[str] = mapped_column(String(32)) before_status: Mapped[str | None] = mapped_column(String(32), nullable=True) after_status: Mapped[str | None] = mapped_column(String(32), nullable=True) approve_opinion: Mapped[str | None] = mapped_column(Text, nullable=True) operator_id: Mapped[int | None] = mapped_column(nullable=True) class OrderSupplierTextLog(TimestampMixin, AuditMixin, Base): """发厂文本日志表(order_supplier_text_log)。 记录为供应商生成的发厂文本内容及确认状态。 confirmed 标记工厂是否已确认文本内容。 被调用方: order_service """ __tablename__ = "order_supplier_text_log" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) supplier_id: Mapped[int] = mapped_column(ForeignKey("supplier.id"), index=True) template_type: Mapped[str | None] = mapped_column(String(64), nullable=True) text_content: Mapped[str] = mapped_column(Text) confirmed: Mapped[int] = mapped_column(default=0) confirmed_by: Mapped[int | None] = mapped_column(nullable=True) confirmed_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True) created_by: Mapped[int | None] = mapped_column(nullable=True) class LogisticsTask(TimestampMixin, AuditMixin, Base): """物流任务表(logistics_task)。 记录每个订单的物流配送任务,包含取货/送货地址、司机、 快递单号、任务状态等。status: pending → accepted → picked_up → delivered。 被调用方: logistics_repository、logistics_service、reminder_service """ __tablename__ = "logistics_task" id: Mapped[int] = mapped_column(primary_key=True) task_no: Mapped[str | None] = mapped_column(String(64), nullable=True, unique=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) driver_id: Mapped[int] = mapped_column(index=True) factory_id: Mapped[int | None] = mapped_column(ForeignKey("supplier.id"), nullable=True) pickup_address: Mapped[str] = mapped_column(String(255)) delivery_address: Mapped[str] = mapped_column(String(255)) pickup_content: Mapped[str] = mapped_column(String(255)) quantity: Mapped[float] = mapped_column(Numeric(18, 4), default=0) status: Mapped[str] = mapped_column(String(32), default="pending") tracking_number: Mapped[str | None] = mapped_column(String(64), nullable=True) express_company: Mapped[str | None] = mapped_column(String(32), nullable=True) created_by: Mapped[int | None] = mapped_column(nullable=True) canceled_by: Mapped[int | None] = mapped_column(nullable=True) cancel_reason: Mapped[str | None] = mapped_column(String(255), nullable=True) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) class LogisticsTrace(Base): """物流轨迹表(logistics_trace)。 记录物流节点的时间、描述、类型、数据来源平台。 支持从快递100等第三方平台同步轨迹数据。 """ __tablename__ = "logistics_trace" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) task_id: Mapped[int | None] = mapped_column(ForeignKey("logistics_task.id"), nullable=True, index=True) node_time: Mapped[DateTime] = mapped_column(DateTime) node_desc: Mapped[str] = mapped_column(String(500)) node_type: Mapped[str | None] = mapped_column(String(64), nullable=True) source_platform: Mapped[str | None] = mapped_column(String(64), nullable=True) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now()) class FileAttachment(Base): """文件附件表(file_attachment)。 通用文件关联表,通过 biz_type + biz_id 关联任意业务对象。 存储文件 URL、类型、大小、阿里云 OSS 存储信息等。 被调用方: file_repository、file_service """ __tablename__ = "file_attachment" id: Mapped[int] = mapped_column(primary_key=True) biz_type: Mapped[str] = mapped_column(String(64), index=True) biz_id: Mapped[int] = mapped_column(index=True) file_name: Mapped[str] = mapped_column(String(255)) file_url: Mapped[str] = mapped_column(String(1000)) file_type: Mapped[str | None] = mapped_column(String(64), nullable=True) file_size: Mapped[int | None] = mapped_column(nullable=True) created_by: Mapped[int | None] = mapped_column(nullable=True) created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now()) storage_provider: Mapped[str | None] = mapped_column(String(32), nullable=True, default="aliyun_oss") bucket_name: Mapped[str | None] = mapped_column(String(128), nullable=True) object_key: Mapped[str | None] = mapped_column(String(1000), nullable=True) content_type: Mapped[str | None] = mapped_column(String(128), nullable=True) file_ext: Mapped[str | None] = mapped_column(String(32), nullable=True) file_category: Mapped[str | None] = mapped_column(String(32), nullable=True) class CustomerArrears(Base): """客户欠款表(customer_arrears)。 记录客户未结清的欠款信息,包括欠款金额、账期起始日、到期日。 status: pending → overdue,超期后触发提醒。 被调用方: reminder_repository、reminder_service """ __tablename__ = "customer_arrears" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customer.id"), index=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) arrears_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) settlement_start_date: Mapped[Date | None] = mapped_column(Date, nullable=True) due_date: Mapped[Date | None] = mapped_column(Date, nullable=True) status: Mapped[str] = mapped_column(String(32), default="pending") reminded_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now()) class SystemReminder(Base): """系统提醒表(system_reminder)。 存储系统自动生成的提醒消息,如物流超时、欠款逾期、沉默客户等。 reminder_type 区分提醒类型,receiver_user_id 指定接收人。 status: pending → read。 被调用方: reminder_repository、reminder_service """ __tablename__ = "system_reminder" id: Mapped[int] = mapped_column(primary_key=True) reminder_type: Mapped[str] = mapped_column(String(64), index=True) biz_type: Mapped[str] = mapped_column(String(64), index=True) biz_id: Mapped[int] = mapped_column(index=True) receiver_user_id: Mapped[int] = mapped_column(index=True) reminder_title: Mapped[str] = mapped_column(String(255)) reminder_content: Mapped[str] = mapped_column(String(2000)) status: Mapped[str] = mapped_column(String(32), default="pending") sent_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now()) class AIRecognitionLog(Base): """AI 识别日志表(ai_recognition_log)。 记录 AI 图片识别的原始结果、置信度、人工修正结果。 用于 AI 识别功能的结果追踪和模型优化。 被调用方: ai_repository、ai_service """ __tablename__ = "ai_recognition_log" id: Mapped[int] = mapped_column(primary_key=True) biz_type: Mapped[str] = mapped_column(String(64), index=True) biz_id: Mapped[int] = mapped_column(index=True) image_url: Mapped[str] = mapped_column(String(1000)) raw_result: Mapped[str | None] = mapped_column(Text, nullable=True) confidence: Mapped[float | None] = mapped_column(Numeric(10, 4), nullable=True) corrected_result: Mapped[str | None] = mapped_column(Text, nullable=True) created_by: Mapped[int | None] = mapped_column(nullable=True) created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now()) class ProductPricingRule(TimestampMixin, AuditMixin, Base): """产品定价规则表(product_pricing_rule)。 定义每个产品的计价方式(按面积/按重量)、基准单价、 计算公式、附加费用等。被 pricing_engine 在创建订单时调用。 """ __tablename__ = "product_pricing_rule" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(unique=True, index=True) product_name: Mapped[str] = mapped_column(String(128)) pricing_type: Mapped[str] = mapped_column(String(32), default="area") base_unit_price: Mapped[float] = mapped_column(Numeric(18, 4), default=0) pricing_unit: Mapped[str] = mapped_column(String(16), default="㎡") pricing_inputs: Mapped[str | None] = mapped_column(Text, nullable=True) formula_expr: Mapped[str | None] = mapped_column(Text, nullable=True) formula_constants: Mapped[str | None] = mapped_column(Text, nullable=True) surcharge_json: Mapped[str | None] = mapped_column(Text, nullable=True) formula_note: Mapped[str | None] = mapped_column(Text, nullable=True) tax_rate: Mapped[float] = mapped_column(Numeric(5, 2), default=0) tax_inclusive: Mapped[int] = mapped_column(default=0) status: Mapped[int] = mapped_column(default=1) class SupplierProductCost(TimestampMixin, AuditMixin, Base): """供应商产品成本表(supplier_product_cost)。 记录每个供应商对每个产品的成本价格,支持按厚度、克重等维度定价。 is_primary 标记主供应商。被 pricing_engine 在计算订单成本时调用。 """ __tablename__ = "supplier_product_cost" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(index=True) supplier_id: Mapped[int] = mapped_column(index=True) supplier_model: Mapped[str | None] = mapped_column(String(64), nullable=True) our_model: Mapped[str | None] = mapped_column(String(64), nullable=True) thickness: Mapped[str | None] = mapped_column(String(32), nullable=True) weight_gsm: Mapped[int | None] = mapped_column(Integer, nullable=True) base_fabric_weight: Mapped[int | None] = mapped_column(Integer, nullable=True) cost_price: Mapped[float] = mapped_column(Numeric(18, 4)) cost_unit: Mapped[str] = mapped_column(String(16), default="㎡") is_primary: Mapped[int] = mapped_column(default=0) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) status: Mapped[int] = mapped_column(default=1) class ProductPriceTier(TimestampMixin, AuditMixin, Base): """产品价格层级表(product_price_tier)。 定义产品的多级价格体系(如普通客户价、VIP客户价)。 tier_code 区分层级,前端根据客户的价格层级选择对应售价。 """ __tablename__ = "product_price_tier" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(index=True) tier_code: Mapped[str] = mapped_column(String(32)) tier_name: Mapped[str] = mapped_column(String(64)) price: Mapped[float] = mapped_column(Numeric(18, 4)) price_unit: Mapped[str] = mapped_column(String(16), default="㎡") remark: Mapped[str | None] = mapped_column(String(255), nullable=True) status: Mapped[int] = mapped_column(default=1) class LogisticsWaybill(TimestampMixin, AuditMixin, Base): """物流运单号表(logistics_waybill)。 记录一个物流任务关联的多个运单号(一票货分多件场景)。 通过 task_id 关联 logistics_task,order_id 冗余关联 sales_order。 被调用方: logistics_repository、logistics_service """ __tablename__ = "logistics_waybill" id: Mapped[int] = mapped_column(primary_key=True) task_id: Mapped[int] = mapped_column(ForeignKey("logistics_task.id"), index=True) order_id: Mapped[int] = mapped_column(ForeignKey("sales_order.id"), index=True) tracking_number: Mapped[str] = mapped_column(String(64), index=True) express_company: Mapped[str | None] = mapped_column(String(32), nullable=True) express_name: Mapped[str | None] = mapped_column(String(64), nullable=True) image_url: Mapped[str | None] = mapped_column(String(1000), nullable=True) ocr_raw: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column(String(32), default="pending") created_by: Mapped[int | None] = mapped_column(nullable=True) class PerformanceStatCache(Base): """业绩统计缓存表(performance_stat_cache)。 存储业绩统计结果缓存,避免重复查询数据库。 created_at 超过 24 小时视为过期。 """ __tablename__ = "performance_stat_cache" id: Mapped[int] = mapped_column(primary_key=True) stat_type: Mapped[str] = mapped_column(String(16), index=True) stat_period: Mapped[str] = mapped_column(String(32), index=True) category_id: Mapped[int | None] = mapped_column(nullable=True) order_count: Mapped[int] = mapped_column(Integer, default=0) order_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) commission_amount: Mapped[float] = mapped_column(Numeric(18, 2), default=0) total_profit: Mapped[float] = mapped_column(Numeric(18, 2), default=0) category_amounts_json: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())