2 天以前 1ba0dbfde5634c8bf2ec20891d6b226b996f6b59
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
@@ -5,74 +5,155 @@
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) {
            if (!req.getBagCode().equals(existing.getBagCode()) || !req.getDeviceCode().equals(existing.getDeviceCode())) {
            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()).setQuantity(req.getQuantity())
                .setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode()).setRemark(req.getRemark())
                .setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now());
        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);
        MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode());
        if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "袋码不存在");
        event.setBagCodeId(bag.getId());
        if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "袋码已入库");
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(bag.getBatchId());
        if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在");
        event.setBatchId(batch.getId()).setBatchCode(batch.getCode()).setItemId(batch.getItemId());
        // 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(batch.getId()).setBizCode(batch.getCode()).setBizLineId(bag.getId())
                    .setItemId(batch.getItemId()).setQuantity(req.getQuantity()).setBatchCode(batch.getCode())
                    .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));
                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true)
                    .setStockState(MesWmStockStateEnum.TEMP.getState()));
            event.setTransactionId(txId).setStatus(MesProScanEventStatusEnum.SUCCESS).setEventTime(LocalDateTime.now());
            eventMapper.updateById(event);
            bagCodeMapper.markInbound(bag.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
            batchMapper.markInbound(batch.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
            // 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) {
            return failed(event, "MES_SCAN_TRANSACTION_FAILED", "库存事务处理失败");
            log.error("[createInbound] 库存事务处理失败,eventId={}, bagCode={}, batchId={}, itemId={}",
                    req.getEventId(), req.getBagCode(), batchId, batch.getItemId(), ex);
            throw ex;
        }
    }
@@ -86,6 +167,7 @@
    @Override public MesProScanEventDO getByEventId(String eventId) { return eventMapper.selectOne(MesProScanEventDO::getEventId, eventId); }
    @Override public PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO) { return eventMapper.selectPage(reqVO); }
    @Override public MesProScanEventDO getLatest(String deviceCode) { return eventMapper.selectLatest(deviceCode); }
    @Override public List<MesProScanEventDO> getByBatchId(Long batchId) { return eventMapper.selectListByBatchId(batchId); }
    @Override public Map<String, Object> getDashboard() {
        Map<String, Object> result = new HashMap<>();
        result.put("latest", eventMapper.selectLatest(null));