package cn.iocoder.yudao.module.mes.service.wm.salesnotice; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.MesWmSalesNoticePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.MesWmSalesNoticeSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.salesnotice.MesWmSalesNoticeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.salesnotice.MesWmSalesNoticeLineMapper; import cn.iocoder.yudao.module.mes.enums.wm.MesWmOutStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO; import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO; import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum; 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.util.Collection; import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum.*; /** * MES 发货通知单 Service 实现类 */ @Service @Validated @Slf4j public class MesWmSalesNoticeServiceImpl implements MesWmSalesNoticeService { @Resource private MesWmSalesNoticeMapper salesNoticeMapper; @Resource private MesWmSalesNoticeLineService salesNoticeLineService; @Resource private CrmCustomerApi customerApi; @Resource private ErpSaleOrderApi saleOrderApi; @Resource private MesMdAutoCodeRecordService autoCodeRecordService; @Resource private MesWmProductSalesMapper productSalesMapper; @Resource private MesWmProductSalesLineMapper productSalesLineMapper; @Resource private MesWmSalesNoticeLineMapper salesNoticeLineMapper; @Override public Long createSalesNotice(MesWmSalesNoticeSaveReqVO createReqVO) { // 校验数据 validateSalesNoticeSave(createReqVO); // 插入 MesWmSalesNoticeDO notice = BeanUtils.toBean(createReqVO, MesWmSalesNoticeDO.class); notice.setStatus(PREPARE.getStatus()); // 草稿状态 salesNoticeMapper.insert(notice); return notice.getId(); } @Override public void updateSalesNotice(MesWmSalesNoticeSaveReqVO updateReqVO) { // 校验存在 + 草稿状态 validateSalesNoticeExistsAndDraft(updateReqVO.getId()); // 校验数据 validateSalesNoticeSave(updateReqVO); // 更新 MesWmSalesNoticeDO updateObj = BeanUtils.toBean(updateReqVO, MesWmSalesNoticeDO.class); salesNoticeMapper.updateById(updateObj); } @Override @Transactional(rollbackFor = Exception.class) public void deleteSalesNotice(Long id) { // 校验存在 + 草稿状态 validateSalesNoticeExistsAndDraft(id); // 级联删除行 salesNoticeLineService.deleteSalesNoticeLineByNoticeId(id); // 删除 salesNoticeMapper.deleteById(id); } @Override public MesWmSalesNoticeDO getSalesNotice(Long id) { MesWmSalesNoticeDO notice = salesNoticeMapper.selectById(id); // 校验客户权限 if (notice != null && notice.getClientId() != null) { List permittedIds = customerApi.getPermittedCustomerIds(); if (permittedIds != null && !permittedIds.contains(notice.getClientId())) { return null; // 无权限则返回 null } } return notice; } @Override public PageResult getSalesNoticePage(MesWmSalesNoticePageReqVO pageReqVO) { // 获取用户有权限的客户 ID 列表 List permittedClientIds = customerApi.getPermittedCustomerIds(); return salesNoticeMapper.selectPage(pageReqVO, permittedClientIds); } @Override @Transactional(rollbackFor = Exception.class) public void submitSalesNotice(Long id) { // 1.1 校验存在 + 草稿状态 MesWmSalesNoticeDO notice = validateSalesNoticeExistsAndDraft(id); // 1.2 检查是否有行项目 List lines = salesNoticeLineService.getSalesNoticeLineListByNoticeId(id); if (CollUtil.isEmpty(lines)) { throw exception(WM_SALES_NOTICE_LINE_EMPTY); } // 2. 更新状态:草稿 → 待出库 salesNoticeMapper.updateById(new MesWmSalesNoticeDO() .setId(id).setStatus(APPROVED.getStatus())); // 3. 回写 ERP 销售订单出库状态为"已预留" if (notice.getSaleOrderId() != null) { saleOrderApi.updateOrderOutStatusTo(notice.getSaleOrderId(), ErpSaleOrderOutStatusEnum.RESERVED.getStatus()); } } @Override public List getSalesNoticeList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return salesNoticeMapper.selectByIds(ids); } @Override public MesWmSalesNoticeDO validateSalesNoticeExists(Long id) { MesWmSalesNoticeDO notice = salesNoticeMapper.selectById(id); if (notice == null) { throw exception(WM_SALES_NOTICE_NOT_EXISTS); } return notice; } /** * 校验发货通知单存在且为草稿状态 */ private MesWmSalesNoticeDO validateSalesNoticeExistsAndDraft(Long id) { MesWmSalesNoticeDO notice = validateSalesNoticeExists(id); if (ObjUtil.notEqual(PREPARE.getStatus(), notice.getStatus())) { throw exception(WM_SALES_NOTICE_STATUS_NOT_ALLOW_DELETE); } return notice; } private void validateSalesNoticeSave(MesWmSalesNoticeSaveReqVO saveReqVO) { // 校验编码唯一 validateNoticeCodeUnique(saveReqVO.getId(), saveReqVO.getCode()); // 校验客户存在 if (saveReqVO.getClientId() != null) { customerApi.validateCustomer(saveReqVO.getClientId()); } } private void validateNoticeCodeUnique(Long id, String code) { MesWmSalesNoticeDO notice = salesNoticeMapper.selectByCode(code); if (notice == null) { return; } if (ObjUtil.notEqual(id, notice.getId())) { throw exception(WM_SALES_NOTICE_CODE_DUPLICATE); } } @Override @Transactional(rollbackFor = Exception.class) public Long generateSalesNoticeFromSaleOrder(Long saleOrderId) { // 1. 校验是否已生成过发货通知单(防止重复生成) MesWmSalesNoticeDO existNotice = salesNoticeMapper.selectBySaleOrderId(saleOrderId); if (existNotice != null) { throw exception(WM_SALES_NOTICE_ALREADY_EXISTS); } // 2. 获取销售订单信息 ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(saleOrderId).getCheckedData(); if (saleOrder == null) { throw exception(WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS); } // 3. 获取销售订单明细 List saleOrderItems = saleOrderApi.getSaleOrderItemList(saleOrderId).getCheckedData(); if (CollUtil.isEmpty(saleOrderItems)) { throw exception(WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY); } // 4. 生成发货通知单编码 String code = autoCodeRecordService.generateAutoCode("SALESNOTICE_CODE", null); // 5. 创建发货通知单主表 MesWmSalesNoticeDO notice = new MesWmSalesNoticeDO(); notice.setCode(code); notice.setName("销售订单[" + saleOrder.getNo() + "]发货通知"); notice.setClientId(saleOrder.getCustomerId()); notice.setSaleOrderId(saleOrderId); notice.setSaleOrderCode(saleOrder.getNo()); notice.setSalesDate(java.time.LocalDateTime.now()); notice.setStatus(PREPARE.getStatus()); salesNoticeMapper.insert(notice); // 6. 遍历明细创建发货通知单行 for (ErpSaleOrderItemRespDTO saleOrderItem : saleOrderItems) { MesWmSalesNoticeLineDO line = new MesWmSalesNoticeLineDO(); line.setNoticeId(notice.getId()); line.setSaleOrderItemId(saleOrderItem.getId()); line.setItemId(saleOrderItem.getProductId()); line.setQuantity(saleOrderItem.getCount()); salesNoticeLineService.createSalesNoticeLine(line); } return notice.getId(); } @Override public void finishSalesNotice(Long id) { // 已废弃,保留兼容性 updateSalesNoticeOutStatus(id); } @Override @Transactional(rollbackFor = Exception.class) public void updateSalesNoticeLineOutCount(Long noticeLineId, BigDecimal outCount) { if (noticeLineId == null || outCount == null) { return; } // 1. 获取发货通知单行 MesWmSalesNoticeLineDO line = salesNoticeLineMapper.selectById(noticeLineId); if (line == null) { return; } // 2. 累加出库数量 BigDecimal newOutCount = (line.getOutCount() != null ? line.getOutCount() : BigDecimal.ZERO).add(outCount); // 3. 更新行 salesNoticeLineMapper.updateById(new MesWmSalesNoticeLineDO() .setId(noticeLineId) .setOutCount(newOutCount)); // 4. 更新主单出库状态 updateSalesNoticeOutStatus(line.getNoticeId()); } @Override public void updateSalesNoticeOutStatus(Long noticeId) { if (noticeId == null) { return; } // 1. 获取发货通知单 MesWmSalesNoticeDO notice = salesNoticeMapper.selectById(noticeId); if (notice == null) { return; } // 2. 获取发货通知单行 List lines = salesNoticeLineMapper.selectListByNoticeId(noticeId); if (CollUtil.isEmpty(lines)) { return; } // 3. 计算出库状态 int outStatus = calculateOutStatus(lines); // 4. 更新出库状态 salesNoticeMapper.updateById(new MesWmSalesNoticeDO() .setId(noticeId) .setOutStatus(outStatus)); // 5. 如果全部出库,更新单据状态为已完成 if (outStatus == MesWmOutStatusEnum.ALL_OUT.getStatus()) { salesNoticeMapper.updateById(new MesWmSalesNoticeDO() .setId(noticeId) .setStatus(FINISHED.getStatus())); } } /** * 计算出库状态 */ private int calculateOutStatus(List lines) { boolean allOut = true; boolean anyOut = false; for (MesWmSalesNoticeLineDO line : lines) { BigDecimal quantity = line.getQuantity() != null ? line.getQuantity() : BigDecimal.ZERO; BigDecimal outCount = line.getOutCount() != null ? line.getOutCount() : BigDecimal.ZERO; if (outCount.compareTo(BigDecimal.ZERO) > 0) { anyOut = true; } if (outCount.compareTo(quantity) < 0) { allOut = false; } } if (allOut) { return MesWmOutStatusEnum.ALL_OUT.getStatus(); } else if (anyOut) { return MesWmOutStatusEnum.PART_OUT.getStatus(); } else { return MesWmOutStatusEnum.NOT_OUT.getStatus(); } } @Override @Transactional(rollbackFor = Exception.class) public Long generateProductSalesFromNotice(Long noticeId) { // 1. 校验发货通知单存在且为待出库状态 MesWmSalesNoticeDO notice = validateSalesNoticeExists(noticeId); if (ObjUtil.notEqual(APPROVED.getStatus(), notice.getStatus())) { throw exception(WM_SALES_NOTICE_STATUS_NOT_APPROVED); } // 2. 校验是否已生成过销售出库单(防止重复生成) if (productSalesMapper.selectByNoticeId(noticeId) != null) { throw exception(WM_PRODUCT_SALES_ALREADY_EXISTS); } // 3. 获取发货通知单行 List noticeLines = salesNoticeLineService.getSalesNoticeLineListByNoticeId(noticeId); if (CollUtil.isEmpty(noticeLines)) { throw exception(WM_SALES_NOTICE_LINE_EMPTY); } // 4. 生成销售出库单编码 String code = autoCodeRecordService.generateAutoCode("WM_PRODUCT_SALES_CODE", null); // 5. 创建销售出库单主表 MesWmProductSalesDO productSales = new MesWmProductSalesDO(); productSales.setCode(code); productSales.setName("发货通知单[" + notice.getCode() + "]出库"); productSales.setClientId(notice.getClientId()); productSales.setSaleOrderId(notice.getSaleOrderId()); productSales.setSalesOrderCode(notice.getSaleOrderCode()); productSales.setNoticeId(noticeId); productSales.setSalesDate(notice.getSalesDate()); // 收货信息从发货通知单带入 productSales.setContactName(notice.getRecipientName()); productSales.setContactTelephone(notice.getRecipientTelephone()); productSales.setContactAddress(notice.getRecipientAddress()); productSales.setStatus(MesWmProductSalesStatusEnum.PREPARE.getStatus()); productSalesMapper.insert(productSales); // 6. 遍历发货通知单行创建销售出库单行 for (MesWmSalesNoticeLineDO noticeLine : noticeLines) { MesWmProductSalesLineDO salesLine = new MesWmProductSalesLineDO(); salesLine.setSalesId(productSales.getId()); salesLine.setSaleOrderItemId(noticeLine.getSaleOrderItemId()); salesLine.setNoticeLineId(noticeLine.getId()); salesLine.setItemId(noticeLine.getItemId()); salesLine.setQuantity(noticeLine.getQuantity()); salesLine.setBatchId(noticeLine.getBatchId()); salesLine.setBatchCode(noticeLine.getBatchCode()); salesLine.setOqcCheckFlag(noticeLine.getOqcCheckFlag()); productSalesLineMapper.insert(salesLine); } return productSales.getId(); } }