Commit Graph

82 Commits

Author SHA1 Message Date
文睿 张
1c316d0105 回归测试用例 2026-10-09 18:18:58 +08:00
文睿 张
b24bb25553 Merge remote-tracking branch 'origin/feature/company-isolation' into feature/company-isolation 2026-10-09 16:38:54 +08:00
文睿 张
2a6a3c6bba docs: 补充后端结构说明并更新公司隔离进度
- 新增《项目后端结构说明》,梳理仓库布局、internal 分层与请求链路
- 《数据库表结构与关系说明》新增第 11 节,记录公司隔离发布后的表、约束与升级阻断规则
- 公司隔离计划补充精简版测试策略,并勾选 Task 2、Task 8 完成项与验收记录
2026-10-09 15:55:52 +08:00
文睿 张
ef4e68b1bd docs: document company isolation rollout and acceptance 2026-10-09 15:54:20 +08:00
文睿 张
dff5420a31 fix: 物料图片上传纳入保存互斥 2026-10-09 14:34:43 +08:00
文睿 张
89368ba3f8 fix: 补齐详情加载的上下文校验与保存互斥
- 入出库详情加载提交前校验公司 ID 与上下文版本,旧公司响应静默丢弃
- 新增 runWithSaving 包裹器,仓库、伙伴、基础配置等直接调接口的页面纳入保存互斥
- 切换守卫卸载时仅撤销自己注册的那一份
2026-10-09 14:25:46 +08:00
文睿 张
3674076377 chore: 忽略编辑器本地历史目录 2026-10-09 14:19:54 +08:00
文睿 张
fafbadef0c chore: 将后端启动文件注释翻译为中文 2026-10-09 14:19:18 +08:00
文睿 张
1a4b48cf9a fix: 切换公司时重置组织级页面状态
- 组织级 store 统一提供 resetForOrganization,清空缓存、查询参数、分页与 loading
- 加载方法提交状态前校验捕获的公司 ID 与上下文版本,旧公司延迟响应不再覆盖新公司
- 页面重挂载清理局部状态(物料选择、单据 ID、申请详情、审批弹窗)
- 新增切换守卫,脏表单时取消切换不清空数据、保存期间禁止切换
- 接上库存事件消费与写请求的保存互斥标记
- 仓库配置页在已有仓库/库位时隐藏新增入口,仅保留编辑与启停
2026-10-09 14:19:17 +08:00
文睿 张
f6a4fc6cb6 fix: 取票失败静默处理,不再弹出连接相关报错 2026-10-09 13:56:48 +08:00
文睿 张
d580efebe5 fix: 接线公司实时连接生命周期并钉住角色撤销语义
- organization store 接线 companySocket:切换后为当前公司重连,登出/空状态只关闭不重连
- companySocket 延迟解析 WebSocket 并静默吞掉取票/建连失败,不阻断公司切换
- authApi 新增 createWebSocketTicket(组织级请求)
- 补 store 三例接线测试:切换触发重连、登出触发关闭、空公司不连接
- 补 Go 用例钉住复核边界:纯业务角色撤销、公司授权仍在时不断流
2026-10-09 13:50:08 +08:00
文睿 张
5021b039c9 feat: 按公司隔离 WebSocket 订阅与事件投递
- 新增 POST /api/auth/websocket-ticket:需 JWT 与公司头,签发 30 秒有效的一次性随机票据
- /ws 握手消费票据并以数据库当前状态复核身份与公司授权,JWT 不进 URL
- Hub 注册/注销/投递选人统一由事件循环串行管理,消除 RLock 内 delete 的并发缺陷
- 事件携带 organization_id,仅投递同公司订阅者,投递前复核失败即关闭连接
- 统一广播签名为 (organizationID, action, orderID, data)
- 新增前端 companySocket 模块:按当前公司取票建连、切换/登出关旧连重连、消息校验组织与上下文版本
2026-10-09 13:39:45 +08:00
文睿 张
a1bc0539dc feat: 按公司隔离物料申请与审批出库 2026-10-09 12:37:08 +08:00
文睿 张
b0cab477db feat: 按公司隔离入出库单据流程 2026-10-09 12:21:53 +08:00
文睿 张
ee18a2e56f fix: 修正普通审批 scope、出库更新加锁顺序与预警物料归属校验 2026-10-09 11:19:39 +08:00
文睿 张
e3cfe88d4e feat: 按公司隔离库存余额与流水 2026-10-09 11:10:13 +08:00
文睿 张
970ba5ccf7 feat: 组织列表为每家公司补充仓库摘要
组织初始化接口 GET /api/auth/organizations 现在为每项授权公司附带
warehouse 摘要({id, name, status, location: {id, name, status} | null} | null),
结构与切换接口一致,供普通用户在公司切换器直接展示,无需放开管理员仓库配置接口。

