feat(business): 业务管理接口(编号生成/明细/状态机/归属留痕/毛利润视图)

- 编号 YW{日期}-{序号}:MAX+FOR UPDATE 并发安全 + 1062 兜底重试(no_generator 三单据共用)
- 明细全量替换重算五项汇总冗余;销售额服务层按数量×单价固化不信任前端
- 状态机终态拦截、终止必填原因、创建写首条留痕;归属变更同事务留痕防旁路
- 毛利润视图按行实时计算(不含运费 #2),阈值按 asOf 取 M1 快照,率输出
  Fraction 分数字符串同 M1 预览口径;成本缺失行标注无法计算
- 入参契约统一为蛇形直出(撤回 customer/business 的驼峰 alias,与前端
  businessRoles.ts 模式对齐)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-13 12:01:48 +08:00
1 parent 1625907380
commit 0cdc6246c4
7 files changed
+1565 -18

No files matched your search

+6 -18
View File
@@ -35,11 +35,11 @@ class CustomerCreateIn(BaseModel):
owner_personnel_id 为**初始归属**,可空(客户先建档、后派归属也合法);
归属的后续调整不走本模型,统一走 ``PUT /{id}/ownership``(见
``CustomerUpdateIn`` 的说明)。camelCase 别名与蛇形字段名均可绑定,
兼容前端两种拼写。
``CustomerUpdateIn`` 的说明)。字段名蛇形直出(M1 契约:前端 api 封装
与 schema 同名对应,仅分页信封 list/pageNum 驼峰)。
"""
model_config = ConfigDict(str_strip_whitespace=True, populate_by_name=True)
model_config = ConfigDict(str_strip_whitespace=True)
company_name: str = Field(min_length=1, max_length=128, description="公司名称")
owner_name: str = Field(
@@ -59,7 +59,6 @@ class CustomerCreateIn(BaseModel):
owner_personnel_id: int | None = Field(
default=None,
ge=1,
alias="ownerPersonnelId",
description="归属负责营销人员ID(初始归属,可空)",
)
remark: str | None = Field(default=None, max_length=255, description="备注")
@@ -91,21 +90,13 @@ class CustomerUpdateIn(BaseModel):
class CustomerQuery(PageQuery):
"""客户列表查询条件。
customer_type / owner_personnel_id 同时接受蛇形(项目既有契约,见前端
`types.ts` 的说明)与驼峰别名,两种拼写不并存即可。
"""
"""客户列表查询条件(字段名蛇形直出,与前端 api 封装同名对应)。"""
keyword: str | None = Field(
default=None, max_length=64, description="公司名称或客户负责人,模糊匹配"
)
customer_type: str | None = Field(
default=None, max_length=32, alias="customerType", description="客户分类筛选"
)
owner_personnel_id: int | None = Field(
default=None, ge=1, alias="ownerPersonnelId", description="归属人员ID筛选"
)
customer_type: str | None = Field(default=None, max_length=32, description="客户分类筛选")
owner_personnel_id: int | None = Field(default=None, ge=1, description="归属人员ID筛选")
class CustomerOwnershipIn(BaseModel):
@@ -114,12 +105,9 @@ class CustomerOwnershipIn(BaseModel):
new_owner_personnel_id 可空:null 表示清空归属(客户暂无负责人也合法)。
"""
model_config = ConfigDict(populate_by_name=True)
new_owner_personnel_id: int | None = Field(
default=None,
ge=1,
alias="newOwnerPersonnelId",
description="变更后归属人员ID,null=清空归属",
)
reason: str | None = Field(default=None, max_length=255, description="变更原因")