修复:
生产领料后的退料应该是选择领料的记录,不是选择库存的记录(同时库存数据需要根据领料退料变动,比如库存100,领用80,然后领用80中退料30,库存应由原来的20变成50)
已修改18个文件
236 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/MesWmProductIssueLineController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/vo/line/MesWmReturnIssueLineRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/vo/line/MesWmReturnIssueLineSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnissue/MesWmReturnIssueLineDO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productissue/MesWmProductIssueLineMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productissue/MesWmProductIssueMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/returnissue/MesWmReturnIssueLineMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/MesWmProductIssueLineController.java
@@ -8,6 +8,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueBomItemRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineReturnableRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
@@ -155,6 +156,15 @@
        return success(result);
    }
    @GetMapping("/returnable-list")
    @Operation(summary = "获得领料出库单可退料行列表",
            description = "返回指定工单下已完成领料单的可退料行,可退数量 = 已领数量 - 已退数量")
    @Parameter(name = "workOrderId", description = "生产工单ID", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mes:wm-product-issue:query')")
    public CommonResult<List<MesWmProductIssueLineReturnableRespVO>> getReturnableList(@RequestParam("workOrderId") Long workOrderId) {
        return success(issueLineService.getReturnableList(workOrderId));
    }
    // ==================== 拼接 VO ====================
    private List<MesWmProductIssueLineRespVO> buildRespVOList(List<MesWmProductIssueLineDO> list) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/vo/line/MesWmReturnIssueLineRespVO.java
@@ -16,6 +16,9 @@
    @Schema(description = "退料单 ID", example = "1")
    private Long issueId;
    @Schema(description = "领料单行 ID", example = "1")
    private Long issueLineId;
    @Schema(description = "生产任务 ID", example = "1")
    private Long taskId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/vo/line/MesWmReturnIssueLineSaveReqVO.java
@@ -18,6 +18,9 @@
    @NotNull(message = "退料单 ID 不能为空")
    private Long issueId;
    @Schema(description = "领料单行 ID", example = "1")
    private Long issueLineId;
    @Schema(description = "生产任务 ID", example = "1")
    private Long taskId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnissue/MesWmReturnIssueLineDO.java
@@ -39,6 +39,13 @@
     */
    private Long issueId;
    /**
     * 领料单行 ID
     *
     * 关联 {@link cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO#getId()}
     * 用于追踪退料来源
     */
    private Long issueLineId;
    /**
     * 生产任务 ID
     *
     * 关联 {@link MesProTaskDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productissue/MesWmProductIssueLineMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
@@ -29,6 +30,10 @@
        return selectList(MesWmProductIssueLineDO::getIssueId, issueId);
    }
    default List<MesWmProductIssueLineDO> selectListByIssueIds(Collection<Long> issueIds) {
        return selectList(MesWmProductIssueLineDO::getIssueId, issueIds);
    }
    default void deleteByIssueId(Long issueId) {
        delete(MesWmProductIssueLineDO::getIssueId, issueId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productissue/MesWmProductIssueMapper.java
@@ -7,6 +7,8 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * MES 领料出库单 Mapper
 */
@@ -29,4 +31,10 @@
        return selectOne(MesWmProductIssueDO::getCode, code);
    }
    default List<MesWmProductIssueDO> selectListByWorkOrderIdAndStatus(Long workOrderId, Integer status) {
        return selectList(new LambdaQueryWrapperX<MesWmProductIssueDO>()
                .eq(MesWmProductIssueDO::getWorkOrderId, workOrderId)
                .eq(MesWmProductIssueDO::getStatus, status));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/returnissue/MesWmReturnIssueLineMapper.java
@@ -10,6 +10,7 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/**
@@ -31,6 +32,10 @@
        return selectList(MesWmReturnIssueLineDO::getIssueId, issueId);
    }
    default List<MesWmReturnIssueLineDO> selectListByIssueLineIds(Collection<Long> issueLineIds) {
        return selectList(MesWmReturnIssueLineDO::getIssueLineId, issueLineIds);
    }
    default void deleteByIssueId(Long issueId) {
        delete(MesWmReturnIssueLineDO::getIssueId, issueId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -117,6 +117,24 @@
                                                  Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime);
    /**
     * 创建新的库存记录(不查找已有记录,始终创建新记录)
     *
     * 用于退料等场景,需要为每次退料创建独立的库存记录
     *
     * @param itemId      物料编号
     * @param warehouseId 仓库编号
     * @param locationId  库区编号
     * @param areaId      库位编号
     * @param batchId     批次编号
     * @param batchCode   批次号
     * @param vendorId    供应商编号
     * @param receiptTime 入库时间(为空则默认当前时间)
     * @return 新创建的库存记录
     */
    MesWmMaterialStockDO createMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
                                              Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime);
    /**
     * 按组合键查找库存记录(只查不创建)
     *
     * @param itemId      物料编号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -203,6 +203,13 @@
        }
        // 2. 不存在则新建
        return createMaterialStock(itemId, warehouseId, locationId, areaId, batchId, batchCode, vendorId, receiptTime);
    }
    @Override
    public MesWmMaterialStockDO createMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
                                                     Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) {
        // 始终创建新库存记录
        MesMdmItemDO item = mdmItemService.validateItemExists(itemId);
        MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder()
                .itemId(itemId).itemTypeId(item.getCategoryId())
@@ -213,7 +220,7 @@
                .frozen(false)
                .build();
        materialStockMapper.insert(newStock);
        // 3. 重新查询,确保拿到数据库生成的完整记录(id、createTime 等)
        // 重新查询,确保拿到数据库生成的完整记录(id、createTime 等)
        MesWmMaterialStockDO inserted = materialStockMapper.selectById(newStock.getId());
        return inserted != null ? inserted : newStock;
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineService.java
@@ -2,6 +2,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineReturnableRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO;
import jakarta.validation.Valid;
@@ -74,4 +75,12 @@
     */
    MesWmProductIssueLineDO validateProductIssueLineExists(Long id);
    /**
     * 获取指定工单下已完成领料单的可退料行列表
     *
     * @param workOrderId 生产工单ID
     * @return 可退料行列表(可退数量 > 0)
     */
    List<MesWmProductIssueLineReturnableRespVO> getReturnableList(Long workOrderId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java
@@ -2,25 +2,41 @@
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineReturnableRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnissue.MesWmReturnIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueLineMapper;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueDetailService;
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueLineService;
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueService;
import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueLineService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_ISSUE_LINE_ITEM_NOT_IN_BOM;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_ISSUE_LINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
 * MES 领料出库单行 Service 实现类
@@ -36,12 +52,17 @@
    @Lazy
    private MesWmProductIssueService issueService;
    @Resource
    private MesMdmItemService mdmItemService;
    private MesMdItemService mdmItemService;
    @Resource
    private MesProWorkOrderBomService workOrderBomService;
    @Lazy
    private MesWmReturnIssueLineService returnIssueLineService;
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    @Lazy
    private MesWmProductIssueDetailService issueDetailService;
    @Resource
    private MesProWorkOrderBomService workOrderBomService;
    @Override
    public Long createProductIssueLine(MesWmProductIssueLineSaveReqVO createReqVO) {
@@ -114,7 +135,7 @@
        // 校验物料在工单 BOM 中
        validateItemInWorkOrderBom(reqVO.getIssueId(), reqVO.getItemId());
        // 校验物料存在
        mdmItemService.mesValidateItemExists(reqVO.getItemId());
        mdmItemService.validateItemExists(reqVO.getItemId());
    }
    private void validateItemInWorkOrderBom(Long issueId, Long itemId) {
@@ -132,4 +153,68 @@
        }
    }
    @Override
    public List<MesWmProductIssueLineReturnableRespVO> getReturnableList(Long workOrderId) {
        // 1. 查询该工单下已完成的领料单
        List<MesWmProductIssueDO> finishedIssues = issueService.getFinishedIssuesByWorkOrderId(workOrderId);
        if (CollUtil.isEmpty(finishedIssues)) {
            return Collections.emptyList();
        }
        // 2. 获取这些领料单的所有行
        List<Long> issueIds = CollectionUtils.convertList(finishedIssues, MesWmProductIssueDO::getId);
        List<MesWmProductIssueLineDO> lines = issueLineMapper.selectListByIssueIds(issueIds);
        if (CollUtil.isEmpty(lines)) {
            return Collections.emptyList();
        }
        // 3. 批量查询已退数量(按 issueLineId 汇总)
        List<Long> lineIds = CollectionUtils.convertList(lines, MesWmProductIssueLineDO::getId);
        List<MesWmReturnIssueLineDO> returnLines = returnIssueLineService.getReturnIssueLineListByIssueLineIds(lineIds);
        // 按 issueLineId 汇总退料数量
        Map<Long, BigDecimal> returnedQtyMap = new java.util.HashMap<>();
        for (MesWmReturnIssueLineDO returnLine : returnLines) {
            if (returnLine.getIssueLineId() != null && returnLine.getQuantity() != null) {
                returnedQtyMap.merge(returnLine.getIssueLineId(), returnLine.getQuantity(), BigDecimal::add);
            }
        }
        // 4. 批量查询物料和单位信息
        Map<Long, MesMdItemDO> itemMap = mdmItemService.getItemMap(
                CollectionUtils.convertSet(lines, MesWmProductIssueLineDO::getItemId));
        Map<Long, MesMdUnitMeasureDO> unitMap = unitMeasureService.getUnitMeasureMap(
                CollectionUtils.convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
        // 5. 组装结果,过滤可退数量 > 0 的行
        List<MesWmProductIssueLineReturnableRespVO> result = new ArrayList<>();
        for (MesWmProductIssueLineDO line : lines) {
            BigDecimal issuedQty = line.getQuantity() != null ? line.getQuantity() : BigDecimal.ZERO;
            BigDecimal returnedQty = returnedQtyMap.getOrDefault(line.getId(), BigDecimal.ZERO);
            BigDecimal returnableQty = issuedQty.subtract(returnedQty);
            if (returnableQty.compareTo(BigDecimal.ZERO) > 0) {
                MesWmProductIssueLineReturnableRespVO vo = new MesWmProductIssueLineReturnableRespVO();
                vo.setId(line.getId());
                vo.setItemId(line.getItemId());
                vo.setBatchId(line.getBatchId());
                vo.setQuantity(returnableQty);
                // 填充物料信息
                MesMdItemDO item = itemMap.get(line.getItemId());
                if (item != null) {
                    vo.setItemCode(item.getCode());
                    vo.setItemName(item.getName());
                    vo.setSpecification(item.getSpecification());
                    // 填充单位信息
                    MesMdUnitMeasureDO unit = unitMap.get(item.getUnitMeasureId());
                    vo.setUnitMeasureName(unit != null ? unit.getName() : null);
                }
                result.add(vo);
            }
        }
        return result;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueService.java
@@ -6,6 +6,8 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * MES 领料出库单 Service 接口
 */
@@ -101,4 +103,12 @@
     */
    Boolean checkProductIssueQuantity(Long id);
    /**
     * 获取指定工单下已完成的领料单列表
     *
     * @param workOrderId 生产工单ID
     * @return 已完成的领料单列表
     */
    List<MesWmProductIssueDO> getFinishedIssuesByWorkOrderId(Long workOrderId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
@@ -410,4 +410,9 @@
        }
    }
    @Override
    public List<MesWmProductIssueDO> getFinishedIssuesByWorkOrderId(Long workOrderId) {
        return issueMapper.selectListByWorkOrderIdAndStatus(workOrderId, MesWmProductIssueStatusEnum.FINISHED.getStatus());
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineService.java
@@ -8,6 +8,7 @@
import jakarta.validation.Valid;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
/**
@@ -104,4 +105,12 @@
    void updateReturnIssueLineWhenRqcFinish(Long sourceLineId, Long sourceDocId, Integer checkResult,
                                            BigDecimal qualifiedQuantity, BigDecimal unqualifiedQuantity);
    /**
     * 根据领料单行ID列表获取退料单行列表
     *
     * @param issueLineIds 领料单行ID列表
     * @return 退料单行列表
     */
    List<MesWmReturnIssueLineDO> getReturnIssueLineListByIssueLineIds(Collection<Long> issueLineIds);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineServiceImpl.java
@@ -20,6 +20,8 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -205,4 +207,12 @@
        }
    }
    @Override
    public List<MesWmReturnIssueLineDO> getReturnIssueLineListByIssueLineIds(Collection<Long> issueLineIds) {
        if (CollUtil.isEmpty(issueLineIds)) {
            return Collections.emptyList();
        }
        return issueLineMapper.selectListByIssueLineIds(issueLineIds);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueServiceImpl.java
@@ -268,12 +268,13 @@
                }
            }
            // 2.3 入上架目标仓库(库存增加)
            // 2.3 入上架目标仓库(库存增加)— 始终创建新的库存记录,不更新已有记录
            wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                    .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
                    .setQuantity(detail.getQuantity()) // 库存增加(退料回仓)
                    .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
                    .setWarehouseId(targetWarehouseId).setLocationId(targetLocationId).setAreaId(targetAreaId)
                    .setForceCreateNewStock(true) // 强制创建新库存记录
                    .setBizType(MesBizTypeConstants.WM_RETURN_ISSUE).setBizId(issue.getId())
                    .setBizCode(issue.getCode()).setBizLineId(detail.getLineId())
                    .setRelatedTransactionId(outTransactionId)); // 关联出库事务
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -81,6 +81,8 @@
        MesWmMaterialStockDO materialStock;
        MesWmTransactionTypeEnum transactionType = MesWmTransactionTypeEnum.valueOf(reqDTO.getType());
        boolean isInbound = transactionType != null && transactionType.isInbound();
        boolean forceCreateNewStock = Boolean.TRUE.equals(reqDTO.getForceCreateNewStock());
        if (reqDTO.getMaterialStockId() != null) {
            // 传入了 materialStockId,直接使用
            materialStock = materialStockService.getMaterialStock(reqDTO.getMaterialStockId());
@@ -95,10 +97,16 @@
                throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH);
            }
        } else if (isInbound) {
            // 入库事务:找不到则自动创建库存记录
            // 入库事务:根据 forceCreateNewStock 标志决定是创建新记录还是查找/创建
            if (forceCreateNewStock) {
                materialStock = materialStockService.createMaterialStock(
                        reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                        reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
            } else {
            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(
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
@@ -107,14 +107,23 @@
    private Long materialStockId;
    /**
     * 关联事务 ID(成对事务中,入库事务关联出库事务)
     * 关联事务 ID
     */
    private Long relatedTransactionId;
    // ===== 可选控制(非存储字段) =====
    /**
     * 是否强制创建新库存记录(仅入库事务有效)
     *
     * 为 true 时,入库事务始终创建新的库存记录,不查找已有记录
     * 用于退料等场景,需要为每次退料创建独立的库存记录
     */
    private Boolean forceCreateNewStock;
    /**
     * 是否校验库存充足(默认 true;线边库消耗等场景设 false 允许负库存)
     * 是否校验库存充足(仅出库事务有效)
     *
     * 为 true(默认)时,出库数量不能超过现有库存
     * 为 false 时,允许负库存(如虚拟线边库场景)
     */
    private Boolean checkFlag;