docs(requirements): 需求裁决对照表 v2、功能模块清单与 M1 设计产物入库(移除被 v2 取代的旧清单)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
8668e58c30
commit
e568ebde61
9 files changed
+1896
No files matched your search
@@ -0,0 +1,268 @@
|
||||
# M1 开发计划:系统设置与参数体系(#28)
|
||||
|
||||
## Context
|
||||
|
||||
《剩余开发计划》已定稿(`docs/superpowers/plans/2026-10-12-剩余开发计划.md`),M1 是首个工作块。本文档即 M1 的正式开发计划(2026-10-12 批准):涵盖设计(四表/参数/模板/接口)、任务分解(T1–T8 步骤级)、执行安排与验收标准;开工后按第二章逐任务执行。
|
||||
|
||||
**为什么先做 M1**:岗位角色被基础数据的人员管理引用(外键上游);参数快照与公式模板是绩效核算的输入契约。先立地基可避免后续模块加表、加外键、改表单的连锁返工;计算预览还可作为业务对账工具先行验收公式口径。
|
||||
|
||||
**#28 需求原文要点**:① 人员岗位角色(营销员/营销经理/信息员等)支持增删,新增岗位时在系统设置中绑定计提公式与参数,**无需改代码**;② 全部计提比例、系数、阈值、分成比例、起付比例统一在系统设置维护,**支持版本管理与留痕**。
|
||||
|
||||
## 现状(开工前已核查)
|
||||
|
||||
| 项 | 状态 |
|
||||
| --- | --- |
|
||||
| 分支 | `feat/system-params`(基于 `feature/system-management` HEAD;后者领先 main 5 提交、未合并——是否先合并 main 由用户决定,不阻塞开发) |
|
||||
| 容器 | Docker Desktop 位于 `D:\work\app\Docker\Docker Desktop.exe`(非默认路径) |
|
||||
| 工作区 | docs/ 下有用户侧整理改动(业务清单删除、v2 新增、superpowers 目录),与 M1 无关,不动 |
|
||||
| 基线 | 301 用例全绿(系统管理交付时);开工先复跑确认 |
|
||||
|
||||
## 范围
|
||||
|
||||
**做**:四张新表与种子(V3 迁移双轨);岗位角色 CRUD + 模板绑定;参数目录/版本化存储/按日期取值/变更留痕/对比/导出;公式纯函数与模板注册表;计算预览;前端两个页面(岗位角色、计提参数)。
|
||||
|
||||
**不做**:核算引擎与台账(M3);基础数据与业务数据(M2);参数 Redis 缓存(一期不做,保留键位);规则引擎(既有决策)。
|
||||
|
||||
---
|
||||
|
||||
## 一、技术设计
|
||||
|
||||
### 1.1 数据模型(四张新表,V3 迁移)
|
||||
|
||||
**`business_roles` 业务岗位角色表**(与系统登录角色 `roles` 严格区分,表注释写明)
|
||||
|
||||
| 列 | 类型 | 约束/说明 |
|
||||
| --- | --- | --- |
|
||||
| id | BIGINT UNSIGNED | PK AUTO_INCREMENT |
|
||||
| role_code | VARCHAR(32) | NOT NULL,uk;创建后不可改 |
|
||||
| role_name | VARCHAR(64) | NOT NULL |
|
||||
| calc_template | VARCHAR(32) | NOT NULL,公式模板编码(以代码注册表为准) |
|
||||
| description | VARCHAR(255) | NULL |
|
||||
| sort_order | INT | NOT NULL DEFAULT 0 |
|
||||
| status | TINYINT | NOT NULL DEFAULT 1(1 启用 0 禁用) |
|
||||
| is_builtin | TINYINT | NOT NULL DEFAULT 0(1 内置禁删) |
|
||||
| create_time / update_time | DATETIME | TimestampMixin(update 用 `text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")`) |
|
||||
|
||||
**`param_definitions` 参数定义表**:`param_code` VARCHAR(64) uk;`param_name` VARCHAR(64);`param_group` VARCHAR(32);`value_type` VARCHAR(16)(DECIMAL/INT/BOOL/ENUM/TEXT);`unit` VARCHAR(16)(‰/%/元/天);`dimension_type` VARCHAR(32) DEFAULT 'NONE'(NONE/PRODUCT_CATEGORY/BUSINESS_ROLE);`options_text` VARCHAR(255)(枚举竖线分隔);`default_value`/`value_min`/`value_max` VARCHAR(64)(Fraction 可解析字符串);`description` VARCHAR(255);`sort_order` INT;`status` TINYINT;TimestampMixin。
|
||||
|
||||
**`param_change_batches` 参数变更批次表**:`version_no` VARCHAR(32) uk(`P{YYYYMMDD}-{当日序号}`);`effective_date` DATE NOT NULL idx;`change_type` TINYINT(1 立即 2 指定日期);`remark` VARCHAR(255);`operator_id` FK→users ON DELETE SET NULL + `operator_name` VARCHAR(64) 冗余固化;`item_count` INT DEFAULT 0;仅 `create_time`(append-only,不继承 TimestampMixin)。
|
||||
|
||||
**`param_values` 参数版本条目表(append-only)**:`param_id` FK→param_definitions RESTRICT idx;`dimension_value` VARCHAR(32) NOT NULL DEFAULT `''`(用空串不用 NULL);`effective_date` DATE NOT NULL idx;`value_text` VARCHAR(64) NOT NULL;`old_value_text` VARCHAR(64);`batch_id` FK→param_change_batches RESTRICT idx;仅 `create_time`。热路径索引 `idx_param_values_param_id_dimension_value_effective_date`。已生效条目禁改禁删;未生效批次可整批撤回。
|
||||
|
||||
**种子**(迁移 + init-dev 双写):3 个内置岗位(`salesperson 营销员`/TPL_SALESPERSON_DECAY、`sales_manager 营销经理`/TPL_MANAGER_SHARE、`info_officer 信息员`/TPL_INFO_SALES_FIXED)+ 16 项参数定义 + 版本条目 + 一条初始批次(`P20260101-00`)。
|
||||
|
||||
### 1.2 参数清单(16 项)
|
||||
|
||||
| param_code | 名称 | 类型/单位 | 维度 | 默认值 | 生效日期 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| salesperson.rate.start | 业务员起始计提比例 | DECIMAL ‰ | BUSINESS_ROLE | 10 | 2026-02-17 |
|
||||
| salesperson.rate.slope | 业务员日递减斜率 | DECIMAL ‰/天 | BUSINESS_ROLE | **4/360** | 2026-02-17 |
|
||||
| salesperson.rate.floor | 业务员计提下限 | DECIMAL ‰ | BUSINESS_ROLE | 4 | 2026-02-17 |
|
||||
| salesperson.days.boundary | 线性递减分界天数 | INT 天 | BUSINESS_ROLE | 360 | 2026-02-17 |
|
||||
| manager.share.team | 经理本团队分成 | DECIMAL % | BUSINESS_ROLE | 12.5 | 2026-08-13 |
|
||||
| manager.share.company | 经理全公司分成 | DECIMAL % | BUSINESS_ROLE | 5 | 2026-08-13 |
|
||||
| enterprise.rate | 企业户计提比例 | DECIMAL ‰ | PRODUCT_CATEGORY | **2(02-17 起)/ 3(08-13 起)** | 双行 |
|
||||
| info.rate | 信息员计提比例 | DECIMAL ‰ | BUSINESS_ROLE | 1 | 2026-08-13 |
|
||||
| prepay.rate | 预付款计提比例 | DECIMAL ‰ | NONE | 10 | 2026-01-01 |
|
||||
| barter.discount | 以物抵顶折算比例 | DECIMAL % | NONE | 80 | 2026-01-01 |
|
||||
| payment.start.ratio | 支付起付比例 | DECIMAL % | NONE | 60 | 2026-01-01 |
|
||||
| payment.weight.mode | 支付权重应用方式 | ENUM FULL\|BALANCE | NONE | FULL | 2026-01-01 |
|
||||
| product.threshold | 折算毛利润额阈值 | DECIMAL % | PRODUCT_CATEGORY | 市政水利 30 / 轨交 20 | 2026-02-17 |
|
||||
| salary.basic.mode | 月工资基本工资口径 | ENUM INCLUDE\|DEDUCT\|EXCLUDE | BUSINESS_ROLE | 业务员 INCLUDE / 经理 DEDUCT | 2026-01-01 |
|
||||
| probation.annual.threshold | 试用期年累计结算阈值 | DECIMAL 元 | NONE | 18000 | 2026-01-01 |
|
||||
| probation.salary.basic | 试用期基本工资标准 | DECIMAL 元 | NONE | 3000 | 2026-01-01 |
|
||||
|
||||
**不设参数**(作废条款落地):经理个人户 8‰ 起始/斜率;协助 25% 分成;含运费口径开关。
|
||||
|
||||
**值校验规则**(`validate_and_normalize`):DECIMAL 用 `Fraction(text)` 解析(拒绝 `1/0`、非数字),并按 `value_min`/`value_max` 校验;INT 必须整数;BOOL 仅 0/1;ENUM 必须在 `options_text` 内;规范化去首尾空白、分数式原样存储。
|
||||
|
||||
### 1.3 公式模板与消费契约
|
||||
|
||||
**模板注册表**(`app/services/calc_templates.py`,代码内有限集合,一期不引入规则引擎的落地方式):
|
||||
|
||||
| 模板编码 | 形态 | 声明参数 |
|
||||
| --- | --- | --- |
|
||||
| TPL_SALESPERSON_DECAY | 分段:≤分界 = 起始 − 天数×斜率;> 分界 = 下限 | salesperson.rate.* / days.boundary |
|
||||
| TPL_MANAGER_SHARE | 聚合:∑本团队×12.5% + ∑全公司×5% | manager.share.* |
|
||||
| TPL_ENTERPRISE_FIXED | 乘法:折算毛利润额 × 比例 | enterprise.rate |
|
||||
| TPL_INFO_SALES_FIXED | 乘法:本人销售额 × 比例 | info.rate |
|
||||
| TPL_PREPAY_FIXED | 乘法:回款额 × 比例 | prepay.rate |
|
||||
|
||||
修饰器(不绑角色,参数驱动):以物抵顶 ×80%、支付权重 R×R、起付 60%、试用期缓发。**绑定模型**:`business_roles.calc_template` → 模板;角色级覆盖经 `dimension_type=BUSINESS_ROLE` 条目;新增岗位 = 新增行 + 选模板(零代码)。
|
||||
|
||||
**消费接口契约**(M3 核算与预览共用):
|
||||
|
||||
```python
|
||||
# app/services/param_service.py
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ParamSnapshot:
|
||||
biz_date: date
|
||||
def get(self, code, *, role_code=None, product_category=None) -> str: ... # 覆盖链:角色 > 产品类别 > 默认
|
||||
def fraction(self, code, **dims) -> Fraction: ...
|
||||
def integer(self, code, **dims) -> int: ...
|
||||
def boolean(self, code, **dims) -> bool: ...
|
||||
def text(self, code, **dims) -> str: ...
|
||||
|
||||
def resolve_snapshot(session, biz_date: date) -> ParamSnapshot
|
||||
def submit_change(session, payload, *, operator, meta=None) -> ParamChangeBatchOut
|
||||
def compare(session, *, from_date: date, to_date: date) -> list[ParamDiffOut]
|
||||
|
||||
# app/services/formula.py(纯函数,禁 float)
|
||||
def payment_days(ship_date, receipt_date) -> int # 发货日首日=1
|
||||
def salesperson_rate(days, *, start, slope, floor, boundary_days) -> Fraction
|
||||
def profit_rate(profit: Decimal, revenue: Decimal) -> Fraction # 不含运费;revenue=0 防除零
|
||||
def converted_profit(revenue, rate, threshold) -> Decimal # 化简式:销售额 ×(毛利润率−阈值)
|
||||
def performance_amount(base, rate) -> Decimal # 乘法方向(对照表 #3)
|
||||
def apply_barter(amount, discount) -> Decimal
|
||||
def payment_weight(r: int) -> Fraction # r×r
|
||||
def start_payment_ok(received, revenue, ratio) -> bool
|
||||
```
|
||||
|
||||
### 1.4 API 设计(/api/v1/system/*,AdminUser)
|
||||
|
||||
**岗位角色**:`GET/POST /business-roles`、`GET/PUT/DELETE /business-roles/{id}`、`PUT /business-roles/{id}/status`、`GET /business-roles/meta/templates`(模板下拉,含声明参数说明)。
|
||||
|
||||
**计提参数**:`GET /params`(目录+当前快照,支持 `asOf`)、`GET /params/{param_code}/history`、`POST /params/changes`(提交变更批次)、`DELETE /params/changes/{batch_id}`(撤回未生效)、`GET /params/changes`(记录分页)、`GET /params/changes/{batch_id}`(明细)、`GET /params/compare`(两日期对比)、`GET /params/export`(Excel,`StreamingResponse` 非统一信封)、`POST /params/preview`(计算预览)。
|
||||
|
||||
**关键示例 1:提交变更**
|
||||
|
||||
```json
|
||||
// POST /api/v1/system/params/changes
|
||||
{
|
||||
"changeType": 2,
|
||||
"effectiveDate": "2026-11-01",
|
||||
"remark": "四季度系数调整",
|
||||
"items": [
|
||||
{ "paramCode": "enterprise.rate", "dimensionValue": "", "value": "3" },
|
||||
{ "paramCode": "product.threshold", "dimensionValue": "rail_transit", "value": "22" }
|
||||
]
|
||||
}
|
||||
// data:
|
||||
{ "batchId": 12, "versionNo": "P20261012-01", "effectiveDate": "2026-11-01",
|
||||
"changeType": 2, "itemCount": 2, "operatorName": "admin", "createTime": "..." }
|
||||
```
|
||||
|
||||
**关键示例 2:计算预览**(示意值已验算)
|
||||
|
||||
```json
|
||||
// POST /api/v1/system/params/preview
|
||||
{ "businessRole": "salesperson", "customerType": "personal",
|
||||
"productCategory": "municipal_water", "shipDate": "2025-12-15",
|
||||
"receiptDate": "2026-01-10", "revenue": "100000", "profit": "45000",
|
||||
"receivedAmount": "60000", "barter": false, "probation": false }
|
||||
// data:
|
||||
{ "paymentDays": 27, "rate": "97/10000", "rateText": "9.7‰(10‰ − 27 天 × 4/360‰)",
|
||||
"profitRate": "9/20", "convertedProfit": "15000", "performanceAmount": "145.5",
|
||||
"startPaymentOk": true, "paymentWeightR": 60, "paymentWeight": "9/25",
|
||||
"payableAmount": "52.38", "paramVersion": "P20260101-00", "paramEffectiveDate": "2026-02-17" }
|
||||
```
|
||||
|
||||
### 1.5 前端设计
|
||||
|
||||
**岗位角色页**(`views/system/business-roles/index.vue`):列表(复用 `usePagedList`/`AppPagination`/`StatusTag`)+ 新增/编辑弹窗(模板下拉,选中后展示模板声明的参数清单与当前值)+ 启停/删除(内置岗位按钮禁用并 title 提示原因)。路由 meta `{ title: '岗位角色', group: '系统管理', requiresAdmin: true, order: 5 }`。
|
||||
|
||||
**计提参数页**(`views/system/params/index.vue`):按 `param_group` 分组展示(名称/当前值+单位/生效日期/口径说明);编辑后进入「待提交」区,顶部选生效方式(立即/指定日期)+ 备注;「提交变更」二次确认(列出每项 旧值→新值);版本管理抽屉(变更记录分页 + 批次明细 + 两日期对比 + Excel 导出下载);预览抽屉(表单 → 结果卡,注明「斜率精确值 4/360」)。路由 meta order 6。导出接口走 axios 实例取 blob(非统一信封,api 文件内注明)。
|
||||
|
||||
---
|
||||
|
||||
## 二、任务分解(T1–T8)
|
||||
|
||||
任务依赖:T1 → T2 → T3 → (T4 ∥ T5) → (T6 ∥ T7) → T8。每任务遵循「写测试 → 确认失败 → 实现 → 确认通过 → commit」。
|
||||
|
||||
### T1 迁移与模型(四表 + 种子)
|
||||
|
||||
**文件**:`app/models/business_role.py`、`app/models/param.py`(创建);`app/models/__init__.py`(改);`alembic/versions/xxxx_*.py`(autogenerate+种子);`db/migration/V3__add_business_roles_and_params.sql`、`db/init/init-dev.sql`(改)、`db/queries/计提参数.sql`(创建)。
|
||||
|
||||
- [ ] 写两个模型文件(参照 `app/models/role.py` 写法;`ParamChangeBatch`/`ParamValue` 不继承 TimestampMixin)
|
||||
- [ ] 注册进 `models/__init__.py`(漏注册会让 autogenerate 生成删表迁移)
|
||||
- [ ] `uv run alembic revision --autogenerate -m "新增岗位角色与计提参数四表"` → 人工检查命名/注释/ON UPDATE → 补种子(3 岗位/16 定义/版本条目/初始批次)
|
||||
- [ ] `uv run alembic upgrade head` → `uv run alembic upgrade <V2_rev>:head --sql > db/migration/V3__*.sql`
|
||||
- [ ] 同步 `db/init/init-dev.sql`(建表 + 种子 + alembic_version 更新)
|
||||
- [ ] 写 `db/queries/计提参数.sql`(时点化解析与对比查询留档)
|
||||
- [ ] 验证:`uv run pytest tests/integration/test_schema_sync.py tests/unit/test_models.py -q` 全绿
|
||||
- [ ] Commit:`feat(system): 新增岗位角色与计提参数四表及 V3 迁移`
|
||||
|
||||
### T2 参数注册表 + 公式纯函数
|
||||
|
||||
**文件**:`app/services/param_registry.py`、`calc_templates.py`、`formula.py`(创建);`app/core/constants.py`(改);`tests/unit/test_formula.py`、`test_param_registry.py`。
|
||||
|
||||
- [ ] 写失败测试(关键断言):`salesperson_rate(360,...) == Fraction(6,1000)`;`361 天 == Fraction(4,1000)`;`第 1 天 == Fraction(10) - Fraction(1,90)`;`payment_days(2025-12-15, 2026-01-10) == 27`;同日回款 == 1;`converted_profit(0,...) == 0`(防除零);负绩效不归零;`payment_weight(60) == Fraction(36,100)`;`parse_fraction("4/360") == Fraction(1,90)`;注册表 16 项且模板声明参数均存在
|
||||
- [ ] 运行确认失败 → 实现三个文件 → 运行 `uv run pytest tests/unit -q` 全绿
|
||||
- [ ] Commit:`feat(system): 计提公式模板注册表与精确分数计算函数`
|
||||
|
||||
### T3 参数仓储 + 取值服务(时点化解析)
|
||||
|
||||
**文件**:`app/repositories/param_repository.py`、`app/services/param_service.py`(解析部分);`tests/unit/test_param_snapshot.py`。
|
||||
|
||||
- [ ] 写失败测试:覆盖链(角色 > 产品类别 > 默认);未定义参数按注册表默认值兜底;同日多次变更取 id 最大
|
||||
- [ ] 实现 `resolve_values(session, biz_date)`(一条 SQL 取全量快照)+ `ParamSnapshot`/`resolve_snapshot` → `uv run pytest tests/unit -q` 全绿
|
||||
- [ ] Commit:`feat(system): 计提参数按业务日期时点化取值服务`
|
||||
|
||||
### T4 岗位角色 CRUD 后端(含审计与保护规则)
|
||||
|
||||
**文件**:`app/repositories/business_role_repository.py`、`app/schemas/business_role.py`、`app/services/business_role_service.py`、`app/api/v1/system/business_roles.py`(创建);`app/api/router.py`、`app/core/constants.py`(改)。整体参照 `roles` 模块模式。
|
||||
|
||||
- [ ] 实现(保护规则:内置禁删禁停、code 不可改、模板必须存在于注册表、人员引用计数钩子预留;写操作包 `audit_service.audit()`)
|
||||
- [ ] 冒烟:新增自定义岗位 ✓、删除内置 → 403、非法模板 → 400
|
||||
- [ ] Commit:`feat(system): 业务岗位角色管理接口与保护规则`
|
||||
|
||||
### T5 参数变更/历史/对比/导出/预览后端
|
||||
|
||||
**文件**:`app/schemas/param.py`、`app/api/v1/system/params.py`(创建);`param_service.py` 扩展、`app/api/router.py`、`constants.py`(改);`pyproject.toml`(`uv add openpyxl`)。
|
||||
|
||||
- [ ] 实现 9 个端点(提交/撤回/历史/分页/明细/对比/导出/预览/目录快照);撤回已生效批次 → 400;导出列含变更人/时间/项/旧值/新值/生效日期
|
||||
- [ ] 冒烟链路:提交(指定未来日期)→ 当前快照不变 → 立即生效提交 → 快照变化 → 撤回未生效 → 回退 → 预览 360/361 → 导出可打开
|
||||
- [ ] Commit:`feat(system): 计提参数版本管理、变更留痕与计算预览接口`
|
||||
|
||||
### T6 前端岗位角色页
|
||||
|
||||
**文件**:`src/api/businessRoles.ts`、`src/views/system/business-roles/index.vue`(创建);`router/index.ts`、`utils/constants.ts`(改)。
|
||||
|
||||
- [ ] 实现页面与路由 → `pnpm type-check && pnpm build` 通过
|
||||
- [ ] Commit:`feat(system): 前端岗位角色管理页面`
|
||||
|
||||
### T7 前端参数配置页(版本管理 + 预览)
|
||||
|
||||
**文件**:`src/api/params.ts`、`src/views/system/params/index.vue`(创建);`router/index.ts`、`utils/constants.ts`(改)。
|
||||
|
||||
- [ ] 实现页面(分组表/生效方式/二次确认/版本抽屉/预览抽屉)→ `pnpm type-check` 通过
|
||||
- [ ] e2e 链路:改参数 → 变更记录出现 → 预览值变化 → 导出可下载
|
||||
- [ ] Commit:`feat(system): 前端计提参数配置与版本管理页面`
|
||||
|
||||
### T8 模块测试统一编写与文档
|
||||
|
||||
**文件**:`tests/unit/test_param_service.py`、`tests/integration/test_business_roles.py`、`test_param_management.py`(创建);`backend/tests/README.md`、`frontend/README.md`、`backend/README.md`、`frontend/e2e/*`(改)。
|
||||
|
||||
- [ ] 集成用例:参数精度全链路(注册表→种子→解析→预览一致);未来生效不泄漏;撤回回退;同日多次变更;审计留痕;权限 403;岗位保护规则
|
||||
- [ ] 全量回归 `uv run pytest -q`(301 基线 + 新增全绿);e2e;三份 README 更新
|
||||
- [ ] Commit:`test(system): 参数体系模块用例与测试记录`
|
||||
|
||||
---
|
||||
|
||||
## 三、执行安排
|
||||
|
||||
- **分支**:`feat/system-params`(已创建);`feature/system-management` 是否先合并 main 由用户决定,不阻塞
|
||||
- **环境**:启动 `D:\work\app\Docker\Docker Desktop.exe` → `docker compose -f docker/docker-compose.dev.yml up -d`;T2 纯函数开发不依赖容器
|
||||
- **方式**:原生执行(当前会话逐任务推进);每任务后跑验证命令;T8 全量回归;收尾一轮 code review
|
||||
- **注意**:Windows uvicorn `--reload` 偶发卡死——验证 reload 以「新 worker 的 startup complete」为准;开放问题 7 项(见下)按默认处理,不阻塞
|
||||
|
||||
## 四、验收标准(M1 完成的定义)
|
||||
|
||||
1. 管理员可新增岗位并绑定公式模板(无需改代码);内置岗位禁删禁停
|
||||
2. 任一参数变更产生含变更人/时间/项/旧值/新值/生效日期的记录(批次 + 操作日志双保险)
|
||||
3. 任意业务日期可解析出当时快照(08-12 企业户=2‰、08-13=3‰ 回溯正确)
|
||||
4. 计算预览精确复现:360 天=6‰、361 天=4‰、12-15→01-10=27 天
|
||||
5. `uv run pytest -q` 301 基线 + 新增全绿;`pnpm type-check && pnpm build` 通过;e2e 链路通过
|
||||
6. `db/migration/V3__*.sql` 与 `db/init/init-dev.sql` 双轨同步(`test_schema_sync.py` 把关)
|
||||
|
||||
## 五、风险与开放问题
|
||||
|
||||
**风险(已定对策)**:参数缓存一期不做(保留 `ys:param.ver:*` 键位,未来按批次版本号精确失效);同日多次变更 UI 明示「按最新一条生效」;`4/360` 展示注明精确值;模板扩展摩擦一期接受(受控下拉);预览出参与需规 2.4.1 明细列同名(减少 M3 映射);openpyxl 在 T5 增补依赖。
|
||||
|
||||
**开放问题(不阻塞,按默认处理)**:① 第 1 天 10‰ vs 9.9889‰ → 按公式实现并参数化;② 企业户 2‰ 历史种子 → 已双种子;③ BALANCE 模式语义 → M3 前确认;④ `probation.salary.basic` 归属 → M2 时复核;⑤ 参数查询权限 → 暂 AdminUser;⑥ 导出列集合 → 以需规 2.3.1(四) 为准;⑦ 经理个人户业绩进入 12.5%+5% 的衔接 → **M3 开工前必须确认**。
|
||||
|
||||
## 六、验证方式
|
||||
|
||||
1. 按 T1–T8 的验证节点逐任务执行(见第二章各任务);T8 后全量回归 + e2e;
|
||||
2. 验收标准 6 条逐条核对(见第四章);
|
||||
3. 与 `ai-aux/AI_CONTEXT.md` 需求裁决节交叉核对口径无冲突。
|
||||
@@ -0,0 +1,607 @@
|
||||
# 剩余开发计划(路线图 + 首工作块实施计划)
|
||||
|
||||
> **面向 AI 代理的工作者:** 首工作块(M1)实施时使用 subagent-driven-development(推荐)或 executing-plans 逐任务实现本计划第二章;步骤使用复选框(`- [ ]`)语法跟踪进度。第一章路线图供全局导航。
|
||||
|
||||
**目标:** 在需求裁决全部落定(2026-10-12)后,为剩余 5 大模块与 T+ 同步给出推进路线;并交付首个工作块「系统设置与参数体系(#28)」的可执行实施计划(任务级,含测试与验证)。
|
||||
|
||||
**架构:** 沿用现有六层(api → services → repositories → models,schemas 承载请求/响应,core 横切);新增「公式模板注册表(代码内有限集合)+ 参数按生效日期时点化存储 + 计算预览」作为核算模块的输入契约,不引入规则引擎。
|
||||
|
||||
**技术栈:** 后端 Python 3.12 + FastAPI + SQLAlchemy 2.0(同步)+ MySQL 8.0 + Redis 7;前端 Vue 3.5 + TS 5.9 + Vite 8 + Element Plus 2.14 + Pinia 4。
|
||||
|
||||
**规格:** `ai-aux/AI_CONTEXT.md`「需求裁决」节(权威口径);`docs/问题与解决方案对照表_v2.xlsx`(28 项裁决);`docs/2026.9.8修改方案.docx`(公式与生效日期)。需规(`docs/衍水绩效管理系统功能需规.docx`)中 4 类作废条款不再引用:26 天示例、含运费公式、协助 25%、经理个人户 8‰。
|
||||
|
||||
## 全局约束
|
||||
|
||||
- 表:小写蛇形复数、列小写蛇形、每列中文 COMMENT、主键 `id`、`uk_`/`idx_`/`fk_` 前缀;金额类 DECIMAL;时间列用 `TimestampMixin`(`ON UPDATE CURRENT_TIMESTAMP` 须 `text()` 显式写);append-only 表不继承 TimestampMixin(与 `login_logs` 先例一致)
|
||||
- 迁移双轨:Alembic 生成迁移 → 导出 SQL 到 `db/migration/V{n}__*.sql`(按序编号)→ 同步 `db/init/init-dev.sql`;`tests/integration/test_schema_sync.py` 拦截漂移
|
||||
- 六层红线:api 只参数绑定(写 `def` 非 `async def`)→ services 唯一事务边界 → repositories 只 `flush()`;统一响应 `{code,message,data}`,分页 `pageNum/pageSize` → `list/total/pageNum/pageSize`
|
||||
- 写操作接入 `audit_service.audit()`;审计脱敏在格式化后执行(顺序不可倒)
|
||||
- 金额全程 `Decimal`/`Fraction`,**禁 float**;`4/360` 按精确分数存储(`"4/360"`,禁 `0.011111`)
|
||||
- Redis 键前缀 `ys:`,TTL 按业务语义;禁 `KEYS *` / `FLUSHALL` / `FLUSHDB`
|
||||
- 前端:TS 锁 `~5.9.3`(不可 7.x);复用 `usePagedList`/`useConfirmAction`/`AppPagination`/`StatusTag`;视觉沿用 `--ys-*` token;新页面必须写 `meta.title`
|
||||
- 测试:业务模块整体功能完成时统一编写用例;记录写入 `backend/tests/README.md` / `frontend/README.md`
|
||||
- 提交:`feat(scope): 中文说明`;不直推 main/develop
|
||||
|
||||
## 审查重点(Review Focus)
|
||||
|
||||
以下输入/失败模式最可能伤到使用者,对应测试已钉进第二章各任务:
|
||||
|
||||
1. 畸形参数值(`"1/0"` 除零分数、非数字、超取值域)→ 保存必须 400 拒绝且不落库(T2/T5)
|
||||
2. 指定未来生效的变更泄漏进当前快照 → 快照解析必须不含(T3/T5)
|
||||
3. 撤回未生效批次后解析未回退 → 必须回退到撤回前值(T5)
|
||||
4. 回款天数边界:同日回款 = 1 天;12-15 → 01-10 = 27 天;跨年连续(T2)
|
||||
5. 计提比例边界:360 天 = 6‰、361 天 = 4‰、第 1 天 = 10 − 4/360 ≈ 9.9889‰(精确分数断言)(T2)
|
||||
6. 毛利润额 = 0 → 折算额 0、绩效 0 不除零;负绩效按公式正常计算不归零(T2)
|
||||
|
||||
---
|
||||
|
||||
## 第一章 模块级路线图
|
||||
|
||||
### 1.1 推荐顺序与依赖
|
||||
|
||||
```text
|
||||
① 系统设置与参数体系(#28) ← 地基:岗位角色 + 参数 + 公式模板
|
||||
② 基础数据管理 ← 人员引用 ① 岗位角色(外键上游)
|
||||
③ 业务数据管理 ← 引用 ② 人员/产品/客户
|
||||
④ 绩效工资核算 ← 消费 ① 参数快照(按回款到账日)+ ②③ 数据
|
||||
⑤ 报表统计(∥ ⑥ T+ 同步) ← ⑤ 只读消费 ③④;⑥ 写入 ②③ 目标表,受 IT 字段映射制约
|
||||
```
|
||||
|
||||
### 1.2 顺序论证(要点)
|
||||
|
||||
**① 先于基础数据:** 外键方向在上游(人员→岗位,先建避免迁移返工);参数取值契约是 ④ 的输入面,先定型让后续模块按契约编码;计算预览(需规 2.3.1 四)首块即可交给业务对账「360 天=6‰、361 天=4‰」,验收前移;参数清单提炼本身 = 28 项裁决逐条复核。阈值参数归 ① 有明文依据(#28「全部阈值统一在系统设置维护」)。
|
||||
|
||||
**⑥ 放最后:** 字段映射未定(对照表 #26 ⏳IT),放前部会把外部不确定性引入关键路径;目标表由 ②③ 定稿后同步才有意义;一期可手工录入闭环。**但预留前移**:②③ 表设计必须含外部唯一键列(`source_no`/`t_plus_id`)、同步批次时间列,并约定「同步不覆盖人工字段(指导价、归属、岗位)」。
|
||||
|
||||
**⑤ 可后置:** 纯消费端无下游,口径依赖真实核算数据校准;唯一约束是 ④ 设计台账时对照需规 2.4.1 明细列(合同编号/发货日/回款日/回款天数/产品类别/客户类型/人员岗位/计提比例/支付权重R/绩效金额)一次性建全。
|
||||
|
||||
### 1.3 各块范围与验收标准
|
||||
|
||||
| 块 | 范围界定 | 验收标准(一句话) |
|
||||
| --- | --- | --- |
|
||||
| ① 系统设置与参数体系 | 岗位角色 CRUD + 模板绑定;参数目录/版本化存储/按日期取值/变更留痕/对比/导出;公式纯函数;计算预览 | 管理员可新增岗位并绑定模板;任一参数变更产生含变更人/时间/项/旧值/新值/生效日期的记录;任意业务日期可解析当时快照;预览精确复现 360 天=6‰、361 天=4‰;301 基线不回归 |
|
||||
| ② 基础数据管理 | 人员(岗位/工资标准/团队/辐射区域/拓展计划)、产品类型与目录、业务(客户)与归属变更 | 三类数据可维护并留痕;人员岗位引用 ① 岗位表;表含 T+ 预留列;客户分类(个人户/经理个人户/企业户)与归属修改不回溯历史 |
|
||||
| ③ 业务数据管理 | 业务明细+指导价、发货(批次/运费/发货日)、回款(批次号/到账日/以物抵顶/手动指定发货批次)、毛利润(不含运费)、客户评级计算器 | 一笔业务可多产品多批次发货、多笔回款;回款天数按「发货日首日=1」自动算;折算走精确分数;评级只出等级不影响金额 |
|
||||
| ④ 绩效工资核算 | 核算引擎(按回款到账日取参数)、单笔台账+核销防重、经理 12.5%+5%、试用期/年累计 18000、起付 60% 与 R×R、预付款/以物抵顶、月工资(分岗口径)、重算授权留痕 | 任一笔回款可复算到分(零误差);同笔回款不重复核算;08-13 前后企业户 2‰/3‰ 回溯正确;月工资表可生成 |
|
||||
| ⑤ 报表统计 | 个人绩效明细、部门汇总、业务统计、回款区间统计、Excel 导出与打印 | 四表口径与台账一致且可下钻单笔;导出精度与页面一致 |
|
||||
| ⑥ T+ 数据同步 | 五类数据抓取/清洗/入库、唯一键去重、失败重试、同步日志、周定时+手动触发 | 手动同步幂等(重跑不重复入库);失败有日志与重试;不覆盖人工字段 |
|
||||
|
||||
### 1.4 里程碑(交付批次)
|
||||
|
||||
| 批次 | 内容 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| **M1** | ① 系统设置与参数体系 | 独立可交付;预览工具可作业务对账入口先行验收 |
|
||||
| **M2** | ② + ③ | 合并交付:一次表结构评审把 T+ 预留列与报表字段清单同时定稿 |
|
||||
| **M3** | ④ 绩效工资核算 | 核心钱袋子;测试重点在精度与参数回溯 |
|
||||
| **M4** | ⑤ 报表 + ⑥ T+ 同步(并行) | ⑥ 受 IT 制约,谁先就绪谁先交 |
|
||||
|
||||
### 1.5 需求依据对照
|
||||
|
||||
| 块 | 依据 | 关联作废条款(不引用) |
|
||||
| --- | --- | --- |
|
||||
| ① | 需规 2.3.1(四)/2.5.2/2.1.2;对照表 #28/#5/#8/#20–#24 | 经理个人户 8‰(不设参数);含运费口径;协助 25%(不设参数) |
|
||||
| ② | 需规 2.1.1–2.1.3;对照表 #9/#19/#26 | 信息部负责人条款(信息员独立角色) |
|
||||
| ③ | 需规 2.2.1–2.2.5;对照表 #1/#2/#12/#13/#14/#18/#25 | 26 天示例;含运费公式(2.2.4/2.3.3 整段) |
|
||||
| ④ | 需规 2.3.1–2.3.3;对照表 #3/#4/#6/#7/#11/#15/#16/#17/#20–#24 | 协助 25%;经理 8‰;26 天示例;含运费公式 |
|
||||
| ⑤ | 需规 2.4.1–2.4.3;对照表 #22(明细展示 R) | — |
|
||||
| ⑥ | 需规各「同步 T+」条;对照表 #10/#26/#27 | — |
|
||||
|
||||
---
|
||||
|
||||
## 第二章 首工作块「系统设置与参数体系(#28)」实施计划
|
||||
|
||||
### 2.1 关键设计取舍(已定)
|
||||
|
||||
1. **参数存储 = 参数定义 + 逐项版本条目(时点化 append-only)+ 变更批次表承载「版本」**;否决全量快照(改一项复制全量、单参数历史需遍历)与单行 JSON(无回溯、无字段校验);
|
||||
2. 数值一律以 **Fraction 可解析字符串**存储(`"4/360"`,禁 `0.011111`),解析走 `Fraction(text)` 不经 float;
|
||||
3. **不设日期唯一键**:同日多次修正是合法留痕,解析按 `effective_date DESC, id DESC` 取最新;
|
||||
4. **一期不做参数 Redis 缓存**(算钱系统缓存失效即错钱;20 行级查询成本可忽略),保留 `ys:param:ver:*` 键位待实测后启用;
|
||||
5. 维度覆盖链:**角色维度 > 产品类别维度 > 默认行**;`dimension_value` 用空串不用 NULL(MySQL 组合索引对 NULL 不去重);
|
||||
6. append-only 表(`param_values`/`param_change_batches`)**不继承 TimestampMixin**(无 update_time,与 `login_logs` 先例一致)。
|
||||
|
||||
### 2.2 表设计(V3 迁移,四张新表)
|
||||
|
||||
**`business_roles` 业务岗位角色表**(与系统登录角色 `roles` 严格区分,表注释写明;这是 #28「人员岗位角色」的落点)
|
||||
|
||||
| 列 | 类型 | 约束/说明 |
|
||||
| --- | --- | --- |
|
||||
| id | BIGINT UNSIGNED | PK AUTO_INCREMENT |
|
||||
| role_code | VARCHAR(32) | NOT NULL,uk;创建后不可改(同 roles 保护) |
|
||||
| role_name | VARCHAR(64) | NOT NULL |
|
||||
| calc_template | VARCHAR(32) | NOT NULL,计提公式模板编码(以代码注册表为准) |
|
||||
| description | VARCHAR(255) | NULL |
|
||||
| sort_order | INT | NOT NULL DEFAULT 0 |
|
||||
| status | TINYINT | NOT NULL DEFAULT 1(1 启用 0 禁用) |
|
||||
| is_builtin | TINYINT | NOT NULL DEFAULT 0(1 内置禁删) |
|
||||
| create_time / update_time | DATETIME | TimestampMixin |
|
||||
|
||||
种子(迁移 + init-dev 双写):`salesperson 营销员`(TPL_SALESPERSON_DECAY)、`sales_manager 营销经理`(TPL_MANAGER_SHARE)、`info_officer 信息员`(TPL_INFO_SALES_FIXED),均内置。
|
||||
|
||||
**`param_definitions` 计提参数定义表**:`param_code` VARCHAR(64) uk;`param_name` VARCHAR(64);`param_group` VARCHAR(32);`value_type` VARCHAR(16)(DECIMAL/INT/BOOL/ENUM/TEXT);`unit` VARCHAR(16)(‰/%/元/天,可 NULL);`dimension_type` VARCHAR(32) NOT NULL DEFAULT 'NONE'(NONE/PRODUCT_CATEGORY/BUSINESS_ROLE);`options_text` VARCHAR(255)(枚举竖线分隔);`default_value` / `value_min` / `value_max` VARCHAR(64)(Fraction 可解析字符串);`description` VARCHAR(255);`sort_order` INT;`status` TINYINT;TimestampMixin。
|
||||
|
||||
**`param_change_batches` 参数变更批次表**:`version_no` VARCHAR(32) uk(如 `P20261012-01`);`effective_date` DATE NOT NULL idx;`change_type` TINYINT(1 立即 2 指定日期);`remark` VARCHAR(255);`operator_id` BIGINT UNSIGNED FK→users ON DELETE SET NULL + `operator_name` VARCHAR(64) 冗余固化;`item_count` INT NOT NULL DEFAULT 0;仅 `create_time`。
|
||||
|
||||
**`param_values` 参数版本条目表(append-only)**:`param_id` FK→param_definitions RESTRICT idx;`dimension_value` VARCHAR(32) NOT NULL DEFAULT `''`;`effective_date` DATE NOT NULL idx;`value_text` VARCHAR(64) NOT NULL;`old_value_text` VARCHAR(64) NULL;`batch_id` FK→param_change_batches RESTRICT idx;仅 `create_time`。索引 `idx_param_values_param_id_dimension_value_effective_date`(解析热路径)。已生效条目禁改禁删;未生效批次可整批撤回(删批次+条目)。解析 SQL 留存 `db/queries/计提参数.sql`。
|
||||
|
||||
### 2.3 参数清单(16 项,迁移种子)
|
||||
|
||||
| param_code | 名称 | 类型/单位 | 维度 | 默认值 | 依据 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| salesperson.rate.start | 业务员起始计提比例 | DECIMAL ‰ | BUSINESS_ROLE | 10 | 需规 2.3.1(一)1 |
|
||||
| salesperson.rate.slope | 业务员日递减斜率 | DECIMAL ‰/天 | BUSINESS_ROLE | **4/360** | 对照表 #4 |
|
||||
| salesperson.rate.floor | 业务员计提下限 | DECIMAL ‰ | BUSINESS_ROLE | 4 | 对照表 #4 |
|
||||
| salesperson.days.boundary | 线性递减分界天数 | INT 天 | BUSINESS_ROLE | 360 | 对照表 #4 |
|
||||
| manager.share.team | 经理本团队分成 | DECIMAL % | BUSINESS_ROLE | 12.5 | #6/#15/#16 |
|
||||
| manager.share.company | 经理全公司分成 | DECIMAL % | BUSINESS_ROLE | 5 | 同上 |
|
||||
| enterprise.rate | 企业户计提比例 | DECIMAL ‰ | PRODUCT_CATEGORY | 3 | #20 |
|
||||
| info.rate | 信息员计提比例 | DECIMAL ‰ | BUSINESS_ROLE | 1 | #9 |
|
||||
| prepay.rate | 预付款计提比例 | DECIMAL ‰ | NONE | 10 | #23 |
|
||||
| barter.discount | 以物抵顶折算比例 | DECIMAL % | NONE | 80 | #24 |
|
||||
| payment.start.ratio | 支付起付比例 | DECIMAL % | NONE | 60 | #21 |
|
||||
| payment.weight.mode | 支付权重应用方式 | ENUM FULL\|BALANCE | NONE | FULL | #22 |
|
||||
| product.threshold | 折算毛利润额阈值 | DECIMAL % | PRODUCT_CATEGORY | 市政水利 30 / 轨交 20 | #12、需规 2.1.2 |
|
||||
| salary.basic.mode | 月工资基本工资口径 | ENUM INCLUDE\|DEDUCT\|EXCLUDE | BUSINESS_ROLE | 业务员 INCLUDE / 经理 DEDUCT | #8 |
|
||||
| probation.annual.threshold | 试用期年累计结算阈值 | DECIMAL 元 | NONE | 18000 | #17 |
|
||||
| probation.salary.basic | 试用期基本工资标准 | DECIMAL 元 | NONE | 3000 | 需规(归人员字段则撤,见开放问题) |
|
||||
|
||||
**不设参数**(作废条款的落地):经理个人户 8‰ 起始/斜率;协助 25% 分成;含运费口径开关。
|
||||
|
||||
**种子生效日期**(迁移与 init-dev 双写):`product.threshold`/`salesperson.*` = 2026-02-17;`enterprise.rate` = **2026-02-17 起 2‰ + 2026-08-13 起 3‰ 两行**;`manager.share.*`/`info.rate` = 2026-08-13;支付环节/预付款/以物抵顶/月工资口径 = 2026-01-01。
|
||||
|
||||
### 2.4 公式模板与角色绑定
|
||||
|
||||
**模板注册表**(`app/services/calc_templates.py`,代码内有限集合):
|
||||
|
||||
| 模板编码 | 名称 | 形态 | 声明参数 |
|
||||
| --- | --- | --- | --- |
|
||||
| TPL_SALESPERSON_DECAY | 业务员个人户·天数线性递减 | 分段:≤分界 = 起始 − 天数×斜率;> 分界 = 下限 | salesperson.rate.* / days.boundary |
|
||||
| TPL_MANAGER_SHARE | 经理·团队与全公司分成 | 聚合:∑本团队×12.5% + ∑全公司×5% | manager.share.* |
|
||||
| TPL_ENTERPRISE_FIXED | 企业户·固定计提 | 乘法:折算毛利润额 × 比例 | enterprise.rate |
|
||||
| TPL_INFO_SALES_FIXED | 信息员·销售额固定比例 | 乘法:本人销售额 × 比例 | info.rate |
|
||||
| TPL_PREPAY_FIXED | 预付款·固定计提 | 乘法:回款额 × 比例 | prepay.rate |
|
||||
|
||||
修饰器(不绑角色,参数驱动):以物抵顶 ×80%、支付权重 R×R、起付 60%、试用期缓发。
|
||||
|
||||
**绑定模型**:`business_roles.calc_template` → 模板;角色级覆盖经 `dimension_type=BUSINESS_ROLE` 条目。新增岗位 = 新增行 + 选模板(零代码,满足 #28 ①)。
|
||||
|
||||
**消费接口契约**(④ 与预览共用,函数签名级):
|
||||
|
||||
```python
|
||||
# app/services/param_service.py
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ParamSnapshot:
|
||||
biz_date: date
|
||||
def get(self, code, *, role_code=None, product_category=None) -> str: ... # 覆盖链解析后原始值
|
||||
def fraction(self, code, **dims) -> Fraction: ...
|
||||
def integer(self, code, **dims) -> int: ...
|
||||
def boolean(self, code, **dims) -> bool: ...
|
||||
def text(self, code, **dims) -> str: ...
|
||||
|
||||
def resolve_snapshot(session, biz_date: date) -> ParamSnapshot
|
||||
def submit_change(session, payload, *, operator, meta=None) -> ParamChangeBatchOut
|
||||
def compare(session, *, from_date: date, to_date: date) -> list[ParamDiffOut]
|
||||
|
||||
# app/services/formula.py(纯函数,预览与 ④ 共用;禁 float)
|
||||
def payment_days(ship_date, receipt_date) -> int # 发货日首日=1
|
||||
def salesperson_rate(days, *, start, slope, floor, boundary_days) -> Fraction
|
||||
def profit_rate(profit: Decimal, revenue: Decimal) -> Fraction # 不含运费;revenue=0 防除零
|
||||
def converted_profit(revenue, rate, threshold) -> Decimal # 化简式:销售额 ×(毛利润率−阈值)
|
||||
def performance_amount(base, rate) -> Decimal # 乘法方向(对照表 #3)
|
||||
def apply_barter(amount, discount) -> Decimal
|
||||
def payment_weight(r: int) -> Fraction # r×r
|
||||
def start_payment_ok(received, revenue, ratio) -> bool
|
||||
```
|
||||
|
||||
### 2.5 文件清单
|
||||
|
||||
#### 后端
|
||||
|
||||
| 层 | 文件 | 内容 |
|
||||
| --- | --- | --- |
|
||||
| models | `app/models/business_role.py`、`app/models/param.py`、`app/models/__init__.py`(改) | 四张表;**必须注册进 `__init__`**(漏注册会让 autogenerate 生成删表迁移) |
|
||||
| repositories | `app/repositories/business_role_repository.py`、`param_repository.py` | 只 flush;`resolve_values(session, biz_date)` 一条 SQL 取全量快照;批次撤回用 `for_update` 锁行 |
|
||||
| schemas | `app/schemas/business_role.py`、`param.py` | Out/CreateIn/UpdateIn/StatusIn/Query、TemplateOptionOut;ParamDefinitionOut/SnapshotOut/ChangeIn/Out/BatchOut/BatchDetailOut/DiffOut/PreviewIn/Out |
|
||||
| services | `app/services/param_registry.py`、`calc_templates.py`、`formula.py`、`business_role_service.py`、`param_service.py` | 参数目录注册表(种子唯一来源 + 运行期校验);模板注册表;纯函数;岗位 CRUD+保护规则;参数提交/历史/对比/预览/导出 |
|
||||
| api | `app/api/v1/system/business_roles.py`、`params.py`、`app/api/router.py`(改) | 挂载 `/api/v1/system/business-roles`、`/api/v1/system/params`(AdminUser) |
|
||||
| core | `app/core/constants.py`(改) | `MODULE_BUSINESS_ROLE`/`MODULE_PARAM`、内置岗位编码、产品类别码 |
|
||||
| 迁移 | `alembic/versions/xxxx_*.py`、`db/migration/V3__add_business_roles_and_params.sql`、`db/init/init-dev.sql`(改)、`db/queries/计提参数.sql` | 四表 + 种子;双轨同步 |
|
||||
| 依赖 | `backend/pyproject.toml`(改) | 增补 `openpyxl`(参数导出用;`uv add openpyxl`) |
|
||||
|
||||
**接口集合**:business-roles 列表/新增/详情/编辑/删除/启停 + `meta/templates` 模板下拉;params 目录快照(`asOf`)、单参数历史、变更批次提交/撤回/分页/明细、双日期对比、Excel 导出(`StreamingResponse`,非统一信封)、计算预览。写操作全走 `audit_service.audit()`;变更批次表与操作日志互为双保险。
|
||||
|
||||
#### 前端
|
||||
|
||||
| 类型 | 文件 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| api | `src/api/businessRoles.ts`、`src/api/params.ts` | params 的导出接口直接用 axios 实例(响应为 blob,非统一信封,文件内注明例外) |
|
||||
| 页面 | `src/views/system/business-roles/index.vue` | 列表+表单+启停+删除(保护提示)+模板选择(展示模板声明参数) |
|
||||
| 页面 | `src/views/system/params/index.vue` | 按 param_group 分组;生效方式(立即/指定日期)+备注;保存二次确认;版本管理抽屉(记录/明细/对比/导出);预览抽屉 |
|
||||
| 路由 | `src/router/index.ts`(改) | `{ title: '岗位角色', group: '系统管理', requiresAdmin: true, order: 5 }`、`{ title: '计提参数', …, order: 6 }` |
|
||||
| 常量 | `src/utils/constants.ts`(改) | MODULE_OPTIONS 增补;参数分组/单位展示映射 |
|
||||
| 复用 | `usePagedList`/`useConfirmAction`/`AppPagination`/`StatusTag`/`--ys-*` | 不新增组件 |
|
||||
|
||||
### 2.6 任务序列(步骤级)
|
||||
|
||||
任务依赖:T1 → T2 → T3 → (T4 ∥ T5) → (T6 ∥ T7) → T8。
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T1:迁移与模型(四张新表 + 种子)
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`backend/app/models/business_role.py`、`backend/app/models/param.py`
|
||||
- 修改:`backend/app/models/__init__.py`
|
||||
- 创建:`backend/alembic/versions/xxxx_add_business_roles_and_params.py`(autogenerate 后人工补种子)
|
||||
- 创建:`db/migration/V3__add_business_roles_and_params.sql`
|
||||
- 修改:`db/init/init-dev.sql`
|
||||
- 创建:`db/queries/计提参数.sql`
|
||||
|
||||
- [ ] **步骤 1:写两个模型文件**
|
||||
|
||||
按 2.2 字段设计实现 `BusinessRole`、`ParamDefinition`、`ParamChangeBatch`、`ParamValue`。写法参照 `backend/app/models/role.py`(`Mapped`/`mapped_column`、`mysql_table_args("中文表注释")`、`BIGINT_UNSIGNED`、`TINYINT_TYPE`);`ParamChangeBatch`/`ParamValue` 不继承 `TimestampMixin`,仅手写 `create_time`(`server_default=text("CURRENT_TIMESTAMP")`);外键目标类型必须与 `users.id` 一致(`BIGINT_UNSIGNED`)。
|
||||
|
||||
- [ ] **步骤 2:注册进 `models/__init__.py`**
|
||||
|
||||
四个类加入导入与 `__all__`(漏注册会让 autogenerate 把新表当成待删除)。
|
||||
|
||||
- [ ] **步骤 3:生成迁移并补种子**
|
||||
|
||||
```bash
|
||||
cd backend && uv run alembic revision --autogenerate -m "新增岗位角色与计提参数四表"
|
||||
```
|
||||
|
||||
人工检查:表/列中文 COMMENT、`uk_`/`idx_`/`fk_` 命名、`ON UPDATE CURRENT_TIMESTAMP` 子句;然后按 2.2/2.3 用 `op.bulk_insert` 或 `op.execute` 补种子:3 个内置岗位、16 项参数定义、版本条目(`enterprise.rate` 两行:2026-02-17=2、2026-08-13=3;其余按 2.3 生效日期表;维度行展开——`product.threshold` 两行市政/轨交、`salary.basic.mode` 两行业务员/经理)。种子条目的 `batch_id` 需要一条「初始版本」批次(`version_no` 如 `P20260101-00`、`remark` 初始版本)。
|
||||
|
||||
- [ ] **步骤 4:应用迁移并导出 SQL**
|
||||
|
||||
```bash
|
||||
cd backend && uv run alembic upgrade head
|
||||
uv run alembic upgrade <V2_revision>:head --sql > ../db/migration/V3__add_business_roles_and_params.sql
|
||||
```
|
||||
|
||||
(V2 revision 从 `alembic/versions/` 中操作日志迁移文件读取。)
|
||||
|
||||
- [ ] **步骤 5:同步 `db/init/init-dev.sql`**
|
||||
|
||||
追加四表 DDL(与迁移产出一致)+ 种子 INSERT + 更新末尾 `alembic_version` 为 V3 revision id。
|
||||
|
||||
- [ ] **步骤 6:写 `db/queries/计提参数.sql`**
|
||||
|
||||
留存解析查询(时点化取值:`WHERE effective_date <= :biz_date ORDER BY effective_date DESC, id DESC` 取每组最新)与对比查询。
|
||||
|
||||
- [ ] **步骤 7:验证**
|
||||
|
||||
```bash
|
||||
cd backend && uv run pytest tests/integration/test_schema_sync.py tests/unit/test_models.py -q
|
||||
```
|
||||
|
||||
预期:全部 PASS(schema 双轨无漂移、新表规范断言通过)。
|
||||
|
||||
- [ ] **步骤 8:Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/models backend/alembic db/init/init-dev.sql db/migration db/queries
|
||||
git commit -m "feat(system): 新增岗位角色与计提参数四表及 V3 迁移"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T2:参数注册表 + 公式纯函数
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`backend/app/services/param_registry.py`、`backend/app/services/calc_templates.py`、`backend/app/services/formula.py`
|
||||
- 修改:`backend/app/core/constants.py`(内置岗位编码、产品类别码常量)
|
||||
- 测试:`backend/tests/unit/test_formula.py`、`backend/tests/unit/test_param_registry.py`
|
||||
|
||||
- [ ] **步骤 1:编写失败的测试(公式,算钱核心)**
|
||||
|
||||
代表性用例(全部精确分数断言,禁近似):
|
||||
|
||||
```python
|
||||
def test_业务员360天_比例恰为6‰():
|
||||
rate = salesperson_rate(360, start=Fraction(10), slope=Fraction(4, 360),
|
||||
floor=Fraction(4), boundary_days=360)
|
||||
assert rate == Fraction(6, 1000)
|
||||
|
||||
def test_业务员361天_固定下限4‰_分界线():
|
||||
rate = salesperson_rate(361, start=Fraction(10), slope=Fraction(4, 360),
|
||||
floor=Fraction(4), boundary_days=360)
|
||||
assert rate == Fraction(4, 1000)
|
||||
|
||||
def test_业务员第1天_按公式为899比90000_精确分数():
|
||||
rate = salesperson_rate(1, start=Fraction(10), slope=Fraction(4, 360),
|
||||
floor=Fraction(4), boundary_days=360)
|
||||
assert rate == Fraction(10) - Fraction(1, 90) # 899/90 ‰
|
||||
|
||||
def test_回款天数_发货日首日为第1天_12月15日到1月10日为27天():
|
||||
assert payment_days(date(2025, 12, 15), date(2026, 1, 10)) == 27
|
||||
|
||||
def test_回款天数_同日回款为1天():
|
||||
d = date(2026, 1, 1)
|
||||
assert payment_days(d, d) == 1
|
||||
|
||||
def test_毛利润额为零_折算额与绩效均为零_不除零():
|
||||
assert converted_profit(Decimal("0"), Fraction(0), Fraction(3, 10)) == Decimal("0")
|
||||
|
||||
def test_毛利润率低于阈值_折算为负_绩效负值不归零():
|
||||
# 销售额 100、毛利润率 10%、阈值 30% → 折算 -20
|
||||
assert converted_profit(Decimal("100"), Fraction(1, 10), Fraction(3, 10)) == Decimal("-20")
|
||||
assert performance_amount(Decimal("-20"), Fraction(6, 1000)) == Decimal("-0.12")
|
||||
|
||||
def test_支付权重_R为60时权重0点36():
|
||||
assert payment_weight(60) == Fraction(36, 100)
|
||||
|
||||
def test_注册表_16项_编码唯一_必填字段非空():
|
||||
assert len(PARAM_REGISTRY) == 16
|
||||
assert len({s.code for s in PARAM_REGISTRY.values()}) == 16
|
||||
# 每项 name/group/value_type 非空
|
||||
|
||||
def test_解析分数_4除以360_按Fraction不丢精度():
|
||||
assert parse_fraction("4/360") == Fraction(1, 90)
|
||||
|
||||
def test_模板注册表_五个模板_声明参数均在注册表内():
|
||||
# TEMPLATES 的 params 引用必须全部存在于 PARAM_REGISTRY(跨表一致性)
|
||||
```
|
||||
|
||||
- [ ] **步骤 2:运行确认失败**
|
||||
|
||||
```bash
|
||||
cd backend && uv run pytest tests/unit/test_formula.py tests/unit/test_param_registry.py -q
|
||||
```
|
||||
|
||||
预期:FAIL(`ModuleNotFoundError` / `NameError`)。
|
||||
|
||||
- [ ] **步骤 3:实现三个 service 文件**
|
||||
|
||||
`formula.py`:8 个纯函数(签名见 2.4 契约)。要点:`salesperson_rate` 中 `days <= boundary_days` 用线性式、否则取 `floor`;`converted_profit` 用化简式 `revenue × (rate − threshold)`;`performance_amount` 为乘法;`payment_days` 为 `(receipt_date - ship_date).days + 1`。
|
||||
|
||||
`param_registry.py`:`ParamSpec` 冻结 dataclass + `PARAM_REGISTRY`(16 项,值与 2.3 种子一致)+ `get_spec`(未注册编码抛 500 级异常,属代码 bug)+ `parse_fraction`(`Fraction(text)`,捕获 `ZeroDivisionError`/`ValueError` → 400)+ `validate_and_normalize`(类型/取值域/枚举校验,返回规范串)。
|
||||
|
||||
`calc_templates.py`:`TemplateSpec` + `TEMPLATES`(5 个模板,见 2.4)+ `get_template`/`required_params`。
|
||||
|
||||
- [ ] **步骤 4:运行确认通过**
|
||||
|
||||
```bash
|
||||
cd backend && uv run pytest tests/unit -q
|
||||
```
|
||||
|
||||
预期:全部 PASS(含既有单元基线)。
|
||||
|
||||
- [ ] **步骤 5:Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/services/formula.py backend/app/services/param_registry.py backend/app/services/calc_templates.py backend/app/core/constants.py backend/tests/unit/test_formula.py backend/tests/unit/test_param_registry.py
|
||||
git commit -m "feat(system): 计提公式模板注册表与精确分数计算函数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T3:参数仓储 + 取值服务(时点化解析)
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`backend/app/repositories/param_repository.py`
|
||||
- 创建:`backend/app/services/param_service.py`(先实现 `ParamSnapshot`/`resolve_snapshot` 与覆盖链选择)
|
||||
- 测试:`backend/tests/unit/test_param_snapshot.py`(覆盖链纯逻辑;真实 DB 解析在 T8 统一集成测试)
|
||||
|
||||
- [ ] **步骤 1:编写失败的测试(覆盖链与兜底)**
|
||||
|
||||
```python
|
||||
def test_覆盖链_角色维度优先于产品类别与默认():
|
||||
# rows: (code, dimension) 含 ('enterprise.rate', ''), ('enterprise.rate', 'municipal_water')
|
||||
# 取 role_code=None, product_category='municipal_water' → 返回产品类别行
|
||||
|
||||
def test_未定义参数_按注册表默认值兜底():
|
||||
# 某参数无任何版本条目 → snapshot.fraction(code) == 注册表 default_value 的 Fraction
|
||||
|
||||
def test_同日多次变更_取id最大的一条():
|
||||
# 同日两行 → 取 id 大者
|
||||
```
|
||||
|
||||
- [ ] **步骤 2:运行确认失败**(`uv run pytest tests/unit/test_param_snapshot.py -q`,预期 FAIL)
|
||||
|
||||
- [ ] **步骤 3:实现**
|
||||
|
||||
`param_repository.py`:`resolve_values(session, biz_date)` 一条 SQL 取全量快照(按 `(param_id, dimension_value)` 分组取 `effective_date DESC, id DESC` 首行;可用窗口函数或子查询,SQL 留存 `db/queries/计提参数.sql`);批次 CRUD 与 `for_update` 加锁函数(签名参照 2.5 与设计报告)。
|
||||
|
||||
`param_service.py`:`ParamSnapshot`(frozen dataclass,持有解析后 `dict[(code, dim)] → value` 与 `biz_date`)+ `resolve_snapshot(session, biz_date)`(读定义 + 读条目 + 合并注册表默认值);`get/fraction/integer/boolean/text` 五类取值(覆盖链:role_code 维度 > product_category 维度 > 默认行;`fraction` 走 `parse_fraction`)。
|
||||
|
||||
- [ ] **步骤 4:运行确认通过**(`uv run pytest tests/unit -q`,预期 PASS)
|
||||
|
||||
- [ ] **步骤 5:Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/repositories/param_repository.py backend/app/services/param_service.py backend/tests/unit/test_param_snapshot.py
|
||||
git commit -m "feat(system): 计提参数按业务日期时点化取值服务"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T4:岗位角色 CRUD 后端(含审计与保护规则)
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`backend/app/repositories/business_role_repository.py`、`backend/app/schemas/business_role.py`、`backend/app/services/business_role_service.py`、`backend/app/api/v1/system/business_roles.py`
|
||||
- 修改:`backend/app/api/router.py`、`backend/app/core/constants.py`(MODULE_BUSINESS_ROLE)
|
||||
|
||||
- [ ] **步骤 1:实现四层文件**
|
||||
|
||||
整体参照 `roles` 模块(`role_repository.py`/`role_service.py`/`api/v1/system/roles.py`)的写法与保护规则模式。接口:列表(分页+关键字+状态)、新增、详情、编辑、删除、启停、`GET /meta/templates` 模板下拉。保护规则:`is_builtin` 禁删禁停;`role_code` 创建后不可改;`calc_template` 必须存在于 `TEMPLATES`(否则 400);删除前检查人员引用(② 落地前以引用计数钩子预留,接入后生效);写操作全部包 `audit_service.audit()`。
|
||||
|
||||
- [ ] **步骤 2:冒烟验证(容器 + 后端在跑)**
|
||||
|
||||
```bash
|
||||
cd backend && uv run uvicorn app.main:app --reload --port 8000
|
||||
# 另一终端:curl 登录取 token 后依次打 7 个端点;重点验证:
|
||||
# - 新增自定义岗位(绑 TPL_ENTERPRISE_FIXED)→ 201/200
|
||||
# - 删除内置 salesperson → 403;删除自定义岗位 → 成功
|
||||
# - calc_template 传不存在的编码 → 400
|
||||
```
|
||||
|
||||
- [ ] **步骤 3:Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/repositories/business_role_repository.py backend/app/schemas/business_role.py backend/app/services/business_role_service.py backend/app/api/v1/system/business_roles.py backend/app/api/router.py backend/app/core/constants.py
|
||||
git commit -m "feat(system): 业务岗位角色管理接口与保护规则"
|
||||
```
|
||||
|
||||
(模块级正式用例按项目约定在 T8 统一编写。)
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T5:参数变更/历史/对比/导出/预览后端
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`backend/app/schemas/param.py`、`backend/app/api/v1/system/params.py`
|
||||
- 修改:`backend/app/services/param_service.py`(提交/撤回/历史/对比/预览/导出)、`backend/app/api/router.py`、`backend/app/core/constants.py`(MODULE_PARAM)、`backend/pyproject.toml`(`uv add openpyxl`)
|
||||
|
||||
- [ ] **步骤 1:加依赖**:`cd backend && uv add openpyxl`(参数导出用)
|
||||
|
||||
- [ ] **步骤 2:实现 schemas 与 service 扩展**
|
||||
|
||||
`submit_change`:事务内校验全部 items(`validate_and_normalize`)→ 建批次(`version_no` 规则 `P{YYYYMMDD}-{当日序号}`)→ 逐项写 `param_values`(`old_value_text` 取当前生效值)→ `item_count` → commit。`withdraw_batch`:已生效(`effective_date <= today`)禁撤(400);`for_update` 锁批次行后删条目与批次。`compare`:两日期各取快照后逐项 diff。`preview`:入参(产品类别/客户类型/岗位/发货日/回款日/回款金额/销售额/毛利润额/是否以物抵顶/是否试用期)→ 输出(回款天数/计提比例/折算毛利润额/单笔绩效/起付判定/R×R 结果/所用参数生效版本)。`export_changes`:openpyxl 流式写变更记录(列:变更人/时间/参数/旧值/新值/生效日期/批次号)。
|
||||
|
||||
- [ ] **步骤 3:实现 api 层**
|
||||
|
||||
9 个端点(目录快照/历史/提交/撤回/批次分页/批次明细/对比/导出/预览);写操作包审计;导出返回 `StreamingResponse`(非统一信封,前端 api 层注明)。
|
||||
|
||||
- [ ] **步骤 4:冒烟验证**
|
||||
|
||||
```bash
|
||||
# 提交一次变更(3 项参数,指定未来日期)→ 当前快照不变
|
||||
# 到该日期(或提交立即生效)→ 快照变化、历史出现记录
|
||||
# 撤回未生效批次 → 快照回退;撤回已生效批次 → 400
|
||||
# 预览:360 天/361 天边界、12-15→01-10=27 天精确复现
|
||||
# 导出 → xlsx 可打开且列齐全
|
||||
```
|
||||
|
||||
- [ ] **步骤 5:Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/schemas/param.py backend/app/services/param_service.py backend/app/api/v1/system/params.py backend/app/api/router.py backend/app/core/constants.py backend/pyproject.toml backend/uv.lock
|
||||
git commit -m "feat(system): 计提参数版本管理、变更留痕与计算预览接口"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T6:前端岗位角色页
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`frontend/src/api/businessRoles.ts`、`frontend/src/views/system/business-roles/index.vue`
|
||||
- 修改:`frontend/src/router/index.ts`、`frontend/src/utils/constants.ts`
|
||||
|
||||
- [ ] **步骤 1:api 封装 + 页面**
|
||||
|
||||
页面整体复用用户管理页模式(`usePagedList` + `useConfirmAction` + `AppPagination` + `StatusTag`);表单含模板下拉(选中后展示模板声明的参数清单与当前值);内置岗位删除/停用按钮禁用并给 title 提示;路由 meta `{ title: '岗位角色', group: '系统管理', requiresAdmin: true, order: 5 }`。
|
||||
|
||||
- [ ] **步骤 2:验证**
|
||||
|
||||
```bash
|
||||
cd frontend && pnpm type-check && pnpm build
|
||||
```
|
||||
|
||||
预期:类型检查与构建通过。
|
||||
|
||||
- [ ] **步骤 3:Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/api/businessRoles.ts frontend/src/views/system/business-roles frontend/src/router/index.ts frontend/src/utils/constants.ts
|
||||
git commit -m "feat(system): 前端岗位角色管理页面"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T7:前端参数配置页(含版本管理抽屉与预览)
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`frontend/src/api/params.ts`、`frontend/src/views/system/params/index.vue`
|
||||
- 修改:`frontend/src/router/index.ts`、`frontend/src/utils/constants.ts`
|
||||
|
||||
- [ ] **步骤 1:api 封装**(导出接口用 axios 实例取 blob,注明非统一信封例外)
|
||||
|
||||
- [ ] **步骤 2:页面实现**
|
||||
|
||||
按 `param_group` 分组展示参数(名称/当前值+单位/生效日期/口径说明);顶部「生效方式(立即/指定日期)+ 生效日期 + 备注」;保存前二次确认(列出改动项与旧值→新值);版本管理抽屉(变更记录分页 + 批次明细 + 两日期对比 + 导出下载);预览抽屉(表单 → 结果卡,注明「斜率精确值 4/360」);路由 meta order 6。
|
||||
|
||||
- [ ] **步骤 3:验证**
|
||||
|
||||
```bash
|
||||
cd frontend && pnpm type-check
|
||||
# e2e(需容器+前后端在跑):改一个参数 → 变更记录出现 → 预览值变化 → 导出可下载
|
||||
```
|
||||
|
||||
- [ ] **步骤 4:Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/api/params.ts frontend/src/views/system/params frontend/src/router/index.ts frontend/src/utils/constants.ts
|
||||
git commit -m "feat(system): 前端计提参数配置与版本管理页面"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 任务 T8:模块测试统一编写与文档更新
|
||||
|
||||
**文件:**
|
||||
|
||||
- 创建:`backend/tests/unit/test_param_service.py`(可 fakeredis 化的部分)、`backend/tests/integration/test_business_roles.py`、`backend/tests/integration/test_param_management.py`
|
||||
- 修改:`backend/tests/README.md`、`frontend/README.md`、`backend/README.md`(接口清单)、`frontend/e2e/system_management.py` 或新增 `frontend/e2e/param_management.py`
|
||||
|
||||
- [ ] **步骤 1:编写集成用例**(覆盖审查重点 1/2/3)
|
||||
|
||||
参数精度专项(Fraction/1/90 全链路:注册表 → 种子 → 解析 → 预览一致);未来生效不泄漏;撤回回退;同日多次变更取最新;审计留痕(成功与失败);权限(普通用户 403);岗位保护规则(内置禁删、模板校验)。
|
||||
|
||||
- [ ] **步骤 2:全量回归**
|
||||
|
||||
```bash
|
||||
cd backend && uv run pytest -q
|
||||
```
|
||||
|
||||
预期:301 基线 + 新增用例全绿。
|
||||
|
||||
- [ ] **步骤 3:e2e 与文档**
|
||||
|
||||
前端 e2e 步骤(菜单出现两个新页、改参数→记录→预览链路);更新三份 README(用例清单/运行记录/发现缺陷/待补用例、接口清单)。
|
||||
|
||||
- [ ] **步骤 4:Commit**
|
||||
|
||||
```bash
|
||||
git add backend/tests frontend/e2e backend/README.md frontend/README.md
|
||||
git commit -m "test(system): 参数体系模块用例与测试记录"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.7 风险与开放问题
|
||||
|
||||
#### 风险(已定对策)
|
||||
|
||||
1. 参数缓存:一期不做(见 2.1-4);若未来启用必须按批次版本号精确失效,禁通配删除;
|
||||
2. 同日多次变更:UI 明示「同日多次变更按最新一条生效」;
|
||||
3. `4/360` 展示:存储分数、展示格式化(预览页注明「精确值 4/360,非截断 0.011111」);
|
||||
4. 模板扩展摩擦:新公式形态仍需加模板(一期接受,与「不引入规则引擎」一致);岗位表单把模板做成带说明的受控下拉;
|
||||
5. 报表字段前移:预览出参尽量与需规 2.4.1 明细列同名,减少 ④ 映射工作;
|
||||
6. 参数导出用 openpyxl:已在 T5 增补依赖(AI_CONTEXT 已认可方向)。
|
||||
|
||||
#### 开放问题(不阻塞首块开工,逐项有默认处理)
|
||||
|
||||
| # | 问题 | 默认处理 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 第 1 天计提:需规图示「第 1 天 10‰」vs 公式 `10 − 4/360 ≈ 9.9889‰` | 按公式实现(上限为参考);起始值参数化,业务如需 10‰ 可在系统设置调整 |
|
||||
| 2 | 企业户 02-17 前历史(2‰ 是否回溯) | 已按双种子(02-17=2‰、08-13=3‰)实现;如无需回溯可撤首行 |
|
||||
| 3 | `payment.weight.mode` BALANCE 模式语义(余额口径) | 首块只存枚举;语义在 ④ 开工前确认 |
|
||||
| 4 | `probation.salary.basic`(3000 元)是参数还是人员工资标准字段 | 暂列参数;② 开工时如归人员字段则移除 |
|
||||
| 5 | 参数查询权限(普通用户可否只读快照) | 建议「查看可、修改仅管理员」,实现为 AdminUser 时可放宽——待确认 |
|
||||
| 6 | 导出格式与列集合(是否含批次说明/操作人) | 以需规 2.3.1(四) 字段为准,导出前与业务对一次列 |
|
||||
| 7 | 经理个人户业绩如何进入 12.5%+5%(裁决 #7 的具体衔接:该业绩是否计入「本人带领业务员」基数) | ④ 开工前必须确认(影响台账归属字段设计) |
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 计划内容与 `ai-aux/AI_CONTEXT.md` 需求裁决节逐条核对(口径无冲突、无遗漏作废条款);
|
||||
2. 计划中的命令与项目现有命令体系一致(uv / pnpm / docker compose);
|
||||
3. 首块每个任务有独立可测交付物;测试要点符合「模块整体完成时统一编写」约定(纯函数与 core 级随实现即时测);
|
||||
4. 执行阶段验证:T1 后 `test_schema_sync.py` 通过;T2/T3 后纯单元全绿;T5 后冒烟链路完整;T8 后全量 301+ 用例与 e2e 全绿。
|
||||
Reference in new issue
Block a user