package cn.iocoder.yudao.module.mes.service.pro.scan; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventCreateReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO; 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.pro.pallet.MesProPalletDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.scan.MesProScanEventMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.MesProScanEventStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; 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.MesWmWarehouseService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; 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.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service @Validated public class MesProScanEventServiceImpl implements MesProScanEventService { @Resource private MesProScanEventMapper eventMapper; @Resource private MesProBagCodeMapper bagCodeMapper; @Resource private MesProBatchMapper batchMapper; @Resource private MesProPalletMapper palletMapper; @Resource private MesWmTransactionService transactionService; @Resource private MesWmWarehouseService warehouseService; @Resource private MesWmBatchService mesWmBatchService; /** 扫码类型:袋码(默认) */ private static final String SCAN_TYPE_BAG = "BAG"; /** 扫码类型:托盘码(按托盘已装袋数整托入库) */ private static final String SCAN_TYPE_PALLET = "PALLET"; /** 扫码类型:批次码(按批次袋数整批入库) */ private static final String SCAN_TYPE_BATCH = "BATCH"; @Override @Transactional(rollbackFor = Exception.class) public MesProScanEventDO createInbound(MesProScanEventCreateReqVO req) { MesProScanEventDO existing = eventMapper.selectByEventIdForUpdate(req.getEventId()); if (existing != null) { boolean contentMatch = req.getBagCode().equals(existing.getBagCode()) && (req.getDeviceCode() == null || req.getDeviceCode().equals(existing.getDeviceCode())); if (!contentMatch) { return failed(existing, "MES_SCAN_EVENT_ID_CONFLICT", "事件号已存在但内容不一致"); } 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()); eventMapper.insert(event); // 1. 按扫码类型解析扫码目标与入库数量(托盘按已装袋数、批次按批次袋数整批/整托入库) Long targetId; Long batchId; BigDecimal quantity; MesProBatchDO batch; // 单袋扫码入库时传递袋码,供库存袋码明细在入库第一时间建立;托盘/批次整批入库为批量,不逐袋建明细 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()); } if (pallet == null) return failed(event, "MES_SCAN_PALLET_NOT_EXISTS", "托盘码不存在"); 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())); } else if (SCAN_TYPE_BATCH.equals(scanType)) { batch = batchMapper.selectByCode(req.getBagCode()); if (batch == null) { batch = batchMapper.selectByTempCode(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())); } else { MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode()); if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "袋码不存在"); event.setBagCodeId(bag.getId()); bagCodeId = bag.getId(); bagCode = req.getBagCode(); 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", "生产批次不存在"); batchId = batch.getId(); targetId = bag.getId(); quantity = req.getQuantity(); } event.setBatchId(batchId).setBatchCode(batch.getCode()).setItemId(batch.getItemId()); 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()); try { // 生产批次桥接仓库批次:库存/流水统一挂在 mes_wm_batch 上,扫码入暂存库待质检 MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch); if (wmBatch == null) { return failed(event, "MES_SCAN_WM_BATCH_NOT_EXISTS", "仓库批次桥接失败"); } Long txId = transactionService.createTransaction(new MesWmTransactionSaveReqDTO() .setType(MesWmTransactionTypeEnum.IN.getType()).setBizType(MesBizTypeConstants.WM_PRODUCT_PRODUCE) .setBizId(batchId).setBizCode(batch.getCode()).setBizLineId(targetId) .setItemId(batch.getItemId()).setQuantity(quantity) .setBatchId(wmBatch.getId()).setBatchCode(batch.getCode()) .setBagCodeId(bagCodeId).setBagCode(bagCode) .setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId()) .setReceiptTime(LocalDateTime.now()).setCheckFlag(true) .setStockState(MesWmStockStateEnum.TEMP.getState())); event.setTransactionId(txId).setStatus(MesProScanEventStatusEnum.SUCCESS).setEventTime(LocalDateTime.now()); eventMapper.updateById(event); // 2. 回写入库标记(托盘/批次整托/整批入库时同步标记其下未入库袋码,保持状态一致) if (SCAN_TYPE_PALLET.equals(scanType)) { 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()); } return event; } catch (Exception ex) { log.error("[createInbound] 库存事务处理失败,eventId={}, bagCode={}, batchId={}, itemId={}", req.getEventId(), req.getBagCode(), batchId, batch.getItemId(), ex); throw ex; } } private MesProScanEventDO failed(MesProScanEventDO event, String code, String message) { event.setStatus(MesProScanEventStatusEnum.FAILED).setErrorCode(code).setErrorMessage(message) .setFailedTime(LocalDateTime.now()).setEventTime(LocalDateTime.now()); eventMapper.updateById(event); return event; } @Override public MesProScanEventDO getByEventId(String eventId) { return eventMapper.selectOne(MesProScanEventDO::getEventId, eventId); } @Override public PageResult getPage(MesProScanEventPageReqVO reqVO) { return eventMapper.selectPage(reqVO); } @Override public MesProScanEventDO getLatest(String deviceCode) { return eventMapper.selectLatest(deviceCode); } @Override public List getByBatchId(Long batchId) { return eventMapper.selectListByBatchId(batchId); } @Override public Map getDashboard() { Map result = new HashMap<>(); result.put("latest", eventMapper.selectLatest(null)); result.put("total", eventMapper.selectCount(null)); result.put("success", eventMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper().eq(MesProScanEventDO::getStatus, MesProScanEventStatusEnum.SUCCESS))); result.put("failed", eventMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper().eq(MesProScanEventDO::getStatus, MesProScanEventStatusEnum.FAILED))); return result; } }