Files
hankin 815cbf9d8c v0.2.0: CRM/ERP 系统升级 - 清理 .gitignore 并移除误提交的 venv/env/db 文件
- 更新 .gitignore:全面覆盖环境变量、数据库、日志、缓存、上传文件
- 移除误跟踪的 server/venv/、crm_data.db、.env 文件
- 新增 server/.env.example 模板
- 新增合同管理、利润核算、AI教练等功能模块
- 新增 Playwright e2e 测试套件
- 前后端多项功能升级和 bug 修复
2026-05-11 07:24:19 +00:00

124 lines
4.6 KiB
Python

"""
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}