| | |
| | | import java.util.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_NOT_EXISTS; |
| | |
| | | @Service @Validated @Slf4j |
| | | public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService { |
| | | private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture"); |
| | | private static final int MAX_BATCH_SIZE = 500, WRITE_BATCH_SIZE = 100; |
| | | private static final int WAIT_RETEST = 10, WAIT_PROCESS = 20, WAIT_INITIAL_FAIL_REVIEW = 25, WAIT_COMPANY = 30, |
| | | RELEASED = 40, WAIT_INITIAL_RECHECK = 50, WAIT_COMPANY_FAIL = 60, SCRAPPED = 70; |
| | | @Resource private MesQcSeedQualityMapper qualityMapper; |
| | |
| | | private Scope scope(String code, String type, boolean lock) { |
| | | if ("BAG".equals(type)) { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(code); |
| | | if (bag == null) throw new IllegalArgumentException("未找到袋码,请核对扫码类型"); |
| | | if (bag == null) throw invalidParamException("未找到袋码,请核对扫码类型和码值"); |
| | | 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("托盘尚未绑定生产批次,无法质检"); |
| | | if (pallet == null) throw invalidParamException("未找到托盘码,请核对扫码类型和码值"); |
| | | if (pallet.getBatchId() == null) throw invalidParamException("托盘尚未绑定生产批次,无法质检"); |
| | | 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("未找到生产批次码,请核对扫码类型"); |
| | | if (batch == null) throw invalidParamException("未找到生产批次码,请核对扫码类型和码值"); |
| | | 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("不支持的质检扫码类型"); |
| | | throw invalidParamException("不支持的质检扫码类型"); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @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<Long> confirmed = batchIds(req.getBagIds(), "袋码"); |
| | | Scope scope = scope(req.getCode(), req.getScanType(), false); |
| | | if (scope.canceled()) throw invalidParamException("质检对象已作废"); |
| | | 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)) |
| | | Set<String> inspected = qualityMapper.selectListByCodes(effective.stream().map(MesProBagCodeDO::getCode).toList()).stream() |
| | | .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet()); |
| | | List<MesProBagCodeDO> pendingPreview = effective.stream().filter(b -> b.getInboundEventId() != null && !inspected.contains(b.getCode())) |
| | | .sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList(); |
| | | List<Long> expectedChunk = pendingPreview.stream().limit(MAX_BATCH_SIZE).map(MesProBagCodeDO::getId).toList(); |
| | | if (expectedChunk.isEmpty() || !confirmed.equals(expectedChunk)) |
| | | throw new IllegalArgumentException("待质检清单已变化或已全部质检,请重新扫描,禁止重复处理"); |
| | | |
| | | List<MesProBagCodeDO> locked = bagCodeMapper.selectListByIdsForUpdate(confirmed); |
| | | if (locked.size() != confirmed.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()))) |
| | | if (!currentIds.containsAll(confirmed)) |
| | | 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())) |
| | | if (!qualityMapper.selectListByCodesForUpdate(locked.stream().map(MesProBagCodeDO::getCode).toList()).isEmpty()) |
| | | 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); |
| | | } |
| | | List<MesProBagCodeDO> pending = locked.stream().sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList(); |
| | | submitInitialBatch(pending, req); |
| | | return Map.of("processedCount", pending.size()); |
| | | } |
| | | |
| | | private void submitInitialBatch(List<MesProBagCodeDO> bags, |
| | | cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityGroupSubmitReqVO req) { |
| | | boolean passed = validateIndicators(req.getIndicators()); |
| | | if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("质检不合格必须填写原因"); |
| | | Long batchId = bags.get(0).getBatchId(); |
| | | if (batchId == null || bags.stream().anyMatch(bag -> !Objects.equals(batchId, bag.getBatchId()))) |
| | | throw new IllegalArgumentException("批量初检只允许同一生产批次的袋码"); |
| | | MesProBatchDO batch = proBatchService.getBatch(batchId); |
| | | 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) |
| | | throw new IllegalArgumentException("该批次库存不在暂存库中,无法进行质检"); |
| | | if (warehouseService.getFirstQualifiedWarehouse() == null) throw exception(WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED); |
| | | MesWmWarehouseDO qualifiedWarehouse = warehouseService.getWarehouse(req.getQualifiedWarehouseId()); |
| | | if (qualifiedWarehouse == null) throw exception(WM_WAREHOUSE_NOT_EXISTS); |
| | | if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())) |
| | | throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED); |
| | | |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | Long operatorId = getLoginUserId(); |
| | | int result = passed ? 1 : 2; |
| | | int status = passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW; |
| | | List<MesQcSeedQualityDO> qualities = bags.stream().map(bag -> MesQcSeedQualityDO.builder() |
| | | .code(bag.getCode()).itemId(batch.getItemId()).batchId(batch.getId()).batchCode(batch.getCode()) |
| | | .result(result).reason(passed ? "" : req.getReason().trim()).workflowStatus(status) |
| | | .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(operatorId).inspectedAt(now).build()).toList(); |
| | | qualityMapper.insertBatch(qualities, WRITE_BATCH_SIZE); |
| | | if (qualities.stream().anyMatch(quality -> quality.getId() == null)) |
| | | throw new IllegalStateException("批量初检记录编号生成失败"); |
| | | |
| | | List<MesQcSeedQualityIndicatorDO> indicators = new ArrayList<>(qualities.size() * REQUIRED.size()); |
| | | List<MesQcSeedQualityActionDO> actions = new ArrayList<>(qualities.size()); |
| | | for (MesQcSeedQualityDO quality : qualities) { |
| | | for (MesQcSeedQualitySubmitReqVO.Indicator item : req.getIndicators()) { |
| | | indicators.add(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode()) |
| | | .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build()); |
| | | } |
| | | actions.add(newAction(quality.getId(), "INITIAL", result, req.getReason(), req.getIndicators(), operatorId, now)); |
| | | } |
| | | indicatorMapper.insertBatch(indicators, WRITE_BATCH_SIZE * REQUIRED.size()); |
| | | actionMapper.insertBatch(actions, WRITE_BATCH_SIZE); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void retestBatch(cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityBatchRetestReqVO req) { |
| | | List<Long> ids = approvalIds(req.getIds()); |
| | | List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByIdsForUpdate(ids); |
| | | validateLockedIds(ids, qualities); |
| | | String batchCode = null; |
| | | Integer workflowStatus = null; |
| | | boolean first = true; |
| | | List<MesQcSeedQualityDO> qualities = new ArrayList<>(); |
| | | for (Long id : ids) { |
| | | MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id); |
| | | if (quality == null || !Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(quality.getWorkflowStatus())) |
| | | for (MesQcSeedQualityDO quality : qualities) { |
| | | if (!Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(quality.getWorkflowStatus())) |
| | | throw new IllegalArgumentException("所选记录包含不可检验的状态,请刷新后重试"); |
| | | if (StrUtil.isBlank(quality.getBatchCode())) |
| | | throw new IllegalArgumentException("记录缺少生产批次,不能批量质检"); |
| | |
| | | } else if (!Objects.equals(batchCode, quality.getBatchCode()) || !Objects.equals(workflowStatus, quality.getWorkflowStatus())) { |
| | | throw new IllegalArgumentException("批量质检只允许同生产批次、同流程状态的记录"); |
| | | } |
| | | qualities.add(quality); |
| | | } |
| | | for (MesQcSeedQualityDO quality : qualities) { |
| | | cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO single = |
| | | new cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO(); |
| | | single.setCode(quality.getCode()); |
| | | single.setIndicators(req.getIndicators()); |
| | | single.setReason(req.getReason()); |
| | | retest(quality.getId(), single); |
| | | } |
| | | applyRetestBatch(qualities, req.getIndicators(), req.getReason()); |
| | | } |
| | | |
| | | private void checkRetestScanType(String scanType) { |
| | |
| | | public Map<String, Object> resolveRetestScope(String code, String scanType) { |
| | | checkRetestScanType(scanType); |
| | | Scope scope = scope(code, scanType, false); |
| | | if (scope.canceled()) throw new IllegalArgumentException("质检对象已作废"); |
| | | if (scope.canceled()) throw invalidParamException("质检对象已作废"); |
| | | List<String> codes = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus()) && b.getInboundEventId() != null) |
| | | .map(MesProBagCodeDO::getCode).toList(); |
| | | List<MesQcSeedQualityDO> qualities = codes.isEmpty() ? List.of() : qualityMapper.selectListByCodes(codes); |
| | |
| | | .map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | List<Long> initialRecheckIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_INITIAL_RECHECK)) |
| | | .map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | long pendingProcessCount = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_PROCESS) |
| | | || Objects.equals(q.getWorkflowStatus(), WAIT_INITIAL_FAIL_REVIEW)).count(); |
| | | long pendingCompanyCount = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_COMPANY) |
| | | || Objects.equals(q.getWorkflowStatus(), WAIT_COMPANY_FAIL)).count(); |
| | | List<Long> pendingProcessIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_PROCESS)) |
| | | .map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | List<Long> pendingInitialFailIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_INITIAL_FAIL_REVIEW)) |
| | | .map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | List<Long> pendingCompanyIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_COMPANY)) |
| | | .map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | List<Long> pendingCompanyFailIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_COMPANY_FAIL)) |
| | | .map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("code", scope.code()); result.put("scanType", scanType); result.put("batchCode", scope.batchCode()); |
| | | result.put("totalCount", codes.size()); result.put("retestIds", retestIds); |
| | | result.put("initialRecheckIds", initialRecheckIds); |
| | | result.put("pendingProcessCount", pendingProcessCount); |
| | | result.put("pendingCompanyCount", pendingCompanyCount); |
| | | result.put("pendingProcessIds", pendingProcessIds); result.put("pendingInitialFailIds", pendingInitialFailIds); |
| | | result.put("pendingCompanyIds", pendingCompanyIds); result.put("pendingCompanyFailIds", pendingCompanyFailIds); |
| | | result.put("pendingProcessCount", pendingProcessIds.size() + pendingInitialFailIds.size()); |
| | | result.put("pendingCompanyCount", pendingCompanyIds.size() + pendingCompanyFailIds.size()); |
| | | return result; |
| | | } |
| | | |
| | |
| | | if (!Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(req.getWorkflowStatus())) |
| | | throw new IllegalArgumentException("批量质检状态无效"); |
| | | List<Long> confirmed = approvalIds(req.getQualityIds()); |
| | | Scope scope = scope(req.getCode(), req.getScanType(), true); |
| | | if (scope.canceled()) throw new IllegalArgumentException("质检对象已作废"); |
| | | Scope scope = scope(req.getCode(), req.getScanType(), false); |
| | | if (scope.canceled()) throw invalidParamException("质检对象已作废"); |
| | | List<MesProBagCodeDO> effective = scope.bags().stream() |
| | | .filter(b -> !Integer.valueOf(30).equals(b.getStatus()) && b.getInboundEventId() != null).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()) && b.getInboundEventId() != null) |
| | | .map(MesProBagCodeDO::getId).collect(java.util.stream.Collectors.toSet()); |
| | | if (!currentIds.equals(new HashSet<>(locked.stream().map(MesProBagCodeDO::getId).toList()))) |
| | | throw new IllegalArgumentException("袋码归属已变化,请重新扫描"); |
| | | List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByCodesForUpdate(locked.stream().map(MesProBagCodeDO::getCode).toList()); |
| | | List<MesQcSeedQualityDO> pending = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), req.getWorkflowStatus())) |
| | | Set<String> effectiveCodes = effective.stream().map(MesProBagCodeDO::getCode).collect(java.util.stream.Collectors.toSet()); |
| | | List<MesQcSeedQualityDO> pendingPreview = qualityMapper.selectListByCodes(effectiveCodes).stream() |
| | | .filter(q -> Objects.equals(q.getWorkflowStatus(), req.getWorkflowStatus())) |
| | | .sorted(Comparator.comparing(MesQcSeedQualityDO::getId)).toList(); |
| | | if (pending.isEmpty() || !confirmed.equals(pending.stream().map(MesQcSeedQualityDO::getId).toList())) |
| | | List<Long> expectedChunk = pendingPreview.stream().limit(MAX_BATCH_SIZE).map(MesQcSeedQualityDO::getId).toList(); |
| | | if (expectedChunk.isEmpty() || !confirmed.equals(expectedChunk)) |
| | | throw new IllegalArgumentException("待质检清单已变化,请重新扫描核对"); |
| | | for (MesQcSeedQualityDO quality : pending) { |
| | | var single = new cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO(); |
| | | single.setCode(quality.getCode()); single.setIndicators(req.getIndicators()); single.setReason(req.getReason()); |
| | | retest(quality.getId(), single); |
| | | } |
| | | List<MesQcSeedQualityDO> pending = qualityMapper.selectListByIdsForUpdate(confirmed); |
| | | validateLockedIds(confirmed, pending); |
| | | if (pending.stream().anyMatch(quality -> !Objects.equals(quality.getWorkflowStatus(), req.getWorkflowStatus()) |
| | | || !effectiveCodes.contains(quality.getCode()))) |
| | | throw new IllegalArgumentException("待质检记录状态或归属已变化,请重新扫描核对"); |
| | | applyRetestBatch(pending, req.getIndicators(), req.getReason()); |
| | | return Map.of("processedCount", pending.size()); |
| | | } |
| | | |
| | | private void applyRetestBatch(List<MesQcSeedQualityDO> qualities, |
| | | List<MesQcSeedQualitySubmitReqVO.Indicator> submittedIndicators, String reason) { |
| | | boolean passed = validateIndicators(submittedIndicators); |
| | | if (!passed && StrUtil.isBlank(reason)) throw new IllegalArgumentException("检验不合格必须填写原因"); |
| | | List<Long> ids = qualities.stream().map(MesQcSeedQualityDO::getId).toList(); |
| | | Map<Long, Map<String, MesQcSeedQualityIndicatorDO>> existingByQuality = new HashMap<>(); |
| | | for (MesQcSeedQualityIndicatorDO indicator : indicatorMapper.selectListByQualityIds(ids)) { |
| | | existingByQuality.computeIfAbsent(indicator.getQualityId(), ignored -> new HashMap<>()) |
| | | .put(indicator.getIndicatorCode(), indicator); |
| | | } |
| | | |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | Long operatorId = getLoginUserId(); |
| | | int result = passed ? 1 : 2; |
| | | List<MesQcSeedQualityDO> qualityUpdates = new ArrayList<>(qualities.size()); |
| | | List<MesQcSeedQualityIndicatorDO> indicatorUpdates = new ArrayList<>(qualities.size() * REQUIRED.size()); |
| | | List<MesQcSeedQualityActionDO> actions = new ArrayList<>(qualities.size()); |
| | | for (MesQcSeedQualityDO quality : qualities) { |
| | | boolean initialRecheck = Objects.equals(quality.getWorkflowStatus(), WAIT_INITIAL_RECHECK); |
| | | if (!initialRecheck && !Objects.equals(quality.getWorkflowStatus(), WAIT_RETEST)) |
| | | throw new IllegalArgumentException("所选记录包含不可检验的状态,请刷新后重试"); |
| | | MesQcSeedQualityDO update = MesQcSeedQualityDO.builder().id(quality.getId()).result(result) |
| | | .reason(passed ? "" : reason.trim()).inspectorId(operatorId).inspectedAt(now) |
| | | .workflowStatus(initialRecheck ? (passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW) |
| | | : (passed ? WAIT_COMPANY : WAIT_COMPANY_FAIL)).build(); |
| | | if (!initialRecheck) { |
| | | update.setRetestResult(result); |
| | | update.setRetestedAt(now); |
| | | } |
| | | qualityUpdates.add(update); |
| | | Map<String, MesQcSeedQualityIndicatorDO> existing = existingByQuality.getOrDefault(quality.getId(), Map.of()); |
| | | for (MesQcSeedQualitySubmitReqVO.Indicator item : submittedIndicators) { |
| | | MesQcSeedQualityIndicatorDO indicator = existing.get(item.getCode()); |
| | | if (indicator == null) throw new IllegalArgumentException("质检指标记录缺失"); |
| | | indicatorUpdates.add(MesQcSeedQualityIndicatorDO.builder().id(indicator.getId()) |
| | | .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build()); |
| | | } |
| | | actions.add(newAction(quality.getId(), initialRecheck ? "INITIAL_RECHECK" : "RETEST", result, |
| | | reason, submittedIndicators, operatorId, now)); |
| | | } |
| | | qualityMapper.updateBatch(qualityUpdates, WRITE_BATCH_SIZE); |
| | | indicatorMapper.updateBatch(indicatorUpdates, WRITE_BATCH_SIZE * REQUIRED.size()); |
| | | actionMapper.insertBatch(actions, WRITE_BATCH_SIZE); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void processApproveBatch(List<Long> ids, boolean approved, String opinion) { |
| | | for (Long id : approvalIds(ids)) processApprove(id, approved, opinion); |
| | | List<Long> confirmed = approvalIds(ids); |
| | | List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByIdsForUpdate(confirmed); |
| | | validateLockedIds(confirmed, qualities); |
| | | if (qualities.stream().anyMatch(quality -> !Set.of(WAIT_PROCESS, WAIT_INITIAL_FAIL_REVIEW).contains(quality.getWorkflowStatus()))) |
| | | throw new IllegalArgumentException("所选记录包含不能进行初检审核的状态,请刷新后重试"); |
| | | approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(), |
| | | MesQcApproveNodeEnum.PROCESS.getNode(), getLoginUserId()); |
| | | if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("驳回时请填写审批意见"); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | Long operatorId = getLoginUserId(); |
| | | int next = approved ? WAIT_RETEST : WAIT_INITIAL_RECHECK; |
| | | qualityMapper.updateBatch(qualities.stream().map(quality -> MesQcSeedQualityDO.builder().id(quality.getId()) |
| | | .workflowStatus(next).processApprovedAt(now).build()).toList(), WRITE_BATCH_SIZE); |
| | | actionMapper.insertBatch(qualities.stream().map(quality -> newAction(quality.getId(), |
| | | approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), opinion, null, operatorId, now)).toList(), |
| | | WRITE_BATCH_SIZE); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void companyApproveBatch(List<Long> ids, boolean approved, String opinion) { |
| | | for (Long id : approvalIds(ids)) companyApprove(id, approved, opinion); |
| | | List<Long> confirmed = approvalIds(ids); |
| | | List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByIdsForUpdate(confirmed); |
| | | validateLockedIds(confirmed, qualities); |
| | | if (qualities.stream().anyMatch(quality -> !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus()))) |
| | | throw new IllegalArgumentException("所选记录包含不能进行复检审核的状态,请刷新后重试"); |
| | | approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(), |
| | | MesQcApproveNodeEnum.COMPANY.getNode(), getLoginUserId()); |
| | | if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("驳回时请填写审批意见"); |
| | | if (approved) { |
| | | for (MesQcSeedQualityDO quality : qualities) finalizeStock(quality, quality.getWorkflowStatus() == WAIT_COMPANY); |
| | | } |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | Long operatorId = getLoginUserId(); |
| | | List<MesQcSeedQualityDO> updates = qualities.stream().map(quality -> MesQcSeedQualityDO.builder().id(quality.getId()) |
| | | .workflowStatus(approved ? (quality.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : SCRAPPED) : WAIT_RETEST) |
| | | .companyApprovedAt(now).build()).toList(); |
| | | qualityMapper.updateBatch(updates, WRITE_BATCH_SIZE); |
| | | actionMapper.insertBatch(qualities.stream().map(quality -> newAction(quality.getId(), |
| | | approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", quality.getResult(), opinion, null, operatorId, now)).toList(), |
| | | WRITE_BATCH_SIZE); |
| | | } |
| | | |
| | | private List<Long> approvalIds(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty() || ids.size() > 500 || ids.stream().anyMatch(Objects::isNull)) |
| | | throw new IllegalArgumentException("请选择 1 至 500 条质检记录"); |
| | | return batchIds(ids, "质检记录"); |
| | | } |
| | | |
| | | private List<Long> batchIds(List<Long> ids, String label) { |
| | | if (ids == null || ids.isEmpty() || ids.size() > MAX_BATCH_SIZE || ids.stream().anyMatch(Objects::isNull)) |
| | | throw new IllegalArgumentException("请选择 1 至 " + MAX_BATCH_SIZE + " 条" + label); |
| | | List<Long> sorted = ids.stream().distinct().sorted().toList(); |
| | | if (sorted.size() != ids.size()) throw new IllegalArgumentException("质检记录不能重复选择"); |
| | | if (sorted.size() != ids.size()) throw new IllegalArgumentException(label + "不能重复选择"); |
| | | return sorted; |
| | | } |
| | | |
| | | private void validateLockedIds(List<Long> expectedIds, List<MesQcSeedQualityDO> qualities) { |
| | | List<Long> actualIds = qualities.stream().map(MesQcSeedQualityDO::getId).sorted().toList(); |
| | | if (!expectedIds.equals(actualIds)) throw new IllegalArgumentException("质检记录不存在或已变化,请刷新后重试"); |
| | | } |
| | | |
| | | private MesQcSeedQualityDO requireStatus(Long id, int expected) { |
| | |
| | | |
| | | private void recordAction(Long id, String action, Integer result, String opinion, |
| | | List<MesQcSeedQualitySubmitReqVO.Indicator> indicators) { |
| | | actionMapper.insert(newAction(id, action, result, opinion, indicators, getLoginUserId(), LocalDateTime.now())); |
| | | } |
| | | |
| | | private MesQcSeedQualityActionDO newAction(Long id, String action, Integer result, String opinion, |
| | | List<MesQcSeedQualitySubmitReqVO.Indicator> indicators, |
| | | Long operatorId, LocalDateTime createTime) { |
| | | MesQcSeedQualityActionDO event = new MesQcSeedQualityActionDO(); |
| | | event.setQualityId(id); |
| | | event.setAction(action); |
| | | event.setResult(result); |
| | | event.setOpinion(opinion); |
| | | event.setIndicatorsJson(indicators == null ? null : JSONUtil.toJsonStr(indicators)); |
| | | event.setOperatorId(getLoginUserId()); |
| | | event.setCreateTime(LocalDateTime.now()); |
| | | actionMapper.insert(event); |
| | | event.setOperatorId(operatorId); |
| | | event.setCreateTime(createTime); |
| | | return event; |
| | | } |
| | | |
| | | /** |