- SecondaryAllocationIn/Out:比例走金额红线口径(str 入参、两位小数、 超 100 拒绝不静默舍入);参与人 schema 层去重(uk 前置友好提示) - create/update 同事务写入(replace_allocations,先删后 flush 再插—— uk 表防御写法);create 放编号重试循环内,rollback 后方案随业务重来 - 人员在职复用 ensure_active(label=分摊参与),personnel_name 冗余固化; FK RESTRICT:被方案引用人员禁删,方案回显不静默失联 - 合计不强制 100%(需求原文):详情 allocation_hint 只展示合计/余额, 余额归属待澄清 #2 不落库 - 单测 13 例(比例边界/去重)+ 集成 9 例(回显/替换清空/离职 400/ 终态禁改/RESTRICT 保护) Co-Authored-By: Claude Code <noreply@anthropic.com>
112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
"""二次分配方案入参校验(schema 层,秒级)。
|
||
|
||
守的是:比例红线(字符串入参、两位小数、超限拒绝不静默舍入)与
|
||
参与人去重——uk(business_id, personnel_id) 的友好前置校验。
|
||
明细行同批创建的正常路径由集成测试覆盖,这里集中打边界。
|
||
"""
|
||
|
||
import pytest
|
||
from pydantic import ValidationError
|
||
|
||
from app.schemas.business import SecondaryAllocationIn
|
||
|
||
|
||
def _make(personnel_id: int = 1, ratio: str = "50.00") -> dict:
|
||
return {"personnel_id": personnel_id, "ratio": ratio}
|
||
|
||
|
||
class TestRatioBoundary:
|
||
"""比例边界:0 / 满额 / 超限 / 精度 / 非数字。"""
|
||
|
||
@pytest.mark.parametrize(
|
||
("ratio", "ok"),
|
||
[
|
||
("0", False), # 必须大于 0
|
||
("0.01", True),
|
||
("33.33", True),
|
||
("100", True),
|
||
("100.00", True),
|
||
("100.01", False), # 超上限
|
||
("-5", False), # 负数
|
||
("33.333", False), # 三位小数:拒绝不静默舍入
|
||
("abc", False), # 非数字
|
||
("", False), # 空串
|
||
],
|
||
)
|
||
def test_比例边界(self, ratio: str, *, ok: bool) -> None:
|
||
if ok:
|
||
assert SecondaryAllocationIn(**_make(ratio=ratio)).ratio == ratio
|
||
else:
|
||
with pytest.raises(ValidationError):
|
||
SecondaryAllocationIn(**_make(ratio=ratio))
|
||
|
||
def test_人员id必须为正整数(self) -> None:
|
||
with pytest.raises(ValidationError):
|
||
SecondaryAllocationIn(**_make(personnel_id=0))
|
||
with pytest.raises(ValidationError):
|
||
SecondaryAllocationIn(**_make(personnel_id=-1))
|
||
|
||
|
||
class TestDuplicate:
|
||
"""参与人去重(Create/Update 两个入参模型同款校验)。"""
|
||
|
||
def test_创建入参_重复参与人被拒(self) -> None:
|
||
from app.schemas.business import BusinessCreateIn
|
||
|
||
payload = {
|
||
"customer_id": 1,
|
||
"items": [
|
||
{
|
||
"product_name": "t_产品",
|
||
"product_category": "municipal_water",
|
||
"quantity": "1.00",
|
||
"sale_price": "10.00",
|
||
}
|
||
],
|
||
"secondary_allocations": [
|
||
_make(personnel_id=5, ratio="30.00"),
|
||
_make(personnel_id=5, ratio="20.00"),
|
||
],
|
||
}
|
||
with pytest.raises(ValidationError, match="重复"):
|
||
BusinessCreateIn(**payload)
|
||
|
||
def test_编辑入参_重复参与人被拒(self) -> None:
|
||
from app.schemas.business import BusinessUpdateIn
|
||
|
||
payload = {
|
||
"customer_id": 1,
|
||
"items": [
|
||
{
|
||
"product_name": "t_产品",
|
||
"product_category": "municipal_water",
|
||
"quantity": "1.00",
|
||
"sale_price": "10.00",
|
||
}
|
||
],
|
||
"secondary_allocations": [_make(personnel_id=5), _make(personnel_id=5)],
|
||
}
|
||
with pytest.raises(ValidationError, match="重复"):
|
||
BusinessUpdateIn(**payload)
|
||
|
||
def test_不同参与人合法(self) -> None:
|
||
from app.schemas.business import BusinessCreateIn
|
||
|
||
payload = {
|
||
"customer_id": 1,
|
||
"items": [
|
||
{
|
||
"product_name": "t_产品",
|
||
"product_category": "municipal_water",
|
||
"quantity": "1.00",
|
||
"sale_price": "10.00",
|
||
}
|
||
],
|
||
"secondary_allocations": [
|
||
_make(personnel_id=5, ratio="30.00"),
|
||
_make(personnel_id=6, ratio="20.00"),
|
||
],
|
||
}
|
||
model = BusinessCreateIn(**payload)
|
||
assert len(model.secondary_allocations) == 2
|