package com.ruoyi.quality.service.impl; import cn.hutool.core.lang.Assert; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.HackLoopTableRenderPolicy; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.web.domain.R; import com.ruoyi.procurementrecord.service.ProcurementRecordService; import com.ruoyi.procurementrecord.utils.StockUtils; import com.ruoyi.quality.dto.BatchQuickInspectRequest; import com.ruoyi.quality.dto.QualityInspectDto; import com.ruoyi.quality.mapper.QualityInspectMapper; import com.ruoyi.quality.mapper.QualityTestStandardMapper; import com.ruoyi.quality.mapper.QualityUnqualifiedMapper; 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.stock.pojo.StockInRecord; import com.ruoyi.stock.service.StockInRecordService; import com.ruoyi.quality.service.IQualityInspectParamService; import com.ruoyi.quality.service.IQualityInspectService; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.stock.dto.StockInventoryDto; import com.ruoyi.stock.service.StockInventoryService; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import java.net.URLEncoder; import java.sql.Date; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; @AllArgsConstructor @Service @Transactional(rollbackFor = Exception.class) public class QualityInspectServiceImpl extends ServiceImpl implements IQualityInspectService { private final StockUtils stockUtils; private final StockInventoryService stockInventoryService; private final StockInRecordService stockInRecordService; private QualityInspectMapper qualityInspectMapper; private IQualityInspectParamService qualityInspectParamService; private QualityTestStandardMapper qualityTestStandardMapper; private QualityUnqualifiedMapper qualityUnqualifiedMapper; private SalesLedgerProductMapper salesLedgerProductMapper; 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 public QualityInspectDto getDetailById(Integer id) { QualityInspect qualityInspect = qualityInspectMapper.selectById(id); List qualityInspectParams = qualityInspectParamService.list(Wrappers.lambdaQuery().eq(QualityInspectParam::getInspectId, id)); QualityInspectDto qualityInspectDto = new QualityInspectDto(); BeanUtils.copyProperties(qualityInspect, qualityInspectDto); qualityInspectDto.setQualityInspectParams(qualityInspectParams); return qualityInspectDto; } //提交 @Override public int submit(QualityInspect inspect) { QualityInspect qualityInspect = qualityInspectMapper.selectById(inspect.getId()); //提交前必须判断是否合格 if (ObjectUtils.isNull(qualityInspect.getCheckResult())) { throw new ServiceException("请先判断是否合格"); } if (ObjectUtils.isNull(qualityInspect.getQualifiedQuantity())) { throw new ServiceException("合格数量不能为空"); } if (ObjectUtils.isNull(qualityInspect.getUnqualifiedQuantity())) { throw new ServiceException("不合格数量不能为空"); } // 如果合格数量为空,设为0 if (qualityInspect.getQualifiedQuantity() == null) { qualityInspect.setQualifiedQuantity(BigDecimal.ZERO); } // 如果不合格数量为空,设为0 if (qualityInspect.getUnqualifiedQuantity() == null) { qualityInspect.setUnqualifiedQuantity(BigDecimal.ZERO); } // 合格直接入库 if(qualityInspect.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0){ //仅添加入库记录 StockInventoryDto stockInventoryDto = new StockInventoryDto(); //如果是采购质检合格入库选用CUSTOMIZATION_UNSTOCK_OUT,其余合格入库选用QUALITYINSPECT_STOCK_IN stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode())); if (ObjectUtils.isNotEmpty(qualityInspect.getPurchaseLedgerId())){ stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode())); } stockInventoryDto.setRecordId(qualityInspect.getId()); stockInventoryDto.setProductModelId(qualityInspect.getProductModelId()); // 入库数量 = 合格数量 * 入库比例 / 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)); } stockInventoryDto.setBatchNo(resolveProductionBatchNo( qualityInspect.getProductMainId(), qualityInspect.getId(), qualityInspect.getProductModelId())); stockInventoryService.addStockInRecordOnly(stockInventoryDto); } // 不合格处理 if(qualityInspect.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) > 0){ QualityUnqualified qualityUnqualified = new QualityUnqualified(); BeanUtils.copyProperties(qualityInspect, qualityUnqualified); qualityUnqualified.setInspectState(0);//待处理 qualityUnqualified.setQuantity(qualityInspect.getUnqualifiedQuantity()); qualityUnqualified.setProductModelId(qualityInspect.getProductModelId()); List inspectParams = qualityInspectParamService.list(Wrappers.lambdaQuery().eq(QualityInspectParam::getInspectId, inspect.getId())); String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(",")); qualityUnqualified.setDefectivePhenomena(text + "这些指标中存在不合格");//不合格现象 qualityUnqualified.setInspectId(qualityInspect.getId()); qualityUnqualifiedMapper.insert(qualityUnqualified); } qualityInspect.setInspectState(1);//已提交 return qualityInspectMapper.updateById(qualityInspect); } @Override public R autoSubmit(Long id) { if (id == null) { return R.fail("检验单ID不能为空"); } QualityInspect qualityInspect = qualityInspectMapper.selectById(id); if (qualityInspect == null) { return R.fail("检验单不存在"); } if (Integer.valueOf(1).equals(qualityInspect.getInspectState())) { return R.ok("检验单已提交"); } if (ObjectUtils.isNull(qualityInspect.getCheckResult())) { qualityInspect.setCheckResult("合格"); } if (ObjectUtils.isNull(qualityInspect.getQualifiedQuantity())) { qualityInspect.setQualifiedQuantity(qualityInspect.getQuantity() == null ? BigDecimal.ZERO : qualityInspect.getQuantity()); } if (ObjectUtils.isNull(qualityInspect.getUnqualifiedQuantity())) { qualityInspect.setUnqualifiedQuantity(BigDecimal.ZERO); } qualityInspectMapper.updateById(qualityInspect); int rows = submit(qualityInspect); return rows > 0 ? R.ok("检验单提交成功") : R.fail("检验单提交失败"); } @Override public R batchQuickInspect(BatchQuickInspectRequest request) { // 1. 数据校验 if (request.getIds() == null || request.getIds().isEmpty()) { return R.fail("请选择至少一条检验单"); } List validResults = Arrays.asList("合格", "不合格", "部分合格"); if (!validResults.contains(request.getCheckResult())) { return R.fail("检测结果必须为:合格、不合格、部分合格"); } if (request.getTestStandardId() == null) { return R.fail("指标标准ID不能为空"); } String checkResult = request.getCheckResult(); // 解析检测日期 Date checkTimeDate = null; if (request.getCheckTime() != null && !request.getCheckTime().isEmpty()) { checkTimeDate = Date.valueOf(LocalDate.parse(request.getCheckTime())); } int success = 0; List errors = new ArrayList<>(); for (Long id : request.getIds()) { try { processSingleInspect(id, request, checkResult, checkTimeDate); success++; } catch (Exception e) { errors.add("检验单 " + id + " 处理失败:" + e.getMessage()); } } if (!errors.isEmpty()) { return R.ok(String.format("快速检验完成:成功 %d 条,失败 %d 条。失败原因:%s", success, errors.size(), String.join(";", errors))); } return R.ok(String.format("快速检验完成:成功 %d 条", success)); } /** * 在独立事务中处理单个检验单 * 支持全检和抽检模式: * - 全检:数量、合格数量默认使用检验单自身的数量,不合格数量为0 * - 抽检:使用前端传入的 sampleQuantity/qualifiedQuantity/unqualifiedQuantity */ @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void processSingleInspect(Long id, BatchQuickInspectRequest request, String checkResult, Date checkTimeDate) { QualityInspect qualityInspect = qualityInspectMapper.selectById(id); if (qualityInspect == null) { throw new RuntimeException("检验单不存在"); } if (Integer.valueOf(1).equals(qualityInspect.getInspectState())) { 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(sampleQty); qualityInspect.setQualifiedQuantity(qualified); qualityInspect.setUnqualifiedQuantity(unqualified); // 更新抽检数量字段 if (!isFullInspect) { qualityInspect.setSampleQuantity(sampleQty); } if (request.getCheckCompany() != null) { qualityInspect.setCheckCompany(request.getCheckCompany()); } if (request.getCheckName() != null) { qualityInspect.setCheckName(request.getCheckName()); } if (checkTimeDate != null) { qualityInspect.setCheckTime(checkTimeDate); } qualityInspect.setInspectState(1); // 3. 保存检验参数 if (request.getParamList() != null && !request.getParamList().isEmpty()) { qualityInspectParamService.remove(Wrappers.lambdaQuery() .eq(QualityInspectParam::getInspectId, id)); for (QualityInspectParam param : request.getParamList()) { param.setInspectId(id); param.setId(null); } qualityInspectParamService.saveBatch(request.getParamList()); } // 4. 更新检验单 qualityInspectMapper.updateById(qualityInspect); // 5. 合格入库处理 if (qualified.compareTo(BigDecimal.ZERO) > 0) { StockInventoryDto stockInventoryDto = new StockInventoryDto(); stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode())); if (ObjectUtils.isNotEmpty(qualityInspect.getPurchaseLedgerId())) { stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode())); } stockInventoryDto.setRecordId(qualityInspect.getId()); stockInventoryDto.setProductModelId(qualityInspect.getProductModelId()); // 入库数量 = 合格数量 * 入库比例 / 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)); } stockInventoryDto.setBatchNo(resolveProductionBatchNo( qualityInspect.getProductMainId(), qualityInspect.getId(), qualityInspect.getProductModelId())); stockInventoryService.addStockInRecordOnly(stockInventoryDto); } // 6. 不合格处理 if (unqualified.compareTo(BigDecimal.ZERO) > 0) { QualityUnqualified qualityUnqualified = new QualityUnqualified(); BeanUtils.copyProperties(qualityInspect, qualityUnqualified); qualityUnqualified.setInspectState(0); qualityUnqualified.setQuantity(unqualified); List inspectParams = qualityInspectParamService.list( Wrappers.lambdaQuery().eq(QualityInspectParam::getInspectId, id)); String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(",")); qualityUnqualified.setDefectivePhenomena(text + "这些指标中存在不合格"); qualityUnqualified.setInspectId(id); qualityUnqualifiedMapper.insert(qualityUnqualified); } } private String resolveProductionBatchNo(Long productionProductMainId, Long qualityInspectId, Long productModelId) { if (productModelId == null) { return null; } if (productionProductMainId != null) { StockInRecord productionRecord = stockInRecordService.getOne( Wrappers.lambdaQuery() .eq(StockInRecord::getRecordId, productionProductMainId) .eq(StockInRecord::getProductModelId, productModelId) .in(StockInRecord::getRecordType, Arrays.asList( StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode())) .isNotNull(StockInRecord::getBatchNo) .orderByDesc(StockInRecord::getId) .last("limit 1"), false); if (productionRecord != null) { return productionRecord.getBatchNo(); } } if (qualityInspectId == null) { return null; } StockInRecord inspectRecord = stockInRecordService.getOne( Wrappers.lambdaQuery() .eq(StockInRecord::getRecordId, qualityInspectId) .eq(StockInRecord::getProductModelId, productModelId) .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode()) .isNotNull(StockInRecord::getBatchNo) .orderByDesc(StockInRecord::getId) .last("limit 1"), false); return inspectRecord == null ? null : inspectRecord.getBatchNo(); } /*生成检验报告*/ @Override public void down(HttpServletResponse response, QualityInspect qualityInspect) { QualityInspect inspect = qualityInspectMapper.selectById(qualityInspect.getId()); String inspectType = ""; switch (inspect.getInspectType()) { case 0: inspectType = "原材料检验"; break; case 1: inspectType = "过程检验"; break; case 2: inspectType = "出厂检验"; break; } List paramList = qualityInspectParamService.list(Wrappers.lambdaQuery().eq(QualityInspectParam::getInspectId, inspect.getId())); int index = 1; for (QualityInspectParam detail : paramList) { detail.setIndex(index); index++; } InputStream inputStream = this.getClass().getResourceAsStream("/static/report-template.docx"); Configure configure = Configure.builder() .bind("paramList", new HackLoopTableRenderPolicy()) .build(); String finalInspectType = inspectType; XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render( new HashMap() {{ put("inspect", inspect); put("inspectType", finalInspectType); put("paramList", paramList); }}); try { response.setContentType("application/msword"); String fileName = URLEncoder.encode( "检验报告", "UTF-8"); response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".docx"); OutputStream os = response.getOutputStream(); template.write(os); os.flush(); os.close(); inputStream.close(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("导出失败"); } } @Override public int updateQualityInspect(QualityInspectDto qualityInspectDto) { if (ObjectUtils.isNotNull(qualityInspectDto.getQualityInspectParams())) { qualityInspectParamService.remove(Wrappers.lambdaQuery().eq(QualityInspectParam::getInspectId, qualityInspectDto.getId())); for (QualityInspectParam qualityInspectParam : qualityInspectDto.getQualityInspectParams()) { qualityInspectParam.setInspectId(qualityInspectDto.getId()); } qualityInspectParamService.saveBatch(qualityInspectDto.getQualityInspectParams()); } QualityInspect qualityInspect = new QualityInspect(); BeanUtils.copyProperties(qualityInspectDto, qualityInspect); return qualityInspectMapper.updateById(qualityInspect); } @Override public IPage qualityInspectListPage(Page page, QualityInspectDto qualityInspect) { return qualityInspectMapper.qualityInspectListPage(page, qualityInspect); } @Override public void qualityInspectExport(HttpServletResponse response, QualityInspect qualityInspect) { List qualityInspects = qualityInspectMapper.qualityInspectExport(qualityInspect); ExcelUtil util = new ExcelUtil(QualityInspect.class); switch (qualityInspect.getInspectType()) { case 0: util.exportExcel(response, qualityInspects, "原材料检验导出"); break; case 1: util.exportExcel(response, qualityInspects, "过程检验导出"); break; case 2: util.exportExcel(response, qualityInspects, "出厂检验导出"); break; } } @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); } }