fix(business): 删除保护补齐与单价小数位口径修复(e2e 发现)

- 客户删除保护:被业务引用 → 400(T4 占位注释在业务表就位后漏补,FK 500 暴露)
- 人员删除保护:业务归属(负责/协助)引用纳入检查(同样漏检 500)
- schema 单价类列(DECIMAL 14,4)小数位放行至 4 位:回显 '5000.0000'
  原样提交被两位校验永久拒收(编辑任何带精度单价的历史行都失败)
- 补 2 个集成用例锁定;文案同步「客户归属或业务归属引用」

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-13 17:06:46 +08:00
1 parent 299b72483c
commit fba8cfc215
8 files changed
+167 -23

No files matched your search

+21 -8
View File
@@ -24,11 +24,18 @@ PRICE_MAX = Decimal("9999999999.9999")
def _check_amount(
value: str | None, label: str, cap: Decimal, allow_positive_only: bool = False
value: str | None,
label: str,
cap: Decimal,
allow_positive_only: bool = False,
scale: int = 2,
) -> str | None:
"""字符串金额的通用校验:数字/有限/非负/容量/两位小数。
"""字符串金额的通用校验:数字/有限/非负/容量/小数位上限。
小数位超过两位直接拒绝而非静默舍入 —— 舍入即算错钱(工资系统红线)。
小数位超限直接拒绝而非静默舍入 —— 舍入即算错钱(工资系统红线)。
``scale`` 按列口径区分:金额/数量列 DECIMAL(14,2) 为 2;单价类列
DECIMAL(14,4) 为 4(回显 "5000.0000" 必须能原样提交,否则任何带
精度的历史单价都被编辑端点永远拒收)。
"""
if value is None:
return None
@@ -45,8 +52,8 @@ def _check_amount(
raise ValueError(f"{label}不能为负数")
if amount > cap:
raise ValueError(f"{label}超出可录入上限")
if -amount.as_tuple().exponent > 2:
raise ValueError(f"{label}最多两位小数")
if -amount.as_tuple().exponent > scale:
raise ValueError(f"{label}最多{'两' if scale == 2 else '四'}位小数")
return value
@@ -88,10 +95,16 @@ class BusinessItemIn(BaseModel):
def _check_quantity(cls, value: str) -> str:
return _check_amount(value, "数量", AMOUNT_MAX, allow_positive_only=True) or value
@field_validator("sale_price", "cost_amount", "guided_price")
@field_validator("sale_price", "guided_price")
@classmethod
def _check_prices(cls, value: str | None) -> str | None:
return _check_amount(value, "金额", PRICE_MAX)
def _check_unit_prices(cls, value: str | None) -> str | None:
"""单价类字段按 DECIMAL(14,4) 口径校验(4 位小数)。"""
return _check_amount(value, "单价", PRICE_MAX, scale=4)
@field_validator("cost_amount")
@classmethod
def _check_cost(cls, value: str | None) -> str | None:
return _check_amount(value, "金额", AMOUNT_MAX)
class BusinessItemOut(BaseModel):
+2 -1
View File
@@ -40,7 +40,8 @@ class ShipmentItemIn(BaseModel):
@field_validator("sale_price")
@classmethod
def _check_price(cls, value: str | None) -> str | None:
return _check_amount(value, "金额", PRICE_MAX)
"""发货单价列 DECIMAL(14,4):按 4 位小数口径(回显可原样提交)。"""
return _check_amount(value, "单价", PRICE_MAX, scale=4)
class ShipmentItemOut(BaseModel):