feat(migration): V7 核算台账/月结/调整单四表与核算纯函数
- V7 四表:performance_ledgers(append-only 版本行,uk(receipt_id,calc_version) 库级防重兜底)/monthly_settlements(月份 uk,通过即锁定终态)/ settlement_items(按人固化可负工资,uk 三列)/adjustment_orders(TZ 单号) - registry +1:approval.adjustment.enabled(19 项,BOOL 首组扩至三开关) - formula 补核算纯函数:clamp_weight_r(从预览内联提取共用)/monthly_salary 三口径/probation_annual_note/manager_team_share(#12 暂口径:基数不含 经理个人户业绩,聚合集中一处答复后只改) - 迁移 downgrade 只保留 drop_table+alter 回滚+种子行反向 DELETE; performance_ledgers 表名复数(test_models 红线断言) - 断言同步:EXPECTED_TABLES 四新表/registry 19/BOOL 三开关/conftest 清理四新表与 ParamValue 边界 22 - schema_sync+全量单测 460 passed Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
30a179fc4c
commit
a5c86640e2
17 files changed
+1464
-51
No files matched your search
@@ -11,10 +11,14 @@ from fractions import Fraction
|
||||
|
||||
from app.services.formula import (
|
||||
apply_barter,
|
||||
clamp_weight_r,
|
||||
converted_profit,
|
||||
manager_team_share,
|
||||
monthly_salary,
|
||||
payment_days,
|
||||
payment_weight,
|
||||
performance_amount,
|
||||
probation_annual_note,
|
||||
profit_rate,
|
||||
salesperson_rate,
|
||||
start_payment_ok,
|
||||
@@ -137,3 +141,68 @@ class TestModifiers:
|
||||
|
||||
def test_起付判定_未达到60点(self) -> None:
|
||||
assert start_payment_ok(Decimal("59999"), Decimal("100000"), Fraction(60)) is False
|
||||
|
||||
|
||||
class TestClampWeightR:
|
||||
"""R 钳制:向下取整 + [0,100] 封顶封底(从预览内联提取的共用实现)。"""
|
||||
|
||||
def test_向下取整(self) -> None:
|
||||
assert clamp_weight_r(Decimal("5999.99"), Decimal("10000")) == 59
|
||||
|
||||
def test_封顶100_封底0(self) -> None:
|
||||
assert clamp_weight_r(Decimal("20000"), Decimal("10000")) == 100
|
||||
assert clamp_weight_r(Decimal("0"), Decimal("10000")) == 0
|
||||
|
||||
def test_销售额0_防御0(self) -> None:
|
||||
assert clamp_weight_r(Decimal("100"), Decimal("0")) == 0
|
||||
|
||||
|
||||
class TestMonthlySalary:
|
||||
"""月工资分岗口径三档(对照表 #8)。"""
|
||||
|
||||
def test_INCLUDE_绩效加基本(self) -> None:
|
||||
assert monthly_salary(Decimal("1000"), Decimal("3000"), "INCLUDE") == Decimal("4000")
|
||||
|
||||
def test_DEDUCT_绩效减基本_可为负(self) -> None:
|
||||
assert monthly_salary(Decimal("1000"), Decimal("3000"), "DEDUCT") == Decimal("-2000")
|
||||
|
||||
def test_EXCLUDE_仅绩效(self) -> None:
|
||||
assert monthly_salary(Decimal("1000"), Decimal("3000"), "EXCLUDE") == Decimal("1000")
|
||||
|
||||
def test_基本缺失按0(self) -> None:
|
||||
assert monthly_salary(Decimal("1000"), None, "INCLUDE") == Decimal("1000")
|
||||
|
||||
def test_未知口径报错(self) -> None:
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
monthly_salary(Decimal("1"), Decimal("1"), "UNKNOWN")
|
||||
|
||||
|
||||
class TestProbationAnnualNote:
|
||||
"""试用期年累计说明:超阈值才有 note,金额精确到分。"""
|
||||
|
||||
def test_未超阈值_无说明(self) -> None:
|
||||
assert probation_annual_note(Decimal("18000"), Decimal("18000")) is None
|
||||
assert probation_annual_note(Decimal("100"), Decimal("18000")) is None
|
||||
|
||||
def test_超额_给出精确差额(self) -> None:
|
||||
note = probation_annual_note(Decimal("20000.5"), Decimal("18000"))
|
||||
assert note is not None and "2000.50" in note
|
||||
|
||||
|
||||
class TestManagerTeamShare:
|
||||
"""经理 12.5%+5% 聚合(#12 暂口径:基数不含经理个人户业绩——由调用方
|
||||
传入的基数集合保证,本函数只做乘法与求和)。"""
|
||||
|
||||
def test_两段乘加_精确分数(self) -> None:
|
||||
# 入参为百分数(与种子一致:12.5 表示 12.5%):10000×12.5%=1250、50000×5%=2500
|
||||
got = manager_team_share(
|
||||
Decimal("10000"), Decimal("50000"), team_pct=Fraction(25, 2), company_pct=Fraction(5)
|
||||
)
|
||||
assert got == Decimal("3750")
|
||||
|
||||
def test_零基数_零结果(self) -> None:
|
||||
assert manager_team_share(
|
||||
Decimal("0"), Decimal("0"), team_pct=Fraction(0), company_pct=Fraction(0)
|
||||
) == Decimal("0")
|
||||
Reference in new issue
Block a user