diff --git a/sql/mysql/2026-06-23-bit-wms-location-stock.sql b/sql/mysql/2026-06-23-bit-wms-location-stock.sql new file mode 100644 index 000000000..4c3366350 --- /dev/null +++ b/sql/mysql/2026-06-23-bit-wms-location-stock.sql @@ -0,0 +1,48 @@ +-- BIT 样品仓库位库存台账 + +CREATE TABLE IF NOT EXISTS `erp_bit_wms_location_stock` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `product_id` bigint NOT NULL COMMENT '样品产品ID', + `warehouse_id` bigint NOT NULL COMMENT '仓库ID', + `area_id` bigint NULL COMMENT '库区ID', + `location_id` bigint NOT NULL COMMENT '库位ID', + `count` decimal(24, 6) NOT NULL DEFAULT 0.000000 COMMENT '当前库存数量', + `unit_id` bigint NULL COMMENT '单位ID', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_product_location` (`product_id`, `location_id`, `deleted`, `tenant_id`) USING BTREE, + KEY `idx_location` (`location_id`) USING BTREE, + KEY `idx_warehouse_area` (`warehouse_id`, `area_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BIT 样品库位库存'; + +CREATE TABLE IF NOT EXISTS `erp_bit_wms_location_stock_record` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `product_id` bigint NOT NULL COMMENT '样品产品ID', + `warehouse_id` bigint NOT NULL COMMENT '仓库ID', + `area_id` bigint NULL COMMENT '库区ID', + `location_id` bigint NOT NULL COMMENT '库位ID', + `count` decimal(24, 6) NOT NULL COMMENT '变动数量', + `total_count` decimal(24, 6) NOT NULL COMMENT '变动后库位结存', + `unit_id` bigint NULL COMMENT '单位ID', + `biz_direction` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务方向', + `biz_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务类型', + `biz_id` bigint NULL COMMENT '业务单ID', + `biz_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '业务单号', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '备注', + `record_time` datetime NOT NULL COMMENT '操作时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_product_location` (`product_id`, `location_id`) USING BTREE, + KEY `idx_biz_no` (`biz_no`) USING BTREE, + KEY `idx_record_time` (`record_time`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BIT 样品库位库存流水'; 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 index 799b2d8fc..4ad69761d 100644 --- 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 @@ -1,8 +1,13 @@ package cn.iocoder.yudao.module.erp.controller.admin.bitwms; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.BitWmsLocationStockPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRespVO; 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; @@ -20,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.validation.Valid; +import java.math.BigDecimal; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -60,6 +66,30 @@ public class BitWmsController { return success(bitWmsService.scan(code)); } + @GetMapping("/location-stock/page") + @Operation(summary = "获得 BIT 样品库位库存分页") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')") + public CommonResult> getLocationStockPage( + @Valid BitWmsLocationStockPageReqVO pageReqVO) { + return success(bitWmsService.getLocationStockPage(pageReqVO)); + } + + @GetMapping("/location-stock/count") + @Operation(summary = "获得 BIT 样品当前库位库存") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')") + public CommonResult getLocationStockCount(@RequestParam("productId") Long productId, + @RequestParam("locationId") Long locationId) { + return success(bitWmsService.getLocationStockCount(productId, locationId)); + } + + @GetMapping("/location-stock-record/page") + @Operation(summary = "获得 BIT 样品库位流水分页") + @PreAuthorize("@ss.hasPermission('erp:bit-wms-record:query')") + public CommonResult> getLocationStockRecordPage( + @Valid BitWmsLocationStockRecordPageReqVO pageReqVO) { + return success(bitWmsService.getLocationStockRecordPage(pageReqVO)); + } + @GetMapping("/sample-label") @Operation(summary = "获得样品标签打印数据") @PreAuthorize("@ss.hasPermission('erp:bit-wms-sample:print')") 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 index a0b0c3dd8..164182170 100644 --- 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 @@ -15,14 +15,14 @@ public class BitWmsInboundReqVO { @NotNull(message = "样品产品ID不能为空") private Long productId; - @Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - @NotNull(message = "仓库ID不能为空") + @Schema(description = "仓库ID", example = "20") private Long warehouseId; @Schema(description = "库区ID", example = "30") private Long areaId; - @Schema(description = "库位ID", example = "40") + @Schema(description = "库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "40") + @NotNull(message = "库位不能为空") private Long locationId; @Schema(description = "入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockPageReqVO.java new file mode 100644 index 000000000..f20dd71a2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockPageReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - BIT 样品库位库存分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BitWmsLocationStockPageReqVO extends PageParam { + + @Schema(description = "样品产品ID", example = "10") + private Long productId; + + @Schema(description = "仓库ID", example = "20") + private Long warehouseId; + + @Schema(description = "库区ID", example = "30") + private Long areaId; + + @Schema(description = "库位ID", example = "40") + private Long locationId; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRecordPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRecordPageReqVO.java new file mode 100644 index 000000000..118e8a1ae --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRecordPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - BIT 样品库位流水分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BitWmsLocationStockRecordPageReqVO extends PageParam { + + @Schema(description = "样品产品ID", example = "10") + private Long productId; + + @Schema(description = "仓库ID", example = "20") + private Long warehouseId; + + @Schema(description = "库区ID", example = "30") + private Long areaId; + + @Schema(description = "库位ID", example = "40") + private Long locationId; + + @Schema(description = "业务单号", example = "QTRK20260623001") + private String bizNo; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRecordRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRecordRespVO.java new file mode 100644 index 000000000..b06d71f9c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRecordRespVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - BIT 样品库位流水 Response VO") +@Data +public class BitWmsLocationStockRecordRespVO { + + private Long id; + private Long productId; + private String productName; + private String barCode; + private Long warehouseId; + private String warehouseName; + private Long areaId; + private String areaName; + private Long locationId; + private String locationCode; + private String locationName; + private BigDecimal count; + private BigDecimal totalCount; + private Long unitId; + private String unitName; + private String bizDirection; + private String bizType; + private Long bizId; + private String bizNo; + private String remark; + private String creator; + private String creatorName; + private LocalDateTime recordTime; + private LocalDateTime createTime; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRespVO.java new file mode 100644 index 000000000..53cd43cb0 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/bitwms/vo/BitWmsLocationStockRespVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - BIT 样品库位库存 Response VO") +@Data +public class BitWmsLocationStockRespVO { + + private Long id; + private Long productId; + private String productName; + private String barCode; + private String sampleCategory; + private String customerName; + private Long warehouseId; + private String warehouseName; + private Long areaId; + private String areaName; + private Long locationId; + private String locationCode; + private String locationName; + private BigDecimal count; + private Long unitId; + private String unitName; + +} 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 index ee23124cb..4eb789561 100644 --- 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 @@ -15,15 +15,21 @@ public class BitWmsMoveReqVO { @NotNull(message = "样品产品ID不能为空") private Long productId; - @Schema(description = "调出仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - @NotNull(message = "调出仓库ID不能为空") + @Schema(description = "调出库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "40") + @NotNull(message = "调出库位不能为空") + private Long fromLocationId; + + @Schema(description = "调入库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "41") + @NotNull(message = "调入库位不能为空") + private Long toLocationId; + + @Schema(description = "调出仓库ID", example = "20") private Long fromWarehouseId; @Schema(description = "调出库区ID", example = "30") private Long fromAreaId; - @Schema(description = "调入仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - @NotNull(message = "调入仓库ID不能为空") + @Schema(description = "调入仓库ID", example = "20") private Long toWarehouseId; @Schema(description = "调入库区ID", example = "31") 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 index 8217b0446..a44e9888a 100644 --- 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 @@ -16,13 +16,16 @@ public class BitWmsOutboundReqVO { @NotNull(message = "样品产品ID不能为空") private Long productId; - @Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - @NotNull(message = "仓库ID不能为空") + @Schema(description = "仓库ID", example = "20") private Long warehouseId; @Schema(description = "库区ID", example = "30") private Long areaId; + @Schema(description = "库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "40") + @NotNull(message = "库位不能为空") + private Long locationId; + @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @NotNull(message = "出库数量不能为空") @DecimalMin(value = "0.000001", message = "出库数量必须大于 0") diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/bitwms/BitWmsLocationStockDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/bitwms/BitWmsLocationStockDO.java new file mode 100644 index 000000000..158077cd7 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/bitwms/BitWmsLocationStockDO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.bitwms; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.math.BigDecimal; + +/** + * BIT 样品库位库存 DO + */ +@TableName("erp_bit_wms_location_stock") +@KeySequence("erp_bit_wms_location_stock_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BitWmsLocationStockDO extends BaseDO { + + @TableId + private Long id; + + private Long productId; + + private Long warehouseId; + + private Long areaId; + + private Long locationId; + + private BigDecimal count; + + private Long unitId; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/bitwms/BitWmsLocationStockRecordDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/bitwms/BitWmsLocationStockRecordDO.java new file mode 100644 index 000000000..68a2e0031 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/bitwms/BitWmsLocationStockRecordDO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.bitwms; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * BIT 样品库位库存流水 DO + */ +@TableName("erp_bit_wms_location_stock_record") +@KeySequence("erp_bit_wms_location_stock_record_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BitWmsLocationStockRecordDO extends BaseDO { + + @TableId + private Long id; + + private Long productId; + + private Long warehouseId; + + private Long areaId; + + private Long locationId; + + private BigDecimal count; + + private BigDecimal totalCount; + + private Long unitId; + + private String bizDirection; + + private String bizType; + + private Long bizId; + + private String bizNo; + + private String remark; + + private LocalDateTime recordTime; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/bitwms/BitWmsLocationStockMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/bitwms/BitWmsLocationStockMapper.java new file mode 100644 index 000000000..1a5874642 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/bitwms/BitWmsLocationStockMapper.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.bitwms; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; + +@Mapper +public interface BitWmsLocationStockMapper extends BaseMapperX { + + default PageResult selectPage(BitWmsLocationStockPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(BitWmsLocationStockDO::getProductId, reqVO.getProductId()) + .eqIfPresent(BitWmsLocationStockDO::getWarehouseId, reqVO.getWarehouseId()) + .eqIfPresent(BitWmsLocationStockDO::getAreaId, reqVO.getAreaId()) + .eqIfPresent(BitWmsLocationStockDO::getLocationId, reqVO.getLocationId()) + .ne(BitWmsLocationStockDO::getCount, BigDecimal.ZERO) + .orderByDesc(BitWmsLocationStockDO::getId)); + } + + default BitWmsLocationStockDO selectByProductIdAndLocationId(Long productId, Long locationId) { + return selectOne(new LambdaQueryWrapperX() + .eq(BitWmsLocationStockDO::getProductId, productId) + .eq(BitWmsLocationStockDO::getLocationId, locationId)); + } + + default int updateCountIncrement(Long id, BigDecimal count, boolean negativeEnable) { + if (count == null || count.compareTo(BigDecimal.ZERO) == 0) { + return 0; + } + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .eq(BitWmsLocationStockDO::getId, id); + if (count.compareTo(BigDecimal.ZERO) > 0) { + updateWrapper.setSql("count = count + " + count); + } else { + if (!negativeEnable) { + updateWrapper.ge(BitWmsLocationStockDO::getCount, count.abs()); + } + updateWrapper.setSql("count = count - " + count.abs()); + } + return update(null, updateWrapper); + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/bitwms/BitWmsLocationStockRecordMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/bitwms/BitWmsLocationStockRecordMapper.java new file mode 100644 index 000000000..a3fb34d74 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/bitwms/BitWmsLocationStockRecordMapper.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.bitwms; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockRecordDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface BitWmsLocationStockRecordMapper extends BaseMapperX { + + default PageResult selectPage(BitWmsLocationStockRecordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(BitWmsLocationStockRecordDO::getProductId, reqVO.getProductId()) + .eqIfPresent(BitWmsLocationStockRecordDO::getWarehouseId, reqVO.getWarehouseId()) + .eqIfPresent(BitWmsLocationStockRecordDO::getAreaId, reqVO.getAreaId()) + .eqIfPresent(BitWmsLocationStockRecordDO::getLocationId, reqVO.getLocationId()) + .likeIfPresent(BitWmsLocationStockRecordDO::getBizNo, reqVO.getBizNo()) + .orderByDesc(BitWmsLocationStockRecordDO::getId)); + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsService.java index 21f9695cd..8f1c9c415 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsService.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsService.java @@ -1,11 +1,17 @@ package cn.iocoder.yudao.module.erp.service.bitwms; +import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.BitWmsLocationStockPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRespVO; 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 java.math.BigDecimal; import javax.validation.Valid; public interface BitWmsService { @@ -18,6 +24,12 @@ public interface BitWmsService { BitWmsScanRespVO scan(String code); + PageResult getLocationStockPage(@Valid BitWmsLocationStockPageReqVO pageReqVO); + + PageResult getLocationStockRecordPage(@Valid BitWmsLocationStockRecordPageReqVO pageReqVO); + + BigDecimal getLocationStockCount(Long productId, Long locationId); + BitWmsLabelRespVO getSampleLabel(Long productId); BitWmsLabelRespVO getLocationLabel(Long locationId); diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImpl.java index 321bf4bcf..6336acd62 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/bitwms/BitWmsServiceImpl.java @@ -1,11 +1,18 @@ package cn.iocoder.yudao.module.erp.service.bitwms; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.common.enums.CodeTypeEnum; import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum; import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService; import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil; 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.BitWmsLocationStockPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRespVO; 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; @@ -16,9 +23,17 @@ import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSa import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutAuditReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVOItem; +import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockRecordDO; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; -import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.warehousearea.WarehouseAreaDO; import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO; +import cn.iocoder.yudao.module.erp.dal.mysql.bitwms.BitWmsLocationStockMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.bitwms.BitWmsLocationStockRecordMapper; import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper; import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper; import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; @@ -26,7 +41,10 @@ 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.ErpStockOutService; -import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService; +import cn.iocoder.yudao.module.erp.service.warehousearea.WarehouseAreaService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -36,8 +54,14 @@ import java.math.BigDecimal; import java.io.UnsupportedEncodingException; import java.time.LocalDateTime; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_CODE_NOT_EXISTS; @@ -60,6 +84,7 @@ public class BitWmsServiceImpl implements BitWmsService { private static final String BIT_WAREHOUSE_LOCATION_CODE_RULE = "BIT_WAREHOUSE_LOCATION_CODE"; private static final int SAMPLE_PRINT_TEMPLATE_TYPE = 7; private static final int LOCATION_PRINT_TEMPLATE_TYPE = 8; + private static final Pattern QRCODE_BIZ_KEY_PATTERN = Pattern.compile("^(.+)[-_](\\d+)$"); @Resource private ErpProductService productService; @@ -74,11 +99,19 @@ public class BitWmsServiceImpl implements BitWmsService { @Resource private ErpStockMoveService stockMoveService; @Resource - private ErpStockService stockService; + private ErpWarehouseService warehouseService; + @Resource + private WarehouseAreaService warehouseAreaService; + @Resource + private BitWmsLocationStockMapper locationStockMapper; + @Resource + private BitWmsLocationStockRecordMapper locationStockRecordMapper; @Resource private QrcodeRecordService qrcodeRecordService; @Resource private AutoCodeUtil autoCodeUtil; + @Resource + private AdminUserApi adminUserApi; @Override @Transactional(rollbackFor = Exception.class) @@ -111,6 +144,9 @@ public class BitWmsServiceImpl implements BitWmsService { auditReqVO.setStatus(ErpAuditStatus.APPROVE.getStatus()); auditReqVO.setRemark(reqVO.getRemark() != null ? reqVO.getRemark() : "样品入库自动入库"); stockInService.auditStockIn(auditReqVO); + ErpStockInDO stockInDO = stockInService.getStockIn(id); + adjustLocationStock(product, location, reqVO.getCount(), "入库", SAMPLE_IN_TYPE, id, + stockInDO != null ? stockInDO.getNo() : null, reqVO.getRemark()); return id; } @@ -118,7 +154,10 @@ public class BitWmsServiceImpl implements BitWmsService { @Transactional(rollbackFor = Exception.class) public Long outbound(BitWmsOutboundReqVO reqVO) { ErpProductRespVO product = validateBitSample(reqVO.getProductId()); - validateStockEnough(reqVO.getProductId(), reqVO.getWarehouseId(), reqVO.getAreaId(), reqVO.getCount()); + WarehouseLocationDO location = resolveOutboundLocation(reqVO); + Long warehouseId = location.getWarehouseId(); + Long areaId = location.getAreaId(); + validateLocationStockEnough(reqVO.getProductId(), location.getId(), reqVO.getCount()); ErpStockOutSaveReqVO stockOut = new ErpStockOutSaveReqVO(); stockOut.setOutType(SAMPLE_OUT_TYPE); @@ -127,8 +166,8 @@ public class BitWmsServiceImpl implements BitWmsService { ErpStockOutSaveReqVOItem item = new ErpStockOutSaveReqVOItem(); item.setProductId(reqVO.getProductId()); - item.setWarehouseId(reqVO.getWarehouseId()); - item.setAreaId(reqVO.getAreaId()); + item.setWarehouseId(warehouseId); + item.setAreaId(areaId); item.setCount(reqVO.getCount()); item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO); item.setRemark(reqVO.getRemark()); @@ -139,6 +178,9 @@ public class BitWmsServiceImpl implements BitWmsService { auditReqVO.setStatus(ErpAuditStatus.APPROVE.getStatus()); auditReqVO.setRemark(reqVO.getRemark() != null ? reqVO.getRemark() : "样品出库自动出库"); stockOutService.auditStockOut(auditReqVO); + ErpStockOutDO stockOutDO = stockOutService.getStockOut(id); + adjustLocationStock(product, location, reqVO.getCount().negate(), "出库", SAMPLE_OUT_TYPE, id, + stockOutDO != null ? stockOutDO.getNo() : null, buildOutboundRemark(reqVO.getOutReason(), reqVO.getRemark())); return id; } @@ -146,7 +188,20 @@ public class BitWmsServiceImpl implements BitWmsService { @Transactional(rollbackFor = Exception.class) public Long move(BitWmsMoveReqVO reqVO) { ErpProductRespVO product = validateBitSample(reqVO.getProductId()); - validateStockEnough(reqVO.getProductId(), reqVO.getFromWarehouseId(), reqVO.getFromAreaId(), reqVO.getCount()); + WarehouseLocationDO fromLocation = resolveLocation(reqVO.getFromLocationId()); + WarehouseLocationDO toLocation = resolveLocation(reqVO.getToLocationId()); + validateLocationStockEnough(reqVO.getProductId(), fromLocation.getId(), reqVO.getCount()); + if (fromLocation.getId().equals(toLocation.getId())) { + throw exception(STOCK_COUNT_NEGATIVE2, reqVO.getProductId(), fromLocation.getId()); + } + if (isSameWarehouseArea(fromLocation, toLocation)) { + String bizNo = buildLocationMoveNo(); + Long recordId = adjustLocationStock(product, fromLocation, reqVO.getCount().negate(), "出库", + SAMPLE_MOVE_TYPE + "出", null, bizNo, reqVO.getRemark()); + adjustLocationStock(product, toLocation, reqVO.getCount(), "入库", SAMPLE_MOVE_TYPE + "入", null, + bizNo, reqVO.getRemark()); + return recordId; + } ErpStockMoveSaveReqVO stockMove = new ErpStockMoveSaveReqVO(); stockMove.setMoveTime(LocalDateTime.now()); @@ -154,10 +209,10 @@ public class BitWmsServiceImpl implements BitWmsService { ErpStockMoveSaveReqVO.Item item = new ErpStockMoveSaveReqVO.Item(); item.setProductId(reqVO.getProductId()); - item.setFromWarehouseId(reqVO.getFromWarehouseId()); - item.setFromAreaId(reqVO.getFromAreaId()); - item.setToWarehouseId(reqVO.getToWarehouseId()); - item.setToAreaId(reqVO.getToAreaId()); + item.setFromWarehouseId(fromLocation.getWarehouseId()); + item.setFromAreaId(fromLocation.getAreaId()); + item.setToWarehouseId(toLocation.getWarehouseId()); + item.setToAreaId(toLocation.getAreaId()); item.setCount(reqVO.getCount()); item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO); item.setRemark(reqVO.getRemark()); @@ -165,32 +220,106 @@ public class BitWmsServiceImpl implements BitWmsService { Long id = stockMoveService.createStockMove(stockMove); stockMoveService.updateStockMoveStatus(id, ErpAuditStatus.APPROVE.getStatus()); + ErpStockMoveDO stockMoveDO = stockMoveService.getStockMove(id); + String bizNo = stockMoveDO != null ? stockMoveDO.getNo() : null; + adjustLocationStock(product, fromLocation, reqVO.getCount().negate(), "出库", SAMPLE_MOVE_TYPE + "出", id, + bizNo, reqVO.getRemark()); + adjustLocationStock(product, toLocation, reqVO.getCount(), "入库", SAMPLE_MOVE_TYPE + "入", id, + bizNo, reqVO.getRemark()); return id; } @Override public BitWmsScanRespVO scan(String code) { - ErpProductDO product = productMapper.selectOne(ErpProductDO::getBarCode, code); + String scanCode = code != null ? code.trim() : ""; + BitWmsScanRespVO qrcodeBizRespVO = scanByQrcodeBizKey(scanCode); + if (qrcodeBizRespVO != null) { + return qrcodeBizRespVO; + } + + ErpProductDO product = productMapper.selectOne(ErpProductDO::getBarCode, scanCode); if (product != null && Boolean.TRUE.equals(product.getIsSample()) && BIT_BIZ_UNIT.equals(product.getBizUnit())) { - BitWmsScanRespVO respVO = new BitWmsScanRespVO(); - respVO.setType(SAMPLE_TYPE); - respVO.setId(product.getId()); - respVO.setCode(product.getBarCode()); - respVO.setName(product.getName()); - return respVO; - } - WarehouseLocationDO location = warehouseLocationMapper.selectByNo(code); + return buildSampleScanResp(product); + } + WarehouseLocationDO location = warehouseLocationMapper.selectByNo(scanCode); if (location != null) { - BitWmsScanRespVO respVO = new BitWmsScanRespVO(); - respVO.setType(LOCATION_TYPE); - respVO.setId(location.getId()); - respVO.setCode(location.getCode()); - respVO.setName(location.getName()); - return respVO; + return buildLocationScanResp(location); } throw exception(PRODUCT_CODE_NOT_EXISTS); } + private BitWmsScanRespVO scanByQrcodeBizKey(String code) { + Matcher matcher = QRCODE_BIZ_KEY_PATTERN.matcher(code); + if (!matcher.matches()) { + return null; + } + String bizType = matcher.group(1); + Long bizId = Long.valueOf(matcher.group(2)); + if (QrcodeBizTypeEnum.PRODUCT.getCode().equalsIgnoreCase(bizType)) { + ErpProductDO product = productMapper.selectById(bizId); + if (product != null && Boolean.TRUE.equals(product.getIsSample()) && BIT_BIZ_UNIT.equals(product.getBizUnit())) { + return buildSampleScanResp(product); + } + throw exception(PRODUCT_CODE_NOT_EXISTS); + } + if (QrcodeBizTypeEnum.WAREHOUSE_LOCATION.getCode().equalsIgnoreCase(bizType)) { + WarehouseLocationDO location = warehouseLocationMapper.selectById(bizId); + if (location != null) { + return buildLocationScanResp(location); + } + throw exception(PRODUCT_CODE_NOT_EXISTS); + } + return null; + } + + private BitWmsScanRespVO buildSampleScanResp(ErpProductDO product) { + BitWmsScanRespVO respVO = new BitWmsScanRespVO(); + respVO.setType(SAMPLE_TYPE); + respVO.setId(product.getId()); + respVO.setCode(product.getBarCode()); + respVO.setName(product.getName()); + return respVO; + } + + private BitWmsScanRespVO buildLocationScanResp(WarehouseLocationDO location) { + BitWmsScanRespVO respVO = new BitWmsScanRespVO(); + respVO.setType(LOCATION_TYPE); + respVO.setId(location.getId()); + respVO.setCode(location.getCode()); + respVO.setName(location.getName()); + return respVO; + } + + @Override + public PageResult getLocationStockPage(BitWmsLocationStockPageReqVO pageReqVO) { + PageResult pageResult = locationStockMapper.selectPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + LookupContext context = buildLookupContext(pageResult.getList(), null); + return BeanUtils.toBean(pageResult, BitWmsLocationStockRespVO.class, + stock -> fillLocationStockResp(stock, context)); + } + + @Override + public PageResult getLocationStockRecordPage(BitWmsLocationStockRecordPageReqVO pageReqVO) { + PageResult pageResult = locationStockRecordMapper.selectPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + LookupContext context = buildLookupContext(null, pageResult.getList()); + return BeanUtils.toBean(pageResult, BitWmsLocationStockRecordRespVO.class, + record -> fillLocationStockRecordResp(record, context)); + } + + @Override + public BigDecimal getLocationStockCount(Long productId, Long locationId) { + validateBitSample(productId); + resolveLocation(locationId); + BitWmsLocationStockDO stock = locationStockMapper.selectByProductIdAndLocationId(productId, locationId); + return stock != null && stock.getCount() != null ? stock.getCount() : BigDecimal.ZERO; + } + @Override public BitWmsLabelRespVO getSampleLabel(Long productId) { ErpProductRespVO product = validateBitSample(productId); @@ -251,12 +380,180 @@ public class BitWmsServiceImpl implements BitWmsService { return location; } - private void validateStockEnough(Long productId, Long warehouseId, Long areaId, BigDecimal count) { - ErpStockDO stock = stockService.getStock(productId, warehouseId, areaId); + private WarehouseLocationDO resolveOutboundLocation(BitWmsOutboundReqVO reqVO) { + return resolveLocation(reqVO.getLocationId()); + } + + private WarehouseLocationDO resolveLocation(Long locationId) { + WarehouseLocationDO location = warehouseLocationMapper.selectById(locationId); + if (location == null) { + throw exception(WAREHOUSE_LOCATION_NOT_EXISTS); + } + return location; + } + + private void validateLocationStockEnough(Long productId, Long locationId, BigDecimal count) { + BitWmsLocationStockDO stock = locationStockMapper.selectByProductIdAndLocationId(productId, locationId); BigDecimal currentCount = stock != null && stock.getCount() != null ? stock.getCount() : BigDecimal.ZERO; if (currentCount.compareTo(count) < 0) { - throw exception(STOCK_COUNT_NEGATIVE2, productId, warehouseId); + throw exception(STOCK_COUNT_NEGATIVE2, productId, locationId); + } + } + + private Long adjustLocationStock(ErpProductRespVO product, WarehouseLocationDO location, BigDecimal count, + String direction, String bizType, Long bizId, String bizNo, String remark) { + BitWmsLocationStockDO stock = locationStockMapper.selectByProductIdAndLocationId(product.getId(), location.getId()); + if (stock == null) { + if (count.compareTo(BigDecimal.ZERO) < 0) { + throw exception(STOCK_COUNT_NEGATIVE2, product.getId(), location.getId()); + } + stock = new BitWmsLocationStockDO() + .setProductId(product.getId()) + .setWarehouseId(location.getWarehouseId()) + .setAreaId(location.getAreaId()) + .setLocationId(location.getId()) + .setCount(count) + .setUnitId(product.getUnitId()); + locationStockMapper.insert(stock); + } else { + int updated = locationStockMapper.updateCountIncrement(stock.getId(), count, false); + if (updated == 0 && count.compareTo(BigDecimal.ZERO) < 0) { + throw exception(STOCK_COUNT_NEGATIVE2, product.getId(), location.getId()); + } + stock = locationStockMapper.selectById(stock.getId()); + } + BitWmsLocationStockRecordDO record = new BitWmsLocationStockRecordDO() + .setProductId(product.getId()) + .setWarehouseId(location.getWarehouseId()) + .setAreaId(location.getAreaId()) + .setLocationId(location.getId()) + .setCount(count.abs()) + .setTotalCount(stock.getCount()) + .setUnitId(product.getUnitId()) + .setBizDirection(direction) + .setBizType(bizType) + .setBizId(bizId) + .setBizNo(bizNo) + .setRemark(remark) + .setRecordTime(LocalDateTime.now()); + locationStockRecordMapper.insert(record); + return record.getId(); + } + + private boolean isSameWarehouseArea(WarehouseLocationDO fromLocation, WarehouseLocationDO toLocation) { + return fromLocation.getWarehouseId().equals(toLocation.getWarehouseId()) + && java.util.Objects.equals(fromLocation.getAreaId(), toLocation.getAreaId()); + } + + private String buildLocationMoveNo() { + return "KWYK" + System.currentTimeMillis(); + } + + private LookupContext buildLookupContext(List stocks, + List records) { + Set productIds = new HashSet<>(); + Set warehouseIds = new HashSet<>(); + Set areaIds = new HashSet<>(); + Set locationIds = new HashSet<>(); + Set creatorIds = new HashSet<>(); + if (stocks != null) { + for (BitWmsLocationStockDO stock : stocks) { + collectCommonIds(productIds, warehouseIds, areaIds, locationIds, + stock.getProductId(), stock.getWarehouseId(), stock.getAreaId(), stock.getLocationId()); + } + } + if (records != null) { + for (BitWmsLocationStockRecordDO record : records) { + collectCommonIds(productIds, warehouseIds, areaIds, locationIds, + record.getProductId(), record.getWarehouseId(), record.getAreaId(), record.getLocationId()); + if (record.getCreator() != null) { + creatorIds.add(Long.parseLong(record.getCreator())); + } + } + } + LookupContext context = new LookupContext(); + context.productMap = productIds.isEmpty() ? Collections.emptyMap() : productService.getProductVOMap(productIds); + context.warehouseMap = warehouseIds.isEmpty() ? Collections.emptyMap() : warehouseService.getWarehouseMap(warehouseIds); + context.areaMap = areaIds.isEmpty() ? Collections.emptyMap() : warehouseAreaService.getWarehouseAreaMap(areaIds); + context.locationMap = locationIds.isEmpty() ? Collections.emptyMap() + : warehouseLocationMapper.selectBatchIds(locationIds).stream() + .collect(Collectors.toMap(WarehouseLocationDO::getId, item -> item, (a, b) -> a)); + context.userMap = creatorIds.isEmpty() ? Collections.emptyMap() : adminUserApi.getUserMap(creatorIds); + return context; + } + + private void collectCommonIds(Set productIds, Set warehouseIds, Set areaIds, Set locationIds, + Long productId, Long warehouseId, Long areaId, Long locationId) { + if (productId != null) { + productIds.add(productId); } + if (warehouseId != null) { + warehouseIds.add(warehouseId); + } + if (areaId != null) { + areaIds.add(areaId); + } + if (locationId != null) { + locationIds.add(locationId); + } + } + + private void fillLocationStockResp(BitWmsLocationStockRespVO respVO, LookupContext context) { + ErpProductRespVO product = context.productMap.get(respVO.getProductId()); + if (product != null) { + respVO.setProductName(product.getName()); + respVO.setBarCode(product.getBarCode()); + respVO.setSampleCategory(product.getSampleCategory()); + respVO.setCustomerName(product.getCustomerName()); + respVO.setUnitName(product.getUnitName()); + } + fillLocationInfo(respVO.getWarehouseId(), respVO.getAreaId(), respVO.getLocationId(), context, + respVO::setWarehouseName, respVO::setAreaName, respVO::setLocationCode, respVO::setLocationName); + } + + private void fillLocationStockRecordResp(BitWmsLocationStockRecordRespVO respVO, LookupContext context) { + ErpProductRespVO product = context.productMap.get(respVO.getProductId()); + if (product != null) { + respVO.setProductName(product.getName()); + respVO.setBarCode(product.getBarCode()); + respVO.setUnitName(product.getUnitName()); + } + fillLocationInfo(respVO.getWarehouseId(), respVO.getAreaId(), respVO.getLocationId(), context, + respVO::setWarehouseName, respVO::setAreaName, respVO::setLocationCode, respVO::setLocationName); + if (respVO.getCreator() != null) { + AdminUserRespDTO user = context.userMap.get(Long.parseLong(respVO.getCreator())); + if (user != null) { + respVO.setCreatorName(user.getNickname()); + } + } + } + + private void fillLocationInfo(Long warehouseId, Long areaId, Long locationId, LookupContext context, + java.util.function.Consumer warehouseSetter, + java.util.function.Consumer areaSetter, + java.util.function.Consumer locationCodeSetter, + java.util.function.Consumer locationNameSetter) { + ErpWarehouseDO warehouse = context.warehouseMap.get(warehouseId); + if (warehouse != null) { + warehouseSetter.accept(warehouse.getName()); + } + WarehouseAreaDO area = context.areaMap.get(areaId); + if (area != null) { + areaSetter.accept(area.getAreaName()); + } + WarehouseLocationDO location = context.locationMap.get(locationId); + if (location != null) { + locationCodeSetter.accept(location.getCode()); + locationNameSetter.accept(location.getName()); + } + } + + private static class LookupContext { + private Map productMap; + private Map warehouseMap; + private Map areaMap; + private Map locationMap; + private Map userMap; } private String buildOutboundRemark(String outReason, String remark) { 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 index 53874b783..a624d2478 100644 --- 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 @@ -9,12 +9,14 @@ 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.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInAuditReqVO; 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.controller.admin.stock.vo.out.ErpStockOutAuditReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper; import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; @@ -25,6 +27,7 @@ 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.ErpStockOutService; import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -34,8 +37,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_CODE_NOT_EXISTS; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_UNIT_NOT_EXISTS; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.WAREHOUSE_LOCATION_NOT_EXISTS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -70,6 +75,66 @@ class BitWmsServiceImplTest { @Mock private AutoCodeUtil autoCodeUtil; + @Test + void scan_whenProductQrcodeBizKey_returnsSample() { + when(productMapper.selectById(332L)).thenReturn(buildBitSampleProductDO(332L)); + + BitWmsScanRespVO respVO = bitWmsService.scan(" PRODUCTMATERIAL-332 "); + + assertEquals("SAMPLE", respVO.getType()); + assertEquals(332L, respVO.getId()); + assertEquals("SP001", respVO.getCode()); + assertEquals("BIT 样品", respVO.getName()); + } + + @Test + void scan_whenLocationQrcodeBizKey_returnsLocation() { + when(warehouseLocationMapper.selectById(5L)).thenReturn(new WarehouseLocationDO() + .setId(5L).setCode("KW001").setName("必硕三楼 A01")); + + BitWmsScanRespVO respVO = bitWmsService.scan("warehouse_location_5"); + + assertEquals("LOCATION", respVO.getType()); + assertEquals(5L, respVO.getId()); + assertEquals("KW001", respVO.getCode()); + assertEquals("必硕三楼 A01", respVO.getName()); + } + + @Test + void scan_whenProductQrcodeBizKeyIsNotBitSample_throwsException() { + ErpProductDO product = buildBitSampleProductDO(332L); + product.setIsSample(false); + when(productMapper.selectById(332L)).thenReturn(product); + + AssertUtils.assertServiceException(() -> bitWmsService.scan("PRODUCTMATERIAL-332"), PRODUCT_CODE_NOT_EXISTS); + } + + @Test + void scan_whenPlainSampleCode_returnsSample() { + when(productMapper.selectOne(org.mockito.ArgumentMatchers.>any(), eq("SP001"))) + .thenReturn(buildBitSampleProductDO(332L)); + + BitWmsScanRespVO respVO = bitWmsService.scan("SP001"); + + assertEquals("SAMPLE", respVO.getType()); + assertEquals(332L, respVO.getId()); + assertEquals("SP001", respVO.getCode()); + assertEquals("BIT 样品", respVO.getName()); + } + + @Test + void scan_whenPlainLocationCode_returnsLocation() { + when(warehouseLocationMapper.selectByNo("KW001")).thenReturn(new WarehouseLocationDO() + .setId(5L).setCode("KW001").setName("必硕三楼 A01")); + + BitWmsScanRespVO respVO = bitWmsService.scan("KW001"); + + assertEquals("LOCATION", respVO.getType()); + assertEquals(5L, respVO.getId()); + assertEquals("KW001", respVO.getCode()); + assertEquals("必硕三楼 A01", respVO.getName()); + } + @Test void inbound_createsSampleStockInWithBitType() { BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO(); @@ -170,12 +235,15 @@ class BitWmsServiceImplTest { void outbound_rejectsCountGreaterThanLocationStock() { BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO(); reqVO.setProductId(10L); - reqVO.setWarehouseId(20L); - reqVO.setAreaId(30L); + reqVO.setWarehouseId(999L); + reqVO.setAreaId(998L); + reqVO.setLocationId(88L); reqVO.setCount(new BigDecimal("999")); reqVO.setOutReason("TEST"); when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct()); + when(warehouseLocationMapper.selectById(88L)).thenReturn(new WarehouseLocationDO() + .setId(88L).setWarehouseId(20L).setAreaId(30L)); when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(BigDecimal.ONE)); AssertUtils.assertServiceException(() -> bitWmsService.outbound(reqVO), STOCK_COUNT_NEGATIVE2, 10L, 20L); @@ -185,13 +253,16 @@ class BitWmsServiceImplTest { void outbound_createsSampleStockOutAndAutoApproves() { BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO(); reqVO.setProductId(10L); - reqVO.setWarehouseId(20L); - reqVO.setAreaId(30L); + reqVO.setWarehouseId(999L); + reqVO.setAreaId(998L); + reqVO.setLocationId(88L); reqVO.setCount(new BigDecimal("1")); reqVO.setOutReason("TEST"); reqVO.setRemark("out remark"); when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct()); + when(warehouseLocationMapper.selectById(88L)).thenReturn(new WarehouseLocationDO() + .setId(88L).setWarehouseId(20L).setAreaId(30L)); when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(new BigDecimal("2"))); when(stockOutService.createStockOut(org.mockito.ArgumentMatchers.any(ErpStockOutSaveReqVO.class))).thenReturn(200L); @@ -216,6 +287,22 @@ class BitWmsServiceImplTest { assertEquals("out remark", auditCaptor.getValue().getRemark()); } + @Test + void outbound_whenLocationNotExists_throwsException() { + BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO(); + reqVO.setProductId(10L); + reqVO.setWarehouseId(20L); + reqVO.setAreaId(30L); + reqVO.setLocationId(88L); + reqVO.setCount(new BigDecimal("1")); + reqVO.setOutReason("TEST"); + + when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct()); + when(warehouseLocationMapper.selectById(88L)).thenReturn(null); + + AssertUtils.assertServiceException(() -> bitWmsService.outbound(reqVO), WAREHOUSE_LOCATION_NOT_EXISTS); + } + @Test void move_keepsTotalStockAndChangesLocation() { BitWmsMoveReqVO reqVO = new BitWmsMoveReqVO(); @@ -307,4 +394,13 @@ class BitWmsServiceImplTest { product.setUnitName("个"); return product; } + + private ErpProductDO buildBitSampleProductDO(Long id) { + return new ErpProductDO() + .setId(id) + .setName("BIT 样品") + .setBarCode("SP001") + .setIsSample(true) + .setBizUnit("BIT"); + } }