diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 2d9e4dd63..4fd6203f8 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/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 > V getSumValue(List from, Function valueFunc, + public static > V getSumValue(Collection from, Function valueFunc, BinaryOperator accumulator, V defaultValue) { if (CollUtil.isEmpty(from)) { return defaultValue; diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java index 958e25898..c25a544ec 100644 --- a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java +++ b/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, "仓库不存在"); diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java index 0beb312ee..8b0aa83ea 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java +++ b/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 = "巧克力") diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java index 3901d9e05..3f85a902a 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java +++ b/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("合计数量") diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java index 5f34e2aa4..81f02ccc3 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java +++ b/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; // ========== 销售退货(出库)) ========== /** diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java index 5cdf3a14c..3a9778aec 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java +++ b/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; // ========== 销售退货(出库)) ========== /** diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java index 3ce6efcbd..57ab55650 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java +++ b/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; /** * 合计数量 diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java index ca0066f4f..f290fb6fd 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java +++ b/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 { .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 { // 可出库 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) diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java index 957667c73..b5fab517c 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java +++ b/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 { return delete(ErpSaleOutItemDO::getOutId, orderId); } + /** + * 基于销售订单编号,查询每个销售订单项的出库数量之和 + * + * @param outIds 出库订单项编号数组 + * @return key:销售订单项编号;value:出库数量之和 + */ + default Map selectOrderItemCountSumMapByOutIds(Collection outIds) { + if (CollUtil.isEmpty(outIds)) { + return Collections.emptyMap(); + } + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .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")); + } + } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java index e146994bf..e530557fb 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java +++ b/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 { return selectOne(ErpSaleOutDO::getNo, no); } + default List selectListByOrderId(Long orderId) { + return selectList(ErpSaleOutDO::getOrderId, orderId); + } + } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java index 0e48e97e1..7e88beb6d 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java +++ b/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; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java index cd32f62c4..0a204b315 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java +++ b/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 returnCountMap); + /** * 删除销售订单 * @@ -55,6 +65,14 @@ public interface ErpSaleOrderService { */ ErpSaleOrderDO getSaleOrder(Long id); + /** + * 校验销售订单,已经审核通过 + * + * @param id 编号 + * @return 销售订单 + */ + ErpSaleOrderDO validateSaleOrder(Long id); + /** * 获得销售订单分页 * diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java index fae8fe114..c9945b5e4 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java +++ b/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 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 returnCountMap) { + List 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 validateSaleOrderItems(List list) { // 1. 校验产品存在 List 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 getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) { return saleOrderMapper.selectPage(pageReqVO); diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java index aae5a60de..7f6ebc777 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java +++ b/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 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 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 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 saleOuts = saleOutMapper.selectListByOrderId(orderId); + // 1.2 查询对应的销售订单项的出库数量 + Map 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; } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index b6cab3030..cea3c3663 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/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. 用户编号不存在