2 天以前 1ba0dbfde5634c8bf2ec20891d6b226b996f6b59
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -14,7 +14,9 @@
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.wm.transaction.MesWmTransactionMapper;
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.wm.MesWmWarehouseTypeEnum;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
@@ -22,6 +24,7 @@
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 cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -57,6 +60,9 @@
    @Resource
    private MesWmWarehouseAreaService areaService;
    @Resource
    private MesWmMaterialStockBagService materialStockBagService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createTransaction(MesWmTransactionSaveReqDTO reqDTO) {
@@ -77,9 +83,12 @@
        // 1.5 关联事务校验
        checkRelatedTransaction(reqDTO);
        // 2.1 获取或创建库存记录(如果传入了 materialStockId 则直接使用,避免重新匹配)
        // 2.1 获取库存记录
        MesWmMaterialStockDO materialStock;
        MesWmTransactionTypeEnum transactionType = MesWmTransactionTypeEnum.valueOf(reqDTO.getType());
        boolean isInbound = transactionType != null && transactionType.isInbound();
        if (reqDTO.getMaterialStockId() != null) {
            // 传入了 materialStockId,直接使用
            materialStock = materialStockService.getMaterialStock(reqDTO.getMaterialStockId());
            if (materialStock == null) {
                throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
@@ -91,29 +100,99 @@
                    || ObjUtil.notEqual(materialStock.getAreaId(), reqDTO.getAreaId())) {
                throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH);
            }
        } else {
        } else if (isInbound) {
            // 入库事务:找不到则自动创建库存记录
            materialStock = materialStockService.getOrCreateMaterialStock(
                    reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                    reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
        } else {
            // 出库事务:库存记录必须已存在,不允许自动创建(除非 checkFlag=false 允许负库存)
            materialStock = materialStockService.getMaterialStockByCompositeKey(
                    reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                    reqDTO.getBatchId(), reqDTO.getVendorId());
            if (materialStock == null) {
                // checkFlag=false 时允许负库存,自动创建库存记录(数量将从 0 扣减为负数)
                boolean allowNegative = !ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true);
                if (allowNegative) {
                    materialStock = materialStockService.getOrCreateMaterialStock(
                            reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                            reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
                } else {
                    throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
                }
            }
        }
        if (materialStock == null || materialStock.getId() == null) {
            throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
        }
        // 2.2 冻结校验(仓库、库区、库位、库存记录)
        checkFrozen(reqDTO, materialStock);
        // 2.3 更新库存数量
        boolean checkFlag = ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true);
        materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag);
        MesWmMaterialStockDO updatedStock = materialStockService.getMaterialStock(materialStock.getId());
        BigDecimal newQuantity = updatedStock.getQuantity();
        // 库存状态:显式指定优先(如扫码入暂存库),否则自动判定——≤0 已出库;
        // >0 时仅目标仓库为合格库(20)才置合格态,否则保持暂存态(10),
        // 避免盘点/调整/调拨等操作把暂存仓库库存翻成合格态
        Integer stockState = reqDTO.getStockState();
        if (stockState == null) {
            if (newQuantity.signum() <= 0) {
                stockState = MesWmStockStateEnum.OUT.getState();
            } else {
                MesWmWarehouseDO warehouse = warehouseService.getWarehouse(reqDTO.getWarehouseId());
                boolean qualifiedWarehouse = warehouse != null
                        && MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(warehouse.getType());
                stockState = qualifiedWarehouse
                        ? MesWmStockStateEnum.QUALIFIED.getState() : MesWmStockStateEnum.TEMP.getState();
            }
        }
        materialStockService.updateMaterialStockState(updatedStock.getId(), stockState);
        // 3. 插入事务流水
        MesWmTransactionDO transaction = MesWmTransactionDO.builder()
                .type(reqDTO.getType()).quantity(reqDTO.getQuantity()).transactionTime(LocalDateTime.now())
                .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode())
                .bagCodeId(reqDTO.getBagCodeId()).bagCode(reqDTO.getBagCode())
                .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId())
                .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId())
                .materialStockId(materialStock.getId()).relatedTransactionId(reqDTO.getRelatedTransactionId())
                .build();
        transactionMapper.insert(transaction);
        // 4. 带袋码时,同步维护库存袋码明细(入库建立/激活、出库置已出库、移入迁移、报废)
        boolean skipBagManage = ObjUtil.defaultIfNull(reqDTO.getSkipBagManage(), false);
        if (!skipBagManage && (StrUtil.isNotBlank(reqDTO.getBagCode()) || reqDTO.getBagCodeId() != null)) {
            handleStockBag(reqDTO, materialStock, transaction.getId(), transactionType);
        }
        return transaction.getId();
    }
    /**
     * 按事务类型维护袋码明细,保证汇总库存、袋码明细、事务流水三者一致。
     */
    private void handleStockBag(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO materialStock,
                                Long transactionId, MesWmTransactionTypeEnum transactionType) {
        switch (transactionType) {
            case MOVE_IN:
                // 移入/调拨/质检迁移:更新明细位置与状态
                materialStockBagService.moveIn(reqDTO, materialStock, transactionId);
                break;
            case IN:
            case ADJUST_IN:
                // 入库/盘盈:建立或激活明细
                materialStockBagService.inbound(reqDTO, materialStock, transactionId);
                break;
            case MOVE_OUT:
            case OUT:
            case ADJUST_OUT:
                // 移出/出库/盘亏:置为已出库
                materialStockBagService.outbound(reqDTO, transactionId);
                break;
            default:
                break;
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createTransactionList(List<MesWmTransactionSaveReqDTO> reqDTOs) {