| | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcImportExcelVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcImportRespVO; |
| | | import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.qc.imports.MesQcImportHelper; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @Resource |
| | | private MesQcImportHelper qcImportHelper; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | ipqcMapper.updateById(updateIpqc); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcImportRespVO importIpqcList(List<MesQcIpqcImportExcelVO> importList) { |
| | | MesQcImportRespVO respVO = MesQcImportRespVO.builder() |
| | | .createList(new ArrayList<>()).failureList(new LinkedHashMap<>()).build(); |
| | | if (CollUtil.isEmpty(importList)) { |
| | | return respVO; |
| | | } |
| | | qcImportHelper.startImport(); |
| | | Map<String, Integer> firstRowNoByKey = new HashMap<>(); |
| | | for (int i = 0; i < importList.size(); i++) { |
| | | MesQcIpqcImportExcelVO row = importList.get(i); |
| | | if (isImportRowEmpty(row)) { |
| | | continue; // 跳过空行 |
| | | } |
| | | String label = StrUtil.isNotBlank(row.getCode()) ? "检验单编号 " + row.getCode().trim() |
| | | : "第 " + (i + 2) + " 行"; |
| | | String importKey = buildImportKey(row); |
| | | try { |
| | | Integer firstRowNo = firstRowNoByKey.get(importKey); |
| | | if (firstRowNo != null) { |
| | | throw MesQcImportHelper.duplicateRowException(firstRowNo, buildImportKeyDesc(row)); |
| | | } |
| | | respVO.getCreateList().add(createIpqcByImport(row)); |
| | | firstRowNoByKey.putIfAbsent(importKey, i + 2); |
| | | } catch (Exception ex) { |
| | | respVO.getFailureList().put(label, MesQcImportHelper.resolveImportErrorMessage(ex)); |
| | | } |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | private boolean isImportRowEmpty(MesQcIpqcImportExcelVO row) { |
| | | return row == null || (StrUtil.isBlank(row.getCode()) && StrUtil.isBlank(row.getName()) |
| | | && StrUtil.isBlank(row.getType()) && StrUtil.isBlank(row.getWorkOrderCode()) |
| | | && StrUtil.isBlank(row.getWorkstationName()) && StrUtil.isBlank(row.getTemplateName()) |
| | | && row.getCheckQuantity() == null && row.getQualifiedQuantity() == null |
| | | && row.getUnqualifiedQuantity() == null && row.getLaborScrapQuantity() == null |
| | | && row.getMaterialScrapQuantity() == null && row.getOtherScrapQuantity() == null |
| | | && StrUtil.isBlank(row.getCheckResult()) && StrUtil.isBlank(row.getInspectorName()) |
| | | && StrUtil.isBlank(row.getInspectDate()) && StrUtil.isBlank(row.getRemark())); |
| | | } |
| | | |
| | | /** |
| | | * 依据导入行创建一张过程检验单:物料由工单推导,状态为「待检验」,检验行由质检方案自动生成 |
| | | * |
| | | * @return 检验单编号 |
| | | */ |
| | | private String createIpqcByImport(MesQcIpqcImportExcelVO row) { |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | // 1. 检验单编号:填写则使用,留空自动生成 |
| | | String code = StrUtil.isNotBlank(row.getCode()) ? row.getCode().trim() |
| | | : qcImportHelper.generateCode(MesMdAutoCodeRuleCodeEnum.QC_IPQC_CODE.getCode()); |
| | | if (ipqcMapper.selectByCode(code) != null) { |
| | | throw new IllegalArgumentException("检验单编号已存在:" + code |
| | | + "。请修改模板中的「检验单编号」,或先在系统中删除该编号的检验单后重新导入"); |
| | | } |
| | | reqVO.setCode(code); |
| | | // 2. 名称、检验类型、工单、工位、质检方案 |
| | | if (StrUtil.isBlank(row.getName())) { |
| | | throw new IllegalArgumentException("检验单名称不能为空:请填写模板中该行的「检验单名称」列"); |
| | | } |
| | | reqVO.setName(row.getName().trim()); |
| | | reqVO.setType(qcImportHelper.resolveDictValue(row.getType(), |
| | | DictTypeConstants.MES_IPQC_TYPE, "检验类型", true)); |
| | | reqVO.setWorkOrderId(qcImportHelper.resolveWorkOrderId(row.getWorkOrderCode())); |
| | | reqVO.setWorkstationId(qcImportHelper.resolveWorkstationId(row.getWorkstationName())); |
| | | reqVO.setTemplateId(qcImportHelper.resolveTemplateId(row.getTemplateName())); |
| | | // 3. 数量:合格品 + 不合格品 必须等于检测数量(检测数量留空则自动合计);工废/料废/其他废留空按 0 |
| | | BigDecimal qualified = qcImportHelper.resolveQuantity(row.getQualifiedQuantity(), "合格品数量", true); |
| | | BigDecimal unqualified = qcImportHelper.resolveQuantity(row.getUnqualifiedQuantity(), "不合格品数量", true); |
| | | reqVO.setQualifiedQuantity(qualified); |
| | | reqVO.setUnqualifiedQuantity(unqualified); |
| | | reqVO.setCheckQuantity(qcImportHelper.resolveCheckQuantity( |
| | | row.getCheckQuantity(), qualified, unqualified, "检测数量")); |
| | | BigDecimal laborScrap = qcImportHelper.resolveQuantity(row.getLaborScrapQuantity(), "工废数量", false); |
| | | BigDecimal materialScrap = qcImportHelper.resolveQuantity(row.getMaterialScrapQuantity(), "料废数量", false); |
| | | BigDecimal otherScrap = qcImportHelper.resolveQuantity(row.getOtherScrapQuantity(), "其他废品数量", false); |
| | | laborScrap = laborScrap != null ? laborScrap : BigDecimal.ZERO; |
| | | materialScrap = materialScrap != null ? materialScrap : BigDecimal.ZERO; |
| | | otherScrap = otherScrap != null ? otherScrap : BigDecimal.ZERO; |
| | | validateScrapQuantity(unqualified, laborScrap, materialScrap, otherScrap); |
| | | reqVO.setLaborScrapQuantity(laborScrap); |
| | | reqVO.setMaterialScrapQuantity(materialScrap); |
| | | reqVO.setOtherScrapQuantity(otherScrap); |
| | | // 4. 检验信息 |
| | | reqVO.setCheckResult(qcImportHelper.resolveDictValue(row.getCheckResult(), |
| | | DictTypeConstants.MES_QC_CHECK_RESULT, "检测结果", false)); |
| | | reqVO.setInspectorUserId(qcImportHelper.resolveInspectorUserId(row.getInspectorName())); |
| | | reqVO.setInspectDate(qcImportHelper.parseDateTime(row.getInspectDate(), "检测日期")); |
| | | reqVO.setRemark(StrUtil.trimToNull(row.getRemark())); |
| | | // 5. 去重:同一 生产工单 + 检验类型 + 工位 + 检测日期 的过程检验单不可重复导入 |
| | | MesQcIpqcDO duplicate = CollUtil.getFirst(ipqcMapper.selectListByImportKey(reqVO.getWorkOrderId(), |
| | | reqVO.getType(), reqVO.getWorkstationId(), reqVO.getInspectDate())); |
| | | if (duplicate != null) { |
| | | throw MesQcImportHelper.duplicateExistingException(buildImportKeyDesc(row), "过程检验单", |
| | | duplicate.getCode(), "质量管理-过程检验"); |
| | | } |
| | | // 6. 复用创建逻辑:校验工单/工位/人员、按物料匹配质检方案、生成检验行 |
| | | try { |
| | | createIpqc(reqVO); |
| | | } catch (ServiceException ex) { |
| | | MesQcImportHelper.rethrowTemplateError(ex, row.getWorkOrderCode(), "过程检验(IPQC)"); |
| | | } |
| | | return code; |
| | | } |
| | | |
| | | /** |
| | | * 校验 工废 + 料废 + 其他废品 不超过不合格品数量 |
| | | */ |
| | | private void validateScrapQuantity(BigDecimal unqualified, BigDecimal laborScrap, |
| | | BigDecimal materialScrap, BigDecimal otherScrap) { |
| | | BigDecimal scrapSum = laborScrap.add(materialScrap).add(otherScrap); |
| | | if (scrapSum.compareTo(unqualified) <= 0) { |
| | | return; |
| | | } |
| | | throw new IllegalArgumentException("工废、料废、其他废品数量之和不能超过不合格品数量:不合格品数量为 " |
| | | + unqualified.stripTrailingZeros().toPlainString() + ",而 工废 " + laborScrap.stripTrailingZeros().toPlainString() |
| | | + " + 料废 " + materialScrap.stripTrailingZeros().toPlainString() + " + 其他废品 " |
| | | + otherScrap.stripTrailingZeros().toPlainString() + " = " + scrapSum.stripTrailingZeros().toPlainString() |
| | | + "。请修改模板中的废品数量或不合格品数量后重新导入"); |
| | | } |
| | | |
| | | /** |
| | | * 导入去重键:同一 生产工单 + 检验类型 + 工位 + 检测日期 视为同一批过程检验数据 |
| | | */ |
| | | private String buildImportKey(MesQcIpqcImportExcelVO row) { |
| | | return String.join("|", StrUtil.trimToEmpty(row.getWorkOrderCode()), StrUtil.trimToEmpty(row.getType()), |
| | | StrUtil.trimToEmpty(row.getWorkstationName()), StrUtil.trimToEmpty(row.getInspectDate())); |
| | | } |
| | | |
| | | private String buildImportKeyDesc(MesQcIpqcImportExcelVO row) { |
| | | return "生产工单[" + MesQcImportHelper.describeImportValue(row.getWorkOrderCode()) |
| | | + "]、检验类型[" + MesQcImportHelper.describeImportValue(row.getType()) |
| | | + "]、工位[" + MesQcImportHelper.describeImportValue(row.getWorkstationName()) |
| | | + "]、检测日期[" + MesQcImportHelper.describeImportValue(row.getInspectDate()) + "]"; |
| | | } |
| | | |
| | | } |