src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -17,6 +17,9 @@
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.quality.dto.AutoJudgeAllResponse;
import com.ruoyi.quality.dto.AutoJudgeRequest;
import com.ruoyi.quality.dto.AutoJudgeResponse;
import com.ruoyi.quality.dto.BatchQuickInspectRequest;
import com.ruoyi.quality.dto.QualityInspectDto;
import com.ruoyi.quality.mapper.QualityInspectMapper;
@@ -25,6 +28,8 @@
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.quality.utils.QualityInspectTemplateExportHelper;
import com.ruoyi.quality.utils.QualityJudgeUtil;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.quality.service.IQualityInspectParamService;
@@ -72,17 +77,46 @@
    private ProcurementRecordService procurementRecordService;
    private final QualityInspectTemplateExportHelper qualityInspectTemplateExportHelper;
    @Override
    public int add(QualityInspectDto qualityInspectDto) {
        QualityInspect qualityInspect = new QualityInspect();
        BeanUtils.copyProperties(qualityInspectDto, qualityInspect);
        qualityInspect.setInspectState(0);//默认未提交
        // 根据 inspectRule 补全抽检比例和抽检数量默认值
        applyInspectRuleDefaults(qualityInspect);
        qualityInspectMapper.insert(qualityInspect);
        for (QualityInspectParam qualityInspectParam : qualityInspectDto.getQualityInspectParams()) {
            qualityInspectParam.setInspectId(qualityInspect.getId());
        }
        qualityInspectParamService.saveBatch(qualityInspectDto.getQualityInspectParams());
        return 0;
    }
    /**
     * 根据 inspectRule 设置抽检比例和抽检数量默认值
     * - inspectRule=0 (全检): sampleRatio=100, sampleQuantity=全部数量
     * - inspectRule=1 (抽检): sampleRatio 取传入值或0, sampleQuantity=数量×比例/100
     */
    private void applyInspectRuleDefaults(QualityInspect inspect) {
        Integer rule = inspect.getInspectRule();
        java.math.BigDecimal quantity = inspect.getQuantity();
        if (rule == null || rule == 0) {
            // 全检
            inspect.setSampleRatio(java.math.BigDecimal.valueOf(100));
            inspect.setSampleQuantity(quantity != null ? quantity : java.math.BigDecimal.ZERO);
        } else {
            // 抽检
            java.math.BigDecimal ratio = inspect.getSampleRatio() != null ? inspect.getSampleRatio() : java.math.BigDecimal.ZERO;
            inspect.setSampleRatio(ratio);
            if (quantity != null && ratio.compareTo(java.math.BigDecimal.ZERO) > 0) {
                inspect.setSampleQuantity(quantity.multiply(ratio)
                        .divide(java.math.BigDecimal.valueOf(100), 0, java.math.RoundingMode.CEILING));
            } else {
                inspect.setSampleQuantity(java.math.BigDecimal.ZERO);
            }
        }
    }
    @Override