- model:新增 WarehouseSummary/LocationSummary/OrganizationSummary,
  OrganizationAccess 每项改为内嵌公司资料再加 warehouse 摘要。
- repository:新增 WarehouseRepository.Summary 复用切换接口的解析规则;
  ListAccessible 逐公司补齐摘要,仓库或库位配置不唯一时报错而非随意取第一条,
  无仓库、无库位或已停用如实返回,不回退旧公司数据。
- service:Switch 复用 List 已解析的摘要,响应格式保持不变。
- 测试:新增服务层 sqlmock 用例与仓储层 MySQL 集成用例,
  覆盖启用唯一仓库加启用库位、无仓库、仓库停用三类。
2026-10-09 10:43:35 +08:00
文睿 张
838889ce9f feat: 按公司隔离物料主数据 2026-10-09 10:38:02 +08:00
文睿 张
ffcf7748fb fix: 补全公司切换的保存互斥与旧读请求中止
- 保存请求执行期间禁止切换:organization store 新增 saving/busy 与
  beginSaving/endSaving,切换守卫与切换器禁用条件均由 switching 改为 busy
- 切换提交时 abort 旧读请求:commitOrganizationId 内调用 abortReadRequests,
  不再只在 resetOrganizationContext 里取消
- validateCandidate 仅将 403 视为候选失效,网络异常/500 等向上抛出;
  initialize 置 loadError,App 据此区分“加载失败”与“请先选择公司”并支持重试
