src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -19,6 +19,7 @@
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.production.bean.dto.ProductStructureDto;
import com.ruoyi.production.bean.dto.ProductionProductMainDto;
import com.ruoyi.production.enums.ProductOrderStatusEnum;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductionProductMainService;
@@ -39,8 +40,10 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@@ -62,7 +65,10 @@
    private final ProductionAccountMapper productionAccountMapper;
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final ProductionOrderMapper productionOrderMapper;
    private final ProductionOrderBomMapper productionOrderBomMapper;
    private final ProductionBomStructureMapper productionBomStructureMapper;
    private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
    private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper;
    private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    private final StockUtils stockUtils;
@@ -86,7 +92,8 @@
    @Override
    public Boolean addProductMain(ProductionProductMainDto dto) {
        if (dto.getProductionOperationTaskId() == null) {
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("请传入生产工单ID");
        }
        return addProductMainByProductionTask(dto);
@@ -107,12 +114,17 @@
    }
    private Boolean addProductMainByProductionTask(ProductionProductMainDto dto) {
        // 报工以订单工序快照为准,避免工艺主数据变更后影响历史工单执行。
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("productionOperationTaskId can not be null");
        }
        SysUser user = userMapper.selectUserById(dto.getUserId());
        ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(dto.getProductionOperationTaskId());
        ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(taskId);
        if (productionOperationTask == null) {
            throw new ServiceException("生产工单不存在");
        }
        ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getTechnologyRoutingOperationId());
        ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId());
        if (routingOperation == null) {
            throw new ServiceException("订单工艺路线工序不存在");
        }
@@ -120,6 +132,7 @@
        if (productionOrder == null) {
            throw new ServiceException("生产订单不存在");
        }
        syncOperationParamInputValue(dto, routingOperation.getId());
        TechnologyRoutingOperation technologyRoutingOperation = technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId());
        TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null
                : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId());
@@ -131,19 +144,19 @@
        ProductionProductMain productionProductMain = new ProductionProductMain();
        productionProductMain.setProductNo(generateProductNo());
        productionProductMain.setUserId(dto.getUserId());
        productionProductMain.setUserName(dto.getUserName());
        productionProductMain.setProductionOperationTaskId(productionOperationTask.getId());
        productionProductMain.setWorkOrderId(productionOperationTask.getId());
        productionProductMain.setUserId(user == null ? dto.getUserId() : user.getUserId());
        productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName());
        productionProductMain.setProductionOperationTaskId(taskId);
        productionProductMain.setStatus(0);
        productionProductMainMapper.insert(productionProductMain);
        List<ProductStructureDto> productStructureDtos = new ArrayList<>();
        ProductStructureDto productStructureDto = new ProductStructureDto();
        productStructureDto.setProductModelId(productModel.getId());
        productStructureDto.setUnitQuantity(BigDecimal.ONE);
        productStructureDtos.add(productStructureDto);
        List<ProductStructureDto> productStructureDtos = resolveInputStructures(
                productionOrder.getId(), routingOperation, productModel.getId());
        if (productStructureDtos.isEmpty()) {
            throw new ServiceException("未找到当前工序对应的BOM投入节点");
        }
        for (ProductStructureDto item : productStructureDtos) {
            // 当前实现按工序成品直接作为投入,后续若接入领料记录可在这里替换来源。
            ProductionProductInput productionProductInput = new ProductionProductInput();
            productionProductInput.setProductionProductMainId(productionProductMain.getId());
            productionProductInput.setProductMainId(productionProductMain.getId());
@@ -151,8 +164,6 @@
            productionProductInput.setInputQuantity(item.getUnitQuantity().multiply(defaultDecimal(dto.getQuantity())));
            productionProductInput.setQuantity(productionProductInput.getInputQuantity());
            productionProductInputMapper.insert(productionProductInput);
            stockUtils.substractStock(item.getProductModelId(), productionProductInput.getInputQuantity(),
                    StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId());
        }
        ProductionProductOutput productionProductOutput = new ProductionProductOutput();
@@ -166,11 +177,12 @@
        List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList(
                Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
                        .eq(ProductionOrderRoutingOperation::getTechnologyRoutingId, routingOperation.getTechnologyRoutingId())
                        .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId())
                        .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId()));
        boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size());
        if (productQty.compareTo(BigDecimal.ZERO) > 0) {
            if (Boolean.TRUE.equals(routingOperation.getIsQuality())) {
                // 质检工序先生成检验单,非质检工序直接入合格品库存。
                int inspectType = isLastOperation ? 2 : 1;
                String process = isLastOperation ? null : technologyOperation == null ? null : technologyOperation.getName();
                Product product = productMapper.selectById(productModel.getProductId());
@@ -209,20 +221,26 @@
            if (ObjectUtils.isNull(productionOperationTask.getActualStartTime())) {
                productionOperationTask.setActualStartTime(LocalDate.now());
            }
            // 报工驱动工单状态流转:有产出即进行中,达到计划量即完工。
            productionOperationTask.setStatus(3);
            if (productionOperationTask.getPlanQuantity() != null
                    && productionOperationTask.getCompleteQuantity().compareTo(productionOperationTask.getPlanQuantity()) >= 0) {
                productionOperationTask.setActualEndTime(LocalDate.now());
                productionOperationTask.setStatus(4);
            }
            productionOperationTaskMapper.updateById(productionOperationTask);
            if (ObjectUtils.isNull(productionOrder.getStartTime())) {
                productionOrder.setStartTime(LocalDateTime.now());
            }
            // 订单状态由最后一道工序的合格产出推动,避免中间工序提前完工。
            productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode());
            if (isLastOperation) {
                productionOrder.setCompleteQuantity(defaultDecimal(productionOrder.getCompleteQuantity()).add(productQty));
                if (productionOrder.getQuantity() != null
                        && productionOrder.getCompleteQuantity().compareTo(productionOrder.getQuantity()) >= 0) {
                    productionOrder.setEndTime(LocalDateTime.now());
                    productionOrder.setStatus(ProductOrderStatusEnum.FINISHED.getCode());
                }
            }
            productionOrderMapper.updateById(productionOrder);
@@ -235,8 +253,8 @@
            }
            ProductionAccount productionAccount = new ProductionAccount();
            productionAccount.setProductionProductMainId(productionProductMain.getId());
            productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId());
            productionAccount.setSalesLedgerProductId(productionOrder.getSaleLedgerProductId() == null ? null : productionOrder.getSaleLedgerProductId().longValue());
//            productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId());
//            productionAccount.setSalesLedgerProductId(productionOrder.getSalesLedgerProductId() == null ? null : productionOrder.getSalesLedgerProductId().longValue());
            productionAccount.setSchedulingUserId(user == null ? null : user.getUserId());
            productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName());
            productionAccount.setFinishedNum(productQty);
@@ -252,7 +270,101 @@
        return true;
    }
    private void syncOperationParamInputValue(ProductionProductMainDto dto, Long productionOrderRoutingOperationId) {
        if (dto == null || productionOrderRoutingOperationId == null) {
            return;
        }
        List<ProductionOrderRoutingOperationParam> paramList = dto.getProductionOperationParamList();
        if (paramList == null || paramList.isEmpty()) {
            return;
        }
        List<ProductionOrderRoutingOperationParam> dbParamList = productionOrderRoutingOperationParamMapper.selectList(
                Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                        .eq(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, productionOrderRoutingOperationId));
        if (dbParamList == null || dbParamList.isEmpty()) {
            return;
        }
        Map<Long, ProductionOrderRoutingOperationParam> dbParamMap = dbParamList.stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(ProductionOrderRoutingOperationParam::getId, item -> item, (left, right) -> left));
        for (ProductionOrderRoutingOperationParam param : paramList) {
            if (param == null || param.getId() == null) {
                continue;
            }
            ProductionOrderRoutingOperationParam dbParam = dbParamMap.get(param.getId());
            if (dbParam == null) {
                throw new ServiceException("工序参数不存在或不属于当前工单工序,ID=" + param.getId());
            }
            if (Objects.equals(dbParam.getInputValue(), param.getInputValue())) {
                continue;
            }
            ProductionOrderRoutingOperationParam updateParam = new ProductionOrderRoutingOperationParam();
            updateParam.setId(dbParam.getId());
            updateParam.setInputValue(param.getInputValue());
            productionOrderRoutingOperationParamMapper.updateById(updateParam);
        }
    }
    private List<ProductStructureDto> resolveInputStructures(Long productionOrderId,
                                                             ProductionOrderRoutingOperation routingOperation,
                                                             Long outputProductModelId) {
        if (productionOrderId == null || routingOperation == null || routingOperation.getTechnologyOperationId() == null) {
            return new ArrayList<>();
        }
        ProductionOrderBom orderBom = productionOrderBomMapper.selectOne(
                Wrappers.<ProductionOrderBom>lambdaQuery()
                        .eq(ProductionOrderBom::getProductionOrderId, productionOrderId)
                        .orderByDesc(ProductionOrderBom::getId)
                        .last("limit 1"));
        if (orderBom == null || orderBom.getId() == null) {
            return new ArrayList<>();
        }
        List<ProductionBomStructure> bomNodeList = productionBomStructureMapper.selectList(
                Wrappers.<ProductionBomStructure>lambdaQuery()
                        .eq(ProductionBomStructure::getProductionOrderBomId, orderBom.getId())
                        .orderByAsc(ProductionBomStructure::getId));
        if (bomNodeList.isEmpty()) {
            return new ArrayList<>();
        }
        Map<Long, ProductionBomStructure> nodeMap = bomNodeList.stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
        Long currentOutputModelId = routingOperation.getProductModelId() != null
                ? routingOperation.getProductModelId()
                : outputProductModelId;
        Map<Long, BigDecimal> unitQtyByProductModel = new LinkedHashMap<>();
        for (ProductionBomStructure node : bomNodeList) {
            if (node == null || node.getParentId() == null || node.getProductModelId() == null) {
                continue;
            }
            if (!Objects.equals(node.getTechnologyOperationId(), routingOperation.getTechnologyOperationId())) {
                continue;
            }
            ProductionBomStructure parent = nodeMap.get(node.getParentId());
            if (parent == null || !Objects.equals(parent.getProductModelId(), currentOutputModelId)) {
                continue;
            }
            unitQtyByProductModel.merge(node.getProductModelId(), defaultDecimal(node.getUnitQuantity()), BigDecimal::add);
        }
        List<ProductStructureDto> result = new ArrayList<>();
        for (Map.Entry<Long, BigDecimal> entry : unitQtyByProductModel.entrySet()) {
            if (entry.getValue().compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            ProductStructureDto item = new ProductStructureDto();
            item.setProductModelId(entry.getKey());
            item.setUnitQuantity(entry.getValue());
            result.add(item);
        }
        return result;
    }
    private Boolean removeProductMainByProductionTask(ProductionProductMain productionProductMain) {
        // 删除报工需要同步回滚质检、库存、工时核算和订单/工单进度。
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(
                Wrappers.<QualityInspect>lambdaQuery().eq(QualityInspect::getProductMainId, productionProductMain.getId()));
        if (qualityInspects.size() > 0) {
@@ -275,22 +387,36 @@
            BigDecimal validQuantity = defaultDecimal(productionProductOutput.getQuantity()).subtract(defaultDecimal(productionProductOutput.getScrapQty()));
            productionOperationTask.setCompleteQuantity(defaultDecimal(productionOperationTask.getCompleteQuantity()).subtract(validQuantity));
            productionOperationTask.setActualEndTime(null);
            if (defaultDecimal(productionOperationTask.getCompleteQuantity()).compareTo(BigDecimal.ZERO) <= 0) {
                productionOperationTask.setCompleteQuantity(BigDecimal.ZERO);
                productionOperationTask.setActualStartTime(null);
                productionOperationTask.setStatus(2);
            } else {
                productionOperationTask.setStatus(3);
            }
            productionOperationTaskMapper.updateById(productionOperationTask);
            ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId());
            ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getTechnologyRoutingOperationId());
            ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId());
            if (productionOrder != null && routingOperation != null) {
                // 只有最后一道工序的报工才会影响生产订单完工数量。
                List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList(
                        Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
                                .eq(ProductionOrderRoutingOperation::getTechnologyRoutingId, routingOperation.getTechnologyRoutingId())
                                .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId())
                                .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId()));
                boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size());
                if (isLastOperation) {
                    BigDecimal newCompleteQty = defaultDecimal(productionOrder.getCompleteQuantity()).subtract(validQuantity);
                    productionOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
                    productionOrder.setEndTime(null);
                    productionOrderMapper.updateById(productionOrder);
                }
                if (defaultDecimal(productionOrder.getCompleteQuantity()).compareTo(BigDecimal.ZERO) <= 0) {
                    productionOrder.setStartTime(null);
                    productionOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode());
                } else {
                    productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode());
                }
                productionOrderMapper.updateById(productionOrder);
            }
        }
@@ -338,6 +464,13 @@
        return value == null ? BigDecimal.ZERO : value;
    }
    private Long resolveTaskId(ProductionProductMainDto dto) {
        if (dto == null) {
            return null;
        }
        return dto.getProductionOperationTaskId();
    }
    @Override
    public ArrayList<Long> listMain(List<Long> idList) {
        return productionProductMainMapper.listMain(idList);