- report_meta:回款天数区间桶(含可空/负值兜底桶)、客户类型标签、导出上限 - schemas/report:明细/汇总查询入参与分页响应,金额比例全程 Decimal - report_repository:报表专用只读聚合,显式排除台账聚合行、口径与月结区分 - constants:新增报表统计模块与导出动作常量 Co-Authored-By: Claude Code <noreply@anthropic.com>
261 lines
11 KiB
Python
261 lines
11 KiB
Python
"""报表统计的请求/响应模型(M4 ⑤)。
|
||
|
||
金额与比例一律 `Decimal`(pydantic v2 序列化为字符串,零精度损失);
|
||
比例原样输出 `rate_text`(如 88/9000),禁止转 float 或截断展示。
|
||
"""
|
||
|
||
from datetime import date
|
||
from decimal import Decimal
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
|
||
from app.core.response import PageQuery
|
||
|
||
# ==== 查询入参 ====
|
||
|
||
class PersonalDetailQuery(PageQuery):
|
||
"""个人绩效明细查询条件(FR-029:人员/月份/客户类型/产品类别/岗位)。"""
|
||
|
||
settlement_month: str | None = Field(default=None, max_length=7, description="归属月 YYYY-MM")
|
||
personnel_id: int | None = Field(default=None, ge=1, description="人员筛选")
|
||
customer_type: str | None = Field(default=None, max_length=32, description="客户类型筛选")
|
||
product_category: str | None = Field(default=None, max_length=32, description="产品类别筛选")
|
||
role_code: str | None = Field(default=None, max_length=32, description="岗位筛选")
|
||
|
||
|
||
class DepartmentSummaryQuery(BaseModel):
|
||
"""部门汇总查询条件(月份必填——部门表按单月聚合,无跨月视角)。"""
|
||
|
||
settlement_month: str = Field(min_length=7, max_length=7, description="归属月 YYYY-MM")
|
||
manager_id: int | None = Field(default=None, ge=1, description="仅看某经理团队")
|
||
|
||
|
||
class BusinessStatsQuery(PageQuery):
|
||
"""业务统计查询条件;回款/绩效为该业务累计值,不随业务日期筛选而变。"""
|
||
|
||
keyword: str | None = Field(default=None, max_length=64, description="业务编号/客户名称")
|
||
customer_id: int | None = Field(default=None, ge=1, description="客户筛选")
|
||
owner_personnel_id: int | None = Field(default=None, ge=1, description="归属人筛选")
|
||
status: int | None = Field(default=None, description="业务状态筛选")
|
||
date_from: date | None = Field(default=None, description="业务日期起")
|
||
date_to: date | None = Field(default=None, description="业务日期止")
|
||
|
||
|
||
class PaymentStatsQuery(BaseModel):
|
||
"""回款统计查询条件(按回款天数区间/客户类型/产品类别统计,FR-031)。"""
|
||
|
||
settlement_month: str | None = Field(default=None, max_length=7, description="归属月 YYYY-MM")
|
||
date_from: date | None = Field(default=None, description="回款日期起")
|
||
date_to: date | None = Field(default=None, description="回款日期止")
|
||
customer_type: str | None = Field(default=None, max_length=32, description="客户类型筛选")
|
||
product_category: str | None = Field(default=None, max_length=32, description="产品类别筛选")
|
||
|
||
|
||
class PaymentDetailQuery(PageQuery):
|
||
"""回款区间下钻(桶编码必填,入参由 payment-stats 页面展开行携带)。"""
|
||
|
||
bucket: str = Field(min_length=1, max_length=16, description="区间桶编码")
|
||
settlement_month: str | None = Field(default=None, max_length=7, description="归属月 YYYY-MM")
|
||
date_from: date | None = Field(default=None, description="回款日期起")
|
||
date_to: date | None = Field(default=None, description="回款日期止")
|
||
customer_type: str | None = Field(default=None, max_length=32, description="客户类型筛选")
|
||
product_category: str | None = Field(default=None, max_length=32, description="产品类别筛选")
|
||
|
||
|
||
# ==== 响应出参 ====
|
||
|
||
class ReportSummary(BaseModel):
|
||
"""全筛选口径合计(与「当前页合计」区分,前端页脚需注明)。"""
|
||
|
||
row_count: int = Field(default=0, description="筛选结果行数")
|
||
performance_total: Decimal | None = Field(default=None, description="绩效金额合计")
|
||
payable_total: Decimal | None = Field(default=None, description="应付绩效合计")
|
||
converted_profit_total: Decimal | None = Field(default=None, description="折算毛利润合计")
|
||
|
||
|
||
class ReportPage[T](BaseModel):
|
||
"""报表分页:标准分页四字段 + 全筛选合计。
|
||
|
||
序列化字段与 PageResult 一致(list/total/pageNum/pageSize),前端
|
||
usePagedList 可直接消费;追加的 summary 由各页页脚单独读取。
|
||
"""
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
records: list[T] = Field(
|
||
default_factory=list, alias="list", serialization_alias="list", description="当前页数据"
|
||
)
|
||
total: int = Field(default=0, description="总记录数")
|
||
page_num: int = Field(
|
||
default=1, alias="pageNum", serialization_alias="pageNum", description="当前页码"
|
||
)
|
||
page_size: int = Field(
|
||
default=10, alias="pageSize", serialization_alias="pageSize", description="每页条数"
|
||
)
|
||
summary: ReportSummary = Field(default_factory=ReportSummary, description="全筛选合计")
|
||
|
||
|
||
class PersonalDetailRow(BaseModel):
|
||
"""个人绩效明细行(需规 2.4.1 列全覆盖)。
|
||
|
||
二次分摊拆行后同一回款按人多行(负责人余额行 + 参与人份额行),行内
|
||
不放「关联回款金额」列——同回款金额重复展示会让合计无法自洽,金额
|
||
下沉到快照下钻。
|
||
"""
|
||
|
||
ledger_id: int
|
||
business_id: int
|
||
business_no: str
|
||
personnel_id: int
|
||
personnel_name: str
|
||
role_code: str
|
||
role_name: str
|
||
customer_type: str
|
||
product_category: str
|
||
ship_date: date | None = None
|
||
receipt_date: date
|
||
payment_days: int | None = None
|
||
rate_text: str
|
||
weight_r: int | None = None
|
||
payment_weight: Decimal | None = None
|
||
performance_amount: Decimal | None = None
|
||
payable_amount: Decimal | None = None
|
||
converted_profit: Decimal | None = None
|
||
is_probation: int
|
||
missing_cost: int
|
||
calc_version: int
|
||
settlement_id: int | None = None
|
||
|
||
|
||
class DepartmentMemberRow(BaseModel):
|
||
"""部门汇总 · 团队成员明细(内联于团队行)。"""
|
||
|
||
personnel_id: int
|
||
personnel_name: str
|
||
role_code: str
|
||
is_probation: int
|
||
performance_sum: Decimal | None = None
|
||
payable_sum: Decimal | None = None
|
||
adjust_extra: Decimal = Field(default=Decimal("0"), description="调整单生效补发合计")
|
||
adjust_deduct: Decimal = Field(default=Decimal("0"), description="调整单生效扣减合计")
|
||
adjust_net: Decimal = Field(default=Decimal("0"), description="调整净额(补发为正)")
|
||
net_total: Decimal | None = None
|
||
ledger_ids: list[int] = Field(default_factory=list, description="台账行 id 列表(下钻锚点)")
|
||
|
||
|
||
class DepartmentTeamRow(BaseModel):
|
||
"""部门汇总 · 经理团队行。"""
|
||
|
||
manager_id: int
|
||
manager_name: str
|
||
manager_status: int = Field(description="经理在职状态(1 在职 0 离职)")
|
||
member_count: int
|
||
team_base: Decimal = Field(description="团队基数(12.5% 的底,等于成员绩效合计)")
|
||
team_share_amount: Decimal = Field(description="12.5% 团队分成")
|
||
company_share_amount: Decimal = Field(description="5% 全公司分成(同一基数,各团队不互斥)")
|
||
company_base_ratio: Decimal = Field(description="团队基数占全公司基数比例(除零为 0)")
|
||
adjust_extra: Decimal = Decimal("0")
|
||
adjust_deduct: Decimal = Decimal("0")
|
||
adjust_net: Decimal = Decimal("0")
|
||
net_total: Decimal = Field(description="团队净额 = 分成 + 调整净额")
|
||
frozen_amount: Decimal | None = Field(default=None, description="月结聚合行冻结额(对照)")
|
||
aggregate_ledger_id: int | None = None
|
||
members: list[DepartmentMemberRow] = Field(default_factory=list)
|
||
|
||
|
||
class DepartmentCompanyBlock(BaseModel):
|
||
"""部门汇总 · 全公司口径块(FR-030「全公司 5% 部分单列」)。"""
|
||
|
||
team_base_total: Decimal = Field(description="各团队基数之和(= 全部单笔绩效合计)")
|
||
company_base: Decimal = Field(description="全公司基数(5% 的底)")
|
||
company_share_pct: str = Field(description="分成比例参数原文")
|
||
company_share_amount: Decimal = Field(description="5% 全公司分成")
|
||
|
||
|
||
class DepartmentSummaryOut(BaseModel):
|
||
"""部门绩效工资汇总响应(FR-030)。"""
|
||
|
||
settlement_month: str
|
||
params: dict | None = Field(default=None, description="分成参数原文与取值时点(对账用)")
|
||
teams: list[DepartmentTeamRow] = Field(default_factory=list)
|
||
unassigned: list[DepartmentMemberRow] = Field(
|
||
default_factory=list, description="无经理人员的绩效(不参与分成,仅展示)"
|
||
)
|
||
company: DepartmentCompanyBlock | None = None
|
||
totals: dict = Field(default_factory=dict, description="全表合计(成员/团队对账自证)")
|
||
|
||
|
||
class BusinessStatsRow(BaseModel):
|
||
"""业务统计行(发货/回款/绩效为该业务累计值)。"""
|
||
|
||
business_id: int
|
||
business_no: str
|
||
business_date: date | None = None
|
||
status: int
|
||
customer_name: str | None = None
|
||
owner_personnel_name: str | None = None
|
||
total_sale_amount: Decimal | None = None
|
||
total_ship_amount: Decimal | None = None
|
||
total_received_amount: Decimal | None = None
|
||
performance_amount: Decimal | None = None
|
||
payable_amount: Decimal | None = None
|
||
|
||
|
||
class BucketRow(BaseModel):
|
||
"""回款统计 · 区间桶行(空桶恒在且为 0,防金额静默消失)。"""
|
||
|
||
code: str
|
||
label: str
|
||
min_days: int | None = None
|
||
max_days: int | None = None
|
||
receipt_amount: Decimal | None = None
|
||
receipt_count: int = 0
|
||
performance_amount: Decimal | None = None
|
||
payable_amount: Decimal | None = None
|
||
ledger_count: int = 0
|
||
|
||
|
||
class GroupRow(BaseModel):
|
||
"""回款统计 · 维度对比行(客户类型 / 产品类别)。"""
|
||
|
||
code: str
|
||
label: str
|
||
receipt_amount: Decimal | None = None
|
||
performance_amount: Decimal | None = None
|
||
payable_amount: Decimal | None = None
|
||
ledger_count: int = 0
|
||
|
||
|
||
class PaymentStatsOut(BaseModel):
|
||
"""回款情况统计响应(FR-031)。
|
||
|
||
口径:已计提回款(有台账行)的金额与绩效;进度登记回款无台账行,
|
||
不在本表(页面需注明)。
|
||
"""
|
||
|
||
settlement_month: str | None = None
|
||
buckets: list[BucketRow] = Field(default_factory=list)
|
||
by_customer_type: list[GroupRow] = Field(default_factory=list)
|
||
by_product_category: list[GroupRow] = Field(default_factory=list)
|
||
totals: dict = Field(default_factory=dict, description="全表合计")
|
||
|
||
|
||
class PaymentDetailRow(BaseModel):
|
||
"""回款区间下钻行(行级展示回款金额与批次号)。"""
|
||
|
||
ledger_id: int
|
||
receipt_id: int | None = None
|
||
receipt_batch_no: str | None = None
|
||
amount: Decimal | None = Field(default=None, description="回款金额(同回款拆行重复展示)")
|
||
business_id: int
|
||
business_no: str
|
||
personnel_id: int
|
||
personnel_name: str
|
||
customer_type: str
|
||
product_category: str
|
||
ship_date: date | None = None
|
||
receipt_date: date
|
||
payment_days: int | None = None
|
||
performance_amount: Decimal | None = None
|
||
payable_amount: Decimal | None = None
|