Explorar o código

✨ ERP:增加入库单的审批功能

YunaiV hai 1 ano
pai
achega
593e1fd59c

+ 10 - 5
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -288,11 +288,16 @@ public class CollectionUtils {
 
     public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
                                                                      BinaryOperator<V> accumulator) {
+        return getSumValue(from, valueFunc, accumulator, null);
+    }
+
+    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
+                                                                     BinaryOperator<V> accumulator, V defaultValue) {
         if (CollUtil.isEmpty(from)) {
-            return null;
+            return defaultValue;
         }
-        assert from.size() > 0; // 断言,避免告警
-        return from.stream().map(valueFunc).reduce(accumulator).get();
+        assert !from.isEmpty(); // 断言,避免告警
+        return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
     }
 
     public static <T> void addIfNotNull(Collection<T> coll, T item) {
@@ -302,8 +307,8 @@ public class CollectionUtils {
         coll.add(item);
     }
 
-    public static <T> Collection<T> singleton(T deptId) {
-        return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
+    public static <T> Collection<T> singleton(T obj) {
+        return obj == null ? Collections.emptyList() : Collections.singleton(obj);
     }
 
 }

+ 20 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.framework.common.util.number;
 
+import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 
+import java.math.BigDecimal;
+
 /**
  * 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能
  *
@@ -37,4 +40,21 @@ public class NumberUtils {
         return distance;
     }
 
+    /**
+     * 提供精确的乘法运算
+     *
+     * 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null
+     *
+     * @param values 多个被乘值
+     * @return 积
+     */
+    public static BigDecimal mul(BigDecimal... values) {
+        for (BigDecimal value : values) {
+            if (value == null) {
+                return null;
+            }
+        }
+        return NumberUtil.mul(values);
+    }
+
 }

+ 5 - 1
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java

@@ -13,7 +13,7 @@ public interface ErrorCodeConstants {
     ErrorCode SUPPLIER_NOT_EXISTS = new ErrorCode(1_030_100_000, "供应商不存在");
     ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_030_100_000, "供应商({})未启用");
 
-    // ========== 销售订单(1-030-200-000) ==========
+    // ========== ERP 销售订单(1-030-200-000) ==========
     ErrorCode SALE_ORDER_NOT_EXISTS = new ErrorCode(1_020_200_000, "销售订单不存在");
 
     // ========== ERP 仓库 1-030-400-000 ==========
@@ -22,6 +22,10 @@ public interface ErrorCodeConstants {
 
     // ========== ERP 其它入库单 1-030-401-000 ==========
     ErrorCode STOCK_IN_NOT_EXISTS = new ErrorCode(1_030_401_000, "其它入库单不存在");
+    ErrorCode STOCK_IN_DELETE_FAIL_APPROVE = new ErrorCode(1_030_401_001, "其它入库单({})已审核,无法删除");
+    ErrorCode STOCK_IN_PROCESS_FAIL = new ErrorCode(1_030_401_002, "反审核失败,只有已审核的入库单才能反审核");
+    ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核");
+
 
     // ========== ERP 产品 1-030-500-000 ==========
     ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在");

+ 20 - 8
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java

@@ -75,12 +75,21 @@ public class ErpStockInController {
         return success(true);
     }
 
+    @PutMapping("/update-status")
+    @Operation(summary = "更新其它入库单的状态")
+    @PreAuthorize("@ss.hasPermission('erp:stock-in:update')")
+    public CommonResult<Boolean> updateStockInStatus(@RequestParam("id") Long id,
+                                                     @RequestParam("status") Integer status) {
+        stockInService.updateStockInStatus(id, status);
+        return success(true);
+    }
+
     @DeleteMapping("/delete")
     @Operation(summary = "删除其它入库单")
-    @Parameter(name = "id", description = "编号", required = true)
+    @Parameter(name = "ids", description = "编号数组", required = true)
     @PreAuthorize("@ss.hasPermission('erp:stock-in:delete')")
-    public CommonResult<Boolean> deleteStockIn(@RequestParam("id") Long id) {
-        stockInService.deleteStockIn(id);
+    public CommonResult<Boolean> deleteStockIn(@RequestParam("ids") List<Long> ids) {
+        stockInService.deleteStockIn(ids);
         return success(true);
     }
 
@@ -93,12 +102,15 @@ public class ErpStockInController {
         if (stockIn == null) {
             return success(null);
         }
-        List<ErpStockInItemDO> stockInItems = stockInService.getStockInItemListByInId(id);
-        // TODO 芋艿:有个锤子;
+        List<ErpStockInItemDO> stockInItemList = stockInService.getStockInItemListByInId(id);
+        Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
+                convertSet(stockInItemList, ErpStockInItemDO::getProductId));
         return success(BeanUtils.toBean(stockIn, ErpStockInRespVO.class, stockInVO ->
-                stockInVO.setItems(BeanUtils.toBean(stockInItems, ErpStockInRespVO.Item.class, item -> {
+                stockInVO.setItems(BeanUtils.toBean(stockInItemList, ErpStockInRespVO.Item.class, item -> {
                     ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId());
                     item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO);
+                    MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
+                            .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
                 }))));
     }
 
@@ -142,8 +154,8 @@ public class ErpStockInController {
         // 2. 开始拼接
         return BeanUtils.toBean(pageResult, ErpStockInRespVO.class, stockIn -> {
             stockIn.setItems(BeanUtils.toBean(stockInItemMap.get(stockIn.getId()), ErpStockInRespVO.Item.class,
-                    item -> MapUtils.findAndThen(productMap, item.getProductId(),
-                            product -> item.setProductName(product.getName()).setProductUnitName(product.getUnitName()))));
+                    item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
+                            .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
             stockIn.setProductNames(CollUtil.join(stockIn.getItems(), ",", ErpStockInRespVO.Item::getProductName));
             MapUtils.findAndThen(supplierMap, stockIn.getSupplierId(), supplier -> stockIn.setSupplierName(supplier.getName()));
             MapUtils.findAndThen(userMap, Long.parseLong(stockIn.getCreator()), user -> stockIn.setCreatorName(user.getNickname()));

+ 2 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java

@@ -97,6 +97,8 @@ public class ErpStockInRespVO {
 
         @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
         private String productName;
+        @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
+        private String productBarCode;
         @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒")
         private String productUnitName;
 

+ 1 - 2
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInSaveReqVO.java

@@ -53,8 +53,7 @@ public class ErpStockInSaveReqVO {
         @NotNull(message = "产品编号不能为空")
         private Long productId;
 
-        @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
-        @NotNull(message = "产品单价不能为空")
+        @Schema(description = "产品单价", example = "100.00")
         private BigDecimal productPrice;
 
         @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")

+ 6 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInMapper.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -34,4 +35,9 @@ public interface ErpStockInMapper extends BaseMapperX<ErpStockInDO> {
         return selectJoinPage(reqVO, ErpStockInDO.class, query);
     }
 
+    default int updateByIdAndStatus(Long id, Integer status, ErpStockInDO updateObj) {
+        return update(updateObj, new LambdaUpdateWrapper<ErpStockInDO>()
+                .eq(ErpStockInDO::getId, id).eq(ErpStockInDO::getStatus, status));
+    }
+
 }

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

@@ -33,11 +33,19 @@ public interface ErpStockInService {
     void updateStockIn(@Valid ErpStockInSaveReqVO updateReqVO);
 
     /**
-     * 删除其它入库单
+     * 更新其它入库单的状态
      *
      * @param id 编号
+     * @param status 状态
+     */
+    void updateStockInStatus(Long id, Integer status);
+
+    /**
+     * 删除其它入库单
+     *
+     * @param ids 编号数组
      */
-    void deleteStockIn(Long id);
+    void deleteStockIn(List<Long> ids);
 
     /**
      * 获得其它入库单

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

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.erp.service.stock;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
@@ -26,9 +27,9 @@ import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_IN_NOT_EXISTS;
-
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
 
+// TODO 芋艿:记录操作日志
 /**
  * ERP 其它入库单 Service 实现类
  *
@@ -62,9 +63,9 @@ public class ErpStockInServiceImpl implements ErpStockInService {
         ErpStockInDO stockIn = BeanUtils.toBean(createReqVO, ErpStockInDO.class, in -> in
                 .setStatus(ErpAuditStatus.PROCESS.getStatus())
                 .setTotalCount(getSumValue(stockInItems, ErpStockInItemDO::getCount, BigDecimal::add))
-                .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add)));
+                .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)));
         stockInMapper.insert(stockIn);
-        // 2. 插入子表
+        // 2.2 插入入库单项
         stockInItems.forEach(o -> o.setInId(stockIn.getId()));
         stockInItemMapper.insertBatch(stockInItems);
         return stockIn.getId();
@@ -89,6 +90,28 @@ public class ErpStockInServiceImpl implements ErpStockInService {
         updateStockInItemList(updateReqVO.getId(), stockInItems);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateStockInStatus(Long id, Integer status) {
+        // 1.1 校验存在
+        ErpStockInDO stockIn = validateStockInExists(id);
+        // 1.2 校验状态
+        if (stockIn.getStatus().equals(status)) {
+            throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ?
+                    STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
+        }
+
+        // 2. 更新状态
+        int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(),
+                new ErpStockInDO().setStatus(status));
+        if (updateCount == 0) {
+            throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ?
+                    STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
+        }
+
+        // 3. TODO 芋艿:调整库存记录
+    }
+
     private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) {
         // 1.1 校验产品存在
         List<ErpProductDO> productList = productService.validProductList(convertSet(list, ErpStockInSaveReqVO.Item::getProductId));
@@ -98,7 +121,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
         // 2. 转化为 ErpStockInItemDO 列表
         return convertList(list, o -> BeanUtils.toBean(o, ErpStockInItemDO.class, item -> item
                 .setProductUnitId(productMap.get(item.getProductId()).getUnitId())
-                .setTotalPrice(item.getProductPrice().multiply(item.getCount()))));
+                .setTotalPrice(NumberUtils.mul(item.getProductPrice(), item.getCount()))));
     }
 
     private void updateStockInItemList(Long id, List<ErpStockInItemDO> newList) {
@@ -122,21 +145,33 @@ public class ErpStockInServiceImpl implements ErpStockInService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void deleteStockIn(Long id) {
-        // 1. 校验存在
-        validateStockInExists(id);
-        // TODO 芋艿:校验一下;
-
-        // 2.1 删除
-        stockInMapper.deleteById(id);
-        // 2.2 删除子表
-        stockInItemMapper.deleteByInId(id);
+    public void deleteStockIn(List<Long> ids) {
+        // 1. 校验不处于已审批
+        List<ErpStockInDO> stockIns = stockInMapper.selectBatchIds(ids);
+        if (CollUtil.isEmpty(stockIns)) {
+            return;
+        }
+        stockIns.forEach(stockIn -> {
+            if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) {
+                throw exception(STOCK_IN_DELETE_FAIL_APPROVE, stockIn.getNo());
+            }
+        });
+
+        // 2. 遍历删除,并记录操作日志
+        stockIns.forEach(stockIn -> {
+            // 2.1 删除入库单
+            stockInMapper.deleteById(stockIn.getId());
+            // 2.2 删除入库单项
+            stockInItemMapper.deleteByInId(stockIn.getId());
+        });
     }
 
-    private void validateStockInExists(Long id) {
-        if (stockInMapper.selectById(id) == null) {
+    private ErpStockInDO validateStockInExists(Long id) {
+        ErpStockInDO stockIn = stockInMapper.selectById(id);
+        if (stockIn == null) {
             throw exception(STOCK_IN_NOT_EXISTS);
         }
+        return stockIn;
     }
 
     @Override
@@ -149,7 +184,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
         return stockInMapper.selectPage(pageReqVO);
     }
 
-    // ==================== 子表(ERP 其它入库 ====================
+    // ==================== 入库项 ====================
 
     @Override
     public List<ErpStockInItemDO> getStockInItemListByInId(Long inId) {