material_manage_system/docs/company-master-data-isolation-acceptance.md
2026-10-09 18:18:58 +08:00

97 lines
12 KiB
Markdown
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.

# 公司隔离验收与交付记录
验收日期:2026-10-09(Asia/Shanghai)。范围:实现计划 Task 8;北京/武汉双公司主数据、库存、单据、申请审批、切换生命周期及实时订阅。
## 环境与数据边界
- 真实数据库为本机 MySQL 8.0.34。`TEST_DB_DSN` 显式指定 `company_isolation_test` 前缀;测试各自创建随机后缀数据库,结束自动删除,不使用业务库。
- 路由验收夹具使用北京/武汉(301/302)、不同分类/单位/规则、同编码 `SHARED` 的不同物料;仅北京、仅武汉、两公司、超级管理员四类账号。前端浏览器库使用另一组独立合成数据(201/202,`SAME-001`,初始余额 10/50)。
- 武汉尚无历史数据,用户于本次会话明确确认。因此武汉真实历史只读副本检查及授权回填清单为**不适用,无执行对象**,没有用合成数据冒充武汉真实数据。将来已有库升级仍须完成以下检查和业务授权确认。
- 浏览器验收服务及延迟代理仅用于本机隔离库;凭据、临时种子和运行日志留在 Git 忽略的 `.dev-run/task8/`,不提交真实密码、JWT 或数据库备份。
## 自动化场景与主测试入口
| 场景 | 主入口 | 验证内容 |
| --- | --- | --- |
| 授权/请求边界 | `internal/handler/company_isolation_integration_test.go` | 四类账号经真实登录、路由、认证和组织中间件;缺失/非法头 400,未授权/公司停用 403,授权内跨公司 ID 404 |
| 主数据 | 同上 | 分页总数、编码搜索、分类/单位/规则读写、物料创建/更新/作废/恢复和日志;跨公司引用 400 且不推进编码流水;请求体伪造归属不起作用 |
| 完整业务链 | 同上 | 两公司分别入库→库存→申请→自动审批出库→出库查询;人工出库修改/删除后的库存回退;预警、流水、申请取消/驳回均按公司隔离 |
| 单据库存回退 | `internal/handler/order_flow_integration_test.go` | 入出库编辑/删除仅改变本公司库存;不足库存、停用仓库失败不留下半单或孤立流水 |
| 库存并发 | `internal/repository/stock_test.go` | 真实 MySQL 并发条件扣减不透支;读取、预警、总数、流水与详情限定公司 |
| 审批事务 | `internal/service/application_organization_db_test.go`及新增路由套件 | 原有不足库存/顺序重复审批场景复用;新增同时审批仅一次成功,MySQL trigger 注入流水写入故障后申请/余额/单据/流水一起回滚,解除故障后可重试 |
| 库存配置 | 新增路由套件 | 无仓库、无库位、仓库停用、库位停用四种 409 和具体提示;历史读取及不扣库存操作仍可执行 |
| 授权即时生效/审计 | `organization_context_integration_test.go`、`user_organization_integration_test.go` | 下一请求重新加载角色/授权;审计写失败使授权事务回滚 |
| 实时订阅 | 新增路由套件及 `websocket_test.go` | 真实 TCP 双公司 WebSocket 不互串;授权撤销、角色撤销、公司停用、登出后握手/投递失败;旧票据及旧令牌失效 |
| 退出登录会话 | `logout_session_test.go` | 当前会话持久撤销,其他会话保留,旧票据及连接不再投递,重载持久撤销记录后旧 JWT 仍无效 |
| 前端竞态与生命周期 | `organization.test.js`、`organizationContext.test.js`、`MaterialsOrganizationLifecycle.test.js`、`companySocket.test.js` | A→B→A 版本校验、取消旧读取/错误、保存互斥、脏表单取消/确认、store 清理、深链接回列表、仓库/库位清理、旧连接回调失效 |
| 升级/约束 | `company_isolation_integration_test.go`(repository)及 generator 测试 | 空库与升级结构一致;历史申请和流水归属回填、对账;异常历史阻断;重复执行保护;默认公司/仓库/库位唯一约束含并发创建;检查脚本嵌入同步 |
按编码读取的 HTTP 入口是列表的 `search` 参数,当前没有独立的按编码详情路由。仓储并发/不足库存等已存在的场景沿用原测试,没有另建重复套件。
## 浏览器验收记录
本次使用构建后的 Vue 页面及真实后端/MySQL,另用本地反向代理延迟指定接口响应。结果记录以实际浏览器操作为准;精确上下文版本、脏表单分支另由代表性页面自动化断言。
| 操作 | 结果 |
| --- | --- |
| 登录默认北京→武汉→北京 | 公司、仓库、库位、同编码物料名称/分类/单位及库存 10/50 正确切换;切换立即清空旧表格 |
| GET 库存响应延迟 8 秒,快速北京→武汉→北京 | 等待结束只出现最终北京数据,未残留武汉行或旧错误提示;旧请求与 WebSocket 被关闭/替换 |
| PUT 预警保存响应延迟 10 秒 | 保存按钮进入等待并禁用,公司切换器同时禁用,完成后恢复;库存归属保持北京 |
| 未保存内容 | 弹窗遮罩阻挡直接操作公司切换器;取消/确认放弃分支由真实组织 store + 代表性物料页面测试验证 |
| 入库与自动出库 | 北京显示 IN-00000001 / OUT-00000001,武汉显示 IN-00000002 / OUT-00000002;各页只出现本公司记录 |
| 申请与审批 | 合成申请 browser-company-201/202 分别显示北京/武汉物料;审批页切换清空旧行;自动出库经真实 API 执行后页面显示对应单据 |
| 库存流水、日志 | 北京流水仅物料 201,包含入库 +1、申请出库 -1;日志页正常加载。完整双公司流水与日志断言见 MySQL 路由测试 |
| 刷新 | /stock、/approval、/outbound 刷新成功,重新验证授权并恢复本账号当前公司;未知 API 和资源仍返回 404 |
| 撤销授权 | 移除仅武汉测试账号授权后,下一库存查询 403;刷新后显示“暂无可用公司”和选择屏障,旧业务页面不挂载。测试后恢复夹具授权 |
| 停用公司 | 临时停用武汉后重新登录只进入启用的北京公司;测试后恢复武汉状态。已连接用户的下一请求/投递拒绝由真实 MySQL/TCP 套件断言 |
| 退出与换账号 | 武汉普通账号退出进入登录页;后端记录 POST /api/auth/logout 200,未出现重复过期提示;换管理员进入北京,没有沿用普通账号武汉选择 |
| 双标签页实时连接 | 北京/武汉库存页并行,入库后各自显示 11/52,未互串;切换重建连接。授权/角色撤销、公司停用和注销后下一事件关闭旧连接另由真实 TCP 测试精确断言 |
## 本次验收修复
1. 物料日志及按物料查询入出库历史原本对跨公司 ID 返回空数组,现先校验物料归属并返回 404;分类/单位/规则/伙伴/仓库/库位更新的归属缺失错误统一映射 404。
2. 原退出登录只清客户端令牌,现持久写入 `revoked_tokens`(`jti` 保存令牌指纹),服务启动加载撤销记录。JWT 增加随机会话标识,避免同用户同秒登录产生相同令牌。票据和连接绑定会话指纹与 JWT 到期时间,消费、握手和投递复核会话;没有有效角色的订阅者被拒绝。
3. 审批页面测试的 Vue Test Utils Teleport 替身反复重建 ElOption 导致 72 个递归更新异常。移除该替身、使用真实 Teleport 后原 4 条断言全部通过;没有忽略或吞掉异常。
4. 集成测试连接启用 `clientFoundRows=true`,与生产连接一致,合法无改动更新保持 200。
5. 撤销记录查询使用 Go 驱动一致的时间边界,避免 MySQL 会话时区不同导致重启恢复已撤销会话;增加 +08:00 会话时区真实数据库回归。
6. 历史物料归属使用独立 EXISTS 查询,允许旧物料规格/单位为 NULL;三个历史接口真实数据库回归通过。
7. 前端退出实际调用注销接口、保留待撤销令牌;App 禁止匿名挂载认证业务页,旧会话延迟 401 不清除新账号令牌。补充页面卸载及会话竞态回归。
8. 提供 Vue 前端路由刷新回退,保留 API/资源 404;修正 Windows PowerShell 5 脚本默认路径求值,检查命令可执行。
## 复现命令与执行证据
在项目根目录设置**专用非生产 MySQL** DSN;账号需要创建/清理隔离库、建表/触发器权限。不要填生产连接。
```powershell
$env:TEST_DB_DSN = '<test-user>:<password>@tcp(127.0.0.1:3306)/company_isolation_test?charset=utf8mb4&parseTime=true'
go test ./... -count=1
npm --prefix frontend test
npm --prefix frontend run build
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/sync-company-integrity.ps1 -Check
git diff --check
```
最终执行结果(2026-10-09):Go 全量退出码 0,208 个顶层测试通过,含子测试共 478 个 pass 事件,测试 skip 0、fail 0;两个无测试包不计为跳过测试。前端 44 个文件、286 条测试通过,未处理异常 0;生产构建退出码 0,保留已有大包体积提示。完整性 SQL 同步检查输出 `Integrity SQL embed is current.`,`git diff --check` 通过。
未配置 `TEST_DB_DSN` 时相关测试会跳过,**不能计为真实数据库验收通过**。本次已显式设置,真实 MySQL 升级和库存事务测试均执行。Windows 沙箱对本机 TCP 和 Vite realpath 的权限限制曾导致环境错误;获得沙箱外执行许可后运行,权限失败记录不计为业务通过。`ExecutionPolicy Bypass` 仅用于本次子进程,不修改系统执行策略。
## 已有库数据检查与升级顺序
1. 备份数据库,在副本上运行 `scripts/checks/company-master-data-integrity.sql`。该脚本使用临时表,不写永久业务数据;若只读账户无法创建临时表,改在该副本派生的可写隔离库执行,不连接生产库做写入验收。
2. 保存异常明细和普通用户授权清单。分类父级、物料分类/基本单位/类型、规则分类、仓库/库位、库存维度、单据/申请物料、流水来源必须能明确归属。多个默认公司、多个仓库/库位等异常先交业务确认,不自动删除/合并,也不按公司名称猜归属。
3. 普通用户授权清单由负责人确认后,以明确 `user_id/organization_id/is_default` 的 SQL 回填;默认公司必须在启用授权范围内。无确认不能自动授权所有公司。新公司不复制旧公司分类、单位和编码规则。
4. 校验 `scripts/sync-company-integrity.ps1 -Check`,在可写隔离副本执行增量脚本 `scripts/upgrades/2026-10-08-company-master-data-isolation.sql`。MySQL 客户端支持其中的 `DELIMITER` 指令;不要把含指令的文件作为单条普通 SQL 发送。
5. 核对 `schema_migrations` 中该版本为 `completed`,核对 `schema_migration_reconciliations` 的公司余额四种数量及单据/申请/流水 `before/after`。测试已验证合成历史夹具的结构、归属、对账和阻断分支;它不代表真实武汉历史副本。
6. 发布时暂停写入、备份,执行确认过的授权回填,以一次协调发布更新前后端;公司组织头后端不能与旧前端长期混用。验证仓库/库位、审计、各角色业务访问及订阅后恢复写入。
`scripts/init.sql` 仅在空库使用,绝不对已有库重跑。增量 SQL 有版本和运行锁保护;MySQL DDL 隐式提交,`running/failed` 版本需要核查部分结构或恢复备份,禁止删除版本记录后盲目重跑。
## 发布限制与回退
HTTP 组织范围来自每次请求验证的公司头,单据/流水保存完整库存维度,申请归属在创建时固定,普通编辑不迁移公司。缺失或停用库存配置不阻挡主数据及历史查询,改变库存的操作返回具体 409;不存在默认公司专属业务限制。
WebSocket 票据及在线连接属于当前单实例服务,重启使票据失效,已撤销令牌通过数据库记录恢复;多实例部署须共享票据和实时会话撤销状态,不能直接复制当前进程缓存方案。
回退保留授权、审计和新增结构。已经向多公司写入后,禁止直接回退到全局读取的旧应用;应暂停业务或向前修复,防止旧代码重新暴露跨公司数据。