Files
huachuang/docs/superpowers/plans/2026-07-22-outbound-create-plan.md
2026-07-22 14:23:13 +08:00

20 KiB
Raw Permalink Blame History

出库单新增功能实现计划

面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development推荐或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(- [ ])语法来跟踪进度。

目标: 按确认的布局实现出库单新增页,通过库存箱号展开全部可用子卷或手工新增物料汇总,并事务保存 wms_iostorinv 主表和 wms_iostorinvdtl 明细。

架构: 前端由主弹窗、库存选择弹窗、手工汇总弹窗组成,页面只维护现有表字段和临时展示字段。后端提供可用库存查询/箱号展开接口与聚合创建接口;聚合服务调用 base 编码 API、重新计算汇总值并在一个事务内写入主表和明细。

技术栈: Java 17、Spring Boot、MyBatis XML、OpenFeign、Vue 3、TypeScript、Ant Design Vue Next、Vben Form、Vxe Grid、Vitest


文件结构

后端

  • 修改 nl-module-wms/nl-module-wms-server/pom.xml:引入 nl-module-base-api
  • 创建 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/rpc/config/RpcConfiguration.java:注册 CodeGenApi Feign 客户端。
  • 创建 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/IostorInvCreateReqVO.java:聚合创建请求及内部明细对象。
  • 创建 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/AvailableInventoryPageReqVO.java:库存筛选与分页参数。
  • 创建 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/AvailableInventoryRespVO.java:库存与页面展示结果。
  • 创建 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/ExpandAvailableInventoryReqVO.java:仓库和已选箱号集合。
  • 修改 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapper.java:声明库存查询与按箱号展开方法。
  • 修改 nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml:实现跨仓库结构和组盘库存的 SQL。
  • 修改 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvService.java:声明库存查询、展开和聚合创建方法。
  • 修改 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceImpl.java:实现校验、编码生成、汇总和事务写入。
  • 修改 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/IostorInvController.java:暴露三个管理端接口。
  • 修改 nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java:增加编码和库存失效错误码。
  • 创建 nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceLocalSpringTest.javaSpring 集成测试。

前端

  • 修改 nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts:增加聚合请求、库存结果类型和接口函数。
  • 创建 nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.ts:纯函数计算明细数、总重量和去重键。
  • 创建 nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.test.ts:汇总联动的红绿测试。
  • 创建 nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue:库存筛选、多选和确认展开。
  • 创建 nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/manual-detail.vue:物料汇总新增。
  • 修改 nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/form.vue:实现确认布局、明细联动和聚合提交。

任务 1建立后端请求契约与 RPC 依赖

  • 步骤 1先创建 Spring 集成测试骨架并验证当前编译失败

IostorInvServiceLocalSpringTest.java 中使用 @SpringBootTest@ActiveProfiles("local")@Resource IostorInvService,先引用尚不存在的 IostorInvCreateReqVOcreateOutbound

@SpringBootTest
@ActiveProfiles("local")
class IostorInvServiceLocalSpringTest {
    @Resource
    private IostorInvService iostorInvService;

    @Test
    void createOutboundRejectsEmptyDetails() {
        IostorInvCreateReqVO reqVO = new IostorInvCreateReqVO();
        Assertions.assertThrows(ConstraintViolationException.class,
                () -> iostorInvService.createOutbound(reqVO));
    }
}

运行:

mvn -pl nl-module-wms/nl-module-wms-server -am -DskipTests compile

预期FAIL提示 IostorInvCreateReqVOcreateOutbound 不存在。

  • 步骤 2增加 base API 依赖和 Feign 注册

在 WMS server 的 pom.xml 增加:

<dependency>
    <groupId>cn.nl.cloud</groupId>
    <artifactId>nl-module-base-api</artifactId>
    <version>${revision}</version>
</dependency>

创建 RpcConfiguration,使用 @Configuration(value = "wmsRpcConfiguration", proxyBeanMethods = false)@EnableFeignClients(clients = CodeGenApi.class)

  • 步骤 3创建带注解校验的聚合请求对象

IostorInvCreateReqVO 顶层包含 @NotEmpty billType@NotEmpty storId@NotNull bizDateremark@NotEmpty @Valid List<Detail> details;内部 Detail 包含:

