| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesImportExcelVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesImportRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | 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.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | | import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService; |
| | | import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; |
| | | import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| | |
| | | /** |
| | | * MES 销售出库单 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class MesWmProductSalesServiceImpl implements MesWmProductSalesService { |
| | | |
| | | @Resource |
| | | private MesWmProductSalesMapper productSalesMapper; |
| | | @Resource |
| | | private MesWmProductSalesLineMapper productSalesLineMapper; |
| | | |
| | | @Resource |
| | | private MesWmProductSalesLineService productSalesLineService; |
| | | @Resource |
| | | private MesWmProductSalesDetailService productSalesDetailService; |
| | | @Resource |
| | | private MesMdClientService clientService; |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private MesWmSalesNoticeService salesNoticeService; |
| | | @Resource |
| | |
| | | private MesWmMaterialStockService materialStockService; |
| | | @Resource |
| | | private MesWmStockReserveService stockReserveService; |
| | | @Resource |
| | | @Lazy |
| | | private MesQcOqcService oqcService; |
| | | |
| | | @Resource |
| | | private ErpSaleOrderApi saleOrderApi; |
| | |
| | | |
| | | @Override |
| | | public PageResult<MesWmProductSalesDO> getProductSalesPage(MesWmProductSalesPageReqVO pageReqVO) { |
| | | return productSalesMapper.selectPage(pageReqVO); |
| | | List<Long> permittedClientIds = customerApi.getPermittedCustomerIds(); |
| | | return productSalesMapper.selectPage(pageReqVO, permittedClientIds); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | } |
| | | |
| | | // 获取出库单信息 |
| | | MesWmProductSalesDO sales = productSalesMapper.selectById(id); |
| | | |
| | | // 检查所有行的 oqcCheckFlag:如果有需要 OQC 检验的行,进入待检测状态 |
| | | boolean needOqc = CollectionUtils.anyMatch(lines, |
| | | line -> Boolean.TRUE.equals(line.getOqcCheckFlag())); |
| | | if (needOqc) { |
| | | // 为需要 OQC 检验且尚未创建检验单的明细创建 OQC 检验单 |
| | | List<MesWmProductSalesLineDO> oqcLines = CollectionUtils.filterList(lines, |
| | | line -> Boolean.TRUE.equals(line.getOqcCheckFlag()) && line.getOqcId() == null); |
| | | for (MesWmProductSalesLineDO line : oqcLines) { |
| | | // 直接调用,让异常抛出,保证数据一致性 |
| | | Long oqcId = oqcService.createOqcFromProductSales( |
| | | sales.getId(), line, sales.getCode(), sales.getClientId()); |
| | | // 更新出库明细的 oqcId |
| | | productSalesLineMapper.updateById(new MesWmProductSalesLineDO() |
| | | .setId(line.getId()) |
| | | .setOqcId(oqcId)); |
| | | } |
| | | |
| | | // 批量初始化 OQC 行的质量状态为"待检验" |
| | | List<Long> oqcLineIds = convertList( |
| | | CollectionUtils.filterList(lines, line -> Boolean.TRUE.equals(line.getOqcCheckFlag())), |
| | |
| | | // 执行拣货(待拣货 → 待填写运单) |
| | | productSalesMapper.updateById(new MesWmProductSalesDO() |
| | | .setId(id).setStatus(MesWmProductSalesStatusEnum.SHIPPING.getStatus())); |
| | | |
| | | // 回写 ERP 销售订单出库状态为"部分出库"(表示已拣货/占用) |
| | | if (sales.getSaleOrderId() != null) { |
| | | saleOrderApi.updateOrderOutStatusTo(sales.getSaleOrderId(), |
| | | ErpSaleOrderOutStatusEnum.PART_OUT.getStatus()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | productSalesMapper.updateById(new MesWmProductSalesDO() |
| | | .setId(id).setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus())); |
| | | |
| | | // 5. 更新关联的发货通知单状态 |
| | | // 5. 更新关联的发货通知单出库数量和状态 |
| | | if (sales.getNoticeId() != null) { |
| | | salesNoticeService.finishSalesNotice(sales.getNoticeId()); |
| | | updateSalesNoticeOutCount(sales); |
| | | } |
| | | |
| | | // 6. 回写 ERP 销售订单的出库数量和状态 |
| | | updateErpSaleOrderOutCount(sales); |
| | | } |
| | | |
| | | /** |
| | | * 更新发货通知单的出库数量和状态 |
| | | */ |
| | | private void updateSalesNoticeOutCount(MesWmProductSalesDO sales) { |
| | | // 获取出库单行 |
| | | List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(sales.getId()); |
| | | if (CollUtil.isEmpty(lines)) { |
| | | return; |
| | | } |
| | | // 遍历行,按 noticeLineId 更新出库数量 |
| | | for (MesWmProductSalesLineDO line : lines) { |
| | | if (line.getNoticeLineId() != null && line.getQuantity() != null) { |
| | | salesNoticeService.updateSalesNoticeLineOutCount(line.getNoticeLineId(), line.getQuantity()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode()) |
| | | .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId()) |
| | | .setBizType(MesBizTypeConstants.WM_PRODUCT_SALES).setBizId(sales.getId()) |
| | | .setBizCode(sales.getCode()).setBizLineId(detail.getLineId()))); |
| | | .setBizCode(sales.getCode()).setBizLineId(detail.getLineId()) |
| | | .setMaterialStockId(detail.getMaterialStockId()))); // 指定库存记录ID,避免重新匹配 |
| | | } |
| | | |
| | | @Override |
| | |
| | | // 校验编码唯一 |
| | | validateCodeUnique(id, reqVO.getCode()); |
| | | // 校验客户存在 |
| | | clientService.validateClientExistsAndEnable(reqVO.getClientId()); |
| | | customerApi.validateCustomer(reqVO.getClientId()); |
| | | // 校验发货通知单 |
| | | if (reqVO.getNoticeId() != null) { |
| | | MesWmSalesNoticeDO notice = salesNoticeService.validateSalesNoticeExists(reqVO.getNoticeId()); |
| | |
| | | productSalesMapper.updateById(new MesWmProductSalesDO().setId(id).setStatus(status)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesWmProductSalesImportRespVO importProductSalesList(List<MesWmProductSalesImportExcelVO> importList, boolean updateSupport) { |
| | | if (CollUtil.isEmpty(importList)) { |
| | | throw exception(WM_PRODUCT_SALES_IMPORT_LIST_IS_EMPTY); |
| | | } |
| | | MesWmProductSalesImportRespVO respVO = MesWmProductSalesImportRespVO.builder() |
| | | .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>()) |
| | | .failureCodes(new LinkedHashMap<>()).build(); |
| | | AtomicInteger index = new AtomicInteger(1); |
| | | importList.forEach(importItem -> { |
| | | int currentIndex = index.getAndIncrement(); |
| | | String code = importItem.getCode(); |
| | | String key = StrUtil.blankToDefault(code, "第 " + currentIndex + " 行"); |
| | | if (StrUtil.isBlank(code)) { |
| | | respVO.getFailureCodes().put(key, "单号不能为空"); |
| | | return; |
| | | } |
| | | if (StrUtil.isBlank(importItem.getName())) { |
| | | respVO.getFailureCodes().put(key, "名称不能为空"); |
| | | return; |
| | | } |
| | | MesWmProductSalesDO existing = productSalesMapper.selectByCode(code); |
| | | if (existing != null && updateSupport) { |
| | | if (!MesWmProductSalesStatusEnum.PREPARE.getStatus().equals(existing.getStatus())) { |
| | | respVO.getFailureCodes().put(key, "单据非草稿状态,不允许覆盖更新"); |
| | | return; |
| | | } |
| | | MesWmProductSalesDO updateObj = BeanUtils.toBean(importItem, MesWmProductSalesDO.class); |
| | | updateObj.setId(existing.getId()); |
| | | updateObj.setStatus(existing.getStatus()); |
| | | productSalesMapper.updateById(updateObj); |
| | | respVO.getUpdateCodes().add(code); |
| | | } else if (existing != null) { |
| | | respVO.getFailureCodes().put(key, "单号已存在:" + code); |
| | | } else { |
| | | MesWmProductSalesDO createObj = BeanUtils.toBean(importItem, MesWmProductSalesDO.class); |
| | | createObj.setId(null); |
| | | createObj.setStatus(MesWmProductSalesStatusEnum.PREPARE.getStatus()); |
| | | productSalesMapper.insert(createObj); |
| | | respVO.getCreateCodes().add(code); |
| | | } |
| | | }); |
| | | return respVO; |
| | | } |
| | | |
| | | } |