"""公式纯函数测试。 守的是:算钱公式的精确性与边界 —— 一旦按 float 或字面量截断计算, 360/361 天分界、4/360 斜率、27 天回款示例会全部错位,直接影响工资金额。 断言一律用 Fraction/Decimal 精确值,禁近似比较。 """ from datetime import date from decimal import Decimal from fractions import Fraction from app.services.formula import ( apply_barter, converted_profit, payment_days, payment_weight, performance_amount, profit_rate, salesperson_rate, start_payment_ok, ) # 与迁移种子一致的业务员参数(存储口径:start/floor 为 ‰ 数,slope 为 ‰/天) SP_START = Fraction(10) SP_SLOPE = Fraction(4, 360) SP_FLOOR = Fraction(4) SP_BOUNDARY = 360 def _sp_rate(days: int) -> Fraction: return salesperson_rate( days, start=SP_START, slope=SP_SLOPE, floor=SP_FLOOR, boundary_days=SP_BOUNDARY ) class TestPaymentDays: """回款天数:发货日首日计第 1 天(对照表 #1;需规「26 天」示例作废)。""" def test_发货日当天回款为第1天(self) -> None: d = date(2026, 1, 1) assert payment_days(d, d) == 1 def test_12月15日发货1月10日回款为27天(self) -> None: assert payment_days(date(2025, 12, 15), date(2026, 1, 10)) == 27 def test_跨年按自然日连续累计(self) -> None: assert payment_days(date(2025, 12, 31), date(2026, 1, 1)) == 2 class TestSalespersonRate: """业务员计提比例:1~360 天线性递减、≥361 天固定下限(对照表 #4)。""" def test_第360天恰为千分之6_线性段终点(self) -> None: assert _sp_rate(360) == Fraction(6, 1000) def test_第361天固定下限千分之4_分界线(self) -> None: # 公式外推为 5.989‰,业务确认「360 为分界线,360 以后按千分之四」 assert _sp_rate(361) == Fraction(4, 1000) def test_第1天按公式为899比90000_非取整为10(self) -> None: # 需规图示「第 1 天对应 10‰」为上限参考;公式值 = (10 − 4/360)/1000 = 899/90000 assert _sp_rate(1) == Fraction(899, 90000) def test_第180天线性段中点千分之8(self) -> None: assert _sp_rate(180) == Fraction(8, 1000) def test_返回值为纯比例_非千分数(self) -> None: # 绩效 = 折算额 × 比例,比例必须是 0.006 而非 6 assert _sp_rate(360) == Fraction(6, 1000) assert _sp_rate(360) != Fraction(6) class TestProfitRate: """毛利润率:不含运费口径(对照表 #2),毛利润额 / 销售额。""" def test_毛利润率_不含运费(self) -> None: assert profit_rate(Decimal("45"), Decimal("100")) == Fraction(9, 20) def test_销售额为零_返回零_防除零(self) -> None: assert profit_rate(Decimal("0"), Decimal("0")) == Fraction(0) class TestConvertedProfit: """折算毛利润额(化简式):销售额 ×(毛利润率 − 阈值)(对照表 #12/#13)。""" def test_化简式结果_市政水利30点阈值(self) -> None: # 100 × (9/20 − 30/100) = 15 assert converted_profit(Decimal("100"), Fraction(9, 20), Fraction(30)) == Decimal("15") def test_化简式与定义式等价(self) -> None: # 定义式:毛利润额 / 毛利润率 × (毛利润率 − 阈值) = 45 / (9/20) × (9/20 − 3/10) = 15 revenue, profit = Decimal("100"), Decimal("45") rate = profit_rate(profit, revenue) assert converted_profit(revenue, rate, Fraction(30)) == Decimal("15") def test_毛利润额为零_折算为零_不除零(self) -> None: assert converted_profit(Decimal("0"), Fraction(0), Fraction(30)) == Decimal("0") def test_毛利润率低于阈值_折算为负_不归零(self) -> None: # 100 × (1/10 − 3/10) = -20(业务确认负值正常计算) assert converted_profit(Decimal("100"), Fraction(1, 10), Fraction(30)) == Decimal("-20") class TestPerformanceAmount: """绩效 = 折算毛利润额 × 计提比例(乘法方向,对照表 #3)。""" def test_乘法方向_折算额乘比例(self) -> None: # 15000 × 97/10000 = 145.5 assert performance_amount(Decimal("15000"), Fraction(97, 10000)) == Decimal("145.5") def test_负折算额_绩效为负不归零(self) -> None: assert performance_amount(Decimal("-20"), Fraction(6, 1000)) == Decimal("-0.12") class TestModifiers: """修饰器:以物抵顶、支付权重 R×R、起付判定(对照表 #21/#22/#24)。""" def test_以物抵顶_按80点折算(self) -> None: assert apply_barter(Decimal("145.5"), Fraction(80)) == Decimal("116.4") def test_支付权重_R为60时0点36(self) -> None: assert payment_weight(60) == Fraction(36, 100) def test_支付权重_R为100时全额(self) -> None: assert payment_weight(100) == Fraction(1) def test_起付判定_达到60点(self) -> None: assert start_payment_ok(Decimal("60000"), Decimal("100000"), Fraction(60)) is True def test_起付判定_未达到60点(self) -> None: assert start_payment_ok(Decimal("59999"), Decimal("100000"), Fraction(60)) is False