| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.seedquality; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.*; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality.*; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | 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.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.wm.batch.MesWmBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | 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; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.jdbc.BadSqlGrammarException; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | 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; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_TYPE_NOT_QUALIFIED; |
| | | |
| | | @Service @Validated @Slf4j |
| | | public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService { |
| | | private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture"); |
| | | @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 MesWmBatchService mesWmBatchService; |
| | | @Resource private MesWmMaterialStockService materialStockService; |
| | | @Resource private MesWmBatchMapper mesWmBatchMapper; |
| | | @Resource private MesWmMaterialStockMapper materialStockMapper; |
| | | @Resource private MesWmMaterialStockBagMapper materialStockBagMapper; |
| | | @Resource private MesWmMaterialStockBagService materialStockBagService; |
| | | @Resource private MesWmTransactionService wmTransactionService; |
| | | @Resource private MesWmWarehouseService warehouseService; |
| | | @Resource private MesWmWarehouseLocationService warehouseLocationService; |
| | | @Resource private MesWmWarehouseAreaService warehouseAreaService; |
| | | |
| | | @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.selectByScanCodeForUpdate(reqVO.getCode()); |
| | | if (bag == null) { |
| | | throw new IllegalArgumentException("未解析到有效袋码,种子质检必须以袋码提交"); |
| | | } |
| | | 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) { |
| | | throw new IllegalArgumentException("该袋码/批次对应的库存不在暂存库中,无法进行质检"); |
| | | } |
| | | // 校验目标合格库:必须存在且仓库类型为合格库(20) |
| | | if (warehouseService.getFirstQualifiedWarehouse() == null) { |
| | | throw exception(WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED); |
| | | } |
| | | MesWmWarehouseDO qualifiedWarehouse = warehouseService.getWarehouse(reqVO.getQualifiedWarehouseId()); |
| | | if (qualifiedWarehouse == null) { |
| | | throw exception(WM_WAREHOUSE_NOT_EXISTS); |
| | | } |
| | | if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())) { |
| | | throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED); |
| | | } |
| | | // 解析合格库默认库区/库位(取该仓库下第一个库区、其下第一个库位,未配置则为 null) |
| | | Long qualifiedLocationId = null; |
| | | Long qualifiedAreaId = null; |
| | | List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId()); |
| | | if (CollUtil.isNotEmpty(locations)) { |
| | | qualifiedLocationId = locations.get(0).getId(); |
| | | List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(qualifiedLocationId); |
| | | if (CollUtil.isNotEmpty(areas)) { |
| | | qualifiedAreaId = areas.get(0).getId(); |
| | | } |
| | | } |
| | | Set<String> codes = new HashSet<>(); |
| | | boolean passed = true; |
| | | MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(reqVO.getCode()).itemId(reqVO.getItemId()) |
| | | .batchCode(reqVO.getBatchCode()).reason(reqVO.getReason()).result(0) |
| | | 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); |
| | | for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) { |
| | |
| | | .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); |
| | | // 质检合格 → 库存整批由暂存库(待质检)转为合格库(正式库存) |
| | | // 按袋码粒度流转库存:锁定该袋在暂存库中的活动明细,禁止按批次任意扣减 |
| | | MesWmMaterialStockBagDO stockBag = materialStockBagMapper.selectActiveByBagCode(bag.getCode()); |
| | | MesWmMaterialStockDO tempStock = resolveTempStock(bag, wmBatch, stockBag); |
| | | // A. 暂存出库(-1):该袋离开暂存库,写入质检暂存出库事务(跳过袋码自动维护,由质检统一控制袋码最终落点) |
| | | MesWmTransactionSaveReqDTO tempOutReq = new MesWmTransactionSaveReqDTO() |
| | | .setType(MesWmTransactionTypeEnum.OUT.getType()) |
| | | .setBizType(MesBizTypeConstants.QC_SEED_QUALITY) |
| | | .setBizId(quality.getId()) |
| | | .setBizCode(reqVO.getCode()) |
| | | .setBizLineId(quality.getId()) |
| | | .setItemId(batch.getItemId()) |
| | | .setQuantity(BigDecimal.ONE.negate()) |
| | | .setBatchId(tempStock.getBatchId()) |
| | | .setBatchCode(tempStock.getBatchCode()) |
| | | .setBagCodeId(bag.getId()) |
| | | .setBagCode(bag.getCode()) |
| | | .setWarehouseId(tempStock.getWarehouseId()) |
| | | .setLocationId(tempStock.getLocationId()) |
| | | .setAreaId(tempStock.getAreaId()) |
| | | .setVendorId(tempStock.getVendorId()) |
| | | .setMaterialStockId(tempStock.getId()) |
| | | .setCheckFlag(true) |
| | | .setSkipBagManage(true); |
| | | Long tempOutTxId = wmTransactionService.createTransaction(tempOutReq); |
| | | if (passed) { |
| | | MesProBatchDO proBatch = proBatchService.getBatchByCode(reqVO.getBatchCode()); |
| | | if (proBatch != null) { |
| | | MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(proBatch); |
| | | if (wmBatch != null) { |
| | | int updated = materialStockService.updateStockStateByBatchId(wmBatch.getId(), |
| | | MesWmStockStateEnum.QUALIFIED.getState(), MesWmStockStateEnum.TEMP.getState()); |
| | | log.info("[submit][质检合格,批次 {} 库存由暂存库转合格库,影响 {} 条]", reqVO.getBatchCode(), updated); |
| | | // B1. 质检合格 → 该袋转入操作员选择的合格库,写入合格入库事务(跳过袋码自动维护) |
| | | MesWmMaterialStockDO qualifiedStock = getOrCreateQualifiedStock(tempStock, qualifiedWarehouse.getId(), qualifiedLocationId, qualifiedAreaId); |
| | | MesWmTransactionSaveReqDTO qualInReq = new MesWmTransactionSaveReqDTO() |
| | | .setType(MesWmTransactionTypeEnum.IN.getType()) |
| | | .setBizType(MesBizTypeConstants.QC_SEED_QUALITY) |
| | | .setBizId(quality.getId()) |
| | | .setBizCode(reqVO.getCode()) |
| | | .setBizLineId(quality.getId()) |
| | | .setItemId(batch.getItemId()) |
| | | .setQuantity(BigDecimal.ONE) |
| | | .setBatchId(qualifiedStock.getBatchId()) |
| | | .setBatchCode(qualifiedStock.getBatchCode()) |
| | | .setBagCodeId(bag.getId()) |
| | | .setBagCode(bag.getCode()) |
| | | .setWarehouseId(qualifiedStock.getWarehouseId()) |
| | | .setLocationId(qualifiedStock.getLocationId()) |
| | | .setAreaId(qualifiedStock.getAreaId()) |
| | | .setVendorId(qualifiedStock.getVendorId()) |
| | | .setMaterialStockId(qualifiedStock.getId()) |
| | | .setCheckFlag(false) |
| | | .setStockState(MesWmStockStateEnum.QUALIFIED.getState()) |
| | | .setSkipBagManage(true); |
| | | Long qualInTxId = wmTransactionService.createTransaction(qualInReq); |
| | | // B2. 同步迁移袋码明细到合格库存落点 |
| | | materialStockBagService.moveIn(qualInReq, qualifiedStock, qualInTxId); |
| | | bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.QUALIFIED.getStatus()); |
| | | log.info("[submit][质检合格,袋码 {} 由暂存库转合格库 {}]", bag.getCode(), qualifiedWarehouse.getName()); |
| | | } else { |
| | | // C. 质检不合格 → 该袋报废;暂存出库事务已扣减暂存库存,标记袋码报废 |
| | | materialStockBagService.scrap(tempOutReq, tempOutTxId); |
| | | bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.UNQUALIFIED.getStatus()); |
| | | log.info("[submit][质检不合格,袋码 {} 报废]", bag.getCode()); |
| | | } |
| | | return quality.getId(); |
| | | } |
| | | |
| | | /** |
| | | * 定位该袋的暂存库存汇总记录(只定位,不扣减;扣减由质检暂存出库事务完成)。 |
| | | * <p> |
| | | * 优先严格按袋码锁定其活动明细对应的暂存记录;历史无明细时回退按批次定位并补建明细。 |
| | | */ |
| | | private MesWmMaterialStockDO resolveTempStock(MesProBagCodeDO bag, MesWmBatchDO wmBatch, MesWmMaterialStockBagDO stockBag) { |
| | | if (stockBag != null && stockBag.getMaterialStockId() != null) { |
| | | MesWmMaterialStockDO stock = materialStockMapper.selectById(stockBag.getMaterialStockId()); |
| | | if (stock != null && MesWmStockStateEnum.TEMP.getState().equals(stock.getStockState())) { |
| | | return stock; |
| | | } |
| | | } |
| | | MesWmMaterialStockDO tempStock = materialStockMapper.selectStockListByBatchId(wmBatch.getId()).stream() |
| | | .filter(s -> MesWmStockStateEnum.TEMP.getState().equals(s.getStockState())) |
| | | .max(Comparator.comparing(MesWmMaterialStockDO::getQuantity)) |
| | | .orElse(null); |
| | | if (tempStock == null) { |
| | | throw new IllegalArgumentException("该批次暂存库存不足,无法质检"); |
| | | } |
| | | getOrCreateStockBag(bag, tempStock); |
| | | return tempStock; |
| | | } |
| | | |
| | | /** |
| | | * 质检合格:将质检通过的那袋转入操作员选择的合格库(同组合键合格库记录 +1,不存在则新建)。 |
| | | * |
| | | * @param tempStock 从暂存库扣减后返回的暂存记录(携带 item/batch/vendor 维度) |
| | | * @param qualifiedWarehouseId 目标合格库仓库编号 |
| | | * @param qualifiedLocationId 目标合格库库区编号(未配置则为 null) |
| | | * @param qualifiedAreaId 目标合格库库位编号(未配置则为 null) |
| | | */ |
| | | private MesWmMaterialStockBagDO getOrCreateStockBag(MesProBagCodeDO bag, MesWmMaterialStockDO tempStock) { |
| | | MesWmMaterialStockBagDO stockBag = materialStockBagMapper.selectByBagCode(bag.getCode()); |
| | | if (stockBag != null) { |
| | | return stockBag; |
| | | } |
| | | stockBag = MesWmMaterialStockBagDO.builder().materialStockId(tempStock.getId()) |
| | | .bagCodeId(bag.getId()).bagCode(bag.getCode()).itemId(tempStock.getItemId()) |
| | | .batchId(tempStock.getBatchId()).batchCode(tempStock.getBatchCode()).vendorId(tempStock.getVendorId()) |
| | | .warehouseId(tempStock.getWarehouseId()).locationId(tempStock.getLocationId()).areaId(tempStock.getAreaId()) |
| | | .stockState(MesWmStockStateEnum.TEMP.getState()).status(1).quantity(BigDecimal.ONE) |
| | | .frozen(false).inboundTime(tempStock.getReceiptTime()).build(); |
| | | materialStockBagMapper.insert(stockBag); |
| | | return stockBag; |
| | | } |
| | | |
| | | private MesWmMaterialStockDO getOrCreateQualifiedStock(MesWmMaterialStockDO tempStock, Long qualifiedWarehouseId, |
| | | Long qualifiedLocationId, Long qualifiedAreaId) { |
| | | MesWmMaterialStockDO qualified = materialStockMapper.selectQualifiedByCompositeKey( |
| | | tempStock.getItemId(), qualifiedWarehouseId, qualifiedLocationId, |
| | | qualifiedAreaId, tempStock.getBatchId(), tempStock.getVendorId()); |
| | | if (qualified != null) { |
| | | return qualified; |
| | | } |
| | | // 不存在则新建合格库记录(仓库/库区/库位采用所选合格库的维度,数量由质检入库事务 +1) |
| | | MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder() |
| | | .itemId(tempStock.getItemId()).itemTypeId(tempStock.getItemTypeId()) |
| | | .warehouseId(qualifiedWarehouseId).locationId(qualifiedLocationId).areaId(qualifiedAreaId) |
| | | .batchId(tempStock.getBatchId()).batchCode(tempStock.getBatchCode()).vendorId(tempStock.getVendorId()) |
| | | .quantity(BigDecimal.ZERO).receiptTime(LocalDateTime.now()) |
| | | .stockState(MesWmStockStateEnum.QUALIFIED.getState()) |
| | | .frozen(false) |
| | | .build(); |
| | | materialStockMapper.insert(newStock); |
| | | return newStock; |
| | | } |
| | | |
| | | /** |
| | | * 由袋码或生产批次解析所属生产批次(袋码优先,批次码兼容临时码) |
| | | */ |
| | | private MesProBatchDO resolveBatch(String code, String batchCode) { |
| | | if (StrUtil.isNotBlank(code)) { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByCode(code); |
| | | if (bag != null && bag.getBatchId() != null) { |
| | | MesProBatchDO batch = proBatchService.getBatch(bag.getBatchId()); |
| | | if (batch != null) { |
| | | return batch; |
| | | } |
| | | } |
| | | } |
| | | // 库位由后台配置服务接管,当前版本保留审计闭环,禁止前端传入库位。 |
| | | return quality.getId(); |
| | | if (StrUtil.isNotBlank(batchCode)) { |
| | | return proBatchService.getBatchByCode(batchCode); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |