From 095b4a19dc9802e2bb22a5f7c076ee5453cfdde9 Mon Sep 17 00:00:00 2001 From: ck-chenkang Date: Mon, 22 Jun 2026 14:49:50 +0800 Subject: [PATCH] feat: add bit sample warehouse operations --- sql/mysql/2026-06-22-bit-sample-wms.sql | 33 +++ .../admin/bitwms/BitWmsController.java | 76 ++++++ .../admin/bitwms/vo/BitWmsInboundReqVO.java | 35 +++ .../admin/bitwms/vo/BitWmsLabelRespVO.java | 32 +++ .../admin/bitwms/vo/BitWmsMoveReqVO.java | 39 +++ .../admin/bitwms/vo/BitWmsOutboundReqVO.java | 37 +++ .../admin/bitwms/vo/BitWmsScanRespVO.java | 21 ++ .../stock/vo/move/ErpStockMoveSaveReqVO.java | 17 +- .../dataobject/stock/ErpStockMoveItemDO.java | 18 +- .../WarehouseLocationMapper.java | 3 + .../erp/service/bitwms/BitWmsService.java | 24 ++ .../erp/service/bitwms/BitWmsServiceImpl.java | 236 ++++++++++++++++++ .../stock/ErpStockMoveServiceImpl.java | 14 +- .../WarehouseLocationMapper.xml | 10 +- .../service/bitwms/BitWmsServiceImplTest.java | 129 ++++++++++ 15 files changed, 715 insertions(+), 9 deletions(-) create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/BitWmsController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsInboundReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLabelRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsMoveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsOutboundReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsScanRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImplTest.java diff --git a/sql/mysql/2026-06-22-bit-sample-wms.sql b/sql/mysql/2026-06-22-bit-sample-wms.sql index 252c2fd92..deaee5297 100644 --- a/sql/mysql/2026-06-22-bit-sample-wms.sql +++ b/sql/mysql/2026-06-22-bit-sample-wms.sql @@ -401,3 +401,36 @@ INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) SELECT @menu_seed, '明细导出', 'erp:bit-wms-record:export', 3, 20, @bit_record_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' WHERE @bit_record_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-record:export' AND deleted = b'0'); + +-- 5. 调拨项库区字段:支持 BIT 样品仓同仓不同库区移库 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'from_area_id') = 0, + 'ALTER TABLE erp_stock_move_item ADD COLUMN from_area_id bigint NULL COMMENT ''调出库区编号'' AFTER from_warehouse_id', + 'SELECT 1'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'from_area_name') = 0, + 'ALTER TABLE erp_stock_move_item ADD COLUMN from_area_name varchar(64) NULL COMMENT ''调出库区名称'' AFTER from_area_id', + 'SELECT 1'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'to_area_id') = 0, + 'ALTER TABLE erp_stock_move_item ADD COLUMN to_area_id bigint NULL COMMENT ''调入库区编号'' AFTER to_warehouse_id', + 'SELECT 1'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'to_area_name') = 0, + 'ALTER TABLE erp_stock_move_item ADD COLUMN to_area_name varchar(64) NULL COMMENT ''调入库区名称'' AFTER to_area_id', + 'SELECT 1'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/BitWmsController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/BitWmsController.java new file mode 100644 index 000000000..799b2d8fc --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/BitWmsController.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO; +import cn.iocoder.yudao.module.erp.service.bitwms.BitWmsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - BIT 样品仓") +@RestController +@RequestMapping("/erp/bit-wms") +@Validated +public class BitWmsController { + + @Resource + private BitWmsService bitWmsService; + + @PostMapping("/inbound") + @Operation(summary = "BIT 样品入库") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-in:create')") + public CommonResult inbound(@Valid @RequestBody BitWmsInboundReqVO reqVO) { + return success(bitWmsService.inbound(reqVO)); + } + + @PostMapping("/outbound") + @Operation(summary = "BIT 样品出库") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-out:create')") + public CommonResult outbound(@Valid @RequestBody BitWmsOutboundReqVO reqVO) { + return success(bitWmsService.outbound(reqVO)); + } + + @PostMapping("/move") + @Operation(summary = "BIT 样品移库") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-move:create')") + public CommonResult move(@Valid @RequestBody BitWmsMoveReqVO reqVO) { + return success(bitWmsService.move(reqVO)); + } + + @GetMapping("/scan") + @Operation(summary = "BIT 样品仓扫码") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')") + public CommonResult scan(@RequestParam("code") String code) { + return success(bitWmsService.scan(code)); + } + + @GetMapping("/sample-label") + @Operation(summary = "获得样品标签打印数据") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-sample:print')") + public CommonResult getSampleLabel(@RequestParam("productId") Long productId) { + return success(bitWmsService.getSampleLabel(productId)); + } + + @GetMapping("/location-label") + @Operation(summary = "获得库位标签打印数据") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-location:print')") + public CommonResult getLocationLabel(@RequestParam("locationId") Long locationId) { + return success(bitWmsService.getLocationLabel(locationId)); + } +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsInboundReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsInboundReqVO.java new file mode 100644 index 000000000..1833c891b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsInboundReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - BIT 样品仓入库 Request VO") +@Data +public class BitWmsInboundReqVO { + + @Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "样品产品ID不能为空") + private Long productId; + + @Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "仓库ID不能为空") + private Long warehouseId; + + @Schema(description = "库区ID", example = "30") + private Long areaId; + + @Schema(description = "入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "入库数量不能为空") + @DecimalMin(value = "0.000001", message = "入库数量必须大于 0") + private BigDecimal count; + + @Schema(description = "供应商ID", example = "1001") + private Long supplierId; + + @Schema(description = "备注", example = "first shelf") + private String remark; +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLabelRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLabelRespVO.java new file mode 100644 index 000000000..a8e6eb1d6 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLabelRespVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "管理后台 - BIT 样品仓标签打印 Response VO") +@Data +public class BitWmsLabelRespVO { + + @Schema(description = "标签对象类型:SAMPLE 样品;LOCATION 库位", example = "SAMPLE") + private String type; + + @Schema(description = "业务ID", example = "10") + private Long id; + + @Schema(description = "编码", example = "SP202606220001") + private String code; + + @Schema(description = "显示名称", example = "样品 A") + private String name; + + @Schema(description = "二维码地址") + private String qrcodeUrl; + + @Schema(description = "打印模板 JSON") + private String templateJson; + + @Schema(description = "打印数据") + private Map printData; +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsMoveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsMoveReqVO.java new file mode 100644 index 000000000..ee23124cb --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsMoveReqVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - BIT 样品仓移库 Request VO") +@Data +public class BitWmsMoveReqVO { + + @Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "样品产品ID不能为空") + private Long productId; + + @Schema(description = "调出仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "调出仓库ID不能为空") + private Long fromWarehouseId; + + @Schema(description = "调出库区ID", example = "30") + private Long fromAreaId; + + @Schema(description = "调入仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "调入仓库ID不能为空") + private Long toWarehouseId; + + @Schema(description = "调入库区ID", example = "31") + private Long toAreaId; + + @Schema(description = "移库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "移库数量不能为空") + @DecimalMin(value = "0.000001", message = "移库数量必须大于 0") + private BigDecimal count; + + @Schema(description = "备注", example = "上架调整") + private String remark; +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsOutboundReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsOutboundReqVO.java new file mode 100644 index 000000000..8217b0446 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsOutboundReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - BIT 样品仓出库 Request VO") +@Data +public class BitWmsOutboundReqVO { + + @Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "样品产品ID不能为空") + private Long productId; + + @Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "仓库ID不能为空") + private Long warehouseId; + + @Schema(description = "库区ID", example = "30") + private Long areaId; + + @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "出库数量不能为空") + @DecimalMin(value = "0.000001", message = "出库数量必须大于 0") + private BigDecimal count; + + @Schema(description = "出库原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "TEST") + @NotBlank(message = "出库原因不能为空") + private String outReason; + + @Schema(description = "备注", example = "测试出库") + private String remark; +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsScanRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsScanRespVO.java new file mode 100644 index 000000000..914272201 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsScanRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - BIT 样品仓扫码 Response VO") +@Data +public class BitWmsScanRespVO { + + @Schema(description = "扫码对象类型:SAMPLE 样品;LOCATION 库位", example = "SAMPLE") + private String type; + + @Schema(description = "业务ID", example = "10") + private Long id; + + @Schema(description = "编码", example = "SP202606220001") + private String code; + + @Schema(description = "名称", example = "样品 A") + private String name; +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java index dc803531b..99e1692f1 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java @@ -48,10 +48,22 @@ public class ErpStockMoveSaveReqVO { @NotNull(message = "调出仓库编号不能为空") private Long fromWarehouseId; + @Schema(description = "调出库区编号", example = "30") + private Long fromAreaId; + + @Schema(description = "调出库区名称", example = "A1") + private String fromAreaName; + @Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") @NotNull(message = "调入仓库编号不能为空") private Long toWarehouseId; + @Schema(description = "调入库区编号", example = "31") + private Long toAreaId; + + @Schema(description = "调入库区名称", example = "A2") + private String toAreaName; + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") @NotNull(message = "产品编号不能为空") private Long productId; @@ -69,9 +81,10 @@ public class ErpStockMoveSaveReqVO { @AssertTrue(message = "调出、调仓仓库不能相同") @JsonIgnore public boolean isWarehouseValid() { - return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId); + return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId) + || ObjectUtil.notEqual(fromAreaId, toAreaId); } } -} \ No newline at end of file +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java index aee203670..b21ca4d8b 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java @@ -41,12 +41,28 @@ public class ErpStockMoveItemDO extends BaseDO { * 关联 {@link ErpWarehouseDO#getId()} */ private Long fromWarehouseId; + /** + * 调出库区编号 + */ + private Long fromAreaId; + /** + * 调出库区名称 + */ + private String fromAreaName; /** * 调入仓库编号 * * 关联 {@link ErpWarehouseDO#getId()} */ private Long toWarehouseId; + /** + * 调入库区编号 + */ + private Long toAreaId; + /** + * 调入库区名称 + */ + private String toAreaName; /** * 产品编号 * @@ -76,4 +92,4 @@ public class ErpStockMoveItemDO extends BaseDO { */ private String remark; -} \ No newline at end of file +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/warehouselocation/WarehouseLocationMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/warehouselocation/WarehouseLocationMapper.java index 50793269b..c11ce88ae 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/warehouselocation/WarehouseLocationMapper.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/warehouselocation/WarehouseLocationMapper.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import cn.iocoder.yudao.module.erp.controller.admin.warehouselocation.vo.*; /** @@ -58,4 +59,6 @@ public interface WarehouseLocationMapper extends BaseMapperX printData = new LinkedHashMap<>(); + printData.put("id", product.getId()); + printData.put("code", product.getBarCode()); + printData.put("name", product.getName()); + printData.put("customerName", product.getCustomerName()); + printData.put("sampleCategory", product.getSampleCategory()); + printData.put("qrcodeUrl", qrcodeUrl); + return buildLabel(SAMPLE_TYPE, product.getId(), product.getBarCode(), product.getName(), qrcodeUrl, + productMapper.selectPrintTemplate(SAMPLE_PRINT_TEMPLATE_TYPE), printData); + } + + @Override + public BitWmsLabelRespVO getLocationLabel(Long locationId) { + WarehouseLocationDO location = warehouseLocationMapper.selectById(locationId); + if (location == null) { + throw exception(WAREHOUSE_LOCATION_NOT_EXISTS); + } + String qrcodeUrl = qrcodeRecordService.selectQrcodeUrlByIdAndCode( + QrcodeBizTypeEnum.WAREHOUSE_LOCATION.getCode(), location.getId(), location.getCode()); + Map printData = new LinkedHashMap<>(); + printData.put("id", location.getId()); + printData.put("code", location.getCode()); + printData.put("name", location.getName()); + printData.put("warehouseId", location.getWarehouseId()); + printData.put("areaId", location.getAreaId()); + printData.put("qrcodeUrl", qrcodeUrl); + return buildLabel(LOCATION_TYPE, location.getId(), location.getCode(), location.getName(), qrcodeUrl, + warehouseLocationMapper.selectPrintTemplate(LOCATION_PRINT_TEMPLATE_TYPE), printData); + } + + private ErpProductRespVO validateBitSample(Long productId) { + ErpProductRespVO product = productService.getProduct(productId); + if (product == null || !Boolean.TRUE.equals(product.getIsSample()) || !BIT_BIZ_UNIT.equals(product.getBizUnit())) { + throw exception(PRODUCT_NOT_EXISTS); + } + return product; + } + + private void validateStockEnough(Long productId, Long warehouseId, Long areaId, BigDecimal count) { + ErpStockDO stock = stockService.getStock(productId, warehouseId, areaId); + BigDecimal currentCount = stock != null && stock.getCount() != null ? stock.getCount() : BigDecimal.ZERO; + if (currentCount.compareTo(count) < 0) { + throw exception(STOCK_COUNT_NEGATIVE2, productId, warehouseId); + } + } + + private String buildOutboundRemark(String outReason, String remark) { + if (remark == null || remark.isEmpty()) { + return "出库原因:" + outReason; + } + return "出库原因:" + outReason + ";" + remark; + } + + private BitWmsLabelRespVO buildLabel(String type, Long id, String code, String name, String qrcodeUrl, + String templateJson, Map printData) { + BitWmsLabelRespVO respVO = new BitWmsLabelRespVO(); + respVO.setType(type); + respVO.setId(id); + respVO.setCode(code); + respVO.setName(name); + respVO.setQrcodeUrl(qrcodeUrl); + respVO.setTemplateJson(templateJson); + respVO.setPrintData(printData); + return respVO; + } +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java index 034741fb8..775ac3758 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java @@ -131,11 +131,15 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService { ErpProductDO productDO = productService.getProduct(stockMoveItem.getProductId()); stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( - stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getFromWarehouseId(), fromCount, - fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime())); + stockMoveItem.getProductId(), productDO.getCategoryId(), null, + stockMoveItem.getFromWarehouseId(), stockMoveItem.getFromAreaId(), stockMoveItem.getFromAreaName(), + fromCount, fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(), + stockMove.getMoveTime())); stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( - stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getToWarehouseId(), toCount, - toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime())); + stockMoveItem.getProductId(), productDO.getCategoryId(), null, + stockMoveItem.getToWarehouseId(), stockMoveItem.getToAreaId(), stockMoveItem.getToAreaName(), + toCount, toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(), + stockMove.getMoveTime())); }); } @@ -228,4 +232,4 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService { return stockMoveItemMapper.selectListByMoveIds(moveIds); } -} \ No newline at end of file +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/warehouselocation/WarehouseLocationMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/warehouselocation/WarehouseLocationMapper.xml index 9d39d7109..4a52898d0 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/warehouselocation/WarehouseLocationMapper.xml +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/warehouselocation/WarehouseLocationMapper.xml @@ -9,4 +9,12 @@ 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ --> - \ No newline at end of file + + + diff --git a/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImplTest.java b/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImplTest.java new file mode 100644 index 000000000..01b234720 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImplTest.java @@ -0,0 +1,129 @@ +package cn.iocoder.yudao.module.erp.service.bitwms; + +import cn.iocoder.yudao.framework.test.core.util.AssertUtils; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockInService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; + +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link BitWmsServiceImpl} 的单元测试类 + */ +@ExtendWith(MockitoExtension.class) +class BitWmsServiceImplTest { + + @InjectMocks + private BitWmsServiceImpl bitWmsService; + + @Mock + private ErpProductService productService; + @Mock + private ErpStockInService stockInService; + @Mock + private ErpStockMoveService stockMoveService; + @Mock + private ErpStockService stockService; + + @Test + void inbound_createsSampleStockInWithBitType() { + BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO(); + reqVO.setProductId(10L); + reqVO.setWarehouseId(20L); + reqVO.setAreaId(30L); + reqVO.setCount(new BigDecimal("2")); + reqVO.setRemark("first shelf"); + + ErpProductRespVO product = buildBitSampleProduct(); + when(productService.getProduct(10L)).thenReturn(product); + when(stockInService.createStockIn(org.mockito.ArgumentMatchers.any(ErpStockInSaveReqVO.class))).thenReturn(100L); + + Long id = bitWmsService.inbound(reqVO); + + assertEquals(100L, id); + ArgumentCaptor captor = ArgumentCaptor.forClass(ErpStockInSaveReqVO.class); + verify(stockInService).createStockIn(captor.capture()); + ErpStockInSaveReqVO stockIn = captor.getValue(); + assertEquals("样品入库", stockIn.getInType()); + assertEquals(1, stockIn.getItems().size()); + assertEquals(10L, stockIn.getItems().get(0).getProductId()); + assertEquals(20L, stockIn.getItems().get(0).getWarehouseId()); + assertEquals(30L, stockIn.getItems().get(0).getAreaId()); + assertEquals(new BigDecimal("2"), stockIn.getItems().get(0).getCount()); + } + + @Test + void outbound_rejectsCountGreaterThanLocationStock() { + BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO(); + reqVO.setProductId(10L); + reqVO.setWarehouseId(20L); + reqVO.setAreaId(30L); + reqVO.setCount(new BigDecimal("999")); + reqVO.setOutReason("TEST"); + + when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct()); + when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(BigDecimal.ONE)); + + AssertUtils.assertServiceException(() -> bitWmsService.outbound(reqVO), STOCK_COUNT_NEGATIVE2, 10L, 20L); + } + + @Test + void move_keepsTotalStockAndChangesLocation() { + BitWmsMoveReqVO reqVO = new BitWmsMoveReqVO(); + reqVO.setProductId(10L); + reqVO.setFromWarehouseId(20L); + reqVO.setFromAreaId(30L); + reqVO.setToWarehouseId(20L); + reqVO.setToAreaId(31L); + reqVO.setCount(new BigDecimal("1")); + + ErpProductRespVO product = buildBitSampleProduct(); + when(productService.getProduct(10L)).thenReturn(product); + when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(new BigDecimal("2"))); + when(stockMoveService.createStockMove(org.mockito.ArgumentMatchers.any(ErpStockMoveSaveReqVO.class))).thenReturn(300L); + + Long id = bitWmsService.move(reqVO); + + assertEquals(300L, id); + ArgumentCaptor captor = ArgumentCaptor.forClass(ErpStockMoveSaveReqVO.class); + verify(stockMoveService).createStockMove(captor.capture()); + ErpStockMoveSaveReqVO.Item item = captor.getValue().getItems().get(0); + assertEquals(20L, item.getFromWarehouseId()); + assertEquals(30L, item.getFromAreaId()); + assertEquals(20L, item.getToWarehouseId()); + assertEquals(31L, item.getToAreaId()); + assertEquals(new BigDecimal("1"), item.getCount()); + verify(stockMoveService).updateStockMoveStatus(eq(300L), eq(ErpAuditStatus.APPROVE.getStatus())); + } + + private ErpProductRespVO buildBitSampleProduct() { + ErpProductRespVO product = new ErpProductRespVO(); + product.setId(10L); + product.setName("BIT 样品"); + product.setIsSample(true); + product.setBizUnit("BIT"); + product.setCategoryId(50L); + return product; + } +}