""" ERP 供应链域 Pydantic V2 Schemas 分类树 / 产品 SKU / 库存流水 """ from __future__ import annotations import uuid from datetime import datetime from pydantic import BaseModel, Field # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # 分类树 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class CategoryNode(BaseModel): """递归树节点,适配前端 el-tree""" id: uuid.UUID parent_id: uuid.UUID | None = None name: str sort_order: int = 0 children: list[CategoryNode] = Field(default_factory=list) model_config = {"from_attributes": True} class CategoryCreate(BaseModel): name: str = Field(..., min_length=1, max_length=100) parent_id: uuid.UUID | None = None sort_order: int = Field(default=0) class CategoryUpdate(BaseModel): name: str | None = Field(default=None, min_length=1, max_length=100) parent_id: uuid.UUID | None = None sort_order: int | None = None # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # 产品 SKU # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class SkuCreate(BaseModel): sku_code: str = Field(..., min_length=1, max_length=50, examples=["SKU-LUB-001"]) name: str = Field(..., min_length=1, max_length=200, examples=["昆仑天润 KR9"]) category_id: uuid.UUID | None = None spec: str | None = Field(default=None, max_length=100, examples=["200L/桶"]) standard_price: float = Field(default=0, ge=0) stock_qty: float = Field(default=0, ge=0, description="初始库存(仅创建时可设,后续只能通过流水变更)") warning_threshold: float = Field(default=0, ge=0) unit: str = Field(default="桶", max_length=20) status: int = Field(default=1, ge=0, le=1) class SkuUpdate(BaseModel): """ ⚠️ 架构师红线:不包含 stock_qty 字段! 库存只能通过 InventoryFlow 事务接口变更。 """ name: str | None = Field(default=None, min_length=1, max_length=200) category_id: uuid.UUID | None = None spec: str | None = Field(default=None, max_length=100) standard_price: float | None = Field(default=None, ge=0) warning_threshold: float | None = Field(default=None, ge=0) unit: str | None = Field(default=None, max_length=20) status: int | None = Field(default=None, ge=0, le=1) class SkuResponse(BaseModel): id: uuid.UUID sku_code: str name: str category_id: uuid.UUID | None = None category_name: str | None = None spec: str | None = None standard_price: float = 0 stock_qty: float = 0 warning_threshold: float = 0 unit: str = "桶" status: int = 1 created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class SkuListResponse(BaseModel): total: int items: list[SkuResponse] page: int size: int # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # 库存流水 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class InventoryFlowCreate(BaseModel): """库存变更请求""" sku_id: uuid.UUID = Field(..., description="产品 SKU ID") change_qty: float = Field(..., description="变更数量,正=入库 负=出库") reason: str = Field( ..., max_length=50, description="变动原因: purchase/shipment/loss/adjust", examples=["purchase"], ) remark: str | None = Field(default=None, description="备注") purchase_unit_price: float = Field(default=0, ge=0, description="采购单价(仅入库时有意义)") is_special_zero_cost: bool = Field(default=False, description="特殊零元入库标识,不参与 MWA 计算") class InventoryFlowResponse(BaseModel): id: uuid.UUID sku_id: uuid.UUID sku_code: str | None = None sku_name: str | None = None change_qty: float reason: str remark: str | None = None operator_id: uuid.UUID | None = None operator_name: str | None = None created_at: datetime model_config = {"from_attributes": True}