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

12 KiB
Raw Permalink Blame History

公司隔离验收与交付记录

验收日期: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;账号需要创建/清理隔离库、建表/触发器权限。不要填生产连接。

$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 票据及在线连接属于当前单实例服务,重启使票据失效,已撤销令牌通过数据库记录恢复;多实例部署须共享票据和实时会话撤销状态,不能直接复制当前进程缓存方案。

回退保留授权、审计和新增结构。已经向多公司写入后,禁止直接回退到全局读取的旧应用;应暂停业务或向前修复,防止旧代码重新暴露跨公司数据。