"""计提公式纯函数(无 IO、无会话)。 契约(与 M3 核算、计算预览共用,口径见 2026-10-12 业务裁决): - 参数值按**存储口径**传入(含单位:比例参数为 ‰ 数如 10、百分比参数为 % 数如 60) - 计算出的中间比率(如毛利润率)为**纯比例**(Fraction,如 9/20) - 金额用 Decimal、比率用 Fraction,全程禁 float;Fraction→Decimal 转换在 分母仅含 2/5 因子时精确,否则按 Decimal 上下文精度(28 位) - 绩效 = 折算毛利润额 × 计提比例(乘法方向,对照表 #3,禁按"互为倒数"实现) """ from datetime import date from decimal import Decimal from fractions import Fraction def to_decimal(value: Fraction) -> Decimal: """Fraction → Decimal。有限小数精确;循环小数按 Decimal 上下文精度(28 位)。""" return Decimal(value.numerator) / Decimal(value.denominator) # 模块内沿用私有名;公开别名供 service 层金额换算复用 _to_decimal = to_decimal def payment_days(ship_date: date, receipt_date: date) -> int: """回款天数:发货日首日计第 1 天(对照表 #1;需规「26 天」示例作废)。""" return (receipt_date - ship_date).days + 1 def salesperson_rate( days: int, *, start: Fraction, slope: Fraction, floor: Fraction, boundary_days: int, ) -> Fraction: """业务员计提比例(纯比例)。 start/floor 为 ‰ 数、slope 为 ‰/天(存储口径,slope 为精确分数 4/360)。 ≤分界天数按「起始 − 天数×斜率」线性递减;超过分界天数固定取下限 (对照表 #4:360 天=6‰,361 天起固定 4‰)。 首日取整(澄清 #14 业务答复 2026-10-19):第 1 天**不递减**、直接取 起始值(需规图示「第 1 天 10‰」覆盖公式的 9.9889‰)——第 2 天起按公式 递减,360 天=6‰ 锚点与 361 天=4‰ 分界均不受影响。 先款后货的回款(payment_days 为负)钳制为 0 天:负天数线性外插会算出 高于起始值的比例(多算绩效),钳到 0 天即按起始比例计提;payment_days 的出参仍照实输出负值(回款登记照常入库,录入异常由台账留痕暴露)。 """ days = max(days, 0) if days > boundary_days: return floor / 1000 if days <= 1: return start / 1000 return (start - days * slope) / 1000 def profit_rate(profit: Decimal, revenue: Decimal) -> Fraction: """毛利润率(纯比例):毛利润额 / 销售额(**不含运费**口径,对照表 #2)。 销售额为 0 时返回 0(防除零;折算额随之为 0)。 """ if revenue == 0: return Fraction(0) return Fraction(profit) / Fraction(revenue) def converted_profit(revenue: Decimal, rate: Fraction, threshold: Fraction) -> Decimal: """折算毛利润额(化简式):销售额 × (毛利润率 − 阈值)。 rate 为纯比例(profit_rate 的输出);threshold 为 % 数(存储口径,如 30)。 化简式等价于定义式「毛利润额 / 毛利润率 × (毛利润率 − 阈值)」,且避免 毛利润率为 0 时除零(对照表 #12/#13)。毛利润率低于阈值时结果为负 —— 业务确认负值正常计算、不归零。零毛利润是独立特例:rate=0 时折算为 0, 不能将该特例扩大为低于阈值的折算额一律归零。 """ if rate == 0: return Decimal("0") return _to_decimal(Fraction(revenue) * (rate - threshold / 100)) def performance_amount(base: Decimal, rate: Fraction) -> Decimal: """绩效金额 = 折算毛利润额 × 计提比例(乘法方向,对照表 #3)。""" return _to_decimal(Fraction(base) * rate) def apply_barter(amount: Decimal, discount: Fraction) -> Decimal: """保留的历史以物抵顶折算函数;当前正式核算不调用。 当前口径是特殊抵顶由线下核实,系统不自动折算;参数和函数仅为历史 快照兼容保留,新增正式计算路径不得调用。 """ return _to_decimal(Fraction(amount) * discount / 100) def payment_weight(r: int) -> Fraction: """支付权重 R×R(纯比例):R 为回款占销售额百分比向下取整(对照表 #22)。 60 → 36/100;100 → 1(全额)。 """ return Fraction(r, 100) * Fraction(r, 100) def start_payment_ok(received: Decimal, revenue: Decimal, ratio: Fraction) -> bool: """起付判定:单项业务回款额达到销售额(不含运费)的 ratio%(对照表 #21)。""" if revenue == 0: return False return Fraction(received) / Fraction(revenue) >= ratio / 100 def clamp_weight_r(received: Decimal, revenue: Decimal) -> int: """支付权重 R:回款占销售额百分比,封顶封底 [0, 100](对照表 #22)。 received 非负时 int() 即向下取整;min/max 同时是红字冲销(负数回款) 接入时的方向防线。从计算预览的内联实现提取 —— ④ 核算与预览共用。 """ if revenue <= 0: return 0 return max(0, min(100, int(Fraction(received) / Fraction(revenue) * 100))) def monthly_salary(performance: Decimal, basic: Decimal | None, mode: str) -> Decimal | None: """月工资分岗口径(对照表 #8,salary.basic.mode 按角色维度取值)。 - INCLUDE:绩效 + 基本工资(业务员默认——绩效之外照发基本工资) - DEDUCT:绩效 − 基本工资(经理默认——基本工资从绩效分成中扣除) - EXCLUDE:仅绩效,基本工资不参与 basic 缺失时按 0 参与(INCLUDE/EXCLUDE 等价于纯绩效)。 """ basic_value = basic if basic is not None else Decimal("0") if mode == "INCLUDE": return performance + basic_value if mode == "DEDUCT": return performance - basic_value if mode == "EXCLUDE": return performance raise ValueError(f"未知的月工资口径:{mode}") def probation_annual_note(year_total: Decimal, threshold: Decimal) -> str | None: """试用期年累计说明:超阈值部分年底一次性结算(对照表 #17)。 未超阈值返回 None(无需说明);阈值按元展示(Decimal 直出)。 """ if year_total <= threshold: return None excess = (year_total - threshold).quantize(Decimal("0.01"), rounding="ROUND_HALF_UP") return f"试用期绩效照算不发放;年累计超 {threshold} 元部分年底一次性结算(当前超 {excess} 元)" def manager_team_share( team_performance: Decimal, company_performance: Decimal, *, team_pct: Fraction, company_pct: Fraction, ) -> Decimal: """经理绩效 = 本团队业务员绩效 × 12.5% + 全公司业务员绩效 × 5%(裁决 #6/#15/#16)。 **#12 策略点(待业务答复,2026-10-16 暂定口径)**:基数**不含**经理本人 名下经理个人户业务的业绩——该部分按经理个人户规则另行处理(单笔落台账 rate=0/绩效 0,待答复后若需并入,只改调用方传入的基数集合)。 团队口径:manager_id=该经理且岗位非「营销经理」的业务员绩效行之和; 全公司口径:全部业务员(非经理岗)绩效行之和。 """ return _to_decimal(Fraction(team_performance) * team_pct / 100) + _to_decimal( Fraction(company_performance) * company_pct / 100 )