4 天以前 741918a903e17b2ec7522556d2c043b8d35dd8a1
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -1,5 +1,6 @@
package com.ruoyi.production.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -14,9 +15,9 @@
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
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.*;
@@ -39,13 +40,14 @@
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -72,8 +74,6 @@
    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;
@@ -219,106 +219,179 @@
    @Override
    public Boolean addProductMain(ProductionProductMainDto dto) {
        // 新增生产报工主记录
        // 兼容旧流程:如果没有开始报工记录ID,走一步报工(自动创建+结束)
        Long startRecordId = dto.resolveStartRecordId();
        if (startRecordId == null) {
            return oneStepWork(dto);
        }
        dto.setId(startRecordId);
        return finishWork(dto);
    }
    @Override
    public Boolean startWork(ProductionProductMainDto dto) {
        // 开始报工:创建报工记录,标记实际开始时间
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("请传入生产工单ID");
        }
        return addProductMainByProductionTask(dto);
    }
    @Override
    public Boolean saveProductionProductMain(ProductionProductMainDto productionProductMainDto) {
        // 保存生产报工主记录
        return addProductMain(productionProductMainDto);
    }
    @Override
    public Boolean removeProductMain(Long id) {
        // 删除生产报工主记录
        ProductionProductMain currentMain = productionProductMainMapper.selectById(id);
        if (currentMain == null) {
            return true;
        }
        return removeProductMainByProductionTask(currentMain);
    }
    private Boolean addProductMainByProductionTask(ProductionProductMainDto dto) {
        // 按生产任务新增报工主记录
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("生产工单ID不能为空");
        }
        SysUser user = userMapper.selectUserById(dto.getUserId());
        ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(taskId);
        if (productionOperationTask == null) {
        ProductionOperationTask task = productionOperationTaskMapper.selectById(taskId);
        if (task == null) {
            throw new ServiceException("生产工单不存在");
        }
        ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId());
        if (routingOperation == null) {
            throw new ServiceException("订单工艺路线工序不存在");
        // 权限校验:已指派时仅被指派人可操作
        validateWorkerPermission(task);
        if (task.getStatus() != null && task.getStatus() != 2 && task.getStatus() != 3) {
            throw new ServiceException("当前工单状态不允许开始报工,仅待生产或生产中状态的工单可操作");
        }
        ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId());
        if (productionOrder == null) {
            throw new ServiceException("生产订单不存在");
        SysUser user = userMapper.selectUserById(dto.getUserId());
        ProductionProductMain main = new ProductionProductMain();
        main.setProductNo(generateProductNo());
        main.setUserId(user == null ? dto.getUserId() : user.getUserId());
        main.setUserName(user == null ? dto.getUserName() : user.getNickName());
        main.setProductionOperationTaskId(taskId);
        main.setStatus(0);
        main.setActualStartTime(LocalDateTime.now());
        productionProductMainMapper.insert(main);
        // 工单状态 -> 生产中
        task.setStatus(3);
        if (task.getActualStartTime() == null) {
            task.setActualStartTime(LocalDateTime.now());
        }
        TechnologyRoutingOperation technologyRoutingOperation = technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId());
        TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null
                : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId());
        ProductModel productModel = productModelMapper.selectById(
                routingOperation.getProductModelId() != null ? routingOperation.getProductModelId() : productionOrder.getProductModelId());
        productionOperationTaskMapper.updateById(task);
        // 生产订单 -> 生产进行中
        ProductionOrder productionOrder = productionOrderMapper.selectById(task.getProductionOrderId());
        if (productionOrder != null && productionOrder.getStartTime() == null) {
            productionOrder.setStartTime(LocalDateTime.now());
            productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode());
            productionOrderMapper.updateById(productionOrder);
        }
        return true;
    }
    private Boolean oneStepWork(ProductionProductMainDto dto) {
        // 兼容旧一步报工流程:自动创建开始记录并立即结束
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("请传入生产工单ID");
        }
        ProductionOperationTask task = productionOperationTaskMapper.selectById(taskId);
        if (task == null) {
            throw new ServiceException("生产工单不存在");
        }
        validateWorkerPermission(task);
        if (task.getStatus() != null && task.getStatus() != 2 && task.getStatus() != 3) {
            throw new ServiceException("当前工单状态不允许报工");
        }
        SysUser user = userMapper.selectUserById(dto.getUserId());
        ProductionProductMain main = new ProductionProductMain();
        main.setProductNo(generateProductNo());
        main.setUserId(user == null ? dto.getUserId() : user.getUserId());
        main.setUserName(user == null ? dto.getUserName() : user.getNickName());
        main.setProductionOperationTaskId(taskId);
        main.setStatus(0);
        main.setActualStartTime(LocalDateTime.now());
        productionProductMainMapper.insert(main);
        if (task.getActualStartTime() == null) {
            task.setActualStartTime(LocalDateTime.now());
        }
        task.setStatus(3);
        productionOperationTaskMapper.updateById(task);
        ProductionOrder productionOrder = productionOrderMapper.selectById(task.getProductionOrderId());
        if (productionOrder != null && productionOrder.getStartTime() == null) {
            productionOrder.setStartTime(LocalDateTime.now());
            productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode());
            productionOrderMapper.updateById(productionOrder);
        }
        dto.setId(main.getId());
        return finishWork(dto);
    }
    private Boolean finishWork(ProductionProductMainDto dto) {
        // 结束报工:更新开始报工记录,创建产出、投入品、核算记录
        ProductionProductMain currentMain = productionProductMainMapper.selectById(dto.getId());
        if (currentMain == null) {
            throw new ServiceException("开始报工记录不存在");
        }
        if (currentMain.getStatus() == null || currentMain.getStatus() != 0) {
            throw new ServiceException("该报工记录已结束或状态异常");
        }
        Long taskId = currentMain.getProductionOperationTaskId();
        ProductionOperationTask task = productionOperationTaskMapper.selectById(taskId);
        if (task == null) {
            throw new ServiceException("生产工单不存在");
        }
        validateWorkerPermission(task);
        // 更新报工记录为已结束,记录结束时间
        currentMain.setActualEndTime(LocalDateTime.now());
        currentMain.setStatus(1);
        if (dto.getWorkHour() != null) {
            currentMain.setWorkHour(dto.getWorkHour());
        } else if (currentMain.getActualStartTime() != null) {
            // 根据开始/结束时间计算实际工时(小时)
            long seconds = Duration.between(currentMain.getActualStartTime(), currentMain.getActualEndTime()).getSeconds();
            currentMain.setWorkHour(BigDecimal.valueOf(seconds).divide(BigDecimal.valueOf(3600), 4, RoundingMode.HALF_UP));
        }
        productionProductMainMapper.updateById(currentMain);
        // 同步工序参数
        ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(task.getProductionOrderRoutingOperationId());
        if (routingOperation != null) {
            syncOperationParamInputValue(dto, routingOperation.getId(), currentMain.getId());
        }
        // 获取产品规格
        ProductModel productModel = null;
        if (routingOperation != null) {
            productModel = productModelMapper.selectById(
                    routingOperation.getProductModelId() != null ? routingOperation.getProductModelId() : getProductionOrderProductModelId(task));
        }
        if (productModel == null) {
            throw new ServiceException("产品规格不存在");
        }
        ProductionProductMain productionProductMain = new ProductionProductMain();
        productionProductMain.setProductNo(generateProductNo());
        productionProductMain.setUserId(user == null ? dto.getUserId() : user.getUserId());
        productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName());
        productionProductMain.setProductionOperationTaskId(taskId);
        productionProductMain.setStatus(0);
        productionProductMain.setWorkHour(dto.getWorkHour());
        productionProductMainMapper.insert(productionProductMain);
        syncOperationParamInputValue(dto, routingOperation.getId(), productionProductMain.getId());
        List<ProductStructureDto> productStructureDtos = resolveInputStructures(
                productionOrder.getId(), routingOperation, productModel.getId());
       // 如果没有bom子节点了,那么投入就是他本身
        if (productStructureDtos.isEmpty()) {
            ProductStructureDto fallbackInput = new ProductStructureDto();
            fallbackInput.setProductModelId(productModel.getId());
            fallbackInput.setUnitQuantity(BigDecimal.ONE);
            productStructureDtos.add(fallbackInput);
        }
        for (ProductStructureDto item : productStructureDtos) {
            // 当前实现按工序成品直接作为投入,后续若接入领料记录可在这里替换来源。
        // 投入品
            ProductionProductInput productionProductInput = new ProductionProductInput();
            productionProductInput.setProductionProductMainId(productionProductMain.getId());
            productionProductInput.setProductModelId(item.getProductModelId());
            productionProductInput.setInputQuantity(item.getUnitQuantity().multiply(defaultDecimal(dto.getQuantity())));
        productionProductInput.setProductionProductMainId(currentMain.getId());
        productionProductInput.setProductModelId(productModel.getId());
        productionProductInput.setInputQuantity(defaultDecimal(dto.getQuantity()));
            productionProductInput.setQuantity(productionProductInput.getInputQuantity());
            productionProductInputMapper.insert(productionProductInput);
        }
        // 产出品
        ProductionProductOutput productionProductOutput = new ProductionProductOutput();
        productionProductOutput.setProductionProductMainId(productionProductMain.getId());
        productionProductOutput.setProductionProductMainId(currentMain.getId());
        productionProductOutput.setProductModelId(productModel.getId());
        productionProductOutput.setQuantity(defaultDecimal(dto.getQuantity()));
        productionProductOutput.setScrapQty(defaultDecimal(dto.getScrapQty()));
        productionProductOutputMapper.insert(productionProductOutput);
        BigDecimal reportQty = defaultDecimal(productionProductOutput.getQuantity());
        BigDecimal scrapQty = defaultDecimal(productionProductOutput.getScrapQty());
        BigDecimal productQty = reportQty;
        String qualifiedBatchNo = null;
        List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList(
        TechnologyRoutingOperation technologyRoutingOperation = routingOperation != null
                ? technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId()) : null;
        TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null
                : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId());
        List<ProductionOrderRoutingOperation> routingOperationList = routingOperation != null ? productionOrderRoutingOperationMapper.selectList(
                Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
                        .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId())
                        .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId()));
        boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size());
                        .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId())) : new ArrayList<>();
        boolean isLastOperation = routingOperation != null && routingOperation.getDragSort() != null
                && routingOperation.getDragSort().equals(routingOperationList.size());
        if (productQty.compareTo(BigDecimal.ZERO) > 0) {
            if (Boolean.TRUE.equals(routingOperation.getIsQuality())) {
                // 质检工序先生成检验单,非质检工序直接入合格品库存。
            if (routingOperation != null && 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());
@@ -331,7 +404,7 @@
                qualityInspect.setProcess(process);
                qualityInspect.setInspectState(0);
                qualityInspect.setInspectType(inspectType);
                qualityInspect.setProductMainId(productionProductMain.getId());
                qualityInspect.setProductMainId(currentMain.getId());
                qualityInspect.setProductModelId(productModel.getId());
                qualityInspectMapper.insert(qualityInspect);
                List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
@@ -350,35 +423,35 @@
                }
            } else {
                StockInventoryDto stockInventoryDto = new StockInventoryDto();
                stockInventoryDto.setRecordId(productionProductMain.getId());
                stockInventoryDto.setRecordId(currentMain.getId());
                stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode()));
                stockInventoryDto.setQualitity(productQty);
                stockInventoryDto.setProductModelId(productModel.getId());
                stockInventoryService.addStockInRecordOnly(stockInventoryDto);
                qualifiedBatchNo = resolveLatestStockInBatchNo(
                        productionProductMain.getId(),
                        currentMain.getId(),
                        StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(),
                        productModel.getId(),
                        "0");
            }
            productionOperationTask.setCompleteQuantity(defaultDecimal(productionOperationTask.getCompleteQuantity()).add(productQty));
            if (ObjectUtils.isNull(productionOperationTask.getActualStartTime())) {
                productionOperationTask.setActualStartTime(LocalDate.now());
            task.setCompleteQuantity(defaultDecimal(task.getCompleteQuantity()).add(productQty));
            if (task.getActualStartTime() == null) {
                task.setActualStartTime(LocalDateTime.now());
            }
            // 报工驱动工单状态流转:有产出即进行中,达到计划量即完工。
            productionOperationTask.setStatus(3);
            if (productionOperationTask.getPlanQuantity() != null
                    && productionOperationTask.getCompleteQuantity().compareTo(productionOperationTask.getPlanQuantity()) >= 0) {
                productionOperationTask.setActualEndTime(LocalDate.now());
                productionOperationTask.setStatus(4);
            task.setStatus(3);
            if (task.getPlanQuantity() != null
                    && task.getCompleteQuantity().compareTo(task.getPlanQuantity()) >= 0) {
                task.setActualEndTime(LocalDateTime.now());
                task.setStatus(4);
            }
            productionOperationTaskMapper.updateById(productionOperationTask);
            productionOperationTaskMapper.updateById(task);
            if (ObjectUtils.isNull(productionOrder.getStartTime())) {
            ProductionOrder productionOrder = productionOrderMapper.selectById(task.getProductionOrderId());
            if (productionOrder != null) {
                if (productionOrder.getStartTime() == null) {
                productionOrder.setStartTime(LocalDateTime.now());
            }
            // 订单状态由最后一道工序的合格产出推动,避免中间工序提前完工。
            productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode());
            if (isLastOperation) {
                productionOrder.setCompleteQuantity(defaultDecimal(productionOrder.getCompleteQuantity()).add(productQty));
@@ -389,6 +462,7 @@
                }
            }
            productionOrderMapper.updateById(productionOrder);
            }
            BigDecimal workHours = BigDecimal.ZERO;
            if (technologyOperation != null && technologyOperation.getSalaryQuota() != null) {
@@ -397,13 +471,14 @@
                        : technologyOperation.getSalaryQuota();
            }
            ProductionAccount productionAccount = new ProductionAccount();
            productionAccount.setProductionProductMainId(productionProductMain.getId());
            productionAccount.setSchedulingUserId(user == null ? null : user.getUserId());
            productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName());
            productionAccount.setProductionProductMainId(currentMain.getId());
            SysUser user = userMapper.selectUserById(dto.getUserId());
            productionAccount.setSchedulingUserId(user == null ? currentMain.getUserId() : user.getUserId());
            productionAccount.setSchedulingUserName(user == null ? currentMain.getUserName() : user.getNickName());
            productionAccount.setFinishedNum(productQty);
            productionAccount.setWorkHours(technologyOperation != null ? technologyOperation.getSalaryQuota() : null);
            productionAccount.setTechnologyOperationName(technologyOperation == null ? null : technologyOperation.getName());
            productionAccount.setSchedulingDate(LocalDateTime.now());
            productionAccount.setSchedulingDate(currentMain.getActualEndTime() != null ? currentMain.getActualEndTime() : LocalDateTime.now());
            productionAccountMapper.insert(productionAccount);
        }
        if (scrapQty.compareTo(BigDecimal.ZERO) > 0) {
@@ -411,10 +486,54 @@
                    productModel.getId(),
                    scrapQty,
                    StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(),
                    productionProductMain.getId(),
                    currentMain.getId(),
                    qualifiedBatchNo);
        }
        return true;
    }
    private Long getProductionOrderProductModelId(ProductionOperationTask task) {
        // 从生产订单获取产品规格ID
        if (task == null || task.getProductionOrderId() == null) {
            return null;
        }
        ProductionOrder order = productionOrderMapper.selectById(task.getProductionOrderId());
        return order == null ? null : order.getProductModelId();
    }
    private void validateWorkerPermission(ProductionOperationTask task) {
        // 校验当前用户是否有权操作此工单:未指派时人人可操作,已指派时仅被指派人可操作
        if (task == null) {
            return;
        }
        String userIds = task.getUserIds();
        if (userIds == null || userIds.isEmpty() || "[]".equals(userIds.trim())) {
            return;
        }
        Long currentUserId = SecurityUtils.getUserId();
        if (currentUserId == null) {
            return;
        }
        List<Long> assignedIds = JSON.parseArray(userIds, Long.class);
        if (assignedIds == null || !assignedIds.contains(currentUserId)) {
            throw new ServiceException("您未被指派到此工单,无法操作");
        }
    }
    @Override
    public Boolean saveProductionProductMain(ProductionProductMainDto productionProductMainDto) {
        // 保存生产报工主记录
        return addProductMain(productionProductMainDto);
    }
    @Override
    public Boolean removeProductMain(Long id) {
        // 删除生产报工主记录
        ProductionProductMain currentMain = productionProductMainMapper.selectById(id);
        if (currentMain == null) {
            return true;
        }
        return removeProductMainByProductionTask(currentMain);
    }
    private String resolveLatestStockInBatchNo(Long recordId,
@@ -502,64 +621,6 @@
        target.setProductionOrderRoutingOperationId(source.getProductionOrderRoutingOperationId());
        target.setProductionProductMainId(productionProductMainId);
        return target;
    }
    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) {