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 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 295 BinaryOperator<V> accumulator, V defaultValue) {
296 296 if (CollUtil.isEmpty(from)) {
297 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 24 ErrorCode SALE_ORDER_APPROVE_FAIL = new ErrorCode(1_020_201_003, "审核失败,只有未审核的销售订单才能审核");
25 25 ErrorCode SALE_ORDER_NO_EXISTS = new ErrorCode(1_020_201_004, "生成销售单号失败,请重新提交");
26 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 39 // ========== ERP 仓库 1-030-400-000 ==========
29 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 90 @ExcelProperty("产品信息")
91 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 128 @Schema(description = "备注", example = "随便")
129 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 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 47 @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
48 48 private Long orderId;
49 49 @Schema(description = "销售订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
50   - private Long orderNo;
  50 + private String orderNo;
51 51  
52 52 @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663")
53 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 106 */
107 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 78 */
79 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 74 *
75 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 30 .likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark())
31 31 .eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator())
32 32 .orderByDesc(ErpSaleOrderDO::getId);
33   - // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 in_count 错误
  33 + // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 out_count 错误
34 34 if (Objects.equals(reqVO.getInStatus(), ErpSaleOrderPageReqVO.IN_STATUS_NONE)) {
35   - query.eq(ErpSaleOrderDO::getInCount, 0);
  35 + query.eq(ErpSaleOrderDO::getOutCount, 0);
36 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 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 42 if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_NONE)) {
... ... @@ -49,7 +49,7 @@ public interface ErpSaleOrderMapper extends BaseMapperX&lt;ErpSaleOrderDO&gt; {
49 49 // 可出库
50 50 if (Boolean.TRUE.equals(reqVO.getOutEnable())) {
51 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 54 if (reqVO.getProductId() != null) {
55 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 1 package cn.iocoder.yudao.module.erp.dal.mysql.sale;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 5 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO;
  6 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
5 7 import org.apache.ibatis.annotations.Mapper;
6 8  
  9 +import java.math.BigDecimal;
7 10 import java.util.Collection;
  11 +import java.util.Collections;
8 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 18 * ERP 销售出库项 Mapper
... ... @@ -27,4 +34,23 @@ public interface ErpSaleOutItemMapper extends BaseMapperX&lt;ErpSaleOutItemDO&gt; {
27 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 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 11 import org.apache.ibatis.annotations.Mapper;
12 12  
13 13 import java.math.BigDecimal;
  14 +import java.util.List;
14 15  
15 16 /**
16 17 * ERP 销售出库 Mapper
... ... @@ -54,4 +55,8 @@ public interface ErpSaleOutMapper extends BaseMapperX&lt;ErpSaleOutDO&gt; {
54 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 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 42 * 销售订单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO}
43 43 */
44 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 50 @Resource
47 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 7 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
8 8 import jakarta.validation.Valid;
9 9  
  10 +import java.math.BigDecimal;
10 11 import java.util.Collection;
11 12 import java.util.List;
  13 +import java.util.Map;
12 14  
13 15 /**
14 16 * ERP 销售订单 Service 接口
... ... @@ -41,6 +43,14 @@ public interface ErpSaleOrderService {
41 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 56 * @param ids 编号数组
... ... @@ -56,6 +66,14 @@ public interface ErpSaleOrderService {
56 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 79 * @param pageReqVO 分页查询
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
1 1 package cn.iocoder.yudao.module.erp.service.sale;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
6 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 16 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
16 17 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
17 18 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
  19 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
18 20 import jakarta.annotation.Resource;
19 21 import org.springframework.stereotype.Service;
20 22 import org.springframework.transaction.annotation.Transactional;
... ... @@ -56,6 +58,9 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
56 58 @Resource
57 59 private ErpAccountService accountService;
58 60  
  61 + @Resource
  62 + private AdminUserApi adminUserApi;
  63 +
59 64 @Override
60 65 @Transactional(rollbackFor = Exception.class)
61 66 public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) {
... ... @@ -67,7 +72,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
67 72 if (createReqVO.getAccountId() != null) {
68 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 80 String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX);
72 81 if (saleOrderMapper.selectByNo(no) != null) {
73 82 throw exception(SALE_ORDER_NO_EXISTS);
... ... @@ -98,7 +107,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
98 107 if (updateReqVO.getAccountId() != null) {
99 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 115 List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(updateReqVO.getItems());
103 116  
104 117 // 2.1 更新订单
... ... @@ -132,7 +145,11 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
132 145 if (saleOrder.getStatus().equals(status)) {
133 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 154 // 2. 更新状态
138 155 int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(),
... ... @@ -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 182 private List<ErpSaleOrderItemDO> validateSaleOrderItems(List<ErpSaleOrderSaveReqVO.Item> list) {
146 183 // 1. 校验产品存在
147 184 List<ErpProductDO> productList = productService.validProductList(
... ... @@ -215,6 +252,15 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
215 252 }
216 253  
217 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 264 public PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) {
219 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 1 package cn.iocoder.yudao.module.erp.service.sale;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
6 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO;
8 9 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO;
9 10 import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
  11 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
10 12 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO;
11 13 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO;
12 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 17 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
16 18 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
17 19 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
  20 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
18 21 import jakarta.annotation.Resource;
  22 +import org.springframework.context.annotation.Lazy;
19 23 import org.springframework.stereotype.Service;
20 24 import org.springframework.transaction.annotation.Transactional;
21 25 import org.springframework.validation.annotation.Validated;
... ... @@ -52,33 +56,45 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
52 56 @Resource
53 57 private ErpProductService productService;
54 58 @Resource
55   - private ErpCustomerService customerService;
  59 + @Lazy // 延迟加载,避免循环依赖
  60 + private ErpSaleOrderService saleOrderService;
56 61 @Resource
57 62 private ErpAccountService accountService;
58 63  
  64 + @Resource
  65 + private AdminUserApi adminUserApi;
  66 +
59 67 @Override
60 68 @Transactional(rollbackFor = Exception.class)
61 69 public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) {
62   - // 1.1 校验订单项的有效性
  70 + // 1.1 校验销售订单已审核
  71 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(createReqVO.getOrderId());
  72 + // 1.2 校验订单项的有效性
63 73 List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(createReqVO.getItems());
64   - // 1.2 校验客户 TODO 芋艿:需要在瞅瞅
65   -// customerService.validateCustomer(createReqVO.getCustomerId());
66 74 // 1.3 校验结算账户
67 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 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 86 // 2.1 插入订单
75 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 90 calculateTotalPrice(saleOut, saleOutItems);
78 91 saleOutMapper.insert(saleOut);
79 92 // 2.2 插入订单项
80 93 saleOutItems.forEach(o -> o.setOutId(saleOut.getId()));
81 94 saleOutItemMapper.insertBatch(saleOutItems);
  95 +
  96 + // 3. 更新销售订单的出库数量
  97 + updateSaleOrderOutCount(createReqVO.getOrderId());
82 98 return saleOut.getId();
83 99 }
84 100  
... ... @@ -88,21 +104,33 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
88 104 // 1.1 校验存在
89 105 ErpSaleOutDO saleOut = validateSaleOutExists(updateReqVO.getId());
90 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 111 // 1.3 校验结算账户
96 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 118 List<ErpSaleOutItemDO> saleOutItems = validateSaleOutItems(updateReqVO.getItems());
99 119  
100 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 123 calculateTotalPrice(updateObj, saleOutItems);
103 124 saleOutMapper.updateById(updateObj);
104 125 // 2.2 更新订单项
105 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 136 private void calculateTotalPrice(ErpSaleOutDO saleOut, List<ErpSaleOutItemDO> saleOutItems) {
... ... @@ -118,6 +146,16 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
118 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 159 @Override
122 160 @Transactional(rollbackFor = Exception.class)
123 161 public void updateSaleOutStatus(Long id, Integer status) {
... ... @@ -126,15 +164,14 @@ public class ErpSaleOutServiceImpl implements ErpSaleOutService {
126 164 ErpSaleOutDO saleOut = validateSaleOutExists(id);
127 165 // 1.2 校验状态
128 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 170 // 2. 更新状态
134 171 int updateCount = saleOutMapper.updateByIdAndStatus(id, saleOut.getStatus(),
135 172 new ErpSaleOutDO().setStatus(status));
136 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 221 }
185 222 saleOuts.forEach(saleOut -> {
186 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 231 saleOutMapper.deleteById(saleOut.getId());
195 232 // 2.2 删除订单项
196 233 saleOutItemMapper.deleteByOutId(saleOut.getId());
  234 +
  235 + // 2.3 更新销售订单的出库数量
  236 + updateSaleOrderOutCount(saleOut.getOrderId());
197 237 });
  238 +
198 239 }
199 240  
200 241 private ErpSaleOutDO validateSaleOutExists(Long id) {
201 242 ErpSaleOutDO saleOut = saleOutMapper.selectById(id);
202 243 if (saleOut == null) {
203   - throw exception(SALE_ORDER_NOT_EXISTS);
  244 + throw exception(SALE_OUT_NOT_EXISTS);
204 245 }
205 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 4 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
5 5  
6 6 import java.util.Collection;
  7 +import java.util.Collections;
7 8 import java.util.List;
8 9 import java.util.Map;
9 10  
... ... @@ -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 82 * 1. 用户编号不存在
71 83 * 2. 用户被禁用
... ...