package cn.iocoder.yudao.module.mes.service.pro.card; 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.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.module.mes.controller.admin.pro.card.vo.MesProCardPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.card.vo.MesProCardSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.card.MesProCardDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.card.MesProCardProcessDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.card.MesProCardMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService; 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; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 生产流转卡 Service 实现类 * * @author 超级管理员 */ @Service @Validated public class MesProCardServiceImpl implements MesProCardService { @Resource private MesProCardMapper cardMapper; @Resource @Lazy private MesProCardProcessService cardProcessService; @Resource private MesProWorkOrderService workOrderService; @Resource private MesMdItemService itemService; @Resource private MesWmBarcodeService barcodeService; @Resource private MesProWorkOrderProcessService workOrderProcessService; @Override @Transactional(rollbackFor = Exception.class) public Long createCard(MesProCardSaveReqVO createReqVO) { // 1. 校验关联数据 MesProWorkOrderDO workOrder = validateCardSaveData(createReqVO); // 2. 插入 MesProCardDO card = BeanUtils.toBean(createReqVO, MesProCardDO.class) .setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus()); cardMapper.insert(card); // 3. 按工单工序 + 收率生成工序记录 generateCardProcesses(card.getId(), workOrder); // 4. 自动生成条码 barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.PROCARD.getValue(), card.getId(), card.getCode(), card.getCode()); return card.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateCard(MesProCardSaveReqVO updateReqVO) { // 1.1 校验存在 + 草稿状态 MesProCardDO card = validateCardExistsAndPrepareInternal(updateReqVO.getId()); // 1.2 校验关联数据 MesProWorkOrderDO workOrder = validateCardSaveData(updateReqVO); // 2. 更新 MesProCardDO updateObj = BeanUtils.toBean(updateReqVO, MesProCardDO.class); cardMapper.updateById(updateObj); // 3. 更换了生产订单:原工序记录已失效,按新工单重建 if (ObjUtil.notEqual(card.getWorkOrderId(), updateReqVO.getWorkOrderId())) { cardProcessService.deleteCardProcessByCardId(card.getId()); generateCardProcesses(card.getId(), workOrder); } } @Override @Transactional(rollbackFor = Exception.class) public void deleteCard(Long id) { // 1. 校验存在 + 草稿状态 validateCardExistsAndPrepare(id); // 2. 删除流转卡 + 级联删除工序记录 cardMapper.deleteById(id); cardProcessService.deleteCardProcessByCardId(id); } @Override public MesProCardDO getCard(Long id) { return cardMapper.selectById(id); } @Override public PageResult getCardPage(MesProCardPageReqVO pageReqVO) { return cardMapper.selectPage(pageReqVO); } @Override public void submitCard(Long id) { // 1. 校验存在 + 草稿状态 validateCardExistsAndStatus(id, MesProWorkOrderStatusEnum.PREPARE); // 2. 提交(草稿 → 已确认) cardMapper.updateById(new MesProCardDO() .setId(id).setStatus(MesProWorkOrderStatusEnum.CONFIRMED.getStatus())); } @Override public void finishCard(Long id) { // 1. 校验存在 + 已确认状态 validateCardExistsAndStatus(id, MesProWorkOrderStatusEnum.CONFIRMED); // 2. 完成(已确认 → 已完成) cardMapper.updateById(new MesProCardDO() .setId(id).setStatus(MesProWorkOrderStatusEnum.FINISHED.getStatus())); } @Override public void cancelCard(Long id) { // 1.1 校验存在 MesProCardDO card = validateCardExistsInternal(id); // 1.2 已完成和已取消不允许取消 if (ObjectUtils.equalsAny(card.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus(), MesProWorkOrderStatusEnum.CANCELED.getStatus())) { throw exception(PRO_CARD_CANCEL_NOT_ALLOWED); } // 2. 取消 cardMapper.updateById(new MesProCardDO() .setId(id).setStatus(MesProWorkOrderStatusEnum.CANCELED.getStatus())); } // ==================== 校验方法 ==================== @Override public void validateCardExists(Long id) { validateCardExistsInternal(id); } @Override public void validateCardExistsAndPrepare(Long id) { validateCardExistsAndPrepareInternal(id); } private MesProCardDO validateCardExistsAndPrepareInternal(Long id) { MesProCardDO card = validateCardExistsInternal(id); if (ObjUtil.notEqual(MesProWorkOrderStatusEnum.PREPARE.getStatus(), card.getStatus())) { throw exception(PRO_CARD_NOT_PREPARE); } return card; } private MesProCardDO validateCardExistsInternal(Long id) { MesProCardDO card = cardMapper.selectById(id); if (card == null) { throw exception(PRO_CARD_NOT_EXISTS); } return card; } private MesProCardDO validateCardExistsAndStatus(Long id, MesProWorkOrderStatusEnum status) { MesProCardDO card = validateCardExistsInternal(id); if (ObjUtil.notEqual(status.getStatus(), card.getStatus())) { throw exception(PRO_CARD_STATUS_ERROR); } return card; } private void validateCardCodeUnique(Long id, String code) { if (code == null) { return; } MesProCardDO card = cardMapper.selectByCode(code); if (card == null) { return; } if (ObjUtil.notEqual(card.getId(), id)) { throw exception(PRO_CARD_CODE_DUPLICATE); } } private MesProWorkOrderDO validateCardSaveData(MesProCardSaveReqVO reqVO) { // 校验编码唯一 validateCardCodeUnique(reqVO.getId(), reqVO.getCode()); // 校验工单存在 MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId()); // 校验物料存在 itemService.validateItemExists(reqVO.getItemId()); if (ObjUtil.notEqual(workOrder.getProductId(), reqVO.getItemId())) { throw exception(PRO_WORK_ORDER_PRODUCT_MISMATCH); } return workOrder; } /** * 按工单工序与工序收率生成流转卡工序记录 * *

数量口径与一键排产一致(见 MesProBatchScheduleService#calcTargetQuantities): * 末道工序产出 = 工单计划数量,前面各道逐级放大后向上取整; * 本道投入 = 上道产出(首道投入 = 首道产出 / 收率,向上取整),不合格品 = 投入 - 产出。 * *

进入/出工序时间、工位、操作人留空,待生产实际发生时填写。 */ private void generateCardProcesses(Long cardId, MesProWorkOrderDO workOrder) { List processes = workOrderProcessService .getWorkOrderProcessListByWorkOrderId(workOrder.getId()); if (CollUtil.isEmpty(processes) || workOrder.getQuantity() == null) { return; // 工单未维护工序或数量缺失,工序记录留待手工维护 } processes = new ArrayList<>(processes); processes.sort(Comparator.comparing(MesProWorkOrderProcessDO::getSort, Comparator.nullsLast(Comparator.naturalOrder()))); // 1. 按收率从末道往前倒推各道产出量(向上取整为整数件) int size = processes.size(); BigDecimal[] outputs = new BigDecimal[size]; BigDecimal cumulativeYield = BigDecimal.ONE; for (int i = size - 1; i >= 0; i--) { outputs[i] = workOrder.getQuantity().divide(cumulativeYield, 0, RoundingMode.CEILING); BigDecimal yieldRate = processes.get(i).getYieldRate(); if (yieldRate != null && yieldRate.signum() > 0) { cumulativeYield = cumulativeYield.multiply(yieldRate); } } // 2. 逐道生成记录:投入 = 上道产出(首道 = 本道产出 / 收率,向上取整) List cardProcesses = new ArrayList<>(size); BigDecimal previousOutput = null; for (int i = 0; i < size; i++) { MesProWorkOrderProcessDO process = processes.get(i); BigDecimal input; if (previousOutput != null) { input = previousOutput; } else { BigDecimal firstYield = process.getYieldRate(); input = firstYield == null || firstYield.signum() <= 0 ? outputs[i] : outputs[i].divide(firstYield, 0, RoundingMode.CEILING); } cardProcesses.add(MesProCardProcessDO.builder() .cardId(cardId).sort(process.getSort()).processId(process.getProcessId()) .inputQuantity(input).outputQuantity(outputs[i]) .unqualifiedQuantity(BigDecimal.ZERO) .build()); previousOutput = outputs[i]; } cardProcessService.createCardProcessBatch(cardProcesses); } }