v0.1.0: CRM/ERP 系统内测版本 - 安全加固完成

- Docker bridge 网络隔离(8000 端口封死)
- Gunicorn 4 Worker 多进程
- Alembic 数据库迁移基线
- 日志轮转 20m×3
- JWT 密钥 + DB 密码 + CORS 收紧
- 3-2-1 备份链路(NAS + R740-B 冷备)
- 连接池 pool_pre_ping + pool_recycle=3600
This commit is contained in:
hankin
2026-03-16 07:31:37 +00:00
commit 423baff73b
2578 changed files with 824643 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
"""
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="备注")
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}