From 7d4ee817ca92f1d28a8a292bee5172bd3cbd2c7e Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 28 十月 2026 14:26:10 +0800
Subject: [PATCH] feat(mes): 实现批次绑定托盘和吨袋的分页查询功能
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java | 346 +++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 299 insertions(+), 47 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
index 2a3ba96..45248cf 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
@@ -22,11 +22,14 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeQcStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcApproveNodeEnum;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcApprovalTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum;
import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
+import cn.iocoder.yudao.module.mes.service.qc.approver.MesQcApproverService;
import cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -44,6 +47,7 @@
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;
@@ -52,9 +56,11 @@
@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 WAIT_RETEST = 10, WAIT_PROCESS = 20, WAIT_COMPANY = 30,
- RELEASED = 40, WAIT_PROCESS_FAIL = 50, WAIT_COMPANY_FAIL = 60, SCRAPPED = 70;
+ 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;
+ @Resource private MesQcApproverService approverService;
@Resource private MesQcSeedQualityActionMapper actionMapper;
@Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
@Resource private MesProBagCodeMapper bagCodeMapper;
@@ -100,23 +106,23 @@
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
@@ -141,32 +147,76 @@
@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)
@@ -210,8 +260,13 @@
.inspectedAt(LocalDateTime.now()).build();
qualityMapper.insert(quality);
for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
+ if (item == null || item.getCode() == null || item.getValue() == null || item.getMinValue() == null || item.getMaxValue() == null)
+ throw new IllegalArgumentException("鍥涢」鎸囨爣鐨勫疄娴嬪�煎拰鏈鍚堟牸鑼冨洿蹇呴』瀹屾暣");
if (!REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())) throw new IllegalArgumentException("鎸囨爣蹇呴』涓虹函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒嗕笖涓嶅彲閲嶅");
if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺");
+ if (item.getValue().compareTo(BigDecimal.ZERO) < 0 || item.getValue().compareTo(new BigDecimal("100")) > 0
+ || item.getMinValue().compareTo(BigDecimal.ZERO) < 0 || item.getMaxValue().compareTo(new BigDecimal("100")) > 0)
+ throw new IllegalArgumentException("鐧惧垎姣旀寚鏍囧繀椤诲浜� 0鈥�100");
if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode())
.value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
@@ -219,10 +274,10 @@
if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("鍥涢」鎸囨爣涓嶅畬鏁�");
if (!passed && StrUtil.isBlank(reqVO.getReason())) throw new IllegalArgumentException("璐ㄦ涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
quality.setResult(passed ? 1 : 2);
- quality.setWorkflowStatus(passed ? WAIT_PROCESS : WAIT_RETEST);
+ quality.setWorkflowStatus(passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW);
qualityMapper.updateById(quality);
recordAction(quality.getId(), "INITIAL", quality.getResult(), reqVO.getReason(), reqVO.getIndicators());
- // 鍒濇浠呰褰曠粨璁猴紱琚嬩粛鐣欏湪鏆傚瓨搴擄紝寰呭妫�鍙婁袱绾у鎵瑰悗鍐嶆斁琛屾垨鎶ュ簾銆�
+ // 鍒濇浠呰褰曠粨璁猴紱琚嬩粛鐣欏湪鏆傚瓨搴擄紝寰呭垵妫�瀹℃牳銆佸妫�鍙婃渶缁堝鏍稿悗鍐嶆斁琛屾垨鎶ュ簾銆�
return quality.getId();
}
@@ -310,15 +365,24 @@
@Override @Transactional(rollbackFor = Exception.class)
public void retest(Long id, cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO req) {
- MesQcSeedQualityDO quality = requireStatus(id, WAIT_RETEST);
- if (!Objects.equals(quality.getCode(), req.getCode())) throw new IllegalArgumentException("澶嶆琚嬬爜涓庡師璐ㄦ璁板綍涓嶄竴鑷�");
+ MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
+ if (quality == null || !Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(quality.getWorkflowStatus()))
+ throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳芥彁浜ゅ妫�锛岃鍒锋柊鍚庨噸璇�");
+ boolean initialRecheck = Objects.equals(quality.getWorkflowStatus(), WAIT_INITIAL_RECHECK);
+ if (!Objects.equals(quality.getCode(), req.getCode())) throw new IllegalArgumentException("妫�楠岃鐮佷笌鍘熻川妫�璁板綍涓嶄竴鑷�");
boolean passed = validateIndicators(req.getIndicators());
- if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("澶嶆涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
- recordAction(id, "RETEST", passed ? 1 : 2, req.getReason(), req.getIndicators());
- qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).result(passed ? 1 : 2)
- .reason(passed ? "" : req.getReason().trim()).retestResult(passed ? 1 : 2).retestedAt(LocalDateTime.now())
+ if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("妫�楠屼笉鍚堟牸蹇呴』濉啓鍘熷洜");
+ recordAction(id, initialRecheck ? "INITIAL_RECHECK" : "RETEST", passed ? 1 : 2, req.getReason(), req.getIndicators());
+ MesQcSeedQualityDO update = MesQcSeedQualityDO.builder().id(id).result(passed ? 1 : 2)
+ .reason(passed ? "" : req.getReason().trim())
.inspectorId(getLoginUserId()).inspectedAt(LocalDateTime.now())
- .workflowStatus(passed ? WAIT_PROCESS : WAIT_PROCESS_FAIL).build());
+ .workflowStatus(initialRecheck ? (passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW)
+ : (passed ? WAIT_COMPANY : WAIT_COMPANY_FAIL)).build();
+ if (!initialRecheck) {
+ update.setRetestResult(passed ? 1 : 2);
+ update.setRetestedAt(LocalDateTime.now());
+ }
+ qualityMapper.updateById(update);
// 椤甸潰銆佸叕寮�杩芥函璇诲彇涓绘寚鏍囪〃锛涘悓姝ヤ负鏈�鏂颁竴娆℃祴鍊硷紝鍘嗘娴嬪�煎湪鎿嶄綔鐣欑棔琛ㄤ繚鐣欍��
Map<String, MesQcSeedQualityIndicatorDO> existing = new HashMap<>();
for (MesQcSeedQualityIndicatorDO indicator : indicatorMapper.selectListByQualityId(id))
@@ -332,12 +396,146 @@
}
@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;
+ 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("璁板綍缂哄皯鐢熶骇鎵规锛屼笉鑳芥壒閲忚川妫�");
+ if (first) {
+ batchCode = quality.getBatchCode();
+ workflowStatus = quality.getWorkflowStatus();
+ first = false;
+ } else if (!Objects.equals(batchCode, quality.getBatchCode()) || !Objects.equals(workflowStatus, quality.getWorkflowStatus())) {
+ throw new IllegalArgumentException("鎵归噺璐ㄦ鍙厑璁稿悓鐢熶骇鎵规銆佸悓娴佺▼鐘舵�佺殑璁板綍");
+ }
+ }
+ applyRetestBatch(qualities, req.getIndicators(), req.getReason());
+ }
+
+ private void checkRetestScanType(String scanType) {
+ if (!Set.of("PALLET", "BATCH").contains(scanType))
+ throw new IllegalArgumentException("鎵归噺璐ㄦ鍙敮鎸佹墭鐩樼爜鎴栫敓浜ф壒娆$爜");
+ }
+
+ @Override
+ public Map<String, Object> resolveRetestScope(String code, String scanType) {
+ checkRetestScanType(scanType);
+ Scope scope = scope(code, scanType, false);
+ 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);
+ List<Long> retestIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_RETEST))
+ .map(MesQcSeedQualityDO::getId).sorted().toList();
+ List<Long> initialRecheckIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_INITIAL_RECHECK))
+ .map(MesQcSeedQualityDO::getId).sorted().toList();
+ 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("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;
+ }
+
+ @Override @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> retestScope(cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityScopeRetestReqVO req) {
+ checkRetestScanType(req.getScanType());
+ 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(), 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("褰撳墠鑼冨洿娌℃湁宸插叆搴撶殑鏈夋晥琚嬬爜");
+ 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();
+ List<Long> expectedChunk = pendingPreview.stream().limit(MAX_BATCH_SIZE).map(MesQcSeedQualityDO::getId).toList();
+ if (expectedChunk.isEmpty() || !confirmed.equals(expectedChunk))
+ throw new IllegalArgumentException("寰呰川妫�娓呭崟宸插彉鍖栵紝璇烽噸鏂版壂鎻忔牳瀵�");
+ 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)
public void processApprove(Long id, boolean approved, String opinion) {
MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
- if (quality == null || !Set.of(WAIT_PROCESS, WAIT_PROCESS_FAIL).contains(quality.getWorkflowStatus()))
- throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽姞宸ラ儴瀹℃壒");
- if (StrUtil.isBlank(opinion)) throw new IllegalArgumentException("璇峰~鍐欏鎵规剰瑙�");
- int next = approved ? (quality.getWorkflowStatus() == WAIT_PROCESS ? WAIT_COMPANY : WAIT_COMPANY_FAIL) : WAIT_RETEST;
+ if (quality == null || !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("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
+ int next = approved ? WAIT_RETEST : WAIT_INITIAL_RECHECK;
qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next)
.processApprovedAt(LocalDateTime.now()).build());
recordAction(id, approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), opinion, null);
@@ -347,8 +545,10 @@
public void companyApprove(Long id, boolean approved, String opinion) {
MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
if (quality == null || !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus()))
- throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽叕鍙歌川妫�瀹℃壒");
- if (StrUtil.isBlank(opinion)) throw new IllegalArgumentException("璇峰~鍐欏鎵规剰瑙�");
+ throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽妫�瀹℃牳");
+ approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
+ MesQcApproveNodeEnum.COMPANY.getNode(), getLoginUserId());
+ if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
if (approved) finalizeStock(quality, quality.getWorkflowStatus() == WAIT_COMPANY);
int next = approved ? (quality.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : SCRAPPED) : WAIT_RETEST;
qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next)
@@ -358,20 +558,63 @@
@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) {
@@ -390,6 +633,9 @@
|| item.getValue() == null || item.getMinValue() == null || item.getMaxValue() == null)
throw new IllegalArgumentException("鎸囨爣蹇呴』涓虹函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒嗕笖涓嶅彲閲嶅");
if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺");
+ if (item.getValue().compareTo(BigDecimal.ZERO) < 0 || item.getValue().compareTo(new BigDecimal("100")) > 0
+ || item.getMinValue().compareTo(BigDecimal.ZERO) < 0 || item.getMaxValue().compareTo(new BigDecimal("100")) > 0)
+ throw new IllegalArgumentException("鐧惧垎姣旀寚鏍囧繀椤诲浜� 0鈥�100");
if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
}
return passed;
@@ -397,15 +643,21 @@
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;
}
/**
--
Gitblit v1.9.3