- 补竞态与中止测试;需要写请求时旧响应仍被版本丢弃的用例改用 POST 覆盖
2026-10-09 10:34:19 +08:00
文睿 张
6cc3cd5dda feat: 新增公司切换与请求上下文
- 新增 company 请求上下文工具、organization store 与公司切换器
- axios 拦截器为组织级请求写入 X-Organization-ID 并校验上下文版本
- 切换公司用 AbortController 取消旧读请求,丢弃旧响应且不弹提示
- 路由与页面加公司初始化屏障,组织级页面按公司 ID + 版本挂载
2026-10-09 10:19:19 +08:00
文睿 张
8f4460b84a feat: 强制校验可信公司上下文 2026-10-09 09:56:21 +08:00
文睿 张
5691e03f00 feat: 配置用户公司授权与默认公司 2026-10-09 09:47:27 +08:00
文睿 张
8043fa198b feat: 配置用户公司授权与默认公司 2026-10-09 09:47:27 +08:00
文睿 张
ab6c2d6012 feat: add organization access and audit foundation 2026-10-08 17:04:09 +08:00
文睿 张
7e56d462b3 feat: 新增组织授权与审计数据底座 2026-10-08 17:04:09 +08:00
文睿 张
cf9301d3c1 Merge remote-tracking branch 'origin/master'
合并远程master分支更新
2026-10-08 12:00:37 +08:00
文睿 张
6ac9f2cb3c 新增伙伴业务分类,优化物料编辑并整理数据库脚本 2026-10-08 11:54:51 +08:00
copper-xw
6a16edfa24 fix: 修正试用脚本就绪探测并支持端口可配置
MySQL 就绪探测改为走 TCP(127.0.0.1:3306),避免初始化阶段只监听 socket
的临时服务器让探测提前通过,导致后端连入时遇到 unexpected EOF。
后端监听端口改为读取 APP_PORT(默认 8082),与脚本探测的空闲端口一致,
并在启动时和最终提示中打印完整访问地址;npm 默认使用 npmmirror 镜像,
同时将根目录编译产物 /server 加入忽略。
2026-09-30 17:39:03 +08:00
copper-xw
ca26f625f5 chore: 取消追踪误提交的前端依赖并新增试用脚本
frontend/node_modules 是在 Windows 上安装后误提交进仓库的依赖,平台相关的
原生包在 Linux 不可用,且每次重装依赖都会产生大量变更。现将其从版本库移除
(本地文件保留),并补充 .dev-run/ 忽略规则;同时新增 dev-verify.sh,
用于本机编译前后端、以一次性 MySQL 容器隔离试用,关闭即清理。
2026-09-30 15:37:48 +08:00
文睿 张
b1c86593f0 docs: limit first BOM view to one unit 2026-09-23 15:02:15 +08:00
文睿 张
c9f0a69f8e 停用显示修复 2026-09-23 14:53:27 +08:00
文睿 张
048b62766b 停用显示修复 2026-09-23 14:52:54 +08:00
文睿 张
7f06b2ee58 bugs修复 2026-09-23 14:46:51 +08:00
文睿 张
d62d6777d1 bugs修复 2026-09-23 14:20:42 +08:00
文睿 张
e1401532f1 docs: show BOM history in a dedicated dialog 2026-09-23 13:41:23 +08:00
文睿 张
0336181832 docs: plan BOM usage-position details 2026-09-23 12:05:39 +08:00
文睿 张
6933cd82cc docs: defer automatic BOM position remarks 2026-09-23 12:00:12 +08:00
文睿 张
0c7da9d394 docs: refine BOM version and audit design 2026-09-23 10:31:40 +08:00
文睿 张
b75696f44a docs: add initial BOM management design 2026-09-22 18:25:38 +08:00
文睿 张
5d508cd2bb fix: 给不存在的仓库建库位时误报创建成功
CreateLocation 的 INSERT...SELECT 匹配不到仓库时影响 0 行,MySQL 不报错:
err 为 nil、LastInsertId() 返回 0。此前只看 err,接口回 201
"Location created successfully" 且 data.id 为 0,而库里一行都没写进去——
刷新后库位消失,重试还是"成功"。

改为以 RowsAffected 为准(INSERT 的 RowsAffected 就是实际插入行数,不受
clientFoundRows 影响),影响 0 行返回 ErrWarehouseNotFound,接口回 400
"仓库不存在,可能已被删除或不属于当前组织"。与既有的 execExpectingRow、
role_permissions 插入保持同一套路。

实测(docker,重建后端镜像后):
  不存在的仓库 999  ->  HTTP 400,warehouse_locations 1 行 -> 1 行
  真实仓库 1        ->  HTTP 201 id=3,1 行 -> 2 行,库中确有此行

新增 warehouse_test.go 覆盖两个分支,变异验证:去掉 affected==0 判断后
"影响 0 行"用例失败(返回 <nil>,正是线上症状)。
2026-09-22 18:21:31 +08:00
文睿 张
aa017a32b4 fix: 已停用角色导致用户无法保存编辑 2026-09-22 16:41:19 +08:00
文睿 张
688dffebaa fix: 物料页两个作废入口的按钮宽度对齐
「作废」和「撤销作废」轮流出现在同一列,2 字对 4 字,宽度不一样,最右边按钮的
位置会参差。把「作废」改成「作废物料」,两边都是 4 个中文字,同字体同字号下自然
等宽,不依赖写死的 px 值——本项目的测试环境是 jsdom,不做布局计算,量不了像素,
写死的值只能靠猜。

顺带修了测试里的一个自欺:那行物料原本叫「作废物料」,和按钮文案撞名,而按钮
自身就在那一行里,用 toContain 断言行归属会恒为真。改成「旧螺丝」才真的在测。

测试补 1 条(锁住两个按钮的文案字数相同)。变异验证:把「作废物料」改回「作废」,
文案断言和字数断言都变红。
2026-09-22 15:15:54 +08:00
文睿 张
b9c0681c47 fix: 物料页的「撤销」按钮文案改成「撤销作废」
「撤销」在中文 UI 里默认指撤销上一步(Ctrl+Z),看不出在撤销什么;这一页又
没有状态列(文案即状态),用户看到一行数据和绿色「撤销」按钮,无从判断这条
物料被作废了。改成「撤销作废」后,按钮自身就说明了这条物料怎么了。

只改这一处按钮文案。全站其他实体(仓库与库位、主数据、组织、角色、往来单位)
仍用「停用/启用」,那是另一套配对,跟这里的「作废/撤销作废」不冲突。

测试补 1 条,同时锁住两个入口的互斥关系(作废的那行不该再有「作废」,有效的
那行不该有「撤销作废」)。变异验证两处:文案改回「撤销」、以及把作废按钮的
状态条件去掉让两个入口同时出现,断言都变红。
2026-09-22 15:06:08 +08:00
文睿 张
43f680219e fix: 物料主数据页检索不到已作废的物料
检索框是 el-select,必须从下拉候选项里选一项才能设上关键字,而候选项取自
materialOptions(status='active'),作废物料压根不在里面,搜不到也选不中。
可这一页的列表查询走 status='all',本身就显示着作废物料——同一份数据两条
路径不一致,而且这一页正是恢复作废物料的入口。

改法照 Logs.vue / Stock.vue 流水检索框的既有做法:这一页改用含作废的
historicalMaterialOptions / fetchHistoricalMaterialOptions()。业务单据页
(入库/出库/申请)保持只用有效物料不动。

后端无需改动,已实测:status=all&search=A010201 返回 1 条 obsolete,
status=active 对同一关键字返回 0 条。

测试补 1 条。变异验证:候选项来源和 onMounted 那处分别改回原样,断言都变红。
2026-09-22 14:53:18 +08:00
文睿 张
bcee877278 fix: 物料/用户增删改后列表的搜索关键字被丢弃
写操作后原地重新拉取列表时,search 参数被写死成缺省的空串:搜索框里还显示着
用户输入的关键字,列表却退回全量。用户会把全量列表当成筛选结果,接下来的作废/
删除操作就打在被误认的那一条上。store 层一直有这个缺陷却没被拦住,因为页面
那层没有测试。

修法照 stock.js / approval.js 已有的约定(store 存一份 filters,写操作原地刷新
时取出来用):

- materials.js:编辑/作废/恢复沿用 search 和 status
- users.js:编辑/删除沿用 search
- 新增走反方向,清空筛选并回第 1 页:新建的项不一定匹配当前关键字,沿用搜索
  就看不见它,用户会以为没建成功。物料按 id 倒序,回第 1 页正好能看见
- Materials.vue / Users.vue:新增成功后把搜索框一起清空,否则 store 清了筛选而
  搜索框没清,同样是表里不一

测试补 14 条(两个 store + 两个页面)。全部做过变异验证:把实现改回原样后,
6 条 store 断言和 2 条页面断言确实变红,且只有它们变红。
2026-09-22 14:40:21 +08:00
文睿 张
fbd0bc1341 fix: 审批/取消后列表的筛选条件被丢弃
写操作后原地重新拉取列表时,status 参数被写死成 '':列表悄悄退回全量(已通过、
已拒绝的单子全冒出来),而页面下拉框仍显示着用户选中的筛选值。用户以为看到的
是筛选结果,实际不是。store 层一直有这个缺陷却没被拦住,因为页面那层没有测试。

修法照 stock.js 已有的约定(stockFilters 配合 stock.test.js 的断言):store 存一份
filters,写操作原地刷新时取出来用。

- approval.js:approve / reject 沿用筛选
- application.js:cancel 沿用筛选
- application.js:create 保持清空筛选并回第 1 页——刚提交的是待审批单,若沿用
  「已通过」之类的筛选就看不见它,用户会以为没提交成功
- Application.vue:提交成功后同步把下拉框重置为「全部」,否则 store 清了筛选而
  下拉没清,同样是表里不一

测试补 9 条(approval/application 两个 store + Application 页面层)。全部做过变异
验证:把实现改回原样后对应断言确实变红,且只有该变红的那几条变红。
2026-09-22 14:10:52 +08:00
文睿 张
bf7f7129dc feat: 审批页状态筛选补上「已取消」
下拉此前只有全部/待处理/已通过/已拒绝。既然已通过、已拒绝都列了,说明这页是想
看历史单子的,已取消的缺席就不一致。

后端无需改动:material_applications.status 是 ENUM('pending','approved',
'rejected','cancelled'),ListAll 直接 AND a.status = ?,没有白名单拦截。
StatusBadge 也已备好 cancelled 的灰色标签,筛选结果不会显示成裸英文。
2026-09-22 14:10:51 +08:00
文睿 张
4905e60f8c feat: 仓库与库位补上编辑与停用/启用入口
后端 PUT /warehouses/:id 与 PUT /warehouses/:warehouseId/locations/:locationId
本就接受 name + status(service/repository 都只改这两列),api 层的 update/updateLocation
也早已封装好,缺的只是前端入口——页面此前只有「新增」,无法改状态。

- 仓库标题栏与库位行各加「编辑」按钮,弹窗内改名称 + 启用/停用
- 标题栏按钮必须 @click.stop:el-collapse-item 整条 header 都挂着展开/收起监听
- 库位状态列从直接显示英文 inactive 改为中文 el-tag,与仓库及主数据页统一
- 编辑态编码只读且不参与必填校验(code 不可变,后端 Update 也不接收)
- 表单重置走已有的 replaceAllKeys,编辑只灌白名单字段,避免残留 id 让新增变成覆盖更新
- 库位所属仓库只存 id 不存对象,load() 整体替换列表后不会读到旧数据
- save() 显式吞掉错误:提示由 axios 响应拦截器统一负责,同时避免 @click 经
  el-button 透传到原生 button、绕过 Vue 异步错误包装而产生的 unhandled rejection
- 测试补 8 条:update/updateLocation 的参数与顺序、编辑态不误报编码、
  改完再新增仍走 create、状态列中文、编辑按钮不收起面板、失败不关窗
2026-09-21 17:49:49 +08:00
文睿 张
696e90893b fix: 主数据编辑的两类静默失败
UPDATE 漏列:
- code_rules 的 SET 补上 category_id。此前改分类提示"保存成功"但不落库,
  是唯一用户可达的活缺陷(同页的分类下拉没有 disabled,且规则只能改不能删,
  没有"删了重建"这条退路)
- code_rules 有 uk_code_rules_org_category,补列后改到已占用的分类会撞唯一键,
  新建与更新统一翻成中文的 ErrDuplicateCodeRule
- 分类/单位/仓库/库位/伙伴的编码定为不可变:分类编码是编码规则前缀的来源
  (init.sql 的种子数据直接取 material_categories.code),伙伴编码是对外业务
  编号,改掉会让历史数据脱节且查不回来。service 的 Update* 不再要求编码非空
  ——原先是"必须传、传了却被丢弃";新建侧的必填校验保留
- 顺带补齐两处同款漏网:organization.go 的 code 与 partner.go 的 partner_code
  (前者 service 本就不校验编码,自洽,无需改)

记录不存在时的静默成功:
- DSN 开启 clientFoundRows,让 RowsAffected 返回"匹配"行数而非"实际改变"行数。
  否则"编辑时一个字段都没改"和"记录不存在"都返回 0,无法区分,直接加检查会把
  正常保存误判成记录不存在(真 MySQL 8.0 实测)
- 新增 repository.execExpectingRow,7 个 Update* 影响 0 行时返回 ErrRecordNotFound;
  伙伴走事务内的 SELECT ... FOR UPDATE,只需翻译 sql.ErrNoRows
- 既有 4 处 RowsAffected 用法逐处核对过,均不受该 flag 影响

测试:补 6 个 Update* 的存在性检查、编码必填约束的两端、唯一键翻译、DSN 拼接;
全部做过变异验证(抽掉实现后对应测试确实变红)。
2026-09-21 17:26:53 +08:00
文睿 张
837cc5787b 前端问题优化 2026-09-21 16:53:44 +08:00