- get_with_names 加 for_update 参数:update/change_status/change_ownership 以锁定读取行,与发货/回款写路径同一锁口径——「有发货禁换客户」的 快照读校验不再被并发新增发货旁路(此前 delete 有锁、唯独编辑无锁) - update_business 补终态门槛(明细/汇总是 M3 核算基线,与发货/回款 服务禁写终态同口径);前端编辑按钮按终态禁用并加 tooltip - 新增 business_repository.has_docs(单条 OR exists):替换 update 换客户与 delete 两处内联双查询,M3 新增挂靠单据类型时只改一处 - 出参组装复用首查 row:change_ownership 的姓名侧按「变更项取校验 函数返回的新人名、未变项沿用首查名」处理(首查名是变更前快照) - 新增终态编辑 400 且明细未动的集成用例 Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| integration | ||
| unit | ||
| __init__.py | ||
| conftest.py | ||
| README.md | ||
后端测试
AI摘要:本项目后端的测试用例清单与运行记录。按「基础设施层随实现即时测、业务模块整模块完成时统一测」分层组织。当前 603 个用例全绿(单元 381 / 集成 222)。
一、测试约定
编写时机(分层对待)
core/等基础设施随实现即时补测 —— 它们是被业务复用多次的地基,越早锁定越省事- 业务模块(系统管理、基础数据管理、业务数据管理、绩效工资核算、报表统计)在整个模块功能完成时统一编写用例并执行,不在模块做到一半时零散补测
目录二分
| 目录 | 依赖 | 用途 |
|---|---|---|
tests/unit/ |
无(不碰 Docker、不连库) | 纯函数与元数据验证,秒级跑完,开发中可高频执行 |
tests/integration/ |
完整应用实例或 MySQL/Redis 容器 | 接口级、跨产物一致性验证 |
单元测试里的 Redis 依赖通过 fake_redis 夹具(fakeredis)替换,因此验证码 GETDEL、限流计数、黑名单 TTL 这些逻辑无需起容器即可覆盖。
注释要求
- 测试类用中文 docstring 说明「这组用例守的是什么」——即如果不测会出什么问题
- 用例名描述场景与预期(
test_中文按字节而非字符计算边界) - 关键断言补注释说明为何该断言重要,尤其是防回归的用例
运行方式
cd backend
uv run pytest tests/unit -q # 纯单元,无需容器,秒级
uv run pytest -q # 全部(集成部分需容器在跑)
uv run pytest tests/integration -q # 仅集成
二、用例清单
基础设施层(core)
| 测试文件 | 守什么 | 用例数 | 关键覆盖点 |
|---|---|---|---|
unit/test_models.py |
建表规范红线 | 49 | 表名小写蛇形复数;每列中文注释且确实输出到 DDL;uk_/idx_/fk_ 命名前缀;ENGINE/CHARSET 显式指定;ON UPDATE CURRENT_TIMESTAMP 存在(server_onupdate 不会生成该子句,是 SQLAlchemy 的行为陷阱);日志表反向断言不含该子句 |
unit/test_logging.py |
日志脱敏与格式化 | 16 | 密码/令牌/Bearer/手机号/身份证五类脱敏;脱敏必须后置于 %-格式化(早期实现在 filter 阶段改写 record.msg,抹掉 %s 占位符导致 logging 抛 TypeError、整条日志丢失);setup_logging 幂等 |
unit/test_config.py |
配置加载与安全边界 | 14 | 密钥强度 fail-fast;JWT ≤120 分钟、验证码 ≤300 秒的硬约束;密码含 @ : / # 时 URL 构造不破坏;渲染 URL 默认隐藏密码 |
unit/test_security.py |
密码哈希 | 11 | BCrypt 格式与定长 60;72 字节边界(24 个汉字正好、25 个超限);超长密码校验时返回 False 而非抛异常(防侧信道);哈希串损坏不炸 |
unit/test_response.py |
统一响应契约 | 11 | 字段固定为 code/message/data;分页序列化为规范要求的驼峰 list/total/pageNum/pageSize |
unit/test_exception_handlers.py |
异常转统一信封 | 5 | 校验错误 422→400 并压成单行中文;HTTP 状态码与 body 的 code 必须一致 |
系统管理 · 登录鉴权
| 测试文件 | 守什么 | 用例数 | 关键覆盖点 |
|---|---|---|---|
unit/test_jwt.py |
令牌有效期语义与版本机制 | 12 | exp 基于 UTC 而非本地时间(混算会产生 8 小时偏差,表现为「刚登录就过期」或「过期了还能用」);过期/篡改签名/alg=none 攻击/畸形字符串一律收敛为 401 而非 500;jti 唯一 |
unit/test_captcha.py |
验证码一次性语义 | 12 | GETDEL 后二次使用必然失败(防重放);校验失败也消费(否则可用一个验证码反复试密码);字符集剔除 0O1Il;每次渲染含随机干扰不可比对;TTL 300 秒 |
unit/test_login_limiter.py |
阈值与滑动窗口 | 9 | 达阈值即锁;每次失败刷新 TTL(否则「4 次失败 + 等 14 分钟 + 1 次失败」可绕过锁定);按用户名隔离,一个账号被攻击不牵连他人;登录成功清除计数 |
unit/test_token_blacklist.py |
黑名单 TTL 语义 | 7 | TTL 等于令牌剩余有效期(写死固定值会导致「登出 N 分钟后又活了」,不设过期则 Redis 无限增长);已过期令牌不写入;jti 互不串号 |
unit/test_auth_schemas.py |
密码按字节校验 | 9 | 24 个汉字 = 72 字节正好、25 个被拒 —— 按字符数校验(如 max_length=20)根本拦不住,会让登录接口 500 |
integration/test_auth_flow.py |
整条链路串联正确性 | 16 | 验证码→登录→持令牌访问→登出失效;验证码重放被拒;失败原因如实落库且日志表无任何密码字段;账号不存在与密码错误文案完全相同(防枚举);禁用账号与 token_version 提升均使旧令牌立即失效 |
系统管理 · 用户与角色管理
| 测试文件 | 守什么 | 用例数 | 关键覆盖点 |
|---|---|---|---|
unit/test_audit_service.py |
审计上下文的语义 | 16 | 正常退出记成功、抛异常记失败;失败时先 rollback 再写日志且原异常原样重抛(顺序颠倒时数据库异常会让失败记录丢成 PendingRollbackError);操作人标量在进入时固化(失败路径不再触碰会过期的 ORM 对象);进入时脏会话告警(防嵌套半提交);审计自身失败不外抛、不替换业务异常 |
unit/test_audit_serialization.py |
入库前截断与脱敏不丢数据、不泄密 | 16 | 各列按模型常量截断;60KB 请求参数按 UTF-8 字节边界截断后是原串的干净前缀(中文不切半);结构化抹除(按敏感键名整值替换):前导空白/空格/逗号/引号密码全被抹除且 JSON 结构完好、嵌套 dict/list 与键名大小写、非敏感键不误伤;手机号仍由 scrub 文本兜底;孤立代理字符不再抛错丢审计 |
unit/test_role_schemas.py |
角色编码格式红线 | 16 | ^[a-z][a-z0-9_]{1,31}$ 全边界(大写/中文/数字或下划线开头/单字符/超长被拒);编辑模型不接受 role_code(多余字段忽略);状态只能 0/1 |
integration/test_user_management.py |
用户 CRUD 串联正确性 + 审计留痕 | 23 | 分页以 pageNum/pageSize 生效(钉死别名契约,改蛇形会静默失效);自删/自禁/移除自己 admin 角色 403;禁用与重置密码使旧令牌立即失效;每个写操作(含失败的)在 operation_logs 有对应记录、request_data 全文扫不到明文密码;账号删除后审计行 user_id 置空而 username 保留;编辑时保留既有角色不撞唯一键(1062 回归);编辑未修改字段不把 NULL 污染成空串;含下划线用户名不误配(LIKE 转义回归) |
integration/test_role_management.py |
角色保护规则(系统自举与授权安全的最低防线) | 13 | admin 角色禁删禁停 403;有绑定用户的角色禁删 400 且消息含用户数、解绑后可删;编码创建后不可改;停用角色不可再分配(接口层拦截);停用后不在 /users/meta/roles 但仍在管理列表;user_count 正确;普通用户 403 |
系统管理 · 日志查询
| 测试文件 | 守什么 | 用例数 | 关键覆盖点 |
|---|---|---|---|
unit/test_log_schemas.py |
时间范围校验与时区归一 | 13 | 起始晚于结束拒绝(两类日志共用基类,放过去则 SQL 条件互斥、查询恒空且无提示);带时区输入转本地 naive(Z 后缀 → 东八区 08:00;混用 aware/naive 不再抛 TypeError 穿透 500);边界相等通过;仅传一端不校验 |
integration/test_log_query.py |
写入与查询的口径一致 | 13 | 写操作产生的审计可按 username/module/action/success/时间范围检索到,摘要含目标用户名;失败记录带中文 error_message;列表不含 request_data、详情端点返回已脱敏参数(两段式,含 404/403);起始晚于结束 400;登录日志直插造数后可按 ip 精确筛选;两类日志普通用户 403 |
应用骨架与交付产物
| 测试文件 | 守什么 | 用例数 | 关键覆盖点 |
|---|---|---|---|
integration/test_health.py |
统一信封与全局异常 | 7 | /api/health 始终 200 且依赖状态如实上报;404/405 返回统一信封而非框架默认 detail 数组;/api/docs 可访问 |
integration/test_schema_sync.py |
DDL 不漂移 | 6 | db/migration/V1__*.sql 与 db/init/init-dev.sql 的表集合与列集合逐表比对——两份 DDL 手工同步迟早漂移,且漂移后果隐蔽(开发库结构与生产不一致,上线才发现) |
M1 计提参数与岗位角色(2026-10-13 完成)
| 文件 | 守什么 | 用例数 |
|---|---|---|
tests/integration/test_business_roles.py |
岗位 CRUD 链路、内置岗位禁删禁停(种子 3 个)、模板编码校验、编辑换模板、权限 403、模板下拉 5 项 | 8 |
tests/integration/test_param_management.py |
目录 16 项与七分组、企业户 2‰→3‰ 按到账日回溯、斜率 4/360 分数原样存储、变更生命周期(立即/未来生效不泄漏/同日取最新/撤回回退/已生效禁撤)、值校验(上限/枚举/除零分数/过去日期)、预览金标准(27 天=97/10000、折算 15000、绩效 145.5、可付 52.38;361 天=4‰)、经理聚合口径拒绝、权限 403 | 18 |
tests/unit/test_formula.py、tests/unit/test_param_registry.py |
八个公式纯函数精确分数断言(第 1 天=899/90000、360=6‰、361=4‰、27 天、防除零、负绩效不归零、R×R)、注册表 16 项与迁移种子同源、模板声明参数闭环 | 43 |
tests/unit/test_param_snapshot.py |
快照覆盖链(角色>产品类别>默认)、注册表默认值兜底、同日取最新、冻结不可变 | 12 |
运行:uv run pytest -q → 406 passed(2026-10-13 全量含回归;T5 冒烟另验证提交/生效/撤回/对比/导出/预览全链路)。
基础数据管理(2026-10-13 完成)
| 文件 | 守什么 | 用例数 |
|---|---|---|
tests/integration/test_personnel.py |
未登录 401、新增默认在职、工资标准 str 直出、经理自引用/不存在或已离职/岗位停用 400、删除保护(团队成员与客户归属引用→引导改离职)、辐射区域与拓展计划全量替换(含清空与详情回显)、在职/离职切换、审计成功与失败双记录 | 20 |
tests/integration/test_products.py |
未登录 401、类别值域 400、名称+规格组合唯一(同名称不同规格放行)、单价负数/非数字 400、启停切换、改类别、审计留痕 | 14 |
tests/integration/test_customers.py |
未登录 401、客户分类值域 400、「公司名称+负责人」组合唯一(同公司不同负责人放行)、归属变更(主表更新+留痕字段断言)、归属人员离职 400、新旧相同含双方为空 400、留痕分页 join 姓名、编辑端点不触归属、审计留痕 | 14 |
运行:uv run pytest tests/integration/test_personnel.py tests/integration/test_products.py tests/integration/test_customers.py -q → 48 passed(2026-10-13,连跑两次幂等)。
业务数据管理(2026-10-13 完成)
| 文件 | 守什么 | 用例数 |
|---|---|---|
tests/integration/test_businesses.py |
未登录 401;编号 YW{YYYYMMDD}-{4位} 与当日序号递增;空明细/数量 0/非法类别/单价非数字 400;销售额服务层固化(schema 无 sale_amount 入参,塞了忽略);特殊业务必填说明;编辑全量替换后三处冗余一致(明细 SUM == 业务冗余列 == DB SUM);状态机(终止无原因 400 → 留痕 old=1/new=3 → 终态再变更 400);归属变更主表+留痕同生(old/new owner 与 assistant、owner_type=2)、归属未变化 400、创建时归属人员已离职 400;有发货/回款禁换客户禁删;空业务删除连带清理明细/状态留痕/业务归属留痕;毛利润视图金标准(销售 10000/成本 30 → 率 997/1000、市政 30% → 折算 6970;销售额 0 全 0 防除零;成本缺失行派生为 null 不计类别汇总;asOf 按业务日期回溯旧阈值);审计成功/失败双记录 |
25 |
tests/integration/test_shipments.py |
未登录 401;编号 SH{YYYYMMDD}-{4位};业务不存在 404;终态业务禁新发货 400;空明细/数量 0/运费负数/运费三位小数 400;行金额与发货单总额服务层固化;业务 total_ship/total_freight 回写一致;编辑后两处汇总同步;被回款指定禁删(解除后可删);删除后业务级汇总回退;终态业务发货单禁改禁删;审计双记录 |
15 |
tests/integration/test_receipts.py |
未登录 401;批次 HK{YYYYMMDD}-{4位};未指定批次可登记且 payment_days 为 null;指定批次派生「发货日首日计第 1 天」(2026-10-05 → 2026-11-02 = 29 天,编辑清指定后回 null);业务不存在 404 / 终态禁新增 400;金额负数/三位小数 400;跨业务指定批次 400(「发货批次不属于该业务,无法指定」,编辑路同校验);合计回写与删除回退;已核算(改库 settlement_status=1)禁改禁删;settlement_status 筛选 0/1 两路;审计双记录 |
16 |
tests/integration/test_ratings.py |
未登录 401;种子四等级全量按 sort_order 升序(A 90-100/B 75-89/C 60-74/D 0-59);区间倒挂/超上限/负下限 → 400「分值区间不合法」;名称唯一 → 400「该等级名称已存在」;新增与编辑两路重叠 400 点名冲突等级;边界相邻不算重叠正例(删 C 后新增 60-74 / 把 D 编辑为 0-74,74 与 75 相邻放行);编辑改名改区间回显;删除后判定表更新、二次删除 404;审计双记录。种子删改由 conftest 兜底恢复 | 14 |
运行:uv run pytest tests/integration/test_{businesses,shipments,receipts,ratings}.py -q → 70 passed(2026-10-13,连跑两次幂等);全量回归 uv run pytest -q → 603 passed(单元 381 / 集成 222)。
三、运行记录
| 日期 | 范围 | 用例数 | 结果 | 备注 |
|---|---|---|---|---|
| 2026-10-09 | 全部 | 120 | ✅ 通过(4.99s) | 阶段 1 骨架 + 阶段 2 数据库完成后首次全量运行 |
| 2026-10-09 | 全部 | 119 | ✅ 通过(5.01s) | ORM 由异步改回同步后回归;净减 1 例(sync_database_url 独立配置已合并) |
| 2026-10-09 | 全部 | 184 | ✅ 通过(25.46s) | 登录鉴权模块完成后统一编写并执行该模块测试套件(新增 65 例)。集成用例需容器在跑 |
| 2026-10-10 | 全部 | 282 | ✅ 通过(103.81s) | 系统管理模块(用户/角色/日志)完成后统一编写该模块套件(新增 91 例:单元 48 / 集成 43)。含操作日志审计机制专项;e2e 发现并修复 replace_roles 唯一键缺陷(见四、缺陷表);集成用例需容器在跑 |
| 2026-10-10 | 全部 | 301 | ✅ 通过(87.40s) | code-review 缺陷修复后全量回归(新增/改造 19 例)。密码结构化脱敏、时区归一、日志详情两段式各有专项;集成套件因密码哈希模块级预计算提速(63s→42s) |
| 2026-10-13 | 基础数据集成三文件 | 48 | ✅ 通过(27.60s) | 基础数据管理模块完成后统一编写(人员 20/产品 14/客户 14)。审计上下文内外的校验位置差异为本次新发现的缺陷类别(见下) |
| 2026-10-13 | 业务数据集成四文件 | 70 | ✅ 通过(54.73s,连跑两次幂等) | 业务数据管理(后端接口)完成后统一编写(业务 25/发货 15/回款 16/评级 14)。全量回归 603 例通过 |
| 2026-10-13 | 全量回归 + 两个 e2e | 605 | ✅ 605 passed(208s);e2e 基础数据 13 步 + 业务全链路 16 步全过 | M2 收尾:删除保护补齐(+2 用例)后最终回归 605;e2e 期间修复三处缺陷(见下表),均已用例锁定 |
| 2026-10-13 | 全部 | 603 | ✅ 通过(5:17) | T11 全量回归(单元 381 / 集成 222),含本模块 70 例 |
四、测试发现并修复的缺陷
| 日期 | 缺陷 | 影响 | 处理 |
|---|---|---|---|
| 2026-10-09 | 日志脱敏在 logging.Filter 阶段改写 record.msg |
抹掉 %s 占位符 → logging 内部 msg % args 抛 TypeError → 整条日志丢失(比不脱敏更危险) |
改为 SensitiveFormatter 在格式化之后脱敏;补回归用例 |
| 2026-10-09 | PasswordHash.recommended() 在 pwdlib 0.3.x 推荐 Argon2 而非 BCrypt |
密码算法与规范不符,且随库版本变化——同一批密码可能混用两种算法 | 改为显式 PasswordHash((BcryptHasher(),)) 钉死算法 |
| 2026-10-09 | 模型未设置表级 comment,update_time 用 server_onupdate |
违反规范红线;server_onupdate 只是 ORM 标记,不生成 ON UPDATE CURRENT_TIMESTAMP 子句,更新时间永不刷新 |
补表注释;改用 text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") |
| 2026-10-09 | pymysql 未带 [rsa] extra,cryptography 缺失 |
MySQL 8.0 默认 caching_sha2_password,容器重启后首次连接走全量认证时抛 RuntimeError,且报错在建连阶段易被误读为「密码错误」 |
依赖改为 pymysql[rsa] |
| 2026-10-09 | setex 在 redis-py 8 已弃用 |
规范明确禁用弃用 API;弃用接口在未来大版本会被移除 | 验证码与黑名单改用 set(key, value, ex=…) |
| 2026-10-09 | 测试中 TTL 断言写死等值 | 属测试自身缺陷:Redis 的 TTL 以毫秒精度存绝对过期时刻、返回时向下取整,刚写入就查询必然可能少 1 秒——会变成偶发失败的闪烁测试 | 断言改为容忍 1 秒偏差,并注明原因 |
| 2026-10-09 | 验证码沿用 captcha 库默认渲染 |
随机彩色字符 + 重噪点,与界面视觉体系冲突;该库最后更新于 2025-03,内部还引用了 Pillow 的旧别名 | 改为自绘(core/captcha_image.py):配色与界面统一,难度由逐字旋转、抖动、干扰曲线与噪点保证;仅沿用其内置字体文件 |
| 2026-10-09 | 噪点用 ellipse 绘制 |
3×3 包围盒画出的椭圆恰好填充中心与四个边中点,形状就是「+」,形成规则的十字纹理——既显眼又容易被整体滤波抹掉,起不到干扰作用 | 改用单像素点与 2×2 方块 |
| 2026-10-09 | tests/unit/test_jwt.py 两处赋值了未使用的 settings(ruff F841) |
变量未被引用,属无效代码 | 删除赋值;该文件的 get_settings 导入仍被其他用例使用 |
| 2026-10-10 | replace_roles 先清后建但中间未落库 |
SQLAlchemy 在同一 flush 中先处理 INSERT 再处理 DELETE,编辑用户保留既有角色时重插同 (user_id, role_id) 撞唯一键(1062)——编辑用户接口 500。由前端 e2e「编辑用户」步骤暴露:集成测试当时只覆盖了「换掉全部角色」(无交集)的路径 |
clear() 后立即 session.flush() 把删除落库,再追加新集合;补集成回归用例(编辑时保留既有角色) |
| 2026-10-10 | 审计脱敏依赖文本正则 | 三种逃逸形态:密码以空白开头时完全不脱敏、含空格/逗号只抹首段、含引号既残留明文又破坏 JSON——明文/残段写入 operation_logs.request_data 且日志页可直接展开查看(由 code-review 发现并逐条实证复现;既有测试所用密码均不含空格,恰好掩盖该路径) |
改为按键名的结构化抹除(递归 dict/list 整值替换 ***),scrub 降级为文本兜底;补前导空白/空格/引号/嵌套/大小写五类边界用例 |
| 2026-10-10 | .gitignore 的 logs/ 规则吞掉日志查询页目录 |
规则无前导斜杠、匹配任意层级:frontend/src/views/system/logs/ 整体被忽略,文件进不了版本库——干净克隆构建失败(本地因文件已在磁盘上而一切"正常",隐蔽性极强) |
收窄为 /backend/logs/(该规则本意即后端运行日志);以 git check-ignore 实证不再命中 |
| 2026-10-10 | 时间筛选未做时区归一 | 混用 aware/naive 抛 TypeError 穿透成 500;带时区输入被驱动静默丢弃偏移(查询窗口偏 8 小时且无提示) | LogQueryBase 加 field_validator:aware → astimezone() 本地 naive、naive 原样;补 4 例专项 |
| 2026-10-10 | 审计时序两处缺陷 | ① 失败路径 rollback 后读 operator 触发额外查询,账号并发被删时 ObjectDeletedError 被兜底吞掉、失败审计静默丢失;② 成功/失败两分支 8 个实参重复展开,且 role/user service 在 commit 之后仍做查询与响应构造——后置查询一旦抛错,「已提交成功」会被记成失败 | operator 标量进入时固化;审计链 DTO 化(audit 4 参 / _safe_write 2 参 / add 2 参);service 的 commit 后查询与响应构造全部前置 |
| 2026-10-13 | 基础数据三 service 的前置校验在审计上下文之外 | 校验失败(岗位停用/经理自引用/分类不合法等 400)完全不留操作日志,违背「成功与失败都记」的既定原则——与 2026-10-10 审计时序缺陷同源,新写的 service 又犯了(各自独立实现时口径漂移) | personnel/product/customer 三 service 的值域与引用校验统一移入 audit_service.audit 上下文内;集成用例锁定(审计成功与失败双记录) |
| 2026-10-13 | change_status 状态留痕的 old_status 取到赋值后的 ORM 属性 |
时间线记成 old==new(如 1→3 的变更记成 3→3)——「变更前状态」无法回溯,状态流转追溯失效。由集成用例断言 (old,new)==(1,3) 抓住 |
赋值前固化 old_status = business.status 再流转;应用日志一并修正;回归用例锁定 |
| 2026-10-13 | 客户/人员删除保护漏检业务引用(T4 占位注释「待业务表就位后补充」在 T7 交付后无人补齐) | 被业务引用的客户、被业务归属(负责/协助)引用的人员删除 → FK RESTRICT 穿透成 500。由 e2e 清理步骤暴露(集成测试只覆盖了 T2/T4 当时的引用面,业务表就位后引用面扩大未回看) | customer 删除加 count_business_refs 检查 + IntegrityError 兜底 400;personnel 删除加 count_business_roles_refs(负责/协助合并统计);补 2 个集成用例锁定;文案更新「客户归属或业务归属引用」 |
| 2026-10-13 | 单价列(DECIMAL(14,4))被两位小数校验拒收(前后端同源) | 编辑任何带精度的历史单价(回显 "5000.0000")被前端 AMOUNT_PATTERN 与后端 _check_amount 双双拒绝——创建后单价带小数位的记录永远无法编辑。由 e2e「编辑业务」步骤暴露(创建用整数、编辑回显带 4 位小数,恰好只在这条路径相遇) |
后端 _check_amount 加 scale 参数(金额/数量 2 位、单价类 4 位);前端业务/发货明细行单价类放行 4 位;集成用例锁定 |
| 2026-10-13 | 发货 freight_amount 漏做金额校验 |
负数/三位小数/超容量的运费直接落库(schema 对 quantity/sale_price 都复用了 _check_amount,唯独运费没套用),业务运费合计与报表随之错乱 |
shipments.py schema 的 Create/Update 加 field_validator("freight_amount")(同套口径,文案「运费金额不能为负数 / 最多两位小数」);集成用例锁定 |
五、待补用例
| 模块 | 状态 | 说明 |
|---|---|---|
| 基础数据管理 | ✅ 已完成 | 2026-10-13 完成(48 例,见上表);「被业务数据引用」的删除保护已随 T11 的业务/发货禁删用例覆盖 |
| 业务数据管理 | ✅ 后端已完成 | 2026-10-13 完成(70 例,见上表);前端页面与绩效核算联动(M3 台账核销回写 settlement_status)待 M3 补测 |
| 绩效工资核算 | 未开始 | 需求已澄清;金额精度需专项用例(Fraction 与 1/90 日斜率已在 M1 公式层覆盖) |
| 报表统计 | 未开始 | 依赖报表口径确认 |
系统管理模块(用户/角色/日志查询)已于 2026-10-10 完成并补齐用例,见上表。