浏览代码

✨ ERP:完善 ERP 销售出库单的保存

YunaiV 1 年之前
父节点
当前提交
0694bc8684
共有 15 个文件被更改,包括 211 次插入38 次删除
  1. 1 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  2. 11 0
      yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
  3. 13 3
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
  4. 1 1
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java
  5. 3 3
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
  6. 3 3
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java
  7. 1 1
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java
  8. 5 5
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
  9. 26 0
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java
  10. 5 0
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java
  11. 4 0
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
  12. 18 0
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
  13. 49 3
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
  14. 59 18
      yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
  15. 12 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java

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

@@ -291,7 +291,7 @@ public class CollectionUtils {
         return getSumValue(from, valueFunc, accumulator, null);
     }
 
-    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
+    public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
                                                                      BinaryOperator<V> accumulator, V defaultValue) {
         if (CollUtil.isEmpty(from)) {
             return defaultValue;

+ 11 - 0
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java

@@ -24,6 +24,17 @@ public interface ErrorCodeConstants {
     ErrorCode SALE_ORDER_APPROVE_FAIL = new ErrorCode(1_020_201_003, "审核失败,只有未审核的销售订单才能审核");
     ErrorCode SALE_ORDER_NO_EXISTS = new ErrorCode(1_020_201_004, "生成销售单号失败,请重新提交");
     ErrorCode SALE_ORDER_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_201_005, "销售订单({})已审核,无法修改");
+    ErrorCode SALE_ORDER_NOT_APPROVE = new ErrorCode(1_020_201_006, "销售订单未审核,无法操作");
+    ErrorCode SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED = new ErrorCode(1_020_201_007, "销售订单项({})超过最大允许出库数量({})");
+    ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_OUT = new ErrorCode(1_020_201_002, "反审核失败,已存在对应的销售出库单");
+
+    // ========== ERP 销售出库(1-030-202-000) ==========
+    ErrorCode SALE_OUT_NOT_EXISTS = new ErrorCode(1_020_202_000, "销售出库单不存在");
+    ErrorCode SALE_OUT_DELETE_FAIL_APPROVE = new ErrorCode(1_020_202_001, "销售出库单({})已审核,无法删除");
+    ErrorCode SALE_OUT_PROCESS_FAIL = new ErrorCode(1_020_202_002, "反审核失败,只有已审核的出库单才能反审核");
+    ErrorCode SALE_OUT_APPROVE_FAIL = new ErrorCode(1_020_202_003, "审核失败,只有未审核的出库单才能审核");
+    ErrorCode SALE_OUT_NO_EXISTS = new ErrorCode(1_020_202_004, "生成出库单失败,请重新提交");
+    ErrorCode SALE_OUT_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_202_005, "销售出库单({})已审核,无法修改");
 
     // ========== ERP 仓库 1-030-400-000 ==========
     ErrorCode WAREHOUSE_NOT_EXISTS = new ErrorCode(1_030_400_000, "仓库不存在");

+ 13 - 3
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java

@@ -90,10 +90,10 @@ public class ErpSaleOrderRespVO {
     @ExcelProperty("产品信息")
     private String productNames;
 
-    // ========== 销售库 ==========
+    // ========== 销售库 ==========
 
-    @Schema(description = "销售库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
-    private BigDecimal inCount;
+    @Schema(description = "销售库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+    private BigDecimal outCount;
 
     // ========== 销售退货(出库)) ==========
 
@@ -128,6 +128,16 @@ public class ErpSaleOrderRespVO {
         @Schema(description = "备注", example = "随便")
         private String remark;
 
+        // ========== 销售出库 ==========
+
+        @Schema(description = "销售出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+        private BigDecimal outCount;
+
+        // ========== 销售退货(出库)) ==========
+
+        @Schema(description = "销售退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+        private BigDecimal returnCount;
+
         // ========== 关联字段 ==========
 
         @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")

+ 1 - 1
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java

@@ -47,7 +47,7 @@ public class ErpSaleOutRespVO {
     @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
     private Long orderId;
     @Schema(description = "销售订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
-    private Long orderNo;
+    private String orderNo;
 
     @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663")
     @ExcelProperty("合计数量")

+ 3 - 3
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java

@@ -106,11 +106,11 @@ public class ErpSaleOrderDO extends BaseDO {
      */
     private String remark;
 
-    // ========== 销售库 ==========
+    // ========== 销售库 ==========
     /**
-     * 销售库数量
+     * 销售库数量
      */
-    private BigDecimal inCount;
+    private BigDecimal outCount;
 
     // ========== 销售退货(出库)) ==========
     /**

+ 3 - 3
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java

@@ -78,11 +78,11 @@ public class ErpSaleOrderItemDO extends BaseDO {
      */
     private String remark;
 
-    // ========== 销售库 ==========
+    // ========== 销售库 ==========
     /**
-     * 销售库数量
+     * 销售库数量
      */
-    private BigDecimal inCount;
+    private BigDecimal outCount;
 
     // ========== 销售退货(出库)) ==========
     /**

+ 1 - 1
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java

@@ -74,7 +74,7 @@ public class ErpSaleOutDO extends BaseDO {
      *
      * 冗余 {@link ErpSaleOrderDO#getNo()}
      */
-    private Long orderNo;
+    private String orderNo;
 
     /**
      * 合计数量

+ 5 - 5
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java

@@ -30,13 +30,13 @@ public interface ErpSaleOrderMapper extends BaseMapperX<ErpSaleOrderDO> {
                 .likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark())
                 .eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator())
                 .orderByDesc(ErpSaleOrderDO::getId);
-        // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 in_count 错误
+        // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 out_count 错误
         if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_NONE)) {
-            query.eq(ErpSaleOrderDO::getInCount, 0);
+            query.eq(ErpSaleOrderDO::getOutCount, 0);
         } else if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_PART)) {
-            query.gt(ErpSaleOrderDO::getInCount, 0).apply("t.in_count < t.total_count");
+            query.gt(ErpSaleOrderDO::getOutCount, 0).apply("t.out_count < t.total_count");
         } else if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_ALL)) {
-            query.apply("t.in_count = t.total_count");
+            query.apply("t.out_count = t.total_count");
         }
         // 退货状态
         if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_NONE)) {
@@ -49,7 +49,7 @@ public interface ErpSaleOrderMapper extends BaseMapperX<ErpSaleOrderDO> {
         // 可出库
         if (Boolean.TRUE.equals(reqVO.getOutEnable())) {
             query.eq(ErpSaleOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus())
-                    .apply("t.in_count < t.total_count");
+                    .apply("t.out_count < t.total_count");
         }
         if (reqVO.getProductId() != null) {
             query.leftJoin(ErpSaleOrderItemDO.class, ErpSaleOrderItemDO::getOrderId, ErpSaleOrderDO::getId)

+ 26 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java

@@ -1,11 +1,18 @@
 package cn.iocoder.yudao.module.erp.dal.mysql.sale;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.math.BigDecimal;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * ERP 销售出库项 Mapper
@@ -27,4 +34,23 @@ public interface ErpSaleOutItemMapper extends BaseMapperX<ErpSaleOutItemDO> {
         return delete(ErpSaleOutItemDO::getOutId, orderId);
     }
 
+    /**
+     * 基于销售订单编号,查询每个销售订单项的出库数量之和
+     *
+     * @param outIds 出库订单项编号数组
+     * @return key:销售订单项编号;value:出库数量之和
+     */
+    default Map<Long, BigDecimal> selectOrderItemCountSumMapByOutIds(Collection<Long> outIds) {
+        if (CollUtil.isEmpty(outIds)) {
+            return Collections.emptyMap();
+        }
+        // SQL sum 查询
+        List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpSaleOutItemDO>()
+                .select("order_item_id, SUM(count) AS sumCount")
+                .groupBy("order_item_id")
+                .in("out_id", outIds));
+        // 获得数量
+        return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount"));
+    }
+
 }

+ 5 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java

@@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 /**
  * ERP 销售出库 Mapper
@@ -54,4 +55,8 @@ public interface ErpSaleOutMapper extends BaseMapperX<ErpSaleOutDO> {
         return selectOne(ErpSaleOutDO::getNo, no);
     }
 
+    default List<ErpSaleOutDO> selectListByOrderId(Long orderId) {
+        return selectList(ErpSaleOutDO::getOrderId, orderId);
+    }
+
 }

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

@@ -42,6 +42,10 @@ public class ErpNoRedisDAO {
      * 销售订单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO}
      */
     public static final String SALE_ORDER_NO_PREFIX = "XSDD";
+    /**
+     * 销售出库 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO}
+     */
+    public static final String SALE_OUT_NO_PREFIX = "XSCK";
 
     @Resource
     private StringRedisTemplate stringRedisTemplate;

+ 18 - 0
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java

@@ -7,8 +7,10 @@ import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
 import jakarta.validation.Valid;
 
+import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * ERP 销售订单 Service 接口
@@ -40,6 +42,14 @@ public interface ErpSaleOrderService {
      */
     void updateSaleOrderStatus(Long id, Integer status);
 
+    /**
+     * 更新销售订单的出库数量
+     *
+     * @param id 编号
+     * @param returnCountMap 出库数量 Map:key 销售订单项编号;value 出库数量
+     */
+    void updateSaleOrderOutCount(Long id, Map<Long, BigDecimal> returnCountMap);
+
     /**
      * 删除销售订单
      *
@@ -55,6 +65,14 @@ public interface ErpSaleOrderService {
      */
     ErpSaleOrderDO getSaleOrder(Long id);
 
+    /**
+     * 校验销售订单,已经审核通过
+     *
+     * @param id 编号
+     * @return 销售订单
+     */
+    ErpSaleOrderDO validateSaleOrder(Long id);
+
     /**
      * 获得销售订单分页
      *

+ 49 - 3
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.erp.service.sale;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -15,6 +16,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.service.finance.ErpAccountService;
 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -56,6 +58,9 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
     @Resource
     private ErpAccountService accountService;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) {
@@ -67,7 +72,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
         if (createReqVO.getAccountId() != null) {
             accountService.validateAccount(createReqVO.getAccountId());
         }
-        // 1.4 生成调拨单号,并校验唯一性
+        // 1.4 校验销售人员
+        if (createReqVO.getSaleUserId() != null) {
+            adminUserApi.validateUser(createReqVO.getSaleUserId());
+        }
+        // 1.5 生成调拨单号,并校验唯一性
         String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX);
         if (saleOrderMapper.selectByNo(no) != null) {
             throw exception(SALE_ORDER_NO_EXISTS);
@@ -98,7 +107,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
         if (updateReqVO.getAccountId() != null) {
             accountService.validateAccount(updateReqVO.getAccountId());
         }
-        // 1.4 校验订单项的有效性
+        // 1.4 校验销售人员
+        if (updateReqVO.getSaleUserId() != null) {
+            adminUserApi.validateUser(updateReqVO.getSaleUserId());
+        }
+        // 1.5 校验订单项的有效性
         List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(updateReqVO.getItems());
 
         // 2.1 更新订单
@@ -132,7 +145,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
         if (saleOrder.getStatus().equals(status)) {
             throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL);
         }
-        // TODO @芋艿:需要校验是不是有入库、有退货
+        // 1.3 存在销售出库单,无法反审核
+        if (!approve && saleOrder.getOutCount().compareTo(BigDecimal.ZERO) > 0) {
+            throw exception(SALE_ORDER_PROCESS_FAIL_EXISTS_OUT);
+        }
+        // TODO @芋艿:需要校验是不是有有退货
 
         // 2. 更新状态
         int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(),
@@ -142,6 +159,26 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
         }
     }
 
+    @Override
+    public void updateSaleOrderOutCount(Long id, Map<Long, BigDecimal> returnCountMap) {
+        List<ErpSaleOrderItemDO> orderItems = saleOrderItemMapper.selectListByOrderId(id);
+        // 1. 更新每个销售订单项
+        orderItems.forEach(item -> {
+            BigDecimal outCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO);
+            if (item.getOutCount().equals(outCount)) {
+                return;
+            }
+            if (outCount.compareTo(item.getCount()) > 0) {
+                throw exception(SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED,
+                        productService.getProduct(item.getProductId()).getName(), item.getCount());
+            }
+            saleOrderItemMapper.updateById(new ErpSaleOrderItemDO().setId(item.getId()).setOutCount(outCount));
+        });
+        // 2. 更新销售订单
+        BigDecimal totalOutCount = getSumValue(returnCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO);
+        saleOrderMapper.updateById(new ErpSaleOrderDO().setId(id).setOutCount(totalOutCount));
+    }
+
     private List<ErpSaleOrderItemDO> validateSaleOrderItems(List<ErpSaleOrderSaveReqVO.Item> list) {
         // 1. 校验产品存在
         List<ErpProductDO> productList = productService.validProductList(
@@ -214,6 +251,15 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
         return saleOrderMapper.selectById(id);
     }
 
+    @Override
+    public ErpSaleOrderDO validateSaleOrder(Long id) {
+        ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
+        if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) {
+            throw exception(SALE_ORDER_NOT_APPROVE);
+        }
+        return saleOrder;
+    }
+
     @Override
     public PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) {
         return saleOrderMapper.selectPage(pageReqVO);

+ 59 - 18
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java

@@ -1,12 +1,14 @@
 package cn.iocoder.yudao.module.erp.service.sale;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO;
 import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOutItemMapper;
@@ -15,7 +17,9 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -52,33 +56,45 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
     @Resource
     private ErpProductService productService;
     @Resource
-    private ErpCustomerService customerService;
+    @Lazy // 延迟加载,避免循环依赖
+    private ErpSaleOrderService saleOrderService;
     @Resource
     private ErpAccountService accountService;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) {
-        // 1.1 校验订单项的有效性
+        // 1.1 校验销售订单已审核
+        ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(createReqVO.getOrderId());
+        // 1.2 校验订单项的有效性
         List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(createReqVO.getItems());
-        // 1.2 校验客户 TODO 芋艿:需要在瞅瞅
-//        customerService.validateCustomer(createReqVO.getCustomerId());
         // 1.3 校验结算账户
         accountService.validateAccount(createReqVO.getAccountId());
-        // 1.4 生成调拨单号,并校验唯一性
-        String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX);
+        // 1.4 校验销售人员
+        if (createReqVO.getSaleUserId() != null) {
+            adminUserApi.validateUser(createReqVO.getSaleUserId());
+        }
+        // 1.5 生成调拨单号,并校验唯一性
+        String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_OUT_NO_PREFIX);
         if (saleOutMapper.selectByNo(no) != null) {
-            throw exception(SALE_ORDER_NO_EXISTS);
+            throw exception(SALE_OUT_NO_EXISTS);
         }
 
         // 2.1 插入订单
         ErpSaleOutDO saleOut = BeanUtils.toBean(createReqVO, ErpSaleOutDO.class, in -> in
-                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()));
+                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
+                .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId());
         calculateTotalPrice(saleOut, saleOutItems);
         saleOutMapper.insert(saleOut);
         // 2.2 插入订单项
         saleOutItems.forEach(o -> o.setOutId(saleOut.getId()));
         saleOutItemMapper.insertBatch(saleOutItems);
+
+        // 3. 更新销售订单的出库数量
+        updateSaleOrderOutCount(createReqVO.getOrderId());
         return saleOut.getId();
     }
 
@@ -88,21 +104,33 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
         // 1.1 校验存在
         ErpSaleOutDO saleOut = validateSaleOutExists(updateReqVO.getId());
         if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) {
-            throw exception(SALE_ORDER_UPDATE_FAIL_APPROVE, saleOut.getNo());
+            throw exception(SALE_OUT_UPDATE_FAIL_APPROVE, saleOut.getNo());
         }
-        // 1.2 校验客户 TODO 芋艿:需要在瞅瞅
-//        customerService.validateCustomer(updateReqVO.getCustomerId());
+        // 1.2 校验销售订单已审核
+        ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(updateReqVO.getOrderId());
         // 1.3 校验结算账户
         accountService.validateAccount(updateReqVO.getAccountId());
-        // 1.4 校验订单项的有效性
+        // 1.4 校验销售人员
+        if (updateReqVO.getSaleUserId() != null) {
+            adminUserApi.validateUser(updateReqVO.getSaleUserId());
+        }
+        // 1.5 校验订单项的有效性
         List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(updateReqVO.getItems());
 
         // 2.1 更新订单
-        ErpSaleOutDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOutDO.class);
+        ErpSaleOutDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOutDO.class)
+                .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId());
         calculateTotalPrice(updateObj, saleOutItems);
         saleOutMapper.updateById(updateObj);
         // 2.2 更新订单项
         updateSaleOutItemList(updateReqVO.getId(), saleOutItems);
+
+        // 3.1 更新销售订单的出库数量
+        updateSaleOrderOutCount(updateObj.getOrderId());
+        // 3.2 注意:如果销售订单编号变更了,需要更新“老”销售订单的出库数量
+        if (ObjectUtil.notEqual(saleOut.getOrderId(), updateObj.getOrderId())) {
+            updateSaleOrderOutCount(saleOut.getOrderId());
+        }
     }
 
     private void calculateTotalPrice(ErpSaleOutDO saleOut, List<ErpSaleOutItemDO> saleOutItems) {
@@ -118,6 +146,16 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
         saleOut.setTotalPrice(saleOut.getTotalPrice().subtract(saleOut.getDiscountPrice()));
     }
 
+    private void updateSaleOrderOutCount(Long orderId) {
+        // 1.1 查询销售订单对应的销售出库单列表
+        List<ErpSaleOutDO> saleOuts = saleOutMapper.selectListByOrderId(orderId);
+        // 1.2 查询对应的销售订单项的出库数量
+        Map<Long, BigDecimal> returnCountMap = saleOutItemMapper.selectOrderItemCountSumMapByOutIds(
+                convertList(saleOuts, ErpSaleOutDO::getId));
+        // 2. 更新销售订单的出库数量
+        saleOrderService.updateSaleOrderOutCount(orderId, returnCountMap);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateSaleOutStatus(Long id, Integer status) {
@@ -126,15 +164,14 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
         ErpSaleOutDO saleOut = validateSaleOutExists(id);
         // 1.2 校验状态
         if (saleOut.getStatus().equals(status)) {
-            throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL);
+            throw exception(approve ? SALE_OUT_APPROVE_FAIL : SALE_OUT_PROCESS_FAIL);
         }
-        // TODO @芋艿:需要校验是不是有入库、有退货
 
         // 2. 更新状态
         int updateCount = saleOutMapper.updateByIdAndStatus(id, saleOut.getStatus(),
                 new ErpSaleOutDO().setStatus(status));
         if (updateCount == 0) {
-            throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL);
+            throw exception(approve ? SALE_OUT_APPROVE_FAIL : SALE_OUT_PROCESS_FAIL);
         }
     }
 
@@ -184,7 +221,7 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
         }
         saleOuts.forEach(saleOut -> {
             if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) {
-                throw exception(SALE_ORDER_DELETE_FAIL_APPROVE, saleOut.getNo());
+                throw exception(SALE_OUT_DELETE_FAIL_APPROVE, saleOut.getNo());
             }
         });
 
@@ -194,13 +231,17 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
             saleOutMapper.deleteById(saleOut.getId());
             // 2.2 删除订单项
             saleOutItemMapper.deleteByOutId(saleOut.getId());
+
+            // 2.3 更新销售订单的出库数量
+            updateSaleOrderOutCount(saleOut.getOrderId());
         });
+
     }
 
     private ErpSaleOutDO validateSaleOutExists(Long id) {
         ErpSaleOutDO saleOut = saleOutMapper.selectById(id);
         if (saleOut == null) {
-            throw exception(SALE_ORDER_NOT_EXISTS);
+            throw exception(SALE_OUT_NOT_EXISTS);
         }
         return saleOut;
     }

+ 12 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -65,6 +66,17 @@ public interface AdminUserApi {
         return CollectionUtils.convertMap(users, AdminUserRespDTO::getId);
     }
 
+    /**
+     * 校验用户是否有效。如下情况,视为无效:
+     * 1. 用户编号不存在
+     * 2. 用户被禁用
+     *
+     * @param id 用户编号
+     */
+    default void validateUser(Long id) {
+        validateUserList(Collections.singleton(id));
+    }
+
     /**
      * 校验用户们是否有效。如下情况,视为无效:
      * 1. 用户编号不存在