| | |
| | | @Resource private MesQcSeedQualityMapper qualityMapper; |
| | | @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper; |
| | | @Resource private MesProBagCodeMapper bagCodeMapper; |
| | | @Resource private cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper palletMapper; |
| | | @Resource private cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper batchMapper; |
| | | @Resource private MesProBatchService proBatchService; |
| | | @Resource private MesWmBatchMapper mesWmBatchMapper; |
| | | @Resource private MesWmMaterialStockMapper materialStockMapper; |
| | |
| | | |
| | | @Override |
| | | public PageResult<MesQcSeedQualityDO> getQualityPage(MesQcSeedQualityPageReqVO reqVO) { |
| | | if (StrUtil.isNotBlank(reqVO.getCode())) { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(reqVO.getCode()); |
| | | if (bag != null) reqVO.setCode(bag.getCode()); |
| | | } |
| | | return qualityMapper.selectPage(reqVO); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> resolveScan(String code) { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(code); |
| | | if (bag == null) throw new IllegalArgumentException("未解析到有效袋码,请扫描袋码"); |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("code", bag.getCode()); |
| | | result.put("businessCode", bag.getBusinessCode()); |
| | | result.put("batchCode", bag.getBatchCode()); |
| | | result.put("inbound", bag.getInboundEventId() != null); |
| | | result.put("canceled", Integer.valueOf(30).equals(bag.getStatus())); |
| | | MesQcSeedQualityDO quality = qualityMapper.selectByCode(bag.getCode()); |
| | | result.put("qualityId", quality == null ? null : quality.getId()); |
| | | result.put("result", quality == null ? null : quality.getResult()); |
| | | return result; |
| | | } |
| | | |
| | | private record Scope(String code, String businessCode, String batchCode, boolean canceled, List<MesProBagCodeDO> bags) { } |
| | | |
| | | private Scope scope(String code, String type, boolean lock) { |
| | | if ("BAG".equals(type)) { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(code); |
| | | if (bag == null) throw new IllegalArgumentException("未找到袋码,请核对扫码类型"); |
| | | return new Scope(bag.getCode(), bag.getBusinessCode(), bag.getBatchCode(), Integer.valueOf(30).equals(bag.getStatus()), List.of(bag)); |
| | | } |
| | | if ("PALLET".equals(type)) { |
| | | var pallet = lock ? palletMapper.selectByScanCodeForUpdate(code) : palletMapper.selectByScanCode(code); |
| | | if (pallet == null) throw new IllegalArgumentException("未找到托盘码,请核对扫码类型"); |
| | | if (pallet.getBatchId() == null) throw new IllegalArgumentException("托盘尚未绑定生产批次,无法质检"); |
| | | return new Scope(StrUtil.blankToDefault(pallet.getCode(), pallet.getTempCode()), pallet.getBusinessCode(), pallet.getBatchCode(), |
| | | Integer.valueOf(20).equals(pallet.getStatus()), lock ? bagCodeMapper.selectListByPalletIdForUpdate(pallet.getId()) : bagCodeMapper.selectListByPalletId(pallet.getId())); |
| | | } |
| | | if ("BATCH".equals(type)) { |
| | | var batch = lock ? batchMapper.selectByScanCodeForUpdate(code) : batchMapper.selectByScanCode(code); |
| | | if (batch == null) throw new IllegalArgumentException("未找到生产批次码,请核对扫码类型"); |
| | | return new Scope(StrUtil.blankToDefault(batch.getCode(), batch.getTempCode()), batch.getBusinessCode(), batch.getCode(), |
| | | Integer.valueOf(40).equals(batch.getStatus()), lock ? bagCodeMapper.selectListByBatchIdForUpdate(batch.getId()) : bagCodeMapper.selectListByBatchId(batch.getId())); |
| | | } |
| | | throw new IllegalArgumentException("不支持的质检扫码类型"); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> resolveScan(String code, String scanType) { |
| | | Scope scope = scope(code, scanType, false); |
| | | List<MesProBagCodeDO> effective = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).toList(); |
| | | Set<String> inspected = qualityMapper.selectListByCodes(effective.stream().map(MesProBagCodeDO::getCode).toList()).stream() |
| | | .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet()); |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("scanType", scanType); result.put("code", scope.code()); result.put("businessCode", scope.businessCode()); |
| | | result.put("batchCode", scope.batchCode()); result.put("canceled", scope.canceled()); |
| | | result.put("totalCount", effective.size()); |
| | | result.put("inbound", !effective.isEmpty() && effective.stream().allMatch(b -> b.getInboundEventId() != null)); |
| | | result.put("pendingBagIds", effective.stream().filter(b -> b.getInboundEventId() != null && !inspected.contains(b.getCode())).map(MesProBagCodeDO::getId).sorted().toList()); |
| | | result.put("completedBagIds", effective.stream().filter(b -> inspected.contains(b.getCode())).map(MesProBagCodeDO::getId).sorted().toList()); |
| | | if ("BAG".equals(scanType)) { |
| | | MesQcSeedQualityDO quality = qualityMapper.selectByCode(scope.code()); |
| | | result.put("qualityId", quality == null ? null : quality.getId()); result.put("result", quality == null ? null : quality.getResult()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, Object> submitGroup(cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityGroupSubmitReqVO req) { |
| | | Scope scope = scope(req.getCode(), req.getScanType(), true); |
| | | if (scope.canceled()) throw new IllegalArgumentException("质检对象已作废"); |
| | | List<MesProBagCodeDO> effective = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).toList(); |
| | | if (effective.isEmpty()) throw new IllegalArgumentException("没有有效袋码"); |
| | | List<MesProBagCodeDO> locked = bagCodeMapper.selectListByIdsForUpdate(effective.stream().map(MesProBagCodeDO::getId).toList()); |
| | | if (locked.size() != effective.size() || locked.stream().anyMatch(b -> Integer.valueOf(30).equals(b.getStatus()) || b.getInboundEventId() == null)) |
| | | throw new IllegalArgumentException("袋码已变化或尚未全部入库,请重新扫描核对"); |
| | | // 在袋码锁之后重新核对归属;其他绑定操作不得改变已确认的检验范围。 |
| | | Scope current = scope(req.getCode(), req.getScanType(), false); |
| | | Set<Long> currentIds = current.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).map(MesProBagCodeDO::getId).collect(java.util.stream.Collectors.toSet()); |
| | | if (!currentIds.equals(new HashSet<>(locked.stream().map(MesProBagCodeDO::getId).toList()))) |
| | | throw new IllegalArgumentException("袋码归属已变化,请重新扫描核对"); |
| | | Set<String> inspected = qualityMapper.selectListByCodesForUpdate(locked.stream().map(MesProBagCodeDO::getCode).toList()).stream() |
| | | .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet()); |
| | | List<MesProBagCodeDO> pending = locked.stream().filter(b -> !inspected.contains(b.getCode())).sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList(); |
| | | List<Long> confirmed = req.getBagIds().stream().sorted().toList(); |
| | | if (pending.isEmpty() || !confirmed.equals(pending.stream().map(MesProBagCodeDO::getId).toList())) |
| | | throw new IllegalArgumentException("待质检清单已变化或已全部质检,请重新扫描,禁止重复处理"); |
| | | for (MesProBagCodeDO bag : pending) { |
| | | MesQcSeedQualitySubmitReqVO single = new MesQcSeedQualitySubmitReqVO(); |
| | | single.setCode(bag.getCode());single.setIndicators(req.getIndicators());single.setReason(req.getReason()); |
| | | single.setQualifiedWarehouseId(req.getQualifiedWarehouseId()); |
| | | // 外层事务覆盖全部袋码;任何一袋库存流转失败都会回滚整次操作。 |
| | | submit(single); |
| | | } |
| | | return Map.of("processedCount", pending.size()); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public Long submit(MesQcSeedQualitySubmitReqVO reqVO) { |
| | | if (reqVO.getIndicators().size() != 4) throw new IllegalArgumentException("必须提交四项指标"); |
| | | if (qualityMapper.selectByCode(reqVO.getCode()) != null) throw new IllegalArgumentException("该袋码已质检,禁止重复提交"); |
| | | // 种子质检必须以袋码(单袋)提交,物料信息由袋码自动解析,前端不再单独选择物料 |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByCode(reqVO.getCode()); |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(reqVO.getCode()); |
| | | if (bag == null) { |
| | | throw new IllegalArgumentException("未解析到有效袋码,种子质检必须以袋码提交"); |
| | | } |
| | | MesProBatchDO batch = resolveBatch(reqVO.getCode(), reqVO.getBatchCode()); |
| | | if (Integer.valueOf(30).equals(bag.getStatus()) || bag.getInboundEventId() == null) |
| | | throw new IllegalArgumentException("袋码已作废或尚未入库,无法质检"); |
| | | // 双码锁定同一袋,并以追溯码记录结果,防止换码或并发重复扣减库存。 |
| | | if (qualityMapper.selectByCodeForUpdate(bag.getCode()) != null) throw new IllegalArgumentException("该袋码已质检,禁止重复提交"); |
| | | MesProBatchDO batch = bag.getBatchId() == null ? null : proBatchService.getBatch(bag.getBatchId()); |
| | | if (batch == null || batch.getItemId() == null) { |
| | | throw new IllegalArgumentException("未根据袋码解析到物料信息,请确认扫码内容或批次号正确"); |
| | | } |
| | | if (Integer.valueOf(40).equals(batch.getStatus())) throw new IllegalArgumentException("生产批次已作废,无法质检"); |
| | | // 袋码对应批次的库存必须处于暂存库(待质检)中,才能进行种子质检 |
| | | MesWmBatchDO wmBatch = mesWmBatchMapper.selectByCode(batch.getCode()); |
| | | if (wmBatch == null || materialStockMapper.selectTempStockCountByBatchId(wmBatch.getId()) == 0) { |
| | |
| | | } |
| | | Set<String> codes = new HashSet<>(); |
| | | boolean passed = true; |
| | | MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(reqVO.getCode()).itemId(batch.getItemId()) |
| | | MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(bag.getCode()).itemId(batch.getItemId()) |
| | | .batchCode(batch.getCode()).reason(reqVO.getReason()).result(0) |
| | | .inspectedAt(LocalDateTime.now()).build(); |
| | | qualityMapper.insert(quality); |
| | |
| | | .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build()); |
| | | } |
| | | if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("四项指标不完整"); |
| | | if (!passed && StrUtil.isBlank(reqVO.getReason())) throw new IllegalArgumentException("质检不合格必须填写原因"); |
| | | quality.setResult(passed ? 1 : 2); |
| | | qualityMapper.updateById(quality); |
| | | // 按袋码粒度流转库存:锁定该袋在暂存库中的活动明细,禁止按批次任意扣减 |