liu
2026-09-04 569df4daf76eb586f1add62707d0ffa2b7ff20f2
Merge remote-tracking branch 'origin/dev_business' into dev_business
已添加2个文件
已修改33个文件
439 ■■■■ 文件已修改
workbench-current-state.png 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcRespVO.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcRespVO.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcRespVO.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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/productissue/vo/line/MesWmProductIssueLineReturnableRespVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | 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/enums/DictTypeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | 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 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/application-local.yaml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/application-yzfx.yaml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
workbench-current-state.png
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
@@ -46,14 +46,14 @@
    @PostMapping("/create")
    @Operation(summary = "创建计量单位")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:create')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:create')")
    public CommonResult<Long> createUnitMeasure(@Valid @RequestBody MdmUnitMeasureSaveReqVO createReqVO) {
        return success(unitMeasureService.createUnitMeasure(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新计量单位")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:update')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:update')")
    public CommonResult<Boolean> updateUnitMeasure(@Valid @RequestBody MdmUnitMeasureSaveReqVO updateReqVO) {
        unitMeasureService.updateUnitMeasure(updateReqVO);
        return success(true);
@@ -63,7 +63,7 @@
    @Operation(summary = "更新计量单位状态")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @Parameter(name = "status", description = "状态", required = true, example = "0")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:update')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:update')")
    public CommonResult<Boolean> updateUnitMeasureStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
        unitMeasureService.updateUnitMeasureStatus(id, status);
        return success(true);
@@ -72,7 +72,7 @@
    @DeleteMapping("/delete")
    @Operation(summary = "删除计量单位")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:delete')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:delete')")
    public CommonResult<Boolean> deleteUnitMeasure(@RequestParam("id") Long id) {
        unitMeasureService.deleteUnitMeasure(id);
        return success(true);
@@ -81,21 +81,21 @@
    @GetMapping("/get")
    @Operation(summary = "获得计量单位")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:query')")
    public CommonResult<MdmUnitMeasureDO> getUnitMeasure(@RequestParam("id") Long id) {
        return success(unitMeasureService.getUnitMeasure(id));
    }
    @GetMapping("/page")
    @Operation(summary = "获得计量单位分页")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:query')")
    public CommonResult<PageResult<MdmUnitMeasureDO>> getUnitMeasurePage(@Valid MdmUnitMeasurePageReqVO pageReqVO) {
        return success(unitMeasureService.getUnitMeasurePage(pageReqVO));
    }
    @GetMapping("/list")
    @Operation(summary = "获得计量单位列表")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:query')")
    public CommonResult<List<MdmUnitMeasureDO>> getUnitMeasureList() {
        return success(unitMeasureService.getUnitMeasureList(null));
    }
@@ -103,14 +103,14 @@
    @GetMapping("/list-by-ids")
    @Operation(summary = "获得计量单位列表")
    @Parameter(name = "ids", description = "编号列表", required = true, example = "1,2,3")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:query')")
    public CommonResult<List<MdmUnitMeasureDO>> getUnitMeasureListByIds(@RequestParam("ids") List<Long> ids) {
        return success(unitMeasureService.getUnitMeasureList(ids));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出计量单位 Excel")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:export')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportUnitMeasureExcel(@Valid MdmUnitMeasurePageReqVO pageReqVO,
                                       HttpServletResponse response) throws IOException {
@@ -138,7 +138,7 @@
    @GetMapping("/get-import-template")
    @Operation(summary = "获得导入计量单位模板")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:import')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:import')")
    public void importTemplate(HttpServletResponse response) throws IOException {
        List<MdmUnitMeasureImportExcelVO> list = Arrays.asList(
                MdmUnitMeasureImportExcelVO.builder().code("PC").name("片").primaryFlag(true).changeRate(new java.math.BigDecimal("1")).status(0).remark("示例:主单位").build(),
@@ -153,7 +153,7 @@
            @Parameter(name = "file", description = "Excel æ–‡ä»¶", required = true),
            @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
    })
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:import')")
    @PreAuthorize("@ss.hasPermission('mdm:unit:import')")
    public CommonResult<MdmUnitMeasureImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
                                                                @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
        List<MdmUnitMeasureImportExcelVO> list = ExcelUtils.read(file, MdmUnitMeasureImportExcelVO.class);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java
@@ -17,15 +17,13 @@
    @NotNull(message = "工艺路线编号不能为空")
    private Long routeId;
    @Schema(description = "工序编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "工序编号不能为空")
    @Schema(description = "工序编号", example = "1")
    private Long processId;
    @Schema(description = "工艺参数模板编号", example = "1")
    private Long paramTemplateId;
    @Schema(description = "序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "序号不能为空")
    @Schema(description = "序号", example = "1")
    private Integer sort;
    @Schema(description = "与下一道工序关系", example = "0")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
@@ -245,7 +245,9 @@
            }
            List<MesProWorkOrderBomDO> items = new ArrayList<>();
            for (MesProWorkOrderBomDO bom : bomList) {
                if (bom.getProcessId().equals(process.getProcessId())) {
                // è·³è¿‡æ— å·¥åºç¼–号的投料行(工单 BOM ä¸­ä»…按工序维护投料明细,无工序的行不参与展开,
                // ä¸” getProcessId å¯èƒ½ä¸º null,直接 equals ä¼š NPE)
                if (Objects.equals(bom.getProcessId(), process.getProcessId())) {
                    items.add(bom);
                }
            }
@@ -263,7 +265,7 @@
        // 3. ä»¥æœ€åŽä¸€é“工序的投料为起点
        Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
        for (MesProWorkOrderBomDO bom : bomList) {
            if (bom.getProcessId().equals(lastProcess.getProcessId())) {
            if (Objects.equals(bom.getProcessId(), lastProcess.getProcessId())) {
                currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add);
            }
        }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java
@@ -2,6 +2,9 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -26,7 +29,8 @@
    private String name;
    @Schema(description = "IPQC æ£€éªŒç±»åž‹", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @ExcelProperty("检验类型")
    @ExcelProperty(value = "检验类型", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_IPQC_TYPE)
    private Integer type;
    @Schema(description = "检验模板 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@@ -89,9 +93,11 @@
    private String itemName;
    @Schema(description = "规格型号", example = "100mm*50mm")
    @ExcelProperty("规格型号")
    private String itemSpecification;
    @Schema(description = "单位名称", example = "个")
    @ExcelProperty("单位")
    private String unitName;
    // ========== æ•°é‡ ==========
@@ -143,7 +149,8 @@
    // ========== æ£€éªŒ ==========
    @Schema(description = "检测结果", example = "1")
    @ExcelProperty("检测结果")
    @ExcelProperty(value = "检测结果", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    @Schema(description = "检测日期")
@@ -158,7 +165,8 @@
    private String inspectorNickname;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @ExcelProperty("状态")
    @ExcelProperty(value = "状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_ORDER_STATUS)
    private Integer status;
    @Schema(description = "备注", example = "备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcRespVO.java
@@ -2,6 +2,9 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -48,6 +51,7 @@
    private Long vendorId;
    @Schema(description = "供应商简称", example = "供应商A")
    @ExcelProperty("供应商简称")
    private String vendorNickname;
    @Schema(description = "供应商批次号", example = "VB20250101")
@@ -114,7 +118,8 @@
    // ========== æ£€éªŒ ==========
    @Schema(description = "检测结果", example = "1")
    @ExcelProperty("检测结果")
    @ExcelProperty(value = "检测结果", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    @Schema(description = "来料日期")
@@ -133,7 +138,8 @@
    private String inspectorNickname;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @ExcelProperty("状态")
    @ExcelProperty(value = "状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_ORDER_STATUS)
    private Integer status;
    @Schema(description = "备注", example = "备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcRespVO.java
@@ -2,6 +2,9 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -69,9 +72,11 @@
    private String itemName;
    @Schema(description = "规格型号", example = "100mm*50mm")
    @ExcelProperty("规格型号")
    private String itemSpecification;
    @Schema(description = "单位名称", example = "个")
    @ExcelProperty("单位")
    private String unitName;
    // ========== æ•°é‡ ==========
@@ -121,7 +126,8 @@
    // ========== æ£€éªŒ ==========
    @Schema(description = "检测结果", example = "1")
    @ExcelProperty("检测结果")
    @ExcelProperty(value = "检测结果", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    @Schema(description = "出货日期")
@@ -140,7 +146,8 @@
    private String inspectorNickname;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @ExcelProperty("状态")
    @ExcelProperty(value = "状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_ORDER_STATUS)
    private Integer status;
    @Schema(description = "备注", example = "备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcRespVO.java
@@ -2,6 +2,9 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -31,6 +34,8 @@
    // ========== æ¥æºå•据 ==========
    @Schema(description = "来源单据类型", example = "116")
    @ExcelProperty(value = "来源单据类型", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_QC_SOURCE_DOC_TYPE)
    private Integer sourceDocType;
    @Schema(description = "来源单据 ID", example = "200")
@@ -40,12 +45,14 @@
    private Long sourceLineId;
    @Schema(description = "来源单据编码", example = "RT20250101001")
    @ExcelProperty("来源单据编码")
    private String sourceDocCode;
    // ========== æ£€éªŒç±»åž‹ ==========
    @Schema(description = "检验类型", example = "1")
    @ExcelProperty("检验类型")
    @ExcelProperty(value = "检验类型", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_RQC_TYPE)
    private Integer type;
    // ========== ç‰©æ–™ ==========
@@ -62,9 +69,11 @@
    private String itemName;
    @Schema(description = "规格型号", example = "100mm*50mm")
    @ExcelProperty("规格型号")
    private String itemSpecification;
    @Schema(description = "单位名称", example = "个")
    @ExcelProperty("单位")
    private String unitName;
    @Schema(description = "批次号", example = "BATCH001")
@@ -108,7 +117,8 @@
    // ========== æ£€éªŒ ==========
    @Schema(description = "检测结果", example = "1")
    @ExcelProperty("检测结果")
    @ExcelProperty(value = "检测结果", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_QC_CHECK_RESULT)
    private Integer checkResult;
    @Schema(description = "检测日期")
@@ -123,7 +133,8 @@
    private String inspectorNickname;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @ExcelProperty("状态")
    @ExcelProperty(value = "状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_ORDER_STATUS)
    private Integer status;
    @Schema(description = "备注", example = "备注")
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/productissue/vo/line/MesWmProductIssueLineReturnableRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - MES é¢†æ–™å‡ºåº“单行可退列表 Response VO")
@Data
public class MesWmProductIssueLineReturnableRespVO {
    @Schema(description = "领料单行ID", example = "1")
    private Long id;
    @Schema(description = "物料ID", example = "1")
    private Long itemId;
    @Schema(description = "物料编码", example = "M001")
    private String itemCode;
    @Schema(description = "物料名称", example = "钢板")
    private String itemName;
    @Schema(description = "规格型号", example = "10mm*100mm")
    private String specification;
    @Schema(description = "计量单位名称", example = "千克")
    private String unitMeasureName;
    @Schema(description = "可退数量", example = "30.00")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
    private Long batchId;
    @Schema(description = "批次号", example = "BATCH20260101001")
    private String batchCode;
}
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/enums/DictTypeConstants.java
@@ -76,6 +76,7 @@
    String MES_DEFECT_LEVEL = "mes_defect_level"; // MES ç¼ºé™·ç­‰çº§
    String MES_QC_TYPE = "mes_qc_type"; // MES æ£€æµ‹ç§ç±»ï¼ˆIQC/IPQC/OQC/RQC)
    String MES_IPQC_TYPE = "mes_ipqc_type"; // MES IPQC æ£€éªŒç±»åž‹
    String MES_RQC_TYPE = "mes_rqc_type"; // MES é€€è´§æ£€éªŒç±»åž‹
    String MES_ORDER_STATUS = "mes_order_status"; // MES å•据状态(IQC/IPQC/OQC/RQC é€šç”¨ï¼‰
    String MES_QC_CHECK_RESULT = "mes_qc_check_result"; // MES æ£€æµ‹ç»“æžœ
    String MES_QC_SOURCE_DOC_TYPE = "mes_qc_source_doc_type"; // MES æ¥æºå•据类型
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -296,16 +296,17 @@
            throw exception(PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS, feedback.getUncheckQuantity());
        }
        // 3. ç‰©æ–™æ¶ˆè€—:倒冲工序按 BOM è‡ªåŠ¨æ‰£æ–™ï¼›éžå€’å†²å·¥åºè·³è¿‡ï¼ˆç‰©æ–™èµ°æ‰‹å·¥é¢†æ–™/投料)
        // 3. ç‰©æ–™æ¶ˆè€—:倒冲工序按本工序投料 BOM è‡ªåŠ¨æ‰£æ–™ï¼›éžå€’å†²å·¥åºè·³è¿‡ï¼ˆç‰©æ–™èµ°æ‰‹å·¥é¢†æ–™/投料)
        boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag());
        if (backflushFlag) {
            // 3.1 æ ¡éªŒå€’冲工序必须配置 BOM æ¶ˆè€—,避免静默不扣料
            List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId(
                    feedback.getWorkOrderId());
            // 3.1 æ ¡éªŒå€’冲工序必须配置本工序的投料 BOM,避免静默不扣料
            //     ï¼ˆåªæ ¡éªŒå½“前报工工序的投料,其他工序投料由各自工序报工时扣除)
            List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId(
                    feedback.getWorkOrderId(), feedback.getProcessId());
            if (CollUtil.isEmpty(boms)) {
                throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED);
            }
            // 3.2 ç”Ÿæˆæ¶ˆè€—记录并执行扣减(BOM Ã— æŠ¥å·¥æ•°é‡ï¼ŒFIFO æ‰£çº¿è¾¹åº“)
            // 3.2 ç”Ÿæˆæ¶ˆè€—记录并执行扣减(本工序投料用量比例 Ã— æŠ¥å·¥æ•°é‡ï¼ŒFIFO æ‰£çº¿è¾¹åº“)
            MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
            if (itemConsume != null) {
                itemConsumeService.finishItemConsume(itemConsume.getId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java
@@ -81,8 +81,14 @@
    public void updateRouteProcess(MesProRouteProcessSaveReqVO updateReqVO) {
        // 1.0 å·²å¯ç”¨çš„工艺路线,不允许操作
        routeService.validateRouteNotEnable(updateReqVO.getRouteId());
        // 1.1 æ ¡éªŒå­˜åœ¨
        validateRouteProcessExists(updateReqVO.getId());
        // 1.1 æ ¡éªŒå­˜åœ¨ï¼›éƒ¨åˆ†æ›´æ–°ï¼ˆå¦‚仅调整产出产品)时未传的必填字段用现有值回填
        MesProRouteProcessDO existing = validateRouteProcessExists(updateReqVO.getId());
        if (updateReqVO.getProcessId() == null) {
            updateReqVO.setProcessId(existing.getProcessId());
        }
        if (updateReqVO.getSort() == null) {
            updateReqVO.setSort(existing.getSort());
        }
        // 1.2 æ ¡éªŒå·¥è‰ºè·¯çº¿ã€å·¥åºå­˜åœ¨
        validateRouteAndProcessExists(updateReqVO.getRouteId(), updateReqVO.getProcessId());
        // 1.3 æ ¡éªŒå”¯ä¸€æ€§
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomService.java
@@ -63,6 +63,15 @@
    List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderId(Long workOrderId);
    /**
     * æ ¹æ®å·¥å•编号与工序编号获得 BOM åˆ—表(倒冲扣料按报工工序的投料明细过滤)
     *
     * @param workOrderId å·¥å•编号
     * @param processId   å·¥åºç¼–号
     * @return BOM åˆ—表
     */
    List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderIdAndProcessId(Long workOrderId, Long processId);
    /**
     * æ ¹æ®å·¥å•编号删除 BOM
     *
     * @param workOrderId å·¥å•编号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomServiceImpl.java
@@ -116,6 +116,11 @@
    }
    @Override
    public List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderIdAndProcessId(Long workOrderId, Long processId) {
        return workOrderBomMapper.selectListByWorkOrderIdAndProcessId(workOrderId, processId);
    }
    @Override
    public void deleteWorkOrderBomByWorkOrderId(Long workOrderId) {
        workOrderBomMapper.deleteByWorkOrderId(workOrderId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
@@ -78,9 +78,10 @@
        if (feedback.getWorkOrderId() == null) {
            throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID);
        }
        // 1.2 æŸ¥è¯¢å·¥å• BOM ç‰©æ–™åˆ—表(倒冲按工单投料,非工艺路线)
        List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId(
                feedback.getWorkOrderId());
        // 1.2 æŸ¥è¯¢è¯¥å€’冲工序的投料 BOM åˆ—表(按报工工序过滤,只扣当前工序的投料明细,
        //     é¿å…æŠŠå…¶ä»–工序的投料一并扣除导致多扣物料)
        List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId(
                feedback.getWorkOrderId(), feedback.getProcessId());
        if (CollUtil.isEmpty(boms)) {
            return null;
        }
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) {
            // å…¥åº“事务:找不到则自动创建库存记录
            materialStock = materialStockService.getOrCreateMaterialStock(
                    reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                    reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
            // å…¥åº“事务:根据 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;
yudao-server/src/main/resources/application-local.yaml
@@ -74,7 +74,7 @@
    redis:
      host: localhost # åœ°å€
      port: 6379 # ç«¯å£
      database: 0 # æ•°æ®åº“索引
      database: 5 # æ•°æ®åº“索引
#      password: # å¯†ç ï¼Œå»ºè®®ç”Ÿäº§çŽ¯å¢ƒå¼€å¯
--- #################### å®šæ—¶ä»»åŠ¡ç›¸å…³é…ç½® ####################
yudao-server/src/main/resources/application-yzfx.yaml
@@ -65,33 +65,10 @@
      primary: master
      datasource:
        master:
          url: jdbc:mysql://172.17.0.1:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿žæŽ¥çš„示例
          #          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X è¿žæŽ¥çš„示例
          #          url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL è¿žæŽ¥çš„示例
          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle è¿žæŽ¥çš„示例
          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer è¿žæŽ¥çš„示例
          #          url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM è¿žæŽ¥çš„示例
          #          url: jdbc:kingbase8://127.0.0.1:54321/test  # äººå¤§é‡‘仓 KingbaseES è¿žæŽ¥çš„示例
          #          url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss è¿žæŽ¥çš„示例
          #          url: jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro # ç€šé«˜ HighGo è¿žæŽ¥çš„示例
#          url: jdbc:mysql://172.17.0.1:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿žæŽ¥çš„示例
          url: jdbc:mysql://42.63.70.231:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿žæŽ¥çš„示例
          username: root
          password: yzfx@123456..
          #          username: sa # SQL Server è¿žæŽ¥çš„示例
          #          password: Yudao@2024 # SQL Server è¿žæŽ¥çš„示例
          #          username: SYSDBA # DM è¿žæŽ¥çš„示例
          #          password: SYSDBA001 # DM è¿žæŽ¥çš„示例
          #          username: root # OpenGauss è¿žæŽ¥çš„示例
          #          password: Yudao@2024 # OpenGauss è¿žæŽ¥çš„示例
          #          username: root # ç€šé«˜ HighGo è¿žæŽ¥çš„示例
          #          password: 123456 # ç€šé«˜ HighGo è¿žæŽ¥çš„示例
#        tdengine: # IoT æ•°æ®åº“(需要 IoT ç‰©è”网再开启噢!)
#          lazy: true # å¼€å¯æ‡’加载,保证启动速度
#          url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # å‚见:https://t.zsxq.com/qaX9c,开启 TDengine WebSocket è‡ªåŠ¨é‡è¿ž
#          driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
#          username: root
#          password: taosdata
#          druid:
#            validation-query: SELECT SERVER_STATUS() # TDengine æ•°æ®æºçš„æœ‰æ•ˆæ€§æ£€æŸ¥ SQL
  # Redis é…ç½®ã€‚Redisson é»˜è®¤çš„配置足够使用,一般不需要进行调优
  data: