2 天以前 f350465db70d6f9db9494483ee0cec7793a9007b
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -90,6 +90,8 @@
    private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
    private ProductionReportPersonMapper productionReportPersonMapper;
    private StockUtils stockUtils;
@@ -176,6 +178,26 @@
        productionProductMain.setAuditUserId(SecurityUtils.getUserId());
        productionProductMainMapper.insert(productionProductMain);
        /*保存报工班组人员产量时长明细*/
        List<ProductionProductMainDto.Person> personList = dto.getPersonList();
        BigDecimal personSum = BigDecimal.ZERO;
        if (CollUtil.isNotEmpty(personList)) {
            for (ProductionProductMainDto.Person person : personList) {
                BigDecimal personQty = person.getQuantity() == null ? BigDecimal.ZERO : person.getQuantity();
                personSum = personSum.add(personQty);
                ProductionReportPerson reportPerson = new ProductionReportPerson();
                reportPerson.setProductMainId(productionProductMain.getId());
                reportPerson.setUserId(person.getUserId());
                reportPerson.setUserName(person.getUserName());
                reportPerson.setQuantity(personQty);
                reportPerson.setWorkHour(person.getWorkHour() != null ? person.getWorkHour() : 0);
                productionReportPersonMapper.insert(reportPerson);
            }
        }
        BigDecimal totalQuantity = dto.getQuantity() == null ? BigDecimal.ZERO : dto.getQuantity();
        if (CollUtil.isNotEmpty(personList) && personSum.compareTo(totalQuantity) != 0) {
            throw new ServiceException("各班组产量之和必须等于报工总产量");
        }
        /*新增报工投入表*/
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(Optional.ofNullable(productProcessRoute.getBomId()).orElse(0), productProcess.getId());
        if (productStructureDtos.isEmpty()) {
@@ -202,9 +224,10 @@
        productionProductOutput.setReplenishQty(dto.getReplenishQty());
        productionProductOutput.setAddQty(dto.getAddQty() != null ? dto.getAddQty() : BigDecimal.ZERO);
        productionProductOutputMapper.insert(productionProductOutput);
        //合格数量=报工数量-报废数量
        BigDecimal productQty = productionProductOutput.getQuantity().subtract(productionProductOutput.getScrapQty());
        //只有合格数量>0才能增加相应数据
        //工序流转数量=产出数量+加放数(报废仅做数据记录,不扣减工序流转基数,各工序总产量保持连贯)
        BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
        BigDecimal productQty = productionProductOutput.getQuantity().add(addQty);
        //只有工序流转数量>0才能增加相应数据
        if (productQty.compareTo(BigDecimal.ZERO) > 0) {
            List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
            /*更新工单和生产订单*/
@@ -219,10 +242,18 @@
            if (!machineRecords.isEmpty()) {
                productWorkOrder.setActualStartTime(machineRecords.get(0).getMachineStartTime());
            }
            // 实际结束时间 = 报工结束时间
            productWorkOrder.setActualEndTime(now);//实际结束时间为报工结束时间
            // 仅完成数量达到计划数量时才填充实际结束时间,未完成则清空已填的结束时间(允许继续分批报工)
            if (productWorkOrder.getCompleteQuantity().compareTo(productWorkOrder.getPlanQuantity()) >= 0) {
                productWorkOrder.setActualEndTime(now);
            productWorkOrder.setEndProductTime(now);
            productWorkOrderMapper.updateById(productWorkOrder);
            } else {
                // updateById 默认忽略 null 字段,需用 UpdateWrapper 显式清空(自愈历史脏数据)
                productWorkOrderMapper.update(null, Wrappers.<ProductWorkOrder>lambdaUpdate()
                        .eq(ProductWorkOrder::getId, productWorkOrder.getId())
                        .set(ProductWorkOrder::getActualEndTime, null)
                        .set(ProductWorkOrder::getEndProductTime, null));
            }
            //生产订单
            ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
            if (ObjectUtils.isNull(productOrder.getStartTime())) {
@@ -281,10 +312,13 @@
                // 减少 更新工单和生产订单
                ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
                ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
                //工序流转数量=产出数量+加放数(与报工累加口径一致,报废不扣减流转基数)
                BigDecimal processQty = productionProductOutput.getQuantity().add(
                        productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty());
                productWorkOrder.setCompleteQuantity(
                        productWorkOrder.getCompleteQuantity()
                                .subtract(productionProductOutput.getQuantity())
                                .subtract(processQty)
                                .max(BigDecimal.ZERO)
                );
                List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
@@ -294,16 +328,16 @@
                    //如果是最后一道工序报工之后生产订单 需要扣除
                    productOrder.setCompleteQuantity(
                            productOrder.getCompleteQuantity()
                                    .subtract(productionProductOutput.getQuantity())
                                    .subtract(processQty)
                                    .max(BigDecimal.ZERO)
                    );
                    if (productOrder.getCompleteQuantity().compareTo(productionProductOutput.getQuantity()) > 0) {
                    if (productOrder.getCompleteQuantity().compareTo(processQty) > 0) {
                        productOrder.setEndTime(null);
                    }
                    productOrderMapper.updateById(productOrder);
                }
                if (productWorkOrder.getCompleteQuantity().compareTo(productionProductOutput.getQuantity()) > 0) {
                if (productWorkOrder.getCompleteQuantity().compareTo(processQty) > 0) {
                    productWorkOrder.setActualEndTime(null);
                }
@@ -326,9 +360,10 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void nextAddProductMain(@NotNull ProductionProductOutput productionProductOutput) {
        //合格数量=报工数量-报废数量
        //实际入库数量=产出数量+加放数-报废数量
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(productionProductOutput.getProductMainId());
        BigDecimal productQty = productionProductOutput.getQuantity().subtract(productionProductOutput.getScrapQty());
        BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
        BigDecimal productQty = productionProductOutput.getQuantity().add(addQty).subtract(productionProductOutput.getScrapQty());
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
        ProductProcess productProcess = productProcessMapper.selectById(productProcessRouteItem.getProcessId());
        //工艺路线中当前工序对应的产出规格型号
@@ -348,32 +383,24 @@
                    inspectType = 2;
                    process = null;
                }
                Product product = productMapper.selectById(productModel.getProductId());
                QualityInspect qualityInspect = new QualityInspect();
                qualityInspect.setProductId(product.getId());
                qualityInspect.setProductName(product.getProductName());
                qualityInspect.setModel(productModel.getModel());
                qualityInspect.setUnit(productModel.getUnit());
                qualityInspect.setQuantity(productQty);
                qualityInspect.setProcess(process);
                qualityInspect.setInspectState(0);
                qualityInspect.setInspectType(inspectType);
                qualityInspect.setProductMainId(productionProductMain.getId());
                qualityInspect.setProductModelId(productModel.getId());
                qualityInspectMapper.insert(qualityInspect);
                List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
                if (!qualityTestStandard.isEmpty()) {
                    qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
                    qualityInspectMapper.updateById(qualityInspect);
                    qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                                    .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))//默认获取最新的
                            .forEach(qualityTestStandardParam -> {
                                QualityInspectParam param = new QualityInspectParam();
                                BeanUtils.copyProperties(qualityTestStandardParam, param);
                                param.setId(null);
                                param.setInspectId(qualityInspect.getId());
                                qualityInspectParamMapper.insert(param);
                            });
                // 排产下达时已为该工单预生成质检单(product_main_id IS NULL),报工审核时认领并更新为实际数量
                QualityInspect preInspect = qualityInspectMapper.selectOne(
                        Wrappers.<QualityInspect>lambdaQuery()
                                .eq(QualityInspect::getWorkOrderId, productionProductMain.getWorkOrderId())
                                .isNull(QualityInspect::getProductMainId)
                                .last("limit 1"));
                if (preInspect != null) {
                    preInspect.setProductMainId(productionProductMain.getId());
                    preInspect.setQuantity(productQty);
                    if (preInspect.getInspectState() != null && preInspect.getInspectState() == 1
                            && preInspect.getInspectType() != null && preInspect.getInspectType() == 2) {
                        // 已提交且为出厂检:删除按计划数量入库的记录,按实际数量重新入库
                        stockUtils.deleteStockInRecord(preInspect.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
                        stockUtils.addStock(productModel.getId(), productQty, StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(), preInspect.getId(), "-", "-", "-");
                    }
                    qualityInspectMapper.updateById(preInspect);
                } else {
                    insertQualityInspect(productProcessRouteItem, inspectType, process, productionProductMain.getId(), productionProductMain.getWorkOrderId(), productQty);
                }
            } else {
                //直接入库
@@ -386,7 +413,49 @@
            /*添加生产核算        区分工序是计件还是计时*/
            BigDecimal workHours = productProcess.getSalaryQuota();
            List<ProductionMachineRecord> productionMachineRecords = productionMachineRecordMapper.selectList(Wrappers.<ProductionMachineRecord>lambdaQuery().eq(ProductionMachineRecord::getWorkOrderId, productionProductMain.getWorkOrderId()));
            if (ObjectUtils.isNotEmpty(productionMachineRecords)) {
            // 报工班组人员产量时长明细(有明细则按人生成核算,无明细则回退机台操作员逻辑)
            List<ProductionReportPerson> personList = productionReportPersonMapper.selectList(Wrappers.<ProductionReportPerson>lambdaQuery().eq(ProductionReportPerson::getProductMainId, productionProductMain.getId()));
            // 设备取报工单设备,未填写时回退取该工单任一台机
            Long reportDeviceId = productionProductMain.getDeviceId() != null
                    ? productionProductMain.getDeviceId()
                    : productionMachineRecords.stream().map(ProductionMachineRecord::getMachineId).filter(Objects::nonNull).findFirst().orElse(null);
            if (CollUtil.isNotEmpty(personList)) {
                // 每次报工独立按人生成核算(支持分批报工直至完成),不受上机记录报工状态影响
                BigDecimal netQty = productQty;
                BigDecimal rawQuantity = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
                for (ProductionReportPerson person : personList) {
                    BigDecimal personQty = person.getQuantity() == null ? BigDecimal.ZERO : person.getQuantity();
                    BigDecimal finishedNum;
                    if (netQty.compareTo(rawQuantity) == 0 || rawQuantity.compareTo(BigDecimal.ZERO) == 0) {
                        // 无报废(或毛产量为0)时净值即毛产量,直接取个人产量
                        finishedNum = personQty;
                    } else {
                        // 有报废时按各人毛产量占比分摊净值,四舍五入保留2位
                        finishedNum = personQty.multiply(netQty).divide(rawQuantity, 2, BigDecimal.ROUND_HALF_UP);
                    }
                    SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder()
                            .productMainId(productionProductMain.getId())
                            .schedulingUserId(person.getUserId())
                            .schedulingUserName(person.getUserName())
                            .finishedNum(finishedNum)
                            .workHours(workHours)
                            .process(productProcess.getName())
                            .schedulingDate(LocalDate.now())
                            .tenantId(productionProductOutput.getTenantId())
                            .deviceId(reportDeviceId)
                            .workHour(person.getWorkHour() == null ? 0L : person.getWorkHour().longValue())
                            .build();
                    salesLedgerProductionAccountingMapper.insert(salesLedgerProductionAccounting);
                }
                // 该工单上机记录本次已报工
                for (ProductionMachineRecord productionMachineRecord : productionMachineRecords) {
                    if (!productionMachineRecord.getReportStatus()) {
                        productionMachineRecord.setReportStatus(true);
                        productionMachineRecordMapper.updateById(productionMachineRecord);
                    }
                }
            } else if (ObjectUtils.isNotEmpty(productionMachineRecords)) {
                // 兼容旧数据:未传人员明细则回退按未报工的机台上机操作员生成核算
                for (ProductionMachineRecord productionMachineRecord : productionMachineRecords) {
                    //说明已经添加过了,不添加了
                    if (productionMachineRecord.getReportStatus()) {
@@ -418,6 +487,115 @@
        }
    }
    /**
     * 生成质检单并回填检测标准/检验参数
     */
    private void insertQualityInspect(ProductProcessRouteItem routeItem, Integer inspectType, String process,
                                      Long productMainId, Long workOrderId, BigDecimal quantity) {
        ProductModel productModel = productModelMapper.selectById(routeItem.getProductModelId());
        Product product = productMapper.selectById(productModel.getProductId());
        QualityInspect qualityInspect = new QualityInspect();
        qualityInspect.setProductId(product.getId());
        qualityInspect.setProductName(product.getProductName());
        qualityInspect.setModel(productModel.getModel());
        qualityInspect.setUnit(productModel.getUnit());
        qualityInspect.setQuantity(quantity);
        qualityInspect.setProcess(process);
        qualityInspect.setInspectState(0);
        qualityInspect.setInspectType(inspectType);
        qualityInspect.setProductMainId(productMainId);
        qualityInspect.setWorkOrderId(workOrderId);
        qualityInspect.setProductModelId(productModel.getId());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
        if (!qualityTestStandard.isEmpty()) {
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))//默认获取最新的
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        BeanUtils.copyProperties(qualityTestStandardParam, param);
                        param.setId(null);
                        param.setInspectId(qualityInspect.getId());
                        qualityInspectParamMapper.insert(param);
                    });
        }
    }
    @Override
    public void preGenerateQualityInspectForOrder(Long productOrderId) {
        if (productOrderId == null) {
            return;
        }
        ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectOne(
                Wrappers.<ProductProcessRoute>lambdaQuery()
                        .eq(ProductProcessRoute::getProductOrderId, productOrderId));
        if (productProcessRoute == null) {
            return;
        }
        List<ProductProcessRouteItem> items = productProcessRouteItemMapper.selectList(
                Wrappers.<ProductProcessRouteItem>lambdaQuery()
                        .eq(ProductProcessRouteItem::getProductRouteId, productProcessRoute.getId())
                        .orderByAsc(ProductProcessRouteItem::getDragSort)
                        .orderByAsc(ProductProcessRouteItem::getId));
        if (items.isEmpty()) {
            return;
        }
        // 第一道工序需质检才预生成
        ProductProcessRouteItem first = items.get(0);
        if (!Boolean.TRUE.equals(first.getIsQuality())) {
            return;
        }
        ProductWorkOrder workOrder = productWorkOrderMapper.selectOne(
                Wrappers.<ProductWorkOrder>lambdaQuery()
                        .eq(ProductWorkOrder::getProductProcessRouteItemId, first.getId()));
        if (workOrder == null) {
            return;
        }
        // 该工单已存在预生成质检单则不重复生成
        Long existCount = qualityInspectMapper.selectCount(
                Wrappers.<QualityInspect>lambdaQuery()
                        .eq(QualityInspect::getWorkOrderId, workOrder.getId())
                        .isNull(QualityInspect::getProductMainId));
        if (existCount != null && existCount > 0) {
            return;
        }
        boolean onlyOne = items.size() == 1;
        int inspectType = onlyOne ? 2 : 1;
        String process = null;
        if (!onlyOne) {
            ProductProcess processEntity = productProcessMapper.selectById(first.getProcessId());
            if (processEntity != null) {
                process = processEntity.getName();
            }
        }
        insertQualityInspect(first, inspectType, process, null, workOrder.getId(), workOrder.getPlanQuantity());
    }
    @Override
    public void cleanPreGeneratedQualityInspects(List<Long> workOrderIds) {
        if (CollUtil.isEmpty(workOrderIds)) {
            return;
        }
        List<QualityInspect> preInspects = qualityInspectMapper.selectList(
                Wrappers.<QualityInspect>lambdaQuery()
                        .in(QualityInspect::getWorkOrderId, workOrderIds)
                        .isNull(QualityInspect::getProductMainId));
        if (CollUtil.isEmpty(preInspects)) {
            return;
        }
        for (QualityInspect pre : preInspects) {
            if (pre.getInspectState() != null && pre.getInspectState() == 1) {
                throw new ServiceException("已提交的检验单不能删除");
            }
        }
        List<Long> inspectIds = preInspects.stream().map(QualityInspect::getId).collect(Collectors.toList());
        qualityInspectParamMapper.delete(
                Wrappers.<QualityInspectParam>lambdaQuery().in(QualityInspectParam::getInspectId, inspectIds));
        qualityInspectMapper.deleteBatchIds(inspectIds);
    }
    @Override
    public Boolean removeProductMain(Long id) {
        //判断该条报工是否不合格处理,如果不合格处理了,则不允许删除
@@ -438,17 +616,22 @@
                new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
                        .eq(SalesLedgerProductionAccounting::getProductMainId, productionProductMain.getId())
        );
        /*删除报工班组人员产量时长明细*/
        productionReportPersonMapper.delete(
                new LambdaQueryWrapper<ProductionReportPerson>()
                        .eq(ProductionReportPerson::getProductMainId, productionProductMain.getId())
        );
        /*更新工单和生产订单*/
        ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
        if (productWorkOrder != null && productionProductOutput != null) {
            BigDecimal outputQty = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
            BigDecimal scrapQty = productionProductOutput.getScrapQty() == null ? BigDecimal.ZERO : productionProductOutput.getScrapQty();
            BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
            BigDecimal completeQty = productWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productWorkOrder.getCompleteQuantity();
            // 必须为审核通过的才会减少数量
            BigDecimal validQuantity = outputQty.subtract(scrapQty);
            BigDecimal validQuantity = outputQty.add(addQty);
            if (productionProductMain.getAuditStatus() != 2) {
                productWorkOrder.setCompleteQuantity(completeQty.subtract(validQuantity).max(BigDecimal.ZERO));
            }
@@ -464,8 +647,8 @@
            if (productOrder != null) {
                BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
                BigDecimal totalQty = productionProductOutput.getQuantity() != null ? productionProductOutput.getQuantity() : BigDecimal.ZERO;
                BigDecimal scrapQty = productionProductOutput.getScrapQty() != null ? productionProductOutput.getScrapQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.subtract(scrapQty);
                BigDecimal addQty = productionProductOutput.getAddQty() != null ? productionProductOutput.getAddQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.add(addQty);
                BigDecimal newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty);
                productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
                productOrder.setEndTime(null);