| | |
| | | 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.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.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.*; |
| | |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | private MesWmBarcodeService barcodeService; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createCard(MesProCardSaveReqVO createReqVO) { |
| | | // 1. 校验关联数据 |
| | | validateCardSaveData(createReqVO); |
| | | MesProWorkOrderDO workOrder = validateCardSaveData(createReqVO); |
| | | |
| | | // 2. 插入 |
| | | MesProCardDO card = BeanUtils.toBean(createReqVO, MesProCardDO.class) |
| | | .setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus()); |
| | | cardMapper.insert(card); |
| | | |
| | | // 3. 自动生成条码 |
| | | // 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 校验存在 + 草稿状态 |
| | | validateCardExistsAndPrepare(updateReqVO.getId()); |
| | | MesProCardDO card = validateCardExistsAndPrepareInternal(updateReqVO.getId()); |
| | | // 1.2 校验关联数据 |
| | | validateCardSaveData(updateReqVO); |
| | | 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 |
| | |
| | | |
| | | @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) { |
| | |
| | | } |
| | | } |
| | | |
| | | private void validateCardSaveData(MesProCardSaveReqVO reqVO) { |
| | | private MesProWorkOrderDO validateCardSaveData(MesProCardSaveReqVO reqVO) { |
| | | // 校验编码唯一 |
| | | validateCardCodeUnique(reqVO.getId(), reqVO.getCode()); |
| | | // 校验工单存在 |
| | |
| | | if (ObjUtil.notEqual(workOrder.getProductId(), reqVO.getItemId())) { |
| | | throw exception(PRO_WORK_ORDER_PRODUCT_MISMATCH); |
| | | } |
| | | return workOrder; |
| | | } |
| | | |
| | | /** |
| | | * 按工单工序与工序收率生成流转卡工序记录 |
| | | * |
| | | * <p>数量口径与一键排产一致(见 MesProBatchScheduleService#calcTargetQuantities): |
| | | * 末道工序产出 = 工单计划数量,前面各道逐级放大后向上取整; |
| | | * 本道投入 = 上道产出(首道投入 = 首道产出 / 收率,向上取整),不合格品 = 投入 - 产出。 |
| | | * |
| | | * <p>进入/出工序时间、工位、操作人留空,待生产实际发生时填写。 |
| | | */ |
| | | private void generateCardProcesses(Long cardId, MesProWorkOrderDO workOrder) { |
| | | List<MesProWorkOrderProcessDO> 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<MesProCardProcessDO> 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); |
| | | } |
| | | |
| | | } |