Răsfoiți Sursa

✨ ERP:增加 ERP 调拨单的实现 100%

YunaiV 1 an în urmă
părinte
comite
573a79cad6

+ 10 - 2
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java

@@ -40,9 +40,17 @@ public interface ErrorCodeConstants {
     ErrorCode STOCK_OUT_NO_EXISTS = new ErrorCode(1_030_402_004, "生成出库单失败,请重新提交");
     ErrorCode STOCK_OUT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "其它出库单({})已审核,无法修改");
 
+    // ========== ERP 其它出库单 1-030-403-000 ==========
+    ErrorCode STOCK_MOVE_NOT_EXISTS = new ErrorCode(1_030_402_000, "库存调拨单不存在");
+    ErrorCode STOCK_MOVE_DELETE_FAIL_APPROVE = new ErrorCode(1_030_402_001, "库存调拨单({})已审核,无法删除");
+    ErrorCode STOCK_MOVE_PROCESS_FAIL = new ErrorCode(1_030_402_002, "反审核失败,只有已审核的出库单才能反审核");
+    ErrorCode STOCK_MOVE_APPROVE_FAIL = new ErrorCode(1_030_402_003, "审核失败,只有未审核的出库单才能审核");
+    ErrorCode STOCK_MOVE_NO_EXISTS = new ErrorCode(1_030_402_004, "生成调拨号失败,请重新提交");
+    ErrorCode STOCK_MOVE_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "库存调拨单({})已审核,无法修改");
+
     // ========== ERP 产品库存 1-030-403-000 ==========
-    ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_403_000, "操作失败,产品当前库存:{},小于变更数量:{}");
-    ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_403_000, "操作失败,库存不足");
+    ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_403_000, "操作失败,产品({})所在仓库({})的库存:{},小于变更数量:{}");
+    ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_403_000, "操作失败,产品({})所在仓库({})的库存不足");
 
     // ========== ERP 产品 1-030-500-000 ==========
     ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在");

+ 5 - 0
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java

@@ -20,6 +20,11 @@ public enum ErpStockRecordBizTypeEnum implements IntArrayValuable {
 
     OTHER_OUT(20, "其它出库"),
     OTHER_OUT_CANCEL(21, "其它出库(作废)"),
+
+    MOVE_IN(30, "调拨入库"),
+    MOVE_IN_CANCEL(31, "调拨入库(作废)"),
+    MOVE_OUT(32, "调拨出库"),
+    MOVE_OUT_CANCEL(33, "调拨出库(作废)"),
     ;
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpStockRecordBizTypeEnum::getType).toArray();

+ 9 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
 
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.Valid;
+import jakarta.validation.constraints.AssertTrue;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
@@ -63,6 +66,12 @@ public class ErpStockMoveSaveReqVO {
         @Schema(description = "备注", example = "随便")
         private String remark;
 
+        @AssertTrue(message = "调出、调仓仓库不能相同")
+        @JsonIgnore
+        public boolean isWarehouseValid() {
+            return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId);
+        }
+
     }
 
 }

+ 2 - 2
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveDO.java

@@ -14,8 +14,8 @@ import java.time.LocalDateTime;
  *
  * @author 芋道源码
  */
-@TableName("erp_stock_in")
-@KeySequence("erp_stock_in_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("erp_stock_move")
+@KeySequence("erp_stock_move_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)

+ 2 - 2
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java

@@ -14,8 +14,8 @@ import java.math.BigDecimal;
  *
  * @author 芋道源码
  */
-@TableName("erp_stock_in_item")
-@KeySequence("erp_stock_in_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("erp_stock_move_item")
+@KeySequence("erp_stock_move_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)

+ 5 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java

@@ -28,6 +28,11 @@ public class ErpNoRedisDAO {
      */
     public static final String STOCK_OUT_NO_PREFIX = "QCKD";
 
+    /**
+     * 库存调拨 {@link cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO}
+     */
+    public static final String STOCK_MOVE_NO_PREFIX = "QCDB";
+
     @Resource
     private StringRedisTemplate stringRedisTemplate;
 

+ 1 - 1
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java

@@ -66,7 +66,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
         List<ErpStockInItemDO> stockInItems = validateStockInItems(createReqVO.getItems());
         // 1.2 校验供应商
         supplierService.validateSupplier(createReqVO.getSupplierId());
-        // 1.3
+        // 1.3 生成入库单号,并校验唯一性
         String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_IN_NO_PREFIX);
         if (stockInMapper.selectByNo(no) != null) {
             throw exception(STOCK_IN_NO_EXISTS);

+ 25 - 25
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java

@@ -15,7 +15,7 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
 import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
-import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService;
+import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -26,6 +26,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -55,8 +56,6 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
     @Resource
     private ErpWarehouseService warehouseService;
     @Resource
-    private ErpCustomerService customerService;
-    @Resource
     private ErpStockRecordService stockRecordService;
 
     @Override
@@ -64,12 +63,10 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
     public Long createStockMove(ErpStockMoveSaveReqVO createReqVO) {
         // 1.1 校验出库项的有效性
         List<ErpStockMoveItemDO> stockMoveItems = validateStockMoveItems(createReqVO.getItems());
-        // 1.2 校验客户
-        customerService.validateCustomer(createReqVO.getCustomerId());
-        // 1.3
-        String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_OUT_NO_PREFIX);
+        // 1.2 生成调拨单号,并校验唯一性
+        String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_MOVE_NO_PREFIX);
         if (stockMoveMapper.selectByNo(no) != null) {
-            throw exception(STOCK_OUT_NO_EXISTS);
+            throw exception(STOCK_MOVE_NO_EXISTS);
         }
 
         // 2.1 插入出库单
@@ -90,11 +87,9 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
         // 1.1 校验存在
         ErpStockMoveDO stockMove = validateStockMoveExists(updateReqVO.getId());
         if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
-            throw exception(STOCK_OUT_UPDATE_FAIL_APPROVE, stockMove.getNo());
+            throw exception(STOCK_MOVE_UPDATE_FAIL_APPROVE, stockMove.getNo());
         }
-        // 1.2 校验客户
-        customerService.validateCustomer(updateReqVO.getCustomerId());
-        // 1.3 校验出库项的有效性
+        // 1.2 校验出库项的有效性
         List<ErpStockMoveItemDO> stockMoveItems = validateStockMoveItems(updateReqVO.getItems());
 
         // 2.1 更新出库单
@@ -114,26 +109,31 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
         ErpStockMoveDO stockMove = validateStockMoveExists(id);
         // 1.2 校验状态
         if (stockMove.getStatus().equals(status)) {
-            throw exception(approve ? STOCK_OUT_APPROVE_FAIL : STOCK_OUT_PROCESS_FAIL);
+            throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
         }
 
         // 2. 更新状态
         int updateCount = stockMoveMapper.updateByIdAndStatus(id, stockMove.getStatus(),
                 new ErpStockMoveDO().setStatus(status));
         if (updateCount == 0) {
-            throw exception(approve ? STOCK_OUT_APPROVE_FAIL : STOCK_OUT_PROCESS_FAIL);
+            throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
         }
 
         // 3. 变更库存
         List<ErpStockMoveItemDO> stockMoveItems = stockMoveItemMapper.selectListByMoveId(id);
-        Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_OUT.getType()
-                : ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
+        Integer fromBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_OUT.getType()
+                : ErpStockRecordBizTypeEnum.MOVE_OUT_CANCEL.getType();
+        Integer toBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_IN.getType()
+                : ErpStockRecordBizTypeEnum.MOVE_IN_CANCEL.getType();
         stockMoveItems.forEach(stockMoveItem -> {
-            BigDecimal count = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
-            // TODO 芋艿:稍后搞
-//            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
-//                    stockMoveItem.getProductId(), stockMoveItem.getWarehouseId(), count,
-//                    bizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
+            BigDecimal fromCount = approve ? stockMoveItem.getCount().negate() : stockMoveItem.getCount();
+            BigDecimal toCount = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
+            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
+                    stockMoveItem.getProductId(), stockMoveItem.getFromWarehouseId(), fromCount,
+                    fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
+            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
+                    stockMoveItem.getProductId(), stockMoveItem.getToWarehouseId(), toCount,
+                    toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
         });
     }
 
@@ -143,8 +143,8 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
                 convertSet(list, ErpStockMoveSaveReqVO.Item::getProductId));
         Map<Long, ErpProductDO> productMap = convertMap(productList, ErpProductDO::getId);
         // 1.2 校验仓库存在
-        // TODO 芋艿:稍后搞
-//        warehouseService.validWarehouseList(convertSet(list, ErpStockMoveSaveReqVO.Item::getWarehouseId));
+        warehouseService.validWarehouseList(convertSetByFlatMap(list,
+                item -> Stream.of(item.getFromWarehouseId(),  item.getToWarehouseId())));
         // 2. 转化为 ErpStockMoveItemDO 列表
         return convertList(list, o -> BeanUtils.toBean(o, ErpStockMoveItemDO.class, item -> item
                 .setProductUnitId(productMap.get(item.getProductId()).getUnitId())
@@ -180,7 +180,7 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
         }
         stockMoves.forEach(stockMove -> {
             if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
-                throw exception(STOCK_OUT_DELETE_FAIL_APPROVE, stockMove.getNo());
+                throw exception(STOCK_MOVE_DELETE_FAIL_APPROVE, stockMove.getNo());
             }
         });
 
@@ -196,7 +196,7 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
     private ErpStockMoveDO validateStockMoveExists(Long id) {
         ErpStockMoveDO stockMove = stockMoveMapper.selectById(id);
         if (stockMove == null) {
-            throw exception(STOCK_OUT_NOT_EXISTS);
+            throw exception(STOCK_MOVE_NOT_EXISTS);
         }
         return stockMove;
     }

+ 2 - 2
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java

@@ -67,7 +67,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
         List<ErpStockOutItemDO> stockOutItems = validateStockOutItems(createReqVO.getItems());
         // 1.2 校验客户
         customerService.validateCustomer(createReqVO.getCustomerId());
-        // 1.3
+        // 1.3 生成出库单号,并校验唯一性
         String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_OUT_NO_PREFIX);
         if (stockOutMapper.selectByNo(no) != null) {
             throw exception(STOCK_OUT_NO_EXISTS);
@@ -130,7 +130,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
         Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_OUT.getType()
                 : ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
         stockOutItems.forEach(stockOutItem -> {
-            BigDecimal count = approve ? stockOutItem.getCount() : stockOutItem.getCount().negate();
+            BigDecimal count = approve ? stockOutItem.getCount().negate() : stockOutItem.getCount();
             stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                     stockOutItem.getProductId(), stockOutItem.getWarehouseId(), count,
                     bizType, stockOutItem.getOutId(), stockOutItem.getId(), stockOut.getNo()));

+ 11 - 2
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
 import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper;
+import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -30,6 +31,11 @@ public class ErpStockServiceImpl implements ErpStockService {
      */
     private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false;
 
+    @Resource
+    private ErpProductService productService;
+    @Resource
+    private ErpWarehouseService warehouseService;
+
     @Resource
     private ErpStockMapper stockMapper;
 
@@ -58,13 +64,16 @@ public class ErpStockServiceImpl implements ErpStockService {
         }
         // 1.2 校验库存是否充足
         if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) {
-            throw exception(STOCK_COUNT_NEGATIVE, stock.getCount(), count);
+            throw exception(STOCK_COUNT_NEGATIVE, productService.getProduct(productId).getName(),
+                    warehouseService.getWarehouse(warehouseId).getName(), stock.getCount(), count);
         }
 
         // 2. 库存变更
         int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
         if (updateCount == 0) {
-            throw exception(STOCK_COUNT_NEGATIVE2); // 此时不好去查询最新库存,所以直接抛出该提示,不提供具体库存数字
+            // 此时不好去查询最新库存,所以直接抛出该提示,不提供具体库存数字
+            throw exception(STOCK_COUNT_NEGATIVE2, productService.getProduct(productId).getName(),
+                    warehouseService.getWarehouse(warehouseId).getName());
         }
 
         // 3. 返回最新库存