| | |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | |
| | | @Slf4j |
| | | @Service |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesProScanEventDO createInbound(MesProScanEventCreateReqVO req) { |
| | | String scanType = (req.getScanType() == null || req.getScanType().isBlank()) ? SCAN_TYPE_BAG : req.getScanType(); |
| | | if (!List.of(SCAN_TYPE_BAG, SCAN_TYPE_PALLET, SCAN_TYPE_BATCH).contains(scanType)) { |
| | | throw new ServiceException(400, "不支持的扫码类型"); |
| | | } |
| | | MesProScanEventDO existing = eventMapper.selectByEventIdForUpdate(req.getEventId()); |
| | | if (existing != null) { |
| | | boolean contentMatch = req.getBagCode().equals(existing.getBagCode()) |
| | | && (req.getDeviceCode() == null || req.getDeviceCode().equals(existing.getDeviceCode())); |
| | | && Objects.equals(req.getDeviceCode(), existing.getDeviceCode()) |
| | | && Objects.equals(req.getLineCode(), existing.getLineCode()) |
| | | && Objects.equals(scanType, existing.getScanType()) |
| | | && Objects.equals(req.getLocationId(), existing.getLocationId()) |
| | | && Objects.equals(req.getAreaId(), existing.getAreaId()) |
| | | && (!SCAN_TYPE_BAG.equals(scanType) || (existing.getQuantity() != null |
| | | && req.getQuantity().compareTo(existing.getQuantity()) == 0)) |
| | | && Objects.equals(String.valueOf(SecurityFrameworkUtils.getLoginUserId()), existing.getCreator()); |
| | | if (!contentMatch) { |
| | | return failed(existing, "MES_SCAN_EVENT_ID_CONFLICT", "事件号已存在但内容不一致"); |
| | | throw new ServiceException(409, "事件号已存在但内容或操作人不一致,请核实原操作"); |
| | | } |
| | | return existing; |
| | | } |
| | | String scanType = (req.getScanType() == null || req.getScanType().isBlank()) ? SCAN_TYPE_BAG : req.getScanType(); |
| | | MesProScanEventDO event = new MesProScanEventDO(); |
| | | event.setEventId(req.getEventId()).setBagCode(req.getBagCode()).setScanType(scanType) |
| | | .setQuantity(req.getQuantity()).setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode()) |
| | | .setRemark(req.getRemark()).setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now()); |
| | | .setRemark(req.getRemark()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId()) |
| | | .setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now()); |
| | | eventMapper.insert(event); |
| | | // 1. 按扫码类型解析扫码目标与入库数量(托盘按已装袋数、批次按批次袋数整批/整托入库) |
| | | Long targetId; |
| | |
| | | Long bagCodeId = null; |
| | | String bagCode = null; |
| | | if (SCAN_TYPE_PALLET.equals(scanType)) { |
| | | MesProPalletDO pallet = palletMapper.selectByCode(req.getBagCode()); |
| | | if (pallet == null) { |
| | | pallet = palletMapper.selectByTempCode(req.getBagCode()); |
| | | } |
| | | MesProPalletDO pallet = palletMapper.selectByScanCodeForUpdate(req.getBagCode()); |
| | | if (pallet == null) return failed(event, "MES_SCAN_PALLET_NOT_EXISTS", "托盘码不存在"); |
| | | if (MesProPalletStatusEnum.CANCELED.getStatus().equals(pallet.getStatus())) |
| | | return failed(event, "MES_SCAN_PALLET_CANCELED", "托盘已作废,不可入库"); |
| | | if (pallet.getInboundEventId() != null) return failed(event, "MES_SCAN_PALLET_ALREADY_INBOUND", "托盘已入库"); |
| | | batch = batchMapper.selectByIdForUpdate(pallet.getBatchId()); |
| | | if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在"); |
| | | batchId = batch.getId(); |
| | | targetId = pallet.getId(); |
| | | quantity = pallet.getBagCount() != null && pallet.getBagCount() > 0 |
| | | ? BigDecimal.valueOf(pallet.getBagCount()) |
| | | : BigDecimal.valueOf(bagCodeMapper.selectCountByPalletId(pallet.getId())); |
| | | quantity = BigDecimal.valueOf(bagCodeMapper.selectPendingInboundCountByPalletId(pallet.getId())); |
| | | } else if (SCAN_TYPE_BATCH.equals(scanType)) { |
| | | batch = batchMapper.selectByCode(req.getBagCode()); |
| | | if (batch == null) { |
| | | batch = batchMapper.selectByTempCode(req.getBagCode()); |
| | | } |
| | | batch = batchMapper.selectByScanCodeForUpdate(req.getBagCode()); |
| | | if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在"); |
| | | if (batch.getInboundEventId() != null) return failed(event, "MES_SCAN_BATCH_ALREADY_INBOUND", "批次已整批入库"); |
| | | batchId = batch.getId(); |
| | | targetId = batch.getId(); |
| | | quantity = batch.getBagQuantity() != null && batch.getBagQuantity() > 0 |
| | | ? BigDecimal.valueOf(batch.getBagQuantity()) |
| | | : BigDecimal.valueOf(bagCodeMapper.selectCountByBatchId(batch.getId())); |
| | | quantity = BigDecimal.valueOf(bagCodeMapper.selectPendingInboundCountByBatchId(batch.getId())); |
| | | } else { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode()); |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(req.getBagCode()); |
| | | if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "袋码不存在"); |
| | | if (MesProBagCodeStatusEnum.CANCELED.getStatus().equals(bag.getStatus())) |
| | | return failed(event, "MES_SCAN_BAG_CANCELED", "袋码已作废,不可入库"); |
| | | event.setBagCodeId(bag.getId()); |
| | | bagCodeId = bag.getId(); |
| | | bagCode = req.getBagCode(); |
| | | // 库存流水保持原追溯码为规范码,业务码仅用于查找同一袋,避免双码拆成两份库存。 |
| | | bagCode = bag.getCode(); |
| | | if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "袋码已入库"); |
| | | batch = batchMapper.selectByIdForUpdate(bag.getBatchId()); |
| | | if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在"); |
| | |
| | | targetId = bag.getId(); |
| | | quantity = req.getQuantity(); |
| | | } |
| | | event.setBatchId(batchId).setBatchCode(batch.getCode()).setItemId(batch.getItemId()); |
| | | if (MesProBatchStatusEnum.CANCELED.getStatus().equals(batch.getStatus())) |
| | | return failed(event, "MES_SCAN_BATCH_CANCELED", "生产批次已作废,不可入库"); |
| | | if (quantity.signum() <= 0) |
| | | return failed(event, "MES_SCAN_NO_PENDING_BAGS", "没有待入库的有效袋码,请勿重复入库"); |
| | | event.setBatchId(batchId).setBatchCode(batch.getCode()).setItemId(batch.getItemId()).setQuantity(quantity); |
| | | MesWmWarehouseDO warehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE); |
| | | if (warehouse == null) return failed(event, "MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS", "WIP虚拟仓不存在"); |
| | | event.setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId()); |
| | |
| | | palletMapper.markInbound(targetId, event.getId()); |
| | | bagCodeMapper.markInboundByPalletId(targetId, event.getId(), req.getLineCode()); |
| | | } else if (SCAN_TYPE_BATCH.equals(scanType)) { |
| | | batchMapper.markBatchInbound(targetId, event.getId(), req.getLineCode(), batch.getBagQuantity()); |
| | | bagCodeMapper.markInboundByBatchId(targetId, event.getId(), req.getLineCode()); |
| | | } else { |
| | | bagCodeMapper.markInbound(targetId, event.getId(), req.getDeviceCode(), req.getLineCode()); |
| | | batchMapper.markInbound(batchId, event.getId(), req.getDeviceCode(), req.getLineCode()); |
| | | } |
| | | palletMapper.markCompletedInboundByBatchId(batchId, event.getId()); |
| | | batchMapper.refreshInboundProgress(batchId, event.getId(), req.getDeviceCode(), req.getLineCode()); |
| | | return event; |
| | | } catch (Exception ex) { |
| | | log.error("[createInbound] 库存事务处理失败,eventId={}, bagCode={}, batchId={}, itemId={}", |