Commit 0694bc868447ddb5210ceb359720fd2d31e72682

Authored by YunaiV
1 parent 09e4763c

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

Showing 15 changed files with 211 additions and 38 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -291,7 +291,7 @@ public class CollectionUtils { @@ -291,7 +291,7 @@ public class CollectionUtils {
291 return getSumValue(from, valueFunc, accumulator, null); 291 return getSumValue(from, valueFunc, accumulator, null);
292 } 292 }
293 293
294 - public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, 294 + public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
295 BinaryOperator<V> accumulator, V defaultValue) { 295 BinaryOperator<V> accumulator, V defaultValue) {
296 if (CollUtil.isEmpty(from)) { 296 if (CollUtil.isEmpty(from)) {
297 return defaultValue; 297 return defaultValue;
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 { @@ -24,6 +24,17 @@ public interface ErrorCodeConstants {
24 ErrorCode SALE_ORDER_APPROVE_FAIL = new ErrorCode(1_020_201_003, "审核失败,只有未审核的销售订单才能审核"); 24 ErrorCode SALE_ORDER_APPROVE_FAIL = new ErrorCode(1_020_201_003, "审核失败,只有未审核的销售订单才能审核");
25 ErrorCode SALE_ORDER_NO_EXISTS = new ErrorCode(1_020_201_004, "生成销售单号失败,请重新提交"); 25 ErrorCode SALE_ORDER_NO_EXISTS = new ErrorCode(1_020_201_004, "生成销售单号失败,请重新提交");
26 ErrorCode SALE_ORDER_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_201_005, "销售订单({})已审核,无法修改"); 26 ErrorCode SALE_ORDER_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_201_005, "销售订单({})已审核,无法修改");
  27 + ErrorCode SALE_ORDER_NOT_APPROVE = new ErrorCode(1_020_201_006, "销售订单未审核,无法操作");
  28 + ErrorCode SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED = new ErrorCode(1_020_201_007, "销售订单项({})超过最大允许出库数量({})");
  29 + ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_OUT = new ErrorCode(1_020_201_002, "反审核失败,已存在对应的销售出库单");
  30 +
  31 + // ========== ERP 销售出库(1-030-202-000) ==========
  32 + ErrorCode SALE_OUT_NOT_EXISTS = new ErrorCode(1_020_202_000, "销售出库单不存在");
  33 + ErrorCode SALE_OUT_DELETE_FAIL_APPROVE = new ErrorCode(1_020_202_001, "销售出库单({})已审核,无法删除");
  34 + ErrorCode SALE_OUT_PROCESS_FAIL = new ErrorCode(1_020_202_002, "反审核失败,只有已审核的出库单才能反审核");
  35 + ErrorCode SALE_OUT_APPROVE_FAIL = new ErrorCode(1_020_202_003, "审核失败,只有未审核的出库单才能审核");
  36 + ErrorCode SALE_OUT_NO_EXISTS = new ErrorCode(1_020_202_004, "生成出库单失败,请重新提交");
  37 + ErrorCode SALE_OUT_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_202_005, "销售出库单({})已审核,无法修改");
27 38
28 // ========== ERP 仓库 1-030-400-000 ========== 39 // ========== ERP 仓库 1-030-400-000 ==========
29 ErrorCode WAREHOUSE_NOT_EXISTS = new ErrorCode(1_030_400_000, "仓库不存在"); 40 ErrorCode WAREHOUSE_NOT_EXISTS = new ErrorCode(1_030_400_000, "仓库不存在");
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 { @@ -90,10 +90,10 @@ public class ErpSaleOrderRespVO {
90 @ExcelProperty("产品信息") 90 @ExcelProperty("产品信息")
91 private String productNames; 91 private String productNames;
92 92
93 - // ========== 销售入库 ========== 93 + // ========== 销售出库 ==========
94 94
95 - @Schema(description = "销售入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")  
96 - private BigDecimal inCount; 95 + @Schema(description = "销售出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  96 + private BigDecimal outCount;
97 97
98 // ========== 销售退货(出库)) ========== 98 // ========== 销售退货(出库)) ==========
99 99
@@ -128,6 +128,16 @@ public class ErpSaleOrderRespVO { @@ -128,6 +128,16 @@ public class ErpSaleOrderRespVO {
128 @Schema(description = "备注", example = "随便") 128 @Schema(description = "备注", example = "随便")
129 private String remark; 129 private String remark;
130 130
  131 + // ========== 销售出库 ==========
  132 +
  133 + @Schema(description = "销售出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  134 + private BigDecimal outCount;
  135 +
  136 + // ========== 销售退货(出库)) ==========
  137 +
  138 + @Schema(description = "销售退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  139 + private BigDecimal returnCount;
  140 +
131 // ========== 关联字段 ========== 141 // ========== 关联字段 ==========
132 142
133 @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") 143 @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
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 { @@ -47,7 +47,7 @@ public class ErpSaleOutRespVO {
47 @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") 47 @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
48 private Long orderId; 48 private Long orderId;
49 @Schema(description = "销售订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") 49 @Schema(description = "销售订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
50 - private Long orderNo; 50 + private String orderNo;
51 51
52 @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") 52 @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663")
53 @ExcelProperty("合计数量") 53 @ExcelProperty("合计数量")
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 { @@ -106,11 +106,11 @@ public class ErpSaleOrderDO extends BaseDO {
106 */ 106 */
107 private String remark; 107 private String remark;
108 108
109 - // ========== 销售入库 ========== 109 + // ========== 销售出库 ==========
110 /** 110 /**
111 - * 销售入库数量 111 + * 销售出库数量
112 */ 112 */
113 - private BigDecimal inCount; 113 + private BigDecimal outCount;
114 114
115 // ========== 销售退货(出库)) ========== 115 // ========== 销售退货(出库)) ==========
116 /** 116 /**
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 { @@ -78,11 +78,11 @@ public class ErpSaleOrderItemDO extends BaseDO {
78 */ 78 */
79 private String remark; 79 private String remark;
80 80
81 - // ========== 销售入库 ========== 81 + // ========== 销售出库 ==========
82 /** 82 /**
83 - * 销售入库数量 83 + * 销售出库数量
84 */ 84 */
85 - private BigDecimal inCount; 85 + private BigDecimal outCount;
86 86
87 // ========== 销售退货(出库)) ========== 87 // ========== 销售退货(出库)) ==========
88 /** 88 /**
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 { @@ -74,7 +74,7 @@ public class ErpSaleOutDO extends BaseDO {
74 * 74 *
75 * 冗余 {@link ErpSaleOrderDO#getNo()} 75 * 冗余 {@link ErpSaleOrderDO#getNo()}
76 */ 76 */
77 - private Long orderNo; 77 + private String orderNo;
78 78
79 /** 79 /**
80 * 合计数量 80 * 合计数量
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&lt;ErpSaleOrderDO&gt; { @@ -30,13 +30,13 @@ public interface ErpSaleOrderMapper extends BaseMapperX&lt;ErpSaleOrderDO&gt; {
30 .likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark()) 30 .likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark())
31 .eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator()) 31 .eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator())
32 .orderByDesc(ErpSaleOrderDO::getId); 32 .orderByDesc(ErpSaleOrderDO::getId);
33 - // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 in_count 错误 33 + // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 out_count 错误
34 if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_NONE)) { 34 if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_NONE)) {
35 - query.eq(ErpSaleOrderDO::getInCount, 0); 35 + query.eq(ErpSaleOrderDO::getOutCount, 0);
36 } else if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_PART)) { 36 } else if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_PART)) {
37 - query.gt(ErpSaleOrderDO::getInCount, 0).apply("t.in_count < t.total_count"); 37 + query.gt(ErpSaleOrderDO::getOutCount, 0).apply("t.out_count < t.total_count");
38 } else if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_ALL)) { 38 } else if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_ALL)) {
39 - query.apply("t.in_count = t.total_count"); 39 + query.apply("t.out_count = t.total_count");
40 } 40 }
41 // 退货状态 41 // 退货状态
42 if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_NONE)) { 42 if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_NONE)) {
@@ -49,7 +49,7 @@ public interface ErpSaleOrderMapper extends BaseMapperX&lt;ErpSaleOrderDO&gt; { @@ -49,7 +49,7 @@ public interface ErpSaleOrderMapper extends BaseMapperX&lt;ErpSaleOrderDO&gt; {
49 // 可出库 49 // 可出库
50 if (Boolean.TRUE.equals(reqVO.getOutEnable())) { 50 if (Boolean.TRUE.equals(reqVO.getOutEnable())) {
51 query.eq(ErpSaleOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) 51 query.eq(ErpSaleOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus())
52 - .apply("t.in_count < t.total_count"); 52 + .apply("t.out_count < t.total_count");
53 } 53 }
54 if (reqVO.getProductId() != null) { 54 if (reqVO.getProductId() != null) {
55 query.leftJoin(ErpSaleOrderItemDO.class, ErpSaleOrderItemDO::getOrderId, ErpSaleOrderDO::getId) 55 query.leftJoin(ErpSaleOrderItemDO.class, ErpSaleOrderItemDO::getOrderId, ErpSaleOrderDO::getId)
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java
1 package cn.iocoder.yudao.module.erp.dal.mysql.sale; 1 package cn.iocoder.yudao.module.erp.dal.mysql.sale;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; 5 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO;
  6 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
5 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
6 8
  9 +import java.math.BigDecimal;
7 import java.util.Collection; 10 import java.util.Collection;
  11 +import java.util.Collections;
8 import java.util.List; 12 import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
9 16
10 /** 17 /**
11 * ERP 销售出库项 Mapper 18 * ERP 销售出库项 Mapper
@@ -27,4 +34,23 @@ public interface ErpSaleOutItemMapper extends BaseMapperX&lt;ErpSaleOutItemDO&gt; { @@ -27,4 +34,23 @@ public interface ErpSaleOutItemMapper extends BaseMapperX&lt;ErpSaleOutItemDO&gt; {
27 return delete(ErpSaleOutItemDO::getOutId, orderId); 34 return delete(ErpSaleOutItemDO::getOutId, orderId);
28 } 35 }
29 36
  37 + /**
  38 + * 基于销售订单编号,查询每个销售订单项的出库数量之和
  39 + *
  40 + * @param outIds 出库订单项编号数组
  41 + * @return key:销售订单项编号;value:出库数量之和
  42 + */
  43 + default Map<Long, BigDecimal> selectOrderItemCountSumMapByOutIds(Collection<Long> outIds) {
  44 + if (CollUtil.isEmpty(outIds)) {
  45 + return Collections.emptyMap();
  46 + }
  47 + // SQL sum 查询
  48 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpSaleOutItemDO>()
  49 + .select("order_item_id, SUM(count) AS sumCount")
  50 + .groupBy("order_item_id")
  51 + .in("out_id", outIds));
  52 + // 获得数量
  53 + return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount"));
  54 + }
  55 +
30 } 56 }
31 \ No newline at end of file 57 \ No newline at end of file
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; @@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
11 import org.apache.ibatis.annotations.Mapper; 11 import org.apache.ibatis.annotations.Mapper;
12 12
13 import java.math.BigDecimal; 13 import java.math.BigDecimal;
  14 +import java.util.List;
14 15
15 /** 16 /**
16 * ERP 销售出库 Mapper 17 * ERP 销售出库 Mapper
@@ -54,4 +55,8 @@ public interface ErpSaleOutMapper extends BaseMapperX&lt;ErpSaleOutDO&gt; { @@ -54,4 +55,8 @@ public interface ErpSaleOutMapper extends BaseMapperX&lt;ErpSaleOutDO&gt; {
54 return selectOne(ErpSaleOutDO::getNo, no); 55 return selectOne(ErpSaleOutDO::getNo, no);
55 } 56 }
56 57
  58 + default List<ErpSaleOutDO> selectListByOrderId(Long orderId) {
  59 + return selectList(ErpSaleOutDO::getOrderId, orderId);
  60 + }
  61 +
57 } 62 }
58 \ No newline at end of file 63 \ No newline at end of file
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 { @@ -42,6 +42,10 @@ public class ErpNoRedisDAO {
42 * 销售订单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO} 42 * 销售订单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO}
43 */ 43 */
44 public static final String SALE_ORDER_NO_PREFIX = "XSDD"; 44 public static final String SALE_ORDER_NO_PREFIX = "XSDD";
  45 + /**
  46 + * 销售出库 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO}
  47 + */
  48 + public static final String SALE_OUT_NO_PREFIX = "XSCK";
45 49
46 @Resource 50 @Resource
47 private StringRedisTemplate stringRedisTemplate; 51 private StringRedisTemplate stringRedisTemplate;
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; @@ -7,8 +7,10 @@ import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
7 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO; 7 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
8 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
9 9
  10 +import java.math.BigDecimal;
10 import java.util.Collection; 11 import java.util.Collection;
11 import java.util.List; 12 import java.util.List;
  13 +import java.util.Map;
12 14
13 /** 15 /**
14 * ERP 销售订单 Service 接口 16 * ERP 销售订单 Service 接口
@@ -41,6 +43,14 @@ public interface ErpSaleOrderService { @@ -41,6 +43,14 @@ public interface ErpSaleOrderService {
41 void updateSaleOrderStatus(Long id, Integer status); 43 void updateSaleOrderStatus(Long id, Integer status);
42 44
43 /** 45 /**
  46 + * 更新销售订单的出库数量
  47 + *
  48 + * @param id 编号
  49 + * @param returnCountMap 出库数量 Map:key 销售订单项编号;value 出库数量
  50 + */
  51 + void updateSaleOrderOutCount(Long id, Map<Long, BigDecimal> returnCountMap);
  52 +
  53 + /**
44 * 删除销售订单 54 * 删除销售订单
45 * 55 *
46 * @param ids 编号数组 56 * @param ids 编号数组
@@ -56,6 +66,14 @@ public interface ErpSaleOrderService { @@ -56,6 +66,14 @@ public interface ErpSaleOrderService {
56 ErpSaleOrderDO getSaleOrder(Long id); 66 ErpSaleOrderDO getSaleOrder(Long id);
57 67
58 /** 68 /**
  69 + * 校验销售订单,已经审核通过
  70 + *
  71 + * @param id 编号
  72 + * @return 销售订单
  73 + */
  74 + ErpSaleOrderDO validateSaleOrder(Long id);
  75 +
  76 + /**
59 * 获得销售订单分页 77 * 获得销售订单分页
60 * 78 *
61 * @param pageReqVO 分页查询 79 * @param pageReqVO 分页查询
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
1 package cn.iocoder.yudao.module.erp.service.sale; 1 package cn.iocoder.yudao.module.erp.service.sale;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; 6 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -15,6 +16,7 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; @@ -15,6 +16,7 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
15 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; 16 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
16 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; 17 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
17 import cn.iocoder.yudao.module.erp.service.product.ErpProductService; 18 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
  19 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
18 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
19 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
20 import org.springframework.transaction.annotation.Transactional; 22 import org.springframework.transaction.annotation.Transactional;
@@ -56,6 +58,9 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { @@ -56,6 +58,9 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
56 @Resource 58 @Resource
57 private ErpAccountService accountService; 59 private ErpAccountService accountService;
58 60
  61 + @Resource
  62 + private AdminUserApi adminUserApi;
  63 +
59 @Override 64 @Override
60 @Transactional(rollbackFor = Exception.class) 65 @Transactional(rollbackFor = Exception.class)
61 public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) { 66 public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) {
@@ -67,7 +72,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { @@ -67,7 +72,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
67 if (createReqVO.getAccountId() != null) { 72 if (createReqVO.getAccountId() != null) {
68 accountService.validateAccount(createReqVO.getAccountId()); 73 accountService.validateAccount(createReqVO.getAccountId());
69 } 74 }
70 - // 1.4 生成调拨单号,并校验唯一性 75 + // 1.4 校验销售人员
  76 + if (createReqVO.getSaleUserId() != null) {
  77 + adminUserApi.validateUser(createReqVO.getSaleUserId());
  78 + }
  79 + // 1.5 生成调拨单号,并校验唯一性
71 String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX); 80 String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX);
72 if (saleOrderMapper.selectByNo(no) != null) { 81 if (saleOrderMapper.selectByNo(no) != null) {
73 throw exception(SALE_ORDER_NO_EXISTS); 82 throw exception(SALE_ORDER_NO_EXISTS);
@@ -98,7 +107,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { @@ -98,7 +107,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
98 if (updateReqVO.getAccountId() != null) { 107 if (updateReqVO.getAccountId() != null) {
99 accountService.validateAccount(updateReqVO.getAccountId()); 108 accountService.validateAccount(updateReqVO.getAccountId());
100 } 109 }
101 - // 1.4 校验订单项的有效性 110 + // 1.4 校验销售人员
  111 + if (updateReqVO.getSaleUserId() != null) {
  112 + adminUserApi.validateUser(updateReqVO.getSaleUserId());
  113 + }
  114 + // 1.5 校验订单项的有效性
102 List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(updateReqVO.getItems()); 115 List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(updateReqVO.getItems());
103 116
104 // 2.1 更新订单 117 // 2.1 更新订单
@@ -132,7 +145,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { @@ -132,7 +145,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
132 if (saleOrder.getStatus().equals(status)) { 145 if (saleOrder.getStatus().equals(status)) {
133 throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); 146 throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL);
134 } 147 }
135 - // TODO @芋艿:需要校验是不是有入库、有退货 148 + // 1.3 存在销售出库单,无法反审核
  149 + if (!approve && saleOrder.getOutCount().compareTo(BigDecimal.ZERO) > 0) {
  150 + throw exception(SALE_ORDER_PROCESS_FAIL_EXISTS_OUT);
  151 + }
  152 + // TODO @芋艿:需要校验是不是有有退货
136 153
137 // 2. 更新状态 154 // 2. 更新状态
138 int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(), 155 int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(),
@@ -142,6 +159,26 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { @@ -142,6 +159,26 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
142 } 159 }
143 } 160 }
144 161
  162 + @Override
  163 + public void updateSaleOrderOutCount(Long id, Map<Long, BigDecimal> returnCountMap) {
  164 + List<ErpSaleOrderItemDO> orderItems = saleOrderItemMapper.selectListByOrderId(id);
  165 + // 1. 更新每个销售订单项
  166 + orderItems.forEach(item -> {
  167 + BigDecimal outCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO);
  168 + if (item.getOutCount().equals(outCount)) {
  169 + return;
  170 + }
  171 + if (outCount.compareTo(item.getCount()) > 0) {
  172 + throw exception(SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED,
  173 + productService.getProduct(item.getProductId()).getName(), item.getCount());
  174 + }
  175 + saleOrderItemMapper.updateById(new ErpSaleOrderItemDO().setId(item.getId()).setOutCount(outCount));
  176 + });
  177 + // 2. 更新销售订单
  178 + BigDecimal totalOutCount = getSumValue(returnCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO);
  179 + saleOrderMapper.updateById(new ErpSaleOrderDO().setId(id).setOutCount(totalOutCount));
  180 + }
  181 +
145 private List<ErpSaleOrderItemDO> validateSaleOrderItems(List<ErpSaleOrderSaveReqVO.Item> list) { 182 private List<ErpSaleOrderItemDO> validateSaleOrderItems(List<ErpSaleOrderSaveReqVO.Item> list) {
146 // 1. 校验产品存在 183 // 1. 校验产品存在
147 List<ErpProductDO> productList = productService.validProductList( 184 List<ErpProductDO> productList = productService.validProductList(
@@ -215,6 +252,15 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { @@ -215,6 +252,15 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
215 } 252 }
216 253
217 @Override 254 @Override
  255 + public ErpSaleOrderDO validateSaleOrder(Long id) {
  256 + ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
  257 + if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) {
  258 + throw exception(SALE_ORDER_NOT_APPROVE);
  259 + }
  260 + return saleOrder;
  261 + }
  262 +
  263 + @Override
218 public PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) { 264 public PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) {
219 return saleOrderMapper.selectPage(pageReqVO); 265 return saleOrderMapper.selectPage(pageReqVO);
220 } 266 }
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
1 package cn.iocoder.yudao.module.erp.service.sale; 1 package cn.iocoder.yudao.module.erp.service.sale;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; 6 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO; 8 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO;
8 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO; 9 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO;
9 import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; 10 import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
  11 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
10 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; 12 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO;
11 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; 13 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO;
12 import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOutItemMapper; 14 import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOutItemMapper;
@@ -15,7 +17,9 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; @@ -15,7 +17,9 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
15 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; 17 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
16 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; 18 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
17 import cn.iocoder.yudao.module.erp.service.product.ErpProductService; 19 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
  20 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
18 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
  22 +import org.springframework.context.annotation.Lazy;
19 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
20 import org.springframework.transaction.annotation.Transactional; 24 import org.springframework.transaction.annotation.Transactional;
21 import org.springframework.validation.annotation.Validated; 25 import org.springframework.validation.annotation.Validated;
@@ -52,33 +56,45 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService { @@ -52,33 +56,45 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
52 @Resource 56 @Resource
53 private ErpProductService productService; 57 private ErpProductService productService;
54 @Resource 58 @Resource
55 - private ErpCustomerService customerService; 59 + @Lazy // 延迟加载,避免循环依赖
  60 + private ErpSaleOrderService saleOrderService;
56 @Resource 61 @Resource
57 private ErpAccountService accountService; 62 private ErpAccountService accountService;
58 63
  64 + @Resource
  65 + private AdminUserApi adminUserApi;
  66 +
59 @Override 67 @Override
60 @Transactional(rollbackFor = Exception.class) 68 @Transactional(rollbackFor = Exception.class)
61 public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) { 69 public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) {
62 - // 1.1 校验订单项的有效性 70 + // 1.1 校验销售订单已审核
  71 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(createReqVO.getOrderId());
  72 + // 1.2 校验订单项的有效性
63 List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(createReqVO.getItems()); 73 List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(createReqVO.getItems());
64 - // 1.2 校验客户 TODO 芋艿:需要在瞅瞅  
65 -// customerService.validateCustomer(createReqVO.getCustomerId());  
66 // 1.3 校验结算账户 74 // 1.3 校验结算账户
67 accountService.validateAccount(createReqVO.getAccountId()); 75 accountService.validateAccount(createReqVO.getAccountId());
68 - // 1.4 生成调拨单号,并校验唯一性  
69 - String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX); 76 + // 1.4 校验销售人员
  77 + if (createReqVO.getSaleUserId() != null) {
  78 + adminUserApi.validateUser(createReqVO.getSaleUserId());
  79 + }
  80 + // 1.5 生成调拨单号,并校验唯一性
  81 + String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_OUT_NO_PREFIX);
70 if (saleOutMapper.selectByNo(no) != null) { 82 if (saleOutMapper.selectByNo(no) != null) {
71 - throw exception(SALE_ORDER_NO_EXISTS); 83 + throw exception(SALE_OUT_NO_EXISTS);
72 } 84 }
73 85
74 // 2.1 插入订单 86 // 2.1 插入订单
75 ErpSaleOutDO saleOut = BeanUtils.toBean(createReqVO, ErpSaleOutDO.class, in -> in 87 ErpSaleOutDO saleOut = BeanUtils.toBean(createReqVO, ErpSaleOutDO.class, in -> in
76 - .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())); 88 + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
  89 + .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId());
77 calculateTotalPrice(saleOut, saleOutItems); 90 calculateTotalPrice(saleOut, saleOutItems);
78 saleOutMapper.insert(saleOut); 91 saleOutMapper.insert(saleOut);
79 // 2.2 插入订单项 92 // 2.2 插入订单项
80 saleOutItems.forEach(o -> o.setOutId(saleOut.getId())); 93 saleOutItems.forEach(o -> o.setOutId(saleOut.getId()));
81 saleOutItemMapper.insertBatch(saleOutItems); 94 saleOutItemMapper.insertBatch(saleOutItems);
  95 +
  96 + // 3. 更新销售订单的出库数量
  97 + updateSaleOrderOutCount(createReqVO.getOrderId());
82 return saleOut.getId(); 98 return saleOut.getId();
83 } 99 }
84 100
@@ -88,21 +104,33 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService { @@ -88,21 +104,33 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
88 // 1.1 校验存在 104 // 1.1 校验存在
89 ErpSaleOutDO saleOut = validateSaleOutExists(updateReqVO.getId()); 105 ErpSaleOutDO saleOut = validateSaleOutExists(updateReqVO.getId());
90 if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) { 106 if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) {
91 - throw exception(SALE_ORDER_UPDATE_FAIL_APPROVE, saleOut.getNo()); 107 + throw exception(SALE_OUT_UPDATE_FAIL_APPROVE, saleOut.getNo());
92 } 108 }
93 - // 1.2 校验客户 TODO 芋艿:需要在瞅瞅  
94 -// customerService.validateCustomer(updateReqVO.getCustomerId()); 109 + // 1.2 校验销售订单已审核
  110 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(updateReqVO.getOrderId());
95 // 1.3 校验结算账户 111 // 1.3 校验结算账户
96 accountService.validateAccount(updateReqVO.getAccountId()); 112 accountService.validateAccount(updateReqVO.getAccountId());
97 - // 1.4 校验订单项的有效性 113 + // 1.4 校验销售人员
  114 + if (updateReqVO.getSaleUserId() != null) {
  115 + adminUserApi.validateUser(updateReqVO.getSaleUserId());
  116 + }
  117 + // 1.5 校验订单项的有效性
98 List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(updateReqVO.getItems()); 118 List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(updateReqVO.getItems());
99 119
100 // 2.1 更新订单 120 // 2.1 更新订单
101 - ErpSaleOutDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOutDO.class); 121 + ErpSaleOutDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOutDO.class)
  122 + .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId());
102 calculateTotalPrice(updateObj, saleOutItems); 123 calculateTotalPrice(updateObj, saleOutItems);
103 saleOutMapper.updateById(updateObj); 124 saleOutMapper.updateById(updateObj);
104 // 2.2 更新订单项 125 // 2.2 更新订单项
105 updateSaleOutItemList(updateReqVO.getId(), saleOutItems); 126 updateSaleOutItemList(updateReqVO.getId(), saleOutItems);
  127 +
  128 + // 3.1 更新销售订单的出库数量
  129 + updateSaleOrderOutCount(updateObj.getOrderId());
  130 + // 3.2 注意:如果销售订单编号变更了,需要更新“老”销售订单的出库数量
  131 + if (ObjectUtil.notEqual(saleOut.getOrderId(), updateObj.getOrderId())) {
  132 + updateSaleOrderOutCount(saleOut.getOrderId());
  133 + }
106 } 134 }
107 135
108 private void calculateTotalPrice(ErpSaleOutDO saleOut, List<ErpSaleOutItemDO> saleOutItems) { 136 private void calculateTotalPrice(ErpSaleOutDO saleOut, List<ErpSaleOutItemDO> saleOutItems) {
@@ -118,6 +146,16 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService { @@ -118,6 +146,16 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
118 saleOut.setTotalPrice(saleOut.getTotalPrice().subtract(saleOut.getDiscountPrice())); 146 saleOut.setTotalPrice(saleOut.getTotalPrice().subtract(saleOut.getDiscountPrice()));
119 } 147 }
120 148
  149 + private void updateSaleOrderOutCount(Long orderId) {
  150 + // 1.1 查询销售订单对应的销售出库单列表
  151 + List<ErpSaleOutDO> saleOuts = saleOutMapper.selectListByOrderId(orderId);
  152 + // 1.2 查询对应的销售订单项的出库数量
  153 + Map<Long, BigDecimal> returnCountMap = saleOutItemMapper.selectOrderItemCountSumMapByOutIds(
  154 + convertList(saleOuts, ErpSaleOutDO::getId));
  155 + // 2. 更新销售订单的出库数量
  156 + saleOrderService.updateSaleOrderOutCount(orderId, returnCountMap);
  157 + }
  158 +
121 @Override 159 @Override
122 @Transactional(rollbackFor = Exception.class) 160 @Transactional(rollbackFor = Exception.class)
123 public void updateSaleOutStatus(Long id, Integer status) { 161 public void updateSaleOutStatus(Long id, Integer status) {
@@ -126,15 +164,14 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService { @@ -126,15 +164,14 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
126 ErpSaleOutDO saleOut = validateSaleOutExists(id); 164 ErpSaleOutDO saleOut = validateSaleOutExists(id);
127 // 1.2 校验状态 165 // 1.2 校验状态
128 if (saleOut.getStatus().equals(status)) { 166 if (saleOut.getStatus().equals(status)) {
129 - throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); 167 + throw exception(approve ? SALE_OUT_APPROVE_FAIL : SALE_OUT_PROCESS_FAIL);
130 } 168 }
131 - // TODO @芋艿:需要校验是不是有入库、有退货  
132 169
133 // 2. 更新状态 170 // 2. 更新状态
134 int updateCount = saleOutMapper.updateByIdAndStatus(id, saleOut.getStatus(), 171 int updateCount = saleOutMapper.updateByIdAndStatus(id, saleOut.getStatus(),
135 new ErpSaleOutDO().setStatus(status)); 172 new ErpSaleOutDO().setStatus(status));
136 if (updateCount == 0) { 173 if (updateCount == 0) {
137 - throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); 174 + throw exception(approve ? SALE_OUT_APPROVE_FAIL : SALE_OUT_PROCESS_FAIL);
138 } 175 }
139 } 176 }
140 177
@@ -184,7 +221,7 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService { @@ -184,7 +221,7 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
184 } 221 }
185 saleOuts.forEach(saleOut -> { 222 saleOuts.forEach(saleOut -> {
186 if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) { 223 if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) {
187 - throw exception(SALE_ORDER_DELETE_FAIL_APPROVE, saleOut.getNo()); 224 + throw exception(SALE_OUT_DELETE_FAIL_APPROVE, saleOut.getNo());
188 } 225 }
189 }); 226 });
190 227
@@ -194,13 +231,17 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService { @@ -194,13 +231,17 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
194 saleOutMapper.deleteById(saleOut.getId()); 231 saleOutMapper.deleteById(saleOut.getId());
195 // 2.2 删除订单项 232 // 2.2 删除订单项
196 saleOutItemMapper.deleteByOutId(saleOut.getId()); 233 saleOutItemMapper.deleteByOutId(saleOut.getId());
  234 +
  235 + // 2.3 更新销售订单的出库数量
  236 + updateSaleOrderOutCount(saleOut.getOrderId());
197 }); 237 });
  238 +
198 } 239 }
199 240
200 private ErpSaleOutDO validateSaleOutExists(Long id) { 241 private ErpSaleOutDO validateSaleOutExists(Long id) {
201 ErpSaleOutDO saleOut = saleOutMapper.selectById(id); 242 ErpSaleOutDO saleOut = saleOutMapper.selectById(id);
202 if (saleOut == null) { 243 if (saleOut == null) {
203 - throw exception(SALE_ORDER_NOT_EXISTS); 244 + throw exception(SALE_OUT_NOT_EXISTS);
204 } 245 }
205 return saleOut; 246 return saleOut;
206 } 247 }
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; @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 4 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
5 5
6 import java.util.Collection; 6 import java.util.Collection;
  7 +import java.util.Collections;
7 import java.util.List; 8 import java.util.List;
8 import java.util.Map; 9 import java.util.Map;
9 10
@@ -66,6 +67,17 @@ public interface AdminUserApi { @@ -66,6 +67,17 @@ public interface AdminUserApi {
66 } 67 }
67 68
68 /** 69 /**
  70 + * 校验用户是否有效。如下情况,视为无效:
  71 + * 1. 用户编号不存在
  72 + * 2. 用户被禁用
  73 + *
  74 + * @param id 用户编号
  75 + */
  76 + default void validateUser(Long id) {
  77 + validateUserList(Collections.singleton(id));
  78 + }
  79 +
  80 + /**
69 * 校验用户们是否有效。如下情况,视为无效: 81 * 校验用户们是否有效。如下情况,视为无效:
70 * 1. 用户编号不存在 82 * 1. 用户编号不存在
71 * 2. 用户被禁用 83 * 2. 用户被禁用