- FOR UPDATE 锁业务行防发货/回款并发丢失更新;终态业务禁新发货 - 行金额服务层按数量×单价固化;明细行 remark 按设计定稿移除(冒烟修复) Co-Authored-By: Claude Code <noreply@anthropic.com>
126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
"""发货管理的请求/响应模型(需求 2.2.2)。
|
||
|
||
金额/数量入参出参一律 str(服务层 Decimal 运算,全程禁 float),容量与
|
||
小数位校验复用 business.py 的 ``_check_amount``(同一套口径,防两处漂移);
|
||
字段契约全线蛇形直出(2026-10-13 定稿:无驼峰 alias)。
|
||
|
||
amount(行金额)与 total_amount(发货总金额)不在入参内 —— 由服务层按
|
||
数量×单价 计算固化,不信任前端传值;运费仅记录展示,不计入毛利润口径。
|
||
"""
|
||
|
||
from datetime import date
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||
|
||
from app.core.response import PageQuery
|
||
from app.schemas.business import AMOUNT_MAX, PRICE_MAX, _check_amount
|
||
|
||
|
||
class ShipmentItemIn(BaseModel):
|
||
"""发货产品明细行(product_id 空=自由录入;价格口径两可,可空人工补)。"""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
product_id: int | None = Field(default=None, ge=1, description="产品ID;空=自由录入")
|
||
product_name: str = Field(min_length=1, max_length=128, description="产品名称")
|
||
spec_model: str | None = Field(
|
||
default=None, max_length=128, description="规格型号,空=空串归一"
|
||
)
|
||
unit: str | None = Field(default=None, max_length=16, description="计量单位")
|
||
quantity: str = Field(description="数量,>0")
|
||
sale_price: str | None = Field(
|
||
default=None, description="发货单价(元),可空=待人工补价,补价前行金额记 0"
|
||
)
|
||
|
||
@field_validator("quantity")
|
||
@classmethod
|
||
def _check_quantity(cls, value: str) -> str:
|
||
return _check_amount(value, "数量", AMOUNT_MAX, allow_positive_only=True) or value
|
||
|
||
@field_validator("sale_price")
|
||
@classmethod
|
||
def _check_price(cls, value: str | None) -> str | None:
|
||
return _check_amount(value, "金额", PRICE_MAX)
|
||
|
||
|
||
class ShipmentItemOut(BaseModel):
|
||
"""发货产品明细行响应。金额/数量一律 str。"""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: int
|
||
product_id: int | None = None
|
||
product_name: str
|
||
spec_model: str | None = None
|
||
unit: str | None = None
|
||
quantity: str
|
||
sale_price: str | None = None
|
||
amount: str
|
||
|
||
|
||
class ShipmentOut(BaseModel):
|
||
"""发货单响应(列表与写操作回包共用);business_no 为 join 附带列。"""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: int
|
||
shipment_no: str
|
||
business_id: int
|
||
business_no: str | None = None
|
||
ship_date: date
|
||
freight_amount: str
|
||
total_amount: str
|
||
remark: str | None = None
|
||
|
||
|
||
class ShipmentDetailOut(ShipmentOut):
|
||
"""发货单详情:附产品明细行。"""
|
||
|
||
items: list[ShipmentItemOut] = Field(default_factory=list)
|
||
|
||
|
||
class ShipmentCreateIn(BaseModel):
|
||
"""创建发货单:业务维度 + 明细行数组(明细 ≥1 行)。
|
||
|
||
字段名全线蛇形直出(同 business.py 契约,仅分页信封 list/pageNum 驼峰)。
|
||
"""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
business_id: int = Field(ge=1, description="业务ID")
|
||
ship_date: date = Field(description="发货日期(回款天数基准日,发货日首日=第 1 天)")
|
||
freight_amount: str | None = Field(
|
||
default=None, description="运费金额(元),空记 0;仅记录展示,不计入毛利润口径"
|
||
)
|
||
items: list[ShipmentItemIn] = Field(min_length=1, description="发货明细行,至少 1 行")
|
||
remark: str | None = Field(default=None, max_length=255)
|
||
|
||
|
||
class ShipmentUpdateIn(BaseModel):
|
||
"""编辑发货单(明细全量替换)。
|
||
|
||
business_id 不在编辑入参内 —— 发货单挂靠的业务创建时定死,改挂会让
|
||
已指定本发货单的回款语义断裂。
|
||
"""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
ship_date: date = Field(description="发货日期(回款天数基准日,发货日首日=第 1 天)")
|
||
freight_amount: str | None = Field(default=None, description="运费金额(元),空记 0")
|
||
items: list[ShipmentItemIn] = Field(
|
||
min_length=1, description="发货明细行,至少 1 行(全量替换)"
|
||
)
|
||
remark: str | None = Field(default=None, max_length=255)
|
||
|
||
|
||
class ShipmentQuery(PageQuery):
|
||
"""发货单列表查询条件(字段名蛇形直出,与前端 api 封装同名对应)。
|
||
|
||
business_id 可空=全量:发货单以业务维度查看为主(一期),但单号可
|
||
独立检索,不强制挂业务条件。
|
||
"""
|
||
|
||
business_id: int | None = Field(default=None, ge=1, description="业务筛选,空=全部")
|
||
ship_date_from: date | None = Field(default=None, description="发货日期起(含)")
|
||
ship_date_to: date | None = Field(default=None, description="发货日期止(含)")
|