@NotEmpty(message = "物料编码不能为空")
private String materialCode;
private String materialId;
private String pcsn;
@NotNull(message = "出库重量不能为空")
@DecimalMin(value = "0.001", message = "出库重量必须大于0")
private BigDecimal planQty;
private String qtyUnitId;
private String qtyUnitName;
private String sourceBillCode;
private String sourceBillType;
private String sourceBilldtlId;
private String remark;

不接收 billCodebillStatusdetailCounttotalWeightseqNo,这些字段全部由服务端生成。

  • 步骤 4声明 Service 方法并验证测试从编译失败进入校验失败/通过
@Validated
public interface IostorInvService {
    String createOutbound(@Valid IostorInvCreateReqVO reqVO);
}

运行:

mvn -pl nl-module-wms/nl-module-wms-server -am -Dtest=IostorInvServiceLocalSpringTest -Dsurefire.failIfNoSpecifiedTests=false test

预期:测试能够启动且空请求因参数校验被拒绝;若 local 数据源不可用,保留测试并记录环境阻塞,继续用编译验证生产契约。

  • 步骤 5提交契约变更
git add nl-module-wms/nl-module-wms-server/pom.xml nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/rpc/config/RpcConfiguration.java nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/IostorInvCreateReqVO.java nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvService.java nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceLocalSpringTest.java
git commit -m "feat: 定义出库单聚合创建契约"

任务 2实现可用库存查询和按箱号展开

  • 步骤 1扩展失败测试定义箱号展开行为

在集成测试准备同一仓库下箱号 BOX-001 的两个可用子卷和另一个仓库的同箱数据,断言:

List<AvailableInventoryRespVO> rows = iostorInvService.expandAvailableInventory(
        "STOR-001", List.of("BOX-001", "BOX-001"));
Assertions.assertEquals(2, rows.size());
Assertions.assertTrue(rows.stream().allMatch(row -> "BOX-001".equals(row.getVehicleCode())));

运行指定测试预期FAIL方法和响应对象尚不存在。

  • 步骤 2创建查询参数和响应对象

AvailableInventoryPageReqVO extends PageParam,字段为 @NotEmpty storIdmaterialCodevehicleCodepcsnAvailableInventoryRespVO 使用库存现有字段:vehicleCodepcsnmaterialIdmaterialCodematerialName(仅返回展示)、availableQty、单位、来源字段及仓库标识。ExpandAvailableInventoryReqVO 包含 @NotEmpty storId@NotEmpty List<String> vehicleCodes

  • 步骤 3声明 Mapper 方法
PageResult<AvailableInventoryRespVO> selectAvailableInventoryPage(AvailableInventoryPageReqVO reqVO);

List<AvailableInventoryRespVO> selectAvailableInventoryByVehicleCodes(
        @Param("storId") String storId,
        @Param("vehicleCodes") Collection<String> vehicleCodes);
  • 步骤 4在 XML 实现统一库存 SQL

基础关联为 wms_group_plate gp INNER JOIN wms_structattr sa ON sa.storagevehicle_code = gp.vehicle_code LEFT JOIN base_materialbase mb ON mb.material_id = gp.material_id AND mb.is_deleted = 0,固定条件为 sa.stor_id = #{storId}gp.status = '可用'gp.qty - gp.frozen_qty > 0 和 WMS 两表 is_deleted = 0。分页查询增加物料、箱号、子卷号筛选;展开查询使用 <foreach> 的箱号集合。结果中 available_qty = gp.qty - gp.frozen_qty,物料名称从 mb.material_name 返回但不写入明细表。

  • 步骤 5Service 对箱号去重后查询全部子卷
List<String> distinctVehicleCodes = vehicleCodes.stream().distinct().toList();
return iostorinvDtlMapper.selectAvailableInventoryByVehicleCodes(storId, distinctVehicleCodes);

控制器提供:

@GetMapping("/availableInventoryPage")
public CommonResult<PageResult<AvailableInventoryRespVO>> getAvailableInventoryPage(
        @Valid AvailableInventoryPageReqVO reqVO)

@PostMapping("/expandAvailableInventory")
public CommonResult<List<AvailableInventoryRespVO>> expandAvailableInventory(
        @Valid @RequestBody ExpandAvailableInventoryReqVO reqVO)
  • 步骤 6运行集成测试和 Mapper 编译
mvn -pl nl-module-wms/nl-module-wms-server -am -Dtest=IostorInvServiceLocalSpringTest -Dsurefire.failIfNoSpecifiedTests=false test

预期:同箱两子卷全部返回、重复箱号不产生重复行、其他仓库数据不返回。

  • 步骤 7提交库存查询变更
git add nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapper.java nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/IostorInvController.java nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceLocalSpringTest.java
git commit -m "feat: 支持按箱号展开可用库存子卷"

任务 3实现主表与明细事务保存

  • 步骤 1补充失败测试

构造两条重量 12.5007.250 的明细,创建后查询数据库并断言:主表 ioType = "OUT"billStatus = "生成"detailCount = 2totalWeight = 19.750;明细序号为 1/2、assignQty = 0unassignQty = planQty。测试请求不传任何汇总字段。

  • 步骤 2实现编码生成和事务写入

createOutbound 标记 @Transactional(rollbackFor = Exception.class)

  1. 构造 CodeGenerateReqDTO,设置项目现有出入库规则编码 IO_CODE
  2. 调用 CodeGenApi.generate,检查 CommonResult.isSuccess() 且编码非空;失败抛出中文业务异常。
  3. 手工逐字段创建 IostorInvDO,固定 ioType = "OUT"billStatus = "生成"
  4. 使用 details.stream().map(Detail::getPlanQty).reduce(BigDecimal.ZERO, BigDecimal::add) 计算总重量并设置明细数。
  5. 插入主表后,循环构造 IostorinvDtlDO,写入关联 ID、连续序号、物料、pcsn、计划重量、分配数量、单位、来源和备注。
  • 步骤 3增加控制器端点和错误码
@PostMapping("/createOutbound")
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:create')")
public CommonResult<String> createOutbound(@Valid @RequestBody IostorInvCreateReqVO reqVO) {
    return success(iostorInvService.createOutbound(reqVO));
}

错误码仅增加“单据号生成失败”和“所选库存已不可用”,不新增数据库字段或表。

  • 步骤 4验证红绿与事务回滚

运行集成测试;再让测试用编码 API 失败配置触发异常,断言主表和明细数量均未增加。

  • 步骤 5提交聚合保存
git add nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceLocalSpringTest.java
git commit -m "feat: 事务保存出库单及明细"

任务 4扩展前端 API 与明细汇总纯函数

  • 步骤 1先写失败的 Vitest 测试
import { describe, expect, it } from 'vitest';
import { summarizeDetails } from './detail-summary';

describe('summarizeDetails', () => {
  it('按明细条数和出库重量计算汇总', () => {
    expect(summarizeDetails([{ planQty: 12.5 }, { planQty: 7.25 }])).toEqual({
      detailCount: 2,
      totalWeight: 19.75,
    });
  });
});

运行:

pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben vitest run apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.test.ts --dom

预期FAIL模块不存在。

  • 步骤 2实现最小汇总函数
export function summarizeDetails(details: Array<{ planQty?: number }>) {
  return {
    detailCount: details.length,
    totalWeight: details.reduce((sum, item) => sum + Number(item.planQty || 0), 0),
  };
}
  • 步骤 3在 API 层增加明确类型和三个接口

定义 OutboundDetailOutboundCreateReqAvailableInventoryAvailableInventoryPageReq,并增加:

export const getAvailableInventoryPage = (params: AvailableInventoryPageReq) =>
  requestClient.get<PageResult<AvailableInventory>>('/wms/iostor-inv/availableInventoryPage', { params });
export const expandAvailableInventory = (storId: string, vehicleCodes: string[]) =>
  requestClient.post<AvailableInventory[]>('/wms/iostor-inv/expandAvailableInventory', { storId, vehicleCodes });
export const createOutbound = (data: OutboundCreateReq) =>
  requestClient.post<string>('/wms/iostor-inv/createOutbound', data);
  • 步骤 4运行 Vitest 和类型检查
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben vitest run apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.test.ts --dom
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben --filter @vben/web-antdv-next typecheck

预期:汇总测试通过,新增 API 类型无错误。

  • 步骤 5提交前端基础能力
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.ts nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.test.ts
git commit -m "feat: 增加出库单前端聚合接口与汇总逻辑"

任务 5实现库存选择与手工汇总弹窗

  • 步骤 1实现库存选择弹窗

inventory-select.vue 接收主弹窗传入的 storId表单筛选物料编码、箱号、子卷号Vxe Grid 使用服务端分页和多选。确认时只收集选中行的 vehicleCode,调用 expandAvailableInventory;返回结果逐子卷映射为明细,planQty 默认为 availableQty。勾选过程不自动修改其他子卷的选中状态。

  • 步骤 2实现手工汇总弹窗

复用 views/base/materialbase/components/MaterialSelectModal.vue 选择一个物料,填写大于 0 的总出库重量和备注;确认对象必须显式包含:

{
  materialId,
  materialCode,
  materialName,
  planQty,
  qtyUnitId,
  qtyUnitName,
  vehicleCode: undefined,
  pcsn: undefined,
  sapBatchNo: undefined,
  remark,
}
  • 步骤 3运行前端类型检查
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben --filter @vben/web-antdv-next typecheck

预期:两个弹窗 props、emit、API 结果映射均无类型错误。

  • 步骤 4提交两个弹窗
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/manual-detail.vue
git commit -m "feat: 增加出库库存选择和手工汇总弹窗"

任务 6按参考图重构新增主弹窗

  • 步骤 1实现主表布局与默认值

重构 form.vue 为宽屏弹窗:单据号显示“保存时自动生成”;仓库和业务类型为必选下拉;状态只读“生成”;业务日期使用 dayjs() 默认为当天;明细数和总重量只读;备注跨列。仓库使用 getBsrealStorAttrSimpleList,业务类型从现有字典缓存中过滤出库业务类型。

  • 步骤 2实现明细表与两种入口

列顺序严格按确认原型序号、物料编码、物料名称、箱号、子卷号、SAP 批次号、出库重量、单位、源单号、明细备注、操作。出库重量可编辑;删除、库存返回、手工汇总返回均调用 summarizeDetails 更新只读汇总。

  • 步骤 3实现仓库切换和提交

仓库值发生实际变化且明细非空时清空明细。提交只发送 billTypestorIdbizDateremarkwms_iostorinvdtl 可持久化字段不得发送箱号、物料名称、SAP 批次号、单据号、状态和汇总字段。

  • 步骤 4运行前端测试、类型检查与构建
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben vitest run apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.test.ts --dom
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben --filter @vben/web-antdv-next typecheck
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben --filter @vben/web-antdv-next build

预期:测试、类型检查和生产构建均以退出码 0 完成。

  • 步骤 5提交主弹窗
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/form.vue
git commit -m "feat: 按参考布局实现出库单新增页"

任务 7端到端核对与最终验证

  • 步骤 1核对数据库字段边界

检查最终 diff确认没有 DDL、迁移脚本或 DO 新字段;前端展示字段没有进入 createOutbound 请求;所有明细只通过 IostorinvDtlMapper 写入 wms_iostorinvdtl

  • 步骤 2运行后端完整模块验证
mvn -pl nl-module-wms/nl-module-wms-server -am clean test -DskipTests=false

预期WMS server 及依赖模块编译成功,测试失败数为 0。

  • 步骤 3运行前端完整验证
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben vitest run apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-summary.test.ts --dom
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben --filter @vben/web-antdv-next typecheck
pnpm --dir nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben --filter @vben/web-antdv-next build

预期:三条命令均退出码 0。

  • 步骤 4人工验收关键流程

启动前后端后确认新增页默认值正确未选仓库不能选择库存选择任一库存记录会按箱号带回全部子卷勾选本身不联动手工汇总的箱号、子卷号、SAP 批次号为空;明细汇总实时变化;切换仓库清空明细;保存后主表和全部明细同时存在。

  • 步骤 5提交验证阶段必要修正

仅当验证产生修正时提交相关文件:

git add nl-module-wms nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv
git commit -m "fix: 完善出库单新增流程验证问题"