PayrollManagementSystem/backend/app/core/report_meta.py
liuwenxin ebb3a92f25 fix(report): 报表统计修复批次——对账口径、导出审计与月份校验
- 部门汇总全公司 5% 按每位有台账聚合行的营销经理各计一次,
  团队应发小计取聚合额 + 调整净额;与月结 total_payable 对拍
  (有调整单时 + adjust_net_total),分列舍入差单列
- 人员归属按行类型拆分(普通/经理个人户/仅调整单),调整单全量并入,
  团队与未分配不重复;按经理筛选只汇总展示团队
- 导出改单 sheet 分块签字稿,审计覆盖取数与超限;筛选页眉 ID→名称
- 月份校验抽 core/month,报表/月结/调整单非法月份统一 400;
  岗位只读选项供财务/领导筛选
- 前端修复:部门下钻带入筛选、回款多桶独立状态、打印截断判定、
  summary 请求序号保护

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 09:44:43 +08:00

80 lines
3.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""报表统计元数据(区间桶、中文标签、导出上限)——叶子模块。
刻意不导入 app 内任何模块:report_service 与 report_export 都要消费这里的
定义,若它反向依赖 service 层会形成循环导入;也不走参数体系——FR-031
「区间可配置」一期以代码常量落地(参数化需注册表 + 迁移 + init-dev +
registry 测试四处同改,业务未提诉求前不做),配置缝收在
get_payment_day_buckets 单函数,未来在线可配时只改该函数。
"""
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class DayBucket:
"""回款天数区间档位:min/max 均为 None 表示「无回款天数」兜底桶。"""
code: str
label: str
min_days: int | None
max_days: int | None
# 8 档区间(催办清单 R2 默认口径)。两个兜底桶必须保留:payment_days 可空
# (回款未指定发货批次)且可为负(先款后货照实负值),缺了它们这些行会
# 静默消失、金额对不上。
PAYMENT_DAY_BUCKETS: tuple[DayBucket, ...] = (
DayBucket("le0", "≤0 天(先款后货)", None, 0),
DayBucket("1-30", "1-30 天", 1, 30),
DayBucket("31-60", "31-60 天", 31, 60),
DayBucket("61-90", "61-90 天", 61, 90),
DayBucket("91-180", "91-180 天", 91, 180),
DayBucket("181-360", "181-360 天", 181, 360),
DayBucket("361+", "361 天以上", 361, None),
DayBucket("none", "无回款天数", None, None),
)
CUSTOMER_TYPE_LABELS: dict[str, str] = {
"personal": "个人户",
"manager_personal": "经理个人户",
"enterprise": "企业户",
}
# mixed 不在 core.constants.PRODUCT_CATEGORIES(那只是可维护的档案类别),
# 但台账行可以是 mixed(混合业务行级明细入 snapshot_json),报表必须能标它
PRODUCT_CATEGORY_LABELS: dict[str, str] = {
"municipal_water": "市政水利",
"rail_transit": "轨道交通",
"mixed": "混合业务",
}
# 业务状态中文标签:导出列、筛选页眉与前端标签共用同一份映射
BUSINESS_STATUS_LABELS: dict[int, str] = {1: "进行中", 2: "已完成", 3: "已终止"}
REPORT_TEMPLATE_VERSION = "V1.2"
# 导出超限即拒绝(需求「大数据量分批/超限提示」二选一取后者):普通
# Workbook 全量载入内存,2 万行约数十 MB,超过该量级必须引导按月导出
REPORT_EXPORT_MAX_ROWS = 20000
def get_payment_day_buckets() -> tuple[DayBucket, ...]:
"""区间档位唯一出口:前端下拉与分桶都以本函数返回为准,禁止硬编码。
未来「区间在线可配」时改此处读参数即可,调用方与前端零改动。
"""
return PAYMENT_DAY_BUCKETS
def bucket_of(payment_days: int | None) -> DayBucket:
"""把单笔回款天数落入对应区间桶;未匹配任何区间按「无回款天数」兜底。"""
if payment_days is None:
return next(b for b in PAYMENT_DAY_BUCKETS if b.code == "none")
for bucket in PAYMENT_DAY_BUCKETS:
if bucket.code == "none":
continue
lower_ok = bucket.min_days is None or payment_days >= bucket.min_days
upper_ok = bucket.max_days is None or payment_days <= bucket.max_days
if lower_ok and upper_ok:
return bucket
return next(b for b in PAYMENT_DAY_BUCKETS if b.code == "none")