VibeCoding 实战:用 Qoder 从零开发 Java Spring Boot WMS 系统(七)
第6章 入库管理模块:状态流转 + 事务写库存
本章目标:开发入库模块,这是本项目第一个有"状态机"和"跨表事务"的复杂业务。入库单要经历"草稿→待审核→已审核→执行入库→已完成"的流转,执行入库时要把商品上架到储位、写库存、记流水——这几步必须在一个事务里完成。沿用第5章的五步流程,但重点练"业务规则提示词怎么写"。
学习目标
- 会用提示词让 AI 实现"单据状态机"(状态只能按规则流转)
- 理解并实现"执行入库"的数据库事务(改明细 + 写库存 + 记流水)
- 会处理主表 + 明细(一对多)的增改查
- 会用"边界用例"验证状态与库存的正确性
6.1 讲清范围:入库单的业务规则最复杂,先对齐
提示词
开发「入库管理」模块。# 引用 docs/需求规格说明书.md 入库流程、#docs/api.md 入库接口、#项目规则。
先不写代码,确认你理解以下业务规则并复述:
单据结构:入库单主表 wms_inbound_order(单号、仓库、状态、制单人、审核人、时间)
+ 明细 wms_inbound_item(商品、计划数量、已入库数量)
+ 执行记录 wms_inbound_record(入库单、商品、储位、实际入库数量)
状态机(只能按规则流转,非法流转要拒绝):
DRAFT草稿 --提交--> PENDING待审核 --审核通过--> APPROVED已审核 --执行入库--> COMPLETED已完成
--审核驳回--> REJECTED已驳回(可改回草稿)
任意未完成状态可作废 CANCELLED
关键业务规则:
1. 单号自动生成,规则:RK + yyyyMMdd + 4位流水(如 RK202610030001)
2. 草稿/驳回状态才可改明细;提交后不可改
3. 只有 PENDING 能被审核;只有 APPROVED 能执行入库
4. 执行入库:对每一"实际入库明细(商品+储位+数量)",
在事务中:wms_inbound_item 累加已入库数量 → upsert wms_inventory(商品+储位) 增加库存 → 写 wms_inventory_log 流水
5. 支持部分入库:已入库数量 < 计划数量时单据仍为 APPROVED;全部入库完成才 COMPLETED
请复述并指出实现难点,我确认后再开发。
预期结果
AI 复述出状态流转表、事务步骤,并可能提到难点:"并发下已入库数量累加"、"upsert 库存用唯一索引 + ON DUPLICATE KEY UPDATE"。
注意事项
- ⚠️ 状态机是本模块灵魂,也是 AI 最容易写漏的地方(比如没校验"草稿不能直接执行")。提示词里把"哪个状态能转去哪个状态"写成硬规则,别只说"要有审核"。
- 💡 单号生成先做简单版(日期+流水)。真正高并发要防重号,留到第10章"进阶"讨论,别在这卡住。
6.2 后端:主表+明细的创建与查询
先做"创建入库单(含明细)"和"查询详情",这是最基础的一对多。
提示词
实现入库模块后端第一步(包 com.yjoffer.miniwms.inbound):
1. 实体与 Mapper:InboundOrder、InboundItem(均映射对应表,逻辑删除、自动填充同前)
2. DTO/VO:InboundFormVO(主表信息 + items 列表[{goodsId, planQty}],带校验:至少1条明细、planQty 为正整数)
InboundQueryDTO(按单号/仓库/状态/时间分页)
3. InboundService:
- create(InboundFormVO):生成单号(RK+日期+4位流水)、状态置 DRAFT、保存主表+明细(一个事务)
- page(query)、detail(id):详情返回主表 + 明细列表(明细带上商品名称/编码)
- updateDraft(id, form):仅 DRAFT/REJECTED 可改,覆盖明细
4. Controller:POST /api/inbound(创建)、GET /api/inbound/page、GET /api/inbound/{id}、PUT /api/inbound/{id}
路径对齐 #docs/api.md,统一 Result 返回
5. mvnw compile 通过;用 curl 演示:创建一张含2条明细的入库单 → 查详情确认主表明细都对,贴结果。
预期结果
- 能创建含多行明细的入库单,状态是 DRAFT,单号形如
RK202610030001 - 详情接口能返回主表 + 明细(带商品名)
注意事项
- ⚠️ 一对多保存要事务:主表插入拿到 id、明细再带外键批量插入,中途失败要回滚。确认
@Transactional加在 create 上。 - ⚠️ 单号流水并发:同一天第二张单要拿到
...0002。测试时连续建两张,确认尾号递增不重复。
6.3 后端:状态流转接口(提交/审核/驳回)
提示词
继续实现入库单状态流转,全部走状态机校验(非法流转抛 BusinessException 并说明当前状态):
- submit(id):DRAFT/REJECTED → PENDING(提交后不可再改明细)
- approve(id):PENDING → APPROVED,记录审核人和审核时间(审核人取当前登录用户)
- reject(id, reason):PENDING → REJECTED,记录驳回原因
- cancel(id):非 COMPLETED → CANCELLED
Controller 相应新增 POST /api/inbound/{id}/submit、/approve、/reject、/cancel。
完成后用 curl 走一遍:创建→提交→审核通过;再单独验证非法流转:对一张 DRAFT 单直接调 approve,
应返回"当前状态不允许审核"的错误。把两次结果贴给我。
预期结果
- 正常链路:草稿→待审核→已审核,每步状态字段正确更新
- 非法链路(对 DRAFT 直接审核)被拒绝并返回友好错误
注意事项
- ⚠️ "能流转"要测,"不能流转"更要测。让 AI 演示非法流转被拦截,是验证状态机真的生效的唯一办法。
- 💡 审核人来自登录上下文(第4章 JWT 里的 userId)。确认 AI 是取当前登录用户,而不是让前端随便传一个 userId(安全)。
6.4 后端:执行入库(本章最难——跨表事务)
执行入库是 WMS 的核心动作:把"计划"变成"实际库存"。一次请求携带若干"商品→储位→数量"的上架明细,要在一个事务里完成三件事。
提示词
实现入库执行接口 POST /api/inbound/{id}/execute:
入参 ExecuteVO:records[{goodsId, locationId, qty}](本次实际入库明细,qty 正整数)
仅 APPROVED 状态可执行。在【单个事务】内,对每条 record 顺序完成:
1. 更新/累加 wms_inbound_item 的已入库数量(按 goodsId 匹配该单对应明细行)
2. upsert wms_inventory:按 (goodsId, locationId) —— 存在则 qty 累加,不存在则新增一行
(用唯一索引 + INSERT ... ON DUPLICATE KEY UPDATE,避免先查后写的并发问题)
3. 写 wms_inventory_log:记录 商品、储位、变动类型=IN、变动数量、变动前数量、变动后数量、关联入库单号
处理完本单所有明细后:
- 若每条明细的 已入库数量 >= 计划数量 → 单据状态置 COMPLETED;否则保持 APPROVED(支持部分入库)
要求:
- 方法加 @Transactional(rollbackFor=Exception.class),任一步失败整体回滚
- 校验:本次入库数量不得超过该明细"计划-已入库"的剩余量,超出报错
mvnw compile 后,用 curl 完整演示一条主链路(见 6.5),并演示"超量入库被拒"和"事务回滚"两个异常场景。
预期结果
- 执行入库后:库存表出现对应"商品+储位"的数量、流水表新增 IN 记录、单据状态更新
- 超量入库被拒绝;人为制造一步失败时,前面的写库操作全部回滚(库存不脏)
6.5 端到端主链路自测(这就是 M1 验收的一半)
提示词
请用 curl 完整跑一遍入库主链路,并把每一步的关键响应贴出来(尤其库存与流水的变化):
1. 登录拿 token
2. 创建入库单(含商品G1计划100、商品G2计划50),状态 DRAFT
3. 提交 → 审核通过 → 状态 APPROVED
4. 执行入库:G1 上架到储位 L1 数量100、G2 上架到储位 L2 数量50
5. 查询库存:确认 G1@L1=100、G2@L2=50
6. 查询库存流水:确认有两条 IN 记录,变动后数量正确、关联单号是第2步的入库单号
7. 查询入库单:状态应为 COMPLETED
每一步结果不符合预期就修复后重跑。
看到第 5、6 步数据正确,你就亲手跑通了 WMS 的"入"这条腿。
6.6 前端:入库单列表 + 创建 + 执行页
提示词
在前端 wms-web 生成入库模块页面,风格与商品页一致:
1. src/api/inbound.ts 封装全部入库接口
2. InboundList.vue:查询区(单号/状态/仓库) + 表格(单号/仓库/状态/制单人/时间/操作) + 分页
操作列按状态动态显示按钮:草稿[编辑/提交/作废]、待审核[审核/驳回]、已审核[执行入库/作废]、已完成[查看]
3. InboundForm.vue(或弹窗):创建/编辑入库单——选仓库、动态增删明细行(选商品+计划数量)
4. InboundExecute.vue(或弹窗):对已审核单据录入"商品+储位(级联选择)+数量"执行入库
5. 加菜单「入库管理」,路由配好。npm run dev 验证整条 UI 流程可点通。
注意事项
- ⚠️ 前端按钮要和后端状态机一致:不能出现"草稿单显示审核按钮"。让 AI 按状态渲染操作按钮(6.3 的规则)。
- 💡 执行入库时储位选择用第5章做的"仓库→库区→储位"级联,直接复用。
6.7 提交
入库模块后端与前端完成、6.5 主链路自测通过。
git add . && git commit -m "feat: 入库模块完成(状态机+一对多明细+执行入库事务+库存流水+前端页面)"
本章检查清单
- [ ] 入库单状态机:正常流转 + 非法流转拦截均验证通过
- [ ] 执行入库事务:改明细 / 写库存 / 记流水三者原子性(含回滚验证)
- [ ] 支持部分入库,全部入库后置 COMPLETED
- [ ] 6.5 端到端主链路 7 步全部符合预期
- [ ] 前端列表按钮按状态渲染,创建/执行页可用
- [ ] 已提交 Git
入库打通,进入 第7章 出库管理模块——方向和入库相反,但多一个"防超卖"的硬约束。
常见问题
Q:为什么执行入库要强调 upsert 而不是"先查有没有再插入"?
A:先查后写在并发下有竞态(两个请求都查到"没有",都插入 → 要么唯一索引报错、要么重复行)。用唯一索引 + ON DUPLICATE KEY UPDATE 一条 SQL 原子完成。这是让 AI 写对、也是你要看懂的关键点。
Q:事务加了 @Transactional 但感觉没回滚?
A:常见原因:异常被 try-catch 吞了没往外抛、或同类方法内部调用(未经代理)。让 AI 检查这两点。借这个问题理解 Spring 事务失效的经典场景。
Q:部分入库、一单多储位,逻辑好绕,AI 也写乱了怎么办? A:退回一步,先用最简用例锁定规则:"一张单一个商品,分两次各入一半",跑通再叠加复杂场景。复杂业务要"小步验证",别指望一把梭。