| | |
| | | import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.contract.CrmContractApi; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO; |
| | | 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.ErpSaleOrderItemDO; |
| | | import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderItemMapper; |
| | | import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper; |
| | | import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; |
| | | import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; |
| | | import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum; |
| | | import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; |
| | | import cn.iocoder.yudao.module.erp.service.product.ErpProductService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi; |
| | | import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| | | import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; |
| | | |
| | | // TODO 芋艿:记录操作日志 |
| | | |
| | | /** |
| | | * ERP 销售订单 Service 实现类 |
| | | * |
| | |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { |
| | | |
| | | @Resource |
| | |
| | | private ErpNoRedisDAO noRedisDAO; |
| | | |
| | | @Resource |
| | | private ErpProductService productService; |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private CrmContractApi contractApi; |
| | | @Resource |
| | | private ErpAccountService accountService; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesProMpsApi mpsApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | |
| | | // 2.1 插入订单 |
| | | ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class, in -> in |
| | | .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())); |
| | | .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()) |
| | | .setOutCount(BigDecimal.ZERO).setOutStatus(ErpSaleOrderOutStatusEnum.NOT_OUT.getStatus())); |
| | | calculateTotalPrice(saleOrder, saleOrderItems); |
| | | saleOrderMapper.insert(saleOrder); |
| | | // 2.2 插入订单项 |
| | |
| | | throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); |
| | | } |
| | | // 1.3 存在销售出库单,无法反审核 |
| | | if (!approve && saleOrder.getOutCount().compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal outCount = saleOrder.getOutCount() != null ? saleOrder.getOutCount() : BigDecimal.ZERO; |
| | | if (!approve && outCount.compareTo(BigDecimal.ZERO) > 0) { |
| | | throw exception(SALE_ORDER_PROCESS_FAIL_EXISTS_OUT); |
| | | } |
| | | // 1.4 存在销售退货单,无法反审核 |
| | | if (!approve && saleOrder.getReturnCount().compareTo(BigDecimal.ZERO) > 0) { |
| | | throw exception(SALE_ORDER_PROCESS_FAIL_EXISTS_RETURN); |
| | | } |
| | | |
| | | // 2. 更新状态 |
| | |
| | | if (updateCount == 0) { |
| | | throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); |
| | | } |
| | | |
| | | // 3. 审核通过时,为需要生产的明细创建 MPS |
| | | if (approve) { |
| | | createMpsForSaleOrder(id, saleOrder); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 为销售订单中需要生产的明细创建 MPS |
| | | * |
| | | * @param orderId 销售订单编号 |
| | | * @param saleOrder 销售订单 |
| | | */ |
| | | private void createMpsForSaleOrder(Long orderId, ErpSaleOrderDO saleOrder) { |
| | | List<ErpSaleOrderItemDO> items = saleOrderItemMapper.selectListByOrderId(orderId); |
| | | if (CollUtil.isEmpty(items)) { |
| | | return; |
| | | } |
| | | |
| | | int mpsCount = 0; |
| | | for (ErpSaleOrderItemDO item : items) { |
| | | if (item.getNeedProduction() == null || item.getNeedProduction() != 1) { |
| | | continue; |
| | | } |
| | | try { |
| | | Long mpsId = createMpsForSaleOrderItem(saleOrder, item); |
| | | if (mpsId != null) { |
| | | mpsCount++; |
| | | // 更新销售订单明细的 MPS 关联 |
| | | saleOrderItemMapper.updateById(new ErpSaleOrderItemDO() |
| | | .setId(item.getId()) |
| | | .setMpsId(mpsId)); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[createMpsForSaleOrder] 创建 MPS 失败,saleOrderItemId={}", item.getId(), e); |
| | | } |
| | | } |
| | | log.info("[createMpsForSaleOrder] 销售订单({}) 审核完成,创建 MPS {} 条", orderId, mpsCount); |
| | | } |
| | | |
| | | /** |
| | | * 为销售订单明细创建 MPS |
| | | */ |
| | | private Long createMpsForSaleOrderItem(ErpSaleOrderDO saleOrder, ErpSaleOrderItemDO item) { |
| | | // 获取 MDM 物料信息(用于获取产品名称) |
| | | String productName = null; |
| | | try { |
| | | MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData(); |
| | | if (mdmItem != null) { |
| | | productName = mdmItem.getName(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("[createMpsForSaleOrderItem] 获取 MDM 物料信息失败,productId={}", item.getProductId(), e); |
| | | } |
| | | |
| | | MesProMpsCreateReqDTO createReqDTO = new MesProMpsCreateReqDTO(); |
| | | createReqDTO.setSaleOrderId(saleOrder.getId()); |
| | | createReqDTO.setSaleOrderNo(saleOrder.getNo()); |
| | | createReqDTO.setSaleOrderItemId(item.getId()); |
| | | createReqDTO.setMdmItemId(item.getProductId()); |
| | | createReqDTO.setProductName(productName); |
| | | createReqDTO.setQuantity(item.getCount()); |
| | | createReqDTO.setClientId(saleOrder.getCustomerId()); |
| | | createReqDTO.setRequestDate(formatRequestDate(saleOrder.getOrderTime())); |
| | | createReqDTO.setRemark("自动生成,来自销售订单:" + saleOrder.getNo()); |
| | | |
| | | Long mpsId = mpsApi.createMps(createReqDTO); |
| | | log.info("[createMpsForSaleOrderItem] MPS 创建成功,mpsId={}, saleOrderItemId={}", mpsId, item.getId()); |
| | | return mpsId; |
| | | } |
| | | |
| | | /** |
| | | * 格式化需求日期 |
| | | */ |
| | | private String formatRequestDate(LocalDateTime dateTime) { |
| | | if (dateTime == null) { |
| | | return null; |
| | | } |
| | | return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void cancelSaleOrder(Long id) { |
| | | // 1.1 校验存在 |
| | | ErpSaleOrderDO saleOrder = validateSaleOrderExists(id); |
| | | // 1.2 校验状态:只有已审核的订单才能作废 |
| | | if (!ErpAuditStatus.APPROVE.getStatus().equals(saleOrder.getStatus())) { |
| | | throw exception(SALE_ORDER_CANCEL_FAIL_NOT_APPROVE, saleOrder.getNo()); |
| | | } |
| | | // 1.3 存在销售出库单,无法作废 |
| | | BigDecimal outCount = saleOrder.getOutCount() != null ? saleOrder.getOutCount() : BigDecimal.ZERO; |
| | | if (outCount.compareTo(BigDecimal.ZERO) > 0) { |
| | | throw exception(SALE_ORDER_CANCEL_FAIL_EXISTS_OUT); |
| | | } |
| | | |
| | | // 2. 更新状态为已作废 |
| | | int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(), |
| | | new ErpSaleOrderDO().setStatus(ErpAuditStatus.CANCEL.getStatus())); |
| | | if (updateCount == 0) { |
| | | throw exception(SALE_ORDER_CANCEL_FAIL); |
| | | } |
| | | |
| | | // 3. 同步更新关联合同状态为已作废 |
| | | if (saleOrder.getContractId() != null) { |
| | | contractApi.updateContractStatus(saleOrder.getContractId(), 50).getCheckedData(); // 50 = 已作废 |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void enableSaleOrder(Long id) { |
| | | // 1.1 校验存在 |
| | | ErpSaleOrderDO saleOrder = validateSaleOrderExists(id); |
| | | // 1.2 校验状态:只有已作废的订单才能重新启用 |
| | | if (!ErpAuditStatus.CANCEL.getStatus().equals(saleOrder.getStatus())) { |
| | | throw exception(SALE_ORDER_ENABLE_FAIL_NOT_CANCEL, saleOrder.getNo()); |
| | | } |
| | | |
| | | // 2. 更新状态为已审核 |
| | | int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(), |
| | | new ErpSaleOrderDO().setStatus(ErpAuditStatus.APPROVE.getStatus())); |
| | | if (updateCount == 0) { |
| | | throw exception(SALE_ORDER_ENABLE_FAIL); |
| | | } |
| | | |
| | | // 3. 同步更新关联合同状态为审核通过 |
| | | if (saleOrder.getContractId() != null) { |
| | | contractApi.updateContractStatus(saleOrder.getContractId(), 20).getCheckedData(); // 20 = 审核通过 |
| | | } |
| | | } |
| | | |
| | | private List<ErpSaleOrderItemDO> validateSaleOrderItems(List<ErpSaleOrderSaveReqVO.Item> list) { |
| | | // 1. 校验产品存在 |
| | | List<ErpProductDO> productList = productService.validProductList( |
| | | convertSet(list, ErpSaleOrderSaveReqVO.Item::getProductId)); |
| | | Map<Long, ErpProductDO> productMap = convertMap(productList, ErpProductDO::getId); |
| | | // 2. 转化为 ErpSaleOrderItemDO 列表 |
| | | // 转化为 ErpSaleOrderItemDO 列表(产品验证已移除) |
| | | return convertList(list, o -> BeanUtils.toBean(o, ErpSaleOrderItemDO.class, item -> { |
| | | item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); |
| | | item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); |
| | | if (item.getTotalPrice() == null) { |
| | | return; |
| | |
| | | } |
| | | if (outCount.compareTo(item.getCount()) > 0) { |
| | | throw exception(SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED, |
| | | productService.getProduct(item.getProductId()).getName(), item.getCount()); |
| | | "产品ID:" + item.getProductId(), item.getCount()); |
| | | } |
| | | saleOrderItemMapper.updateById(new ErpSaleOrderItemDO().setId(item.getId()).setOutCount(outCount)); |
| | | }); |
| | | // 2. 更新销售订单 |
| | | BigDecimal totalOutCount = getSumValue(outCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); |
| | | saleOrderMapper.updateById(new ErpSaleOrderDO().setId(id).setOutCount(totalOutCount)); |
| | | } |
| | | |
| | | @Override |
| | | public void updateSaleOrderReturnCount(Long orderId, Map<Long, BigDecimal> returnCountMap) { |
| | | List<ErpSaleOrderItemDO> orderItems = saleOrderItemMapper.selectListByOrderId(orderId); |
| | | // 1. 更新每个销售订单项 |
| | | orderItems.forEach(item -> { |
| | | BigDecimal returnCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); |
| | | if (item.getReturnCount().equals(returnCount)) { |
| | | return; |
| | | } |
| | | if (returnCount.compareTo(item.getOutCount()) > 0) { |
| | | throw exception(SALE_ORDER_ITEM_RETURN_FAIL_OUT_EXCEED, |
| | | productService.getProduct(item.getProductId()).getName(), item.getOutCount()); |
| | | } |
| | | saleOrderItemMapper.updateById(new ErpSaleOrderItemDO().setId(item.getId()).setReturnCount(returnCount)); |
| | | }); |
| | | // 2. 更新销售订单 |
| | | BigDecimal totalReturnCount = getSumValue(returnCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); |
| | | saleOrderMapper.updateById(new ErpSaleOrderDO().setId(orderId).setReturnCount(totalReturnCount)); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) { |
| | | return saleOrderMapper.selectPage(pageReqVO); |
| | | List<Long> permittedCustomerIds = customerApi.getPermittedCustomerIds(); |
| | | return saleOrderMapper.selectPage(pageReqVO, permittedCustomerIds); |
| | | } |
| | | |
| | | // ==================== 订单项 ==================== |
| | |
| | | return saleOrderItemMapper.selectListByOrderIds(orderIds); |
| | | } |
| | | |
| | | } |
| | | @Override |
| | | public void updateSaleOrderOutStatus(Long orderId) { |
| | | // 1. 获取销售订单 |
| | | ErpSaleOrderDO order = validateSaleOrderExists(orderId); |
| | | // 2. 获取销售订单明细 |
| | | List<ErpSaleOrderItemDO> items = saleOrderItemMapper.selectListByOrderId(orderId); |
| | | if (CollUtil.isEmpty(items)) { |
| | | return; |
| | | } |
| | | // 3. 计算出库状态 |
| | | int outStatus = calculateOutStatus(items); |
| | | // 4. 更新销售订单出库状态 |
| | | saleOrderMapper.updateById(new ErpSaleOrderDO() |
| | | .setId(orderId).setOutStatus(outStatus)); |
| | | } |
| | | |
| | | /** |
| | | * 计算出库状态 |
| | | * |
| | | * @param items 销售订单明细列表 |
| | | * @return 出库状态 |
| | | */ |
| | | private int calculateOutStatus(List<ErpSaleOrderItemDO> items) { |
| | | boolean allOut = true; |
| | | boolean anyOut = false; |
| | | |
| | | for (ErpSaleOrderItemDO item : items) { |
| | | BigDecimal count = item.getCount() != null ? item.getCount() : BigDecimal.ZERO; |
| | | BigDecimal outCount = item.getOutCount() != null ? item.getOutCount() : BigDecimal.ZERO; |
| | | |
| | | if (outCount.compareTo(BigDecimal.ZERO) > 0) { |
| | | anyOut = true; |
| | | } |
| | | if (outCount.compareTo(count) < 0) { |
| | | allOut = false; |
| | | } |
| | | } |
| | | |
| | | if (allOut) { |
| | | return ErpSaleOrderOutStatusEnum.ALL_OUT.getStatus(); |
| | | } else if (anyOut) { |
| | | return ErpSaleOrderOutStatusEnum.PART_OUT.getStatus(); |
| | | } else { |
| | | return ErpSaleOrderOutStatusEnum.NOT_OUT.getStatus(); |
| | | } |
| | | } |
| | | |
| | | } |