@@ -134,7 +168,15 @@
            }
            stockInventoryDto.setRecordId(qualityInspect.getId());
            stockInventoryDto.setProductModelId(qualityInspect.getProductModelId());
            stockInventoryDto.setQualitity(qualityInspect.getQualifiedQuantity());
            // 入库数量 = 合格数量 * 入库比例 / 100,入库比例默认100%
            BigDecimal stockInRatio = qualityInspect.getStockInRatio();
            if (stockInRatio == null || stockInRatio.compareTo(BigDecimal.ZERO) <= 0) {
                stockInRatio = new BigDecimal("100.00");
            }
            BigDecimal actualStockInQuantity = qualityInspect.getQualifiedQuantity()
                    .multiply(stockInRatio)
                    .divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP);
            stockInventoryDto.setQualitity(actualStockInQuantity);
            if (qualityInspect.getCheckTime() != null) {
                LocalDate stockCreateDate = DateUtils.toLocalDate(qualityInspect.getCheckTime()).plusDays(1);
                stockInventoryDto.setCreateTime(LocalDateTime.of(stockCreateDate, java.time.LocalTime.MIDNIGHT));
@@ -200,33 +242,11 @@
        if (!validResults.contains(request.getCheckResult())) {
            return R.fail("检测结果必须为:合格、不合格、部分合格");
        }
        if (request.getQuantity() == null || request.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
            return R.fail("总数量必须大于0");
        }
        if (request.getTestStandardId() == null) {
            return R.fail("指标标准ID不能为空");
        }
        // quantity = qualifiedQuantity + unqualifiedQuantity
        BigDecimal qty = request.getQuantity();
        BigDecimal qualified = request.getQualifiedQuantity() != null ? request.getQualifiedQuantity() : BigDecimal.ZERO;
        BigDecimal unqualified = request.getUnqualifiedQuantity() != null ? request.getUnqualifiedQuantity() : BigDecimal.ZERO;
        if (qty.compareTo(qualified.add(unqualified)) != 0) {
            return R.fail("总数量必须等于合格数量加不合格数量");
        }
        String checkResult = request.getCheckResult();
        if ("合格".equals(checkResult)) {
            if (qualified.compareTo(qty) != 0 || unqualified.compareTo(BigDecimal.ZERO) != 0) {
                return R.fail("检验结果为合格时,合格数量应等于总数量,不合格数量应为0");
            }
        } else if ("不合格".equals(checkResult)) {
            if (qualified.compareTo(BigDecimal.ZERO) != 0 || unqualified.compareTo(qty) != 0) {
                return R.fail("检验结果为不合格时,合格数量应为0,不合格数量应等于总数量");
            }
        } else {
            if (qualified.compareTo(BigDecimal.ZERO) <= 0 || unqualified.compareTo(BigDecimal.ZERO) <= 0) {
                return R.fail("检验结果为部分合格时,合格数量和不合格数量都必须大于0");
            }
        }
        // 解析检测日期
        Date checkTimeDate = null;
@@ -239,8 +259,7 @@
        for (Long id : request.getIds()) {
            try {
                // 使用独立事务处理每个检验单,避免单个失败影响整体事务
                processSingleInspect(id, request, checkResult, qty, qualified, unqualified, checkTimeDate);
                processSingleInspect(id, request, checkResult, checkTimeDate);
                success++;
            } catch (Exception e) {
                errors.add("检验单 " + id + " 处理失败:" + e.getMessage());
@@ -256,12 +275,13 @@
    /**
     * 在独立事务中处理单个检验单
     * 支持全检和抽检模式:
     * - 全检:数量、合格数量默认使用检验单自身的数量,不合格数量为0
     * - 抽检:使用前端传入的 sampleQuantity/qualifiedQuantity/unqualifiedQuantity
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void processSingleInspect(Long id, BatchQuickInspectRequest request,
                                     String checkResult, BigDecimal qty,
                                     BigDecimal qualified, BigDecimal unqualified,
                                     Date checkTimeDate) {
                                     String checkResult, Date checkTimeDate) {
        QualityInspect qualityInspect = qualityInspectMapper.selectById(id);
        if (qualityInspect == null) {
            throw new RuntimeException("检验单不存在");
@@ -270,12 +290,66 @@
            throw new RuntimeException("检验单已提交");
        }
        // 判断检验规则:全检(0/null) 或 抽检(1)
        Integer inspectRule = qualityInspect.getInspectRule();
        boolean isFullInspect = inspectRule == null || inspectRule == 0;
        BigDecimal totalQty = qualityInspect.getQuantity() != null ? qualityInspect.getQuantity() : BigDecimal.ZERO;
        BigDecimal sampleQty;
        BigDecimal qualified;
        BigDecimal unqualified;
        if (isFullInspect) {
            // 全检模式:检验数量 = 总数量
            sampleQty = totalQty;
            // 如果前端传入了合格/不合格数量则使用,否则默认全部合格
            if (request.getQualifiedQuantity() != null && request.getUnqualifiedQuantity() != null) {
                qualified = request.getQualifiedQuantity();
                unqualified = request.getUnqualifiedQuantity();
            } else {
                qualified = totalQty;
                unqualified = BigDecimal.ZERO;
            }
        } else {
            // 抽检模式
            // 抽检数量:优先使用前端传入值,其次使用检验单已有的 sampleQuantity,最后按比例计算
            if (request.getSampleQuantity() != null && request.getSampleQuantity().compareTo(BigDecimal.ZERO) > 0) {
                sampleQty = request.getSampleQuantity();
            } else if (qualityInspect.getSampleQuantity() != null && qualityInspect.getSampleQuantity().compareTo(BigDecimal.ZERO) > 0) {
                sampleQty = qualityInspect.getSampleQuantity();
            } else {
                // 按抽检比例计算
                BigDecimal ratio = qualityInspect.getSampleRatio() != null ? qualityInspect.getSampleRatio() : BigDecimal.ZERO;
                sampleQty = totalQty.multiply(ratio)
                        .divide(BigDecimal.valueOf(100), 0, BigDecimal.ROUND_CEILING);
            }
            // 校验抽检数量不能超过总数量
            if (sampleQty.compareTo(totalQty) > 0) {
                sampleQty = totalQty;
            }
            // 合格/不合格数量:优先使用前端传入值
            if (request.getQualifiedQuantity() != null || request.getUnqualifiedQuantity() != null) {
                qualified = request.getQualifiedQuantity() != null ? request.getQualifiedQuantity() : BigDecimal.ZERO;
                unqualified = request.getUnqualifiedQuantity() != null ? request.getUnqualifiedQuantity() : BigDecimal.ZERO;
            } else {
                // 默认抽检样本全部合格
                qualified = sampleQty;
                unqualified = BigDecimal.ZERO;
            }
        }
        // 2. 更新检验单字段
        qualityInspect.setCheckResult(checkResult);
        qualityInspect.setTestStandardId(request.getTestStandardId());
        qualityInspect.setQuantity(qty);
        // 记录实际检验数量(抽检时为样本数量,全检时为总数量)
        qualityInspect.setQuantity(sampleQty);
        qualityInspect.setQualifiedQuantity(qualified);
        qualityInspect.setUnqualifiedQuantity(unqualified);
        // 更新抽检数量字段
        if (!isFullInspect) {
            qualityInspect.setSampleQuantity(sampleQty);
        }
        if (request.getCheckCompany() != null) {
            qualityInspect.setCheckCompany(request.getCheckCompany());
        }
@@ -310,7 +384,15 @@
            }
            stockInventoryDto.setRecordId(qualityInspect.getId());
            stockInventoryDto.setProductModelId(qualityInspect.getProductModelId());
            stockInventoryDto.setQualitity(qualified);
            // 入库数量 = 合格数量 * 入库比例 / 100,入库比例默认100%
            BigDecimal stockInRatio = qualityInspect.getStockInRatio();
            if (stockInRatio == null || stockInRatio.compareTo(BigDecimal.ZERO) <= 0) {
                stockInRatio = new BigDecimal("100.00");
            }
            BigDecimal actualStockInQuantity = qualified
                    .multiply(stockInRatio)
                    .divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP);
            stockInventoryDto.setQualitity(actualStockInQuantity);
            if (qualityInspect.getCheckTime() != null) {
                LocalDate stockCreateDate = DateUtils.toLocalDate(qualityInspect.getCheckTime()).plusDays(1);
                stockInventoryDto.setCreateTime(LocalDateTime.of(stockCreateDate, java.time.LocalTime.MIDNIGHT));
@@ -463,5 +545,111 @@
    }
    @Override
    public void exportWeiLong(HttpServletResponse response, Long id) {
        qualityInspectTemplateExportHelper.exportWeiLong(response, id);
    }
    @Override
    public void exportBaiShi(HttpServletResponse response, Long id) {
        qualityInspectTemplateExportHelper.exportBaiShi(response, id);
    }
    @Override
    public void exportDaLi(HttpServletResponse response, Long id) {
        qualityInspectTemplateExportHelper.exportDaLi(response, id);
    }
    @Override
    public String analyzeTemplate(String templatePath) {
        return qualityInspectTemplateExportHelper.analyzeTemplate(templatePath);
    }
    @Override
    public AutoJudgeResponse autoJudge(AutoJudgeRequest request) {
        QualityJudgeUtil.JudgeResult result = QualityJudgeUtil.judge(
                request.getTestValue(),
                request.getStandardValue(),
                request.getMinValue(),
                request.getMaxValue(),
                request.getJudgeType()
        );
        return new AutoJudgeResponse(result.getQualified(), result.getMessage());
    }
    @Override
    public AutoJudgeAllResponse autoJudgeAll(Long inspectId) {
        List<QualityInspectParam> params = qualityInspectParamService.list(
                Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, inspectId));
        AutoJudgeAllResponse response = new AutoJudgeAllResponse();
        List<AutoJudgeAllResponse.ParamJudgeResult> paramResults = new ArrayList<>();
        boolean hasRequiredUnqualified = false;
        boolean allTextDescription = true;
        boolean hasRequiredParam = false;
        for (QualityInspectParam param : params) {
            AutoJudgeAllResponse.ParamJudgeResult paramResult = new AutoJudgeAllResponse.ParamJudgeResult();
            paramResult.setParamId(param.getId());
            paramResult.setParameterItem(param.getParameterItem());
            paramResult.setIsRequired(param.getIsRequired() != null && param.getIsRequired() == 1);
            if (paramResult.getIsRequired()) {
                hasRequiredParam = true;
            }
            String judgeType = param.getJudgeType();
            if (QualityJudgeUtil.JUDGE_TYPE_TEXT_DESCRIPTION.equals(judgeType)) {
                paramResult.setIsQualified(null);
                paramResult.setMessage("文字描述类型需手动判断");
            } else {
                allTextDescription = false;
                QualityJudgeUtil.JudgeResult result = QualityJudgeUtil.judge(
                        param.getTestValue(),
                        param.getStandardValue(),
                        param.getMinValue(),
                        param.getMaxValue(),
                        judgeType
                );
                paramResult.setIsQualified(result.getQualified());
                paramResult.setMessage(result.getMessage());
                // 更新参数项的判断结果
                if (result.getQualified() != null) {
                    param.setIsQualified(result.getQualified() ? 1 : 0);
                    qualityInspectParamService.updateById(param);
                }
                // 检查必要判断参数是否不合格
                if (paramResult.getIsRequired() && Boolean.FALSE.equals(result.getQualified())) {
                    hasRequiredUnqualified = true;
                }
            }
            paramResults.add(paramResult);
        }
        response.setParamResults(paramResults);
        response.setHasRequiredUnqualified(hasRequiredUnqualified);
        response.setAllTextDescription(allTextDescription);
        // 确定整体判断结果
        if (allTextDescription) {
            response.setAutoJudgeResult(null);
        } else if (hasRequiredUnqualified) {
            response.setAutoJudgeResult("不合格");
        } else {
            // 所有需要自动判断的参数项都合格,整体为合格
            response.setAutoJudgeResult("合格");
        }
        // 更新质检主表的自动判断结果
        QualityInspect inspect = qualityInspectMapper.selectById(inspectId);
        if (inspect != null) {
            inspect.setAutoJudgeResult(response.getAutoJudgeResult());
            qualityInspectMapper.updateById(inspect);
        }
        return response;
    }
}