诚发五金:入库/调整/移库/调拨/退料/盘点不再要求手选库区库位,后端自动落到仓库默认库区库位

- 新增 MesWmWarehouseDefaultService:按仓库自动创建/复用「默认库区」「默认库位」
- 入库事务、库存调整、移库未指定库区库位时自动补齐默认值
- 杂项入库行、调拨移入明细、销售退货明细、生产退料明细去掉库区/库位必填,保存时按仓库兜底
- 盘点结果新增不再强制要求库区/库位:选中盘点清单时从清单补齐,未选时按仓库取默认
- 修正同一物料同一库位存在多条盘点清单行时 selectOne 抛异常的兜底逻辑

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加2个文件
已修改15个文件
323 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockAdjustReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockMoveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/vo/line/MesWmMiscReceiptLineSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/vo/detail/MesWmReturnIssueDetailSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/detail/MesWmReturnSalesDetailSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/result/MesWmStockTakingTaskResultSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transfer/vo/detail/MesWmTransferDetailSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptLineServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueDetailServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesDetailServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferDetailServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseDefaultService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseDefaultServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockAdjustReqVO.java
@@ -27,12 +27,10 @@
    @NotNull(message = "仓库ID不能为空")
    private Long warehouseId;
    @Schema(description = "库区ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库区ID不能为空")
    @Schema(description = "库区ID(可选,未传时落到仓库的默认库区)", example = "1")
    private Long locationId;
    @Schema(description = "库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库位ID不能为空")
    @Schema(description = "库位ID(可选,未传时落到仓库的默认库位)", example = "1")
    private Long areaId;
    // ========== æ‰¹æ¬¡ç›¸å…³å­—段 ==========
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockMoveReqVO.java
@@ -18,12 +18,10 @@
    @NotNull(message = "目标仓库不能为空")
    private Long toWarehouseId;
    @Schema(description = "目标库区ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "目标库区不能为空")
    @Schema(description = "目标库区ID(可选,未传时落到目标仓库的默认库区)", example = "1")
    private Long toLocationId;
    @Schema(description = "目标库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "目标库位不能为空")
    @Schema(description = "目标库位ID(可选,未传时落到目标仓库的默认库位)", example = "1")
    private Long toAreaId;
    @Schema(description = "移库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/vo/line/MesWmMiscReceiptLineSaveReqVO.java
@@ -34,12 +34,10 @@
    @NotNull(message = "仓库不能为空")
    private Long warehouseId;
    @Schema(description = "库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库区不能为空")
    @Schema(description = "库区编号(不传时使用仓库的默认库区)", example = "1")
    private Long locationId;
    @Schema(description = "库位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库位不能为空")
    @Schema(description = "库位编号(不传时使用仓库的默认库位)", example = "1")
    private Long areaId;
    @Schema(description = "备注", example = "备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/vo/detail/MesWmReturnIssueDetailSaveReqVO.java
@@ -44,12 +44,10 @@
    @NotNull(message = "仓库编号不能为空")
    private Long warehouseId;
    @Schema(description = "库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库区编号不能为空")
    @Schema(description = "库区编号(不传时使用仓库的默认库区)", example = "1")
    private Long locationId;
    @Schema(description = "库位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库位编号不能为空")
    @Schema(description = "库位编号(不传时使用仓库的默认库位)", example = "1")
    private Long areaId;
    @Schema(description = "备注", example = "备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/detail/MesWmReturnSalesDetailSaveReqVO.java
@@ -46,12 +46,10 @@
    @NotNull(message = "仓库ID不能为空")
    private Long warehouseId;
    @Schema(description = "库区ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库区ID不能为空")
    @Schema(description = "库区ID(不传时使用仓库的默认库区)", example = "1")
    private Long locationId;
    @Schema(description = "库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库位ID不能为空")
    @Schema(description = "库位ID(不传时使用仓库的默认库位)", example = "1")
    private Long areaId;
    @Schema(description = "备注", example = "备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/result/MesWmStockTakingTaskResultSaveReqVO.java
@@ -37,12 +37,10 @@
    @NotNull(message = "仓库 ID ä¸èƒ½ä¸ºç©º")
    private Long warehouseId;
    @Schema(description = "库区 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库区 ID ä¸èƒ½ä¸ºç©º")
    @Schema(description = "库区 ID(不传时使用仓库的默认库区)", example = "1")
    private Long locationId;
    @Schema(description = "库位 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "库位 ID ä¸èƒ½ä¸ºç©º")
    @Schema(description = "库位 ID(不传时使用仓库的默认库位)", example = "1")
    private Long areaId;
    @Schema(description = "盘点数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.000000")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transfer/vo/detail/MesWmTransferDetailSaveReqVO.java
@@ -38,12 +38,10 @@
    @NotNull(message = "移入仓库编号不能为空")
    private Long toWarehouseId;
    @Schema(description = "移入库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "移入库区编号不能为空")
    @Schema(description = "移入库区编号(不传时使用移入仓库的默认库区)", example = "1")
    private Long toLocationId;
    @Schema(description = "移入库位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "移入库位编号不能为空")
    @Schema(description = "移入库位编号(不传时使用移入仓库的默认库位)", example = "1")
    private Long toAreaId;
    @Schema(description = "备注", example = "备注信息")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java
@@ -33,11 +33,19 @@
                .orderByAsc(MesWmStockTakingTaskLineDO::getId));
    }
    /**
     * æŒ‰ ä»»åŠ¡ + ç‰©æ–™ + åº“位 æŸ¥è¯¢ç›˜ç‚¹æ¸…单行
     *
     * åŒä¸€ç‰©æ–™åœ¨åŒä¸€åº“位可能因批次/供应商不同存在多行,此时取最早生成的一行,
     * é¿å… selectOne å› å¤šè¡Œç›´æŽ¥æŠ›å¼‚常(用户未选择盘点清单时走该兜底逻辑)。
     */
    default MesWmStockTakingTaskLineDO selectByTaskIdAndItemIdAndAreaId(Long taskId, Long itemId, Long areaId) {
        return selectOne(new LambdaQueryWrapperX<MesWmStockTakingTaskLineDO>()
        List<MesWmStockTakingTaskLineDO> list = selectList(new LambdaQueryWrapperX<MesWmStockTakingTaskLineDO>()
                .eq(MesWmStockTakingTaskLineDO::getTaskId, taskId)
                .eq(MesWmStockTakingTaskLineDO::getItemId, itemId)
                .eqIfPresent(MesWmStockTakingTaskLineDO::getAreaId, areaId));
                .eqIfPresent(MesWmStockTakingTaskLineDO::getAreaId, areaId)
                .orderByAsc(MesWmStockTakingTaskLineDO::getId));
        return list.isEmpty() ? null : list.get(0);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -39,6 +39,7 @@
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.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import com.mzt.logapi.starter.annotation.LogRecord;
@@ -85,6 +86,9 @@
    @Resource
    @Lazy
    private MesWmWarehouseService warehouseService;
    @Resource
    @Lazy
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Resource
    @Lazy
    private MesWmTransactionService wmTransactionService;
@@ -245,6 +249,10 @@
    @Override
    public void checkAreaMixingRule(Long areaId, Long itemId, Long batchId) {
        // æœªæŒ‡å®šåº“位(客户未启用库区/库位管理)时不校验混放,后续入库事务会落到仓库默认库位
        if (areaId == null) {
            return;
        }
        // 1.1 æ ¡éªŒåº“位必须存在
        MesWmWarehouseAreaDO area = areaService.validateWarehouseAreaExists(areaId);
@@ -403,7 +411,14 @@
            }
        }
        // 3. æ ¡éªŒåº“位混放规则
        // 3. æœªæŒ‡å®šåº“区/库位时,落到仓库默认库区/库位(客户未启用库区/库位管理)
        if (reqVO.getWarehouseId() != null
                && (reqVO.getLocationId() == null || reqVO.getAreaId() == null)) {
            MesWmWarehouseAreaDO defaultArea = warehouseDefaultService.getOrCreateDefaultArea(reqVO.getWarehouseId());
            reqVO.setLocationId(defaultArea.getLocationId());
            reqVO.setAreaId(defaultArea.getId());
        }
        // 3.1 æ ¡éªŒåº“位混放规则
        checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), batchId);
        // 4. èŽ·å–æˆ–åˆ›å»ºåº“å­˜è®°å½•
@@ -540,7 +555,16 @@
            throw exception(WM_MATERIAL_STOCK_FROZEN);
        }
        // 4. æ ¡éªŒç›®æ ‡åº“位混放规则
        // 4. ç›®æ ‡åº“区/库位未指定时,落到目标仓库的默认库区/库位(客户未启用库区/库位管理)
        if (reqVO.getToWarehouseId() == null) {
            reqVO.setToWarehouseId(sourceStock.getWarehouseId());
        }
        if ((reqVO.getToLocationId() == null || reqVO.getToAreaId() == null)) {
            MesWmWarehouseAreaDO defaultArea = warehouseDefaultService.getOrCreateDefaultArea(reqVO.getToWarehouseId());
            reqVO.setToLocationId(defaultArea.getLocationId());
            reqVO.setToAreaId(defaultArea.getId());
        }
        // 4.1 æ ¡éªŒç›®æ ‡åº“位混放规则
        checkAreaMixingRule(reqVO.getToAreaId(), sourceStock.getItemId(), sourceStock.getBatchId());
        // 5. ç”Ÿæˆç§»åº“单号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptLineServiceImpl.java
@@ -5,9 +5,11 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.line.MesWmMiscReceiptLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.line.MesWmMiscReceiptLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscreceipt.MesWmMiscReceiptLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscreceipt.MesWmMiscReceiptLineMapper;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -39,6 +41,8 @@
    private MesMdmItemService mdmItemService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Resource
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -62,6 +66,8 @@
        // 1. æ ¡éªŒ
        MesWmMiscReceiptLineDO line = validateMiscReceiptLineExists(updateReqVO.getId());
        updateReqVO.setReceiptId(line.getReceiptId());
        // æœªæŒ‡å®šåº“区/库位时,落到仓库默认库区/库位(客户未启用库区/库位管理)
        fillDefaultLocationAndArea(updateReqVO);
        // 2. æ›´æ–°è¡Œ
        MesWmMiscReceiptLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmMiscReceiptLineDO.class);
@@ -126,9 +132,24 @@
        miscReceiptService.validateMiscReceiptEditable(reqVO.getReceiptId());
        // æ ¡éªŒç‰©æ–™å­˜åœ¨
        mdmItemService.validateItemExistsAndEnable(reqVO.getItemId());
        // æœªæŒ‡å®šåº“区/库位时,落到仓库默认库区/库位(客户未启用库区/库位管理)
        fillDefaultLocationAndArea(reqVO);
        // æ ¡éªŒä»“库层级关系(仓库 - åº“位 - åº“区)
        warehouseAreaService.validateWarehouseAreaExists(reqVO.getWarehouseId(),
                reqVO.getLocationId(), reqVO.getAreaId());
    }
    /**
     * æœªæŒ‡å®šåº“区/库位时,落到仓库默认库区/库位
     */
    private void fillDefaultLocationAndArea(MesWmMiscReceiptLineSaveReqVO reqVO) {
        if (reqVO.getWarehouseId() == null
                || (reqVO.getLocationId() != null && reqVO.getAreaId() != null)) {
            return;
        }
        MesWmWarehouseAreaDO defaultArea = warehouseDefaultService.getOrCreateDefaultArea(reqVO.getWarehouseId());
        reqVO.setLocationId(defaultArea.getLocationId());
        reqVO.setAreaId(defaultArea.getId());
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueDetailServiceImpl.java
@@ -7,9 +7,11 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnissue.MesWmReturnIssueDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnissue.MesWmReturnIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnissue.MesWmReturnIssueDetailMapper;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -38,6 +40,8 @@
    private MesWmReturnIssueLineService issueLineService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Resource
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Resource
    @Lazy
    private MesWmMaterialStockService materialStockService;
@@ -114,6 +118,14 @@
        if (ObjUtil.notEqual(line.getItemId(), reqVO.getItemId())) {
            throw exception(WM_RETURN_ISSUE_DETAIL_ITEM_MISMATCH);
        }
        // æœªæŒ‡å®šä¸Šæž¶ç›®æ ‡åº“区/库位时,落到仓库默认库区/库位(客户未启用库区/库位管理)
        if (reqVO.getWarehouseId() != null
                && (reqVO.getLocationId() == null || reqVO.getAreaId() == null)) {
            MesWmWarehouseAreaDO defaultArea =
                    warehouseDefaultService.getOrCreateDefaultArea(reqVO.getWarehouseId());
            reqVO.setLocationId(defaultArea.getLocationId());
            reqVO.setAreaId(defaultArea.getId());
        }
        // æ ¡éªŒä¸Šæž¶ç›®æ ‡ä»“库、库区、库位的关联关系
        warehouseAreaService.validateWarehouseAreaExists(
                reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesDetailServiceImpl.java
@@ -9,11 +9,13 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesDetailMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -45,6 +47,8 @@
    private MesWmReturnSalesService returnSalesService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Resource
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Resource
    private MesWmBatchService batchService;
    @Resource
@@ -136,6 +140,14 @@
        MesWmReturnSalesLineDO line = returnSalesLineService.validateReturnSalesLineExists(reqVO.getLineId());
        // æ ¡éªŒé€€è´§å•为待上架状态
        validateReturnSalesStatusApproved(line.getReturnId());
        // æœªæŒ‡å®šåº“区/库位时,落到仓库默认库区/库位(客户未启用库区/库位管理)
        if (reqVO.getWarehouseId() != null
                && (reqVO.getLocationId() == null || reqVO.getAreaId() == null)) {
            MesWmWarehouseAreaDO defaultArea =
                    warehouseDefaultService.getOrCreateDefaultArea(reqVO.getWarehouseId());
            reqVO.setLocationId(defaultArea.getLocationId());
            reqVO.setAreaId(defaultArea.getId());
        }
        // æ ¡éªŒä»“库、库区、库位的关联关系
        warehouseAreaService.validateWarehouseAreaExists(
                reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java
@@ -1,13 +1,16 @@
package cn.iocoder.yudao.module.mes.service.wm.stocktaking.task;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskResultMapper;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -36,6 +39,8 @@
    private MesWmStockTakingTaskLineService stockTakingTaskLineService;
    @Resource
    private MesWmStockTakingTaskService stockTakingTaskService;
    @Resource
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Override
    public PageResult<MesWmStockTakingTaskResultDO> getStockTakingTaskResultPage(
@@ -57,19 +62,43 @@
        // 2. ç¡®å®š lineId å’Œè´¦é¢æ•°é‡
        Long lineId = createReqVO.getLineId();
        MesWmStockTakingTaskLineDO existingLine = null;
        if (lineId != null) {
            // 2.1 å¦‚果传递了 lineId,从 line ä¸­èŽ·å– quantity,并补齐仓库/库区/库位/批次
            existingLine = stockTakingTaskLineService.validateStockTakingTaskLineExists(lineId);
            if (createReqVO.getWarehouseId() == null) {
                createReqVO.setWarehouseId(existingLine.getWarehouseId());
            }
            if (createReqVO.getLocationId() == null) {
                createReqVO.setLocationId(existingLine.getLocationId());
            }
            if (createReqVO.getAreaId() == null) {
                createReqVO.setAreaId(existingLine.getAreaId());
            }
            if (createReqVO.getBatchId() == null) {
                createReqVO.setBatchId(existingLine.getBatchId());
            }
            if (StrUtil.isEmpty(createReqVO.getBatchCode())) {
                createReqVO.setBatchCode(existingLine.getBatchCode());
            }
        }
        // 2.2 æœªæŒ‡å®šåº“区/库位时,落到仓库默认库区/库位(客户未启用库区/库位管理)
        if (createReqVO.getWarehouseId() != null
                && (createReqVO.getLocationId() == null || createReqVO.getAreaId() == null)) {
            MesWmWarehouseAreaDO defaultArea =
                    warehouseDefaultService.getOrCreateDefaultArea(createReqVO.getWarehouseId());
            createReqVO.setLocationId(defaultArea.getLocationId());
            createReqVO.setAreaId(defaultArea.getId());
        }
        if (lineId == null) {
            // 2.1 åŸºäºŽ taskId + itemId + areaId æŸ¥è¯¢å·²å­˜åœ¨çš„ line
            // 2.3 åŸºäºŽ taskId + itemId + areaId æŸ¥è¯¢å·²å­˜åœ¨çš„ line
            existingLine = stockTakingTaskLineService.getStockTakingTaskLine(
                    createReqVO.getTaskId(), createReqVO.getItemId(), createReqVO.getAreaId());
            if (existingLine != null) {
                lineId = existingLine.getId();
            } else {
                // 2.2 åˆ›å»ºæ–°çš„ line(注意:当盘点结果没有对应的盘点清单行时,会自动创建一个新的盘点任务行)
                // 2.4 åˆ›å»ºæ–°çš„ line(注意:当盘点结果没有对应的盘点清单行时,会自动创建一个新的盘点任务行)
                lineId = stockTakingTaskLineService.createStockTakingTaskLine(createReqVO);
            }
        } else {
            // 2.3 å¦‚果传递了 lineId,从 line ä¸­èŽ·å– quantity
            existingLine = stockTakingTaskLineService.validateStockTakingTaskLineExists(lineId);
        }
        if (existingLine != null
                && existingLine.getTakingQuantity() != null
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -21,6 +21,7 @@
import cn.iocoder.yudao.module.mes.service.wm.stockwarning.MesWmStockWarningService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import jakarta.annotation.Resource;
@@ -60,6 +61,8 @@
    @Resource
    private MesWmWarehouseAreaService areaService;
    @Resource
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Resource
    private MesWmStockWarningService stockWarningService;
    @Override
@@ -75,6 +78,10 @@
        checkQuantity(reqDTO, inbound);
        // 1.3 å…¥åº“事务:批次号校验 + batchId/batchCode äº’è¡¥
        checkBatch(reqDTO, inbound);
        // 1.3.1 å…¥åº“事务未指定库区/库位时,自动落到仓库默认库区/库位(客户未启用库区/库位管理)
        if (inbound) {
            fillDefaultLocationAndArea(reqDTO);
        }
        // 1.4 å…¥åº“事务:库位混放规则校验
        if (inbound) {
            materialStockService.checkAreaMixingRule(reqDTO.getAreaId(), reqDTO.getItemId(), reqDTO.getBatchId());
@@ -171,6 +178,21 @@
    // ==================== ç§æœ‰æ ¡éªŒæ–¹æ³• ====================
    /**
     * å…¥åº“事务未指定库区/库位时,自动使用仓库的默认库区/默认库位
     *
     * å®¢æˆ·æœªå¯ç”¨åº“区/库位管理,前端不再让用户选择,库存仍需要完整的仓库-库区-库位维度。
     */
    private void fillDefaultLocationAndArea(MesWmTransactionSaveReqDTO reqDTO) {
        if (reqDTO.getWarehouseId() == null
                || (reqDTO.getLocationId() != null && reqDTO.getAreaId() != null)) {
            return;
        }
        MesWmWarehouseAreaDO defaultArea = warehouseDefaultService.getOrCreateDefaultArea(reqDTO.getWarehouseId());
        reqDTO.setLocationId(defaultArea.getLocationId());
        reqDTO.setAreaId(defaultArea.getId());
    }
    /**
     * æ ¡éªŒæ•°é‡æ­£è´Ÿå·ä¸Žäº‹åŠ¡æ–¹å‘ä¸€è‡´
     */
    private void checkQuantity(MesWmTransactionSaveReqDTO reqDTO, boolean inbound) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferDetailServiceImpl.java
@@ -6,8 +6,10 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.detail.MesWmTransferDetailSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.transfer.MesWmTransferDetailMapper;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseDefaultService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -39,6 +41,8 @@
    private MesWmTransferLineService transferLineService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Resource
    private MesWmWarehouseDefaultService warehouseDefaultService;
    @Override
    public Long createTransferDetail(MesWmTransferDetailSaveReqVO createReqVO) {
@@ -117,6 +121,14 @@
    private void validateDetailSaveData(MesWmTransferDetailSaveReqVO reqVO, Long excludeDetailId) {
        // æ ¡éªŒçˆ¶æ•°æ®å­˜åœ¨
        MesWmTransferLineDO line = transferLineService.validateTransferLineExists(reqVO.getLineId());
        // æœªæŒ‡å®šç§»å…¥åº“区/库位时,落到移入仓库默认库区/库位(客户未启用库区/库位管理)
        if (reqVO.getToWarehouseId() != null
                && (reqVO.getToLocationId() == null || reqVO.getToAreaId() == null)) {
            MesWmWarehouseAreaDO defaultArea =
                    warehouseDefaultService.getOrCreateDefaultArea(reqVO.getToWarehouseId());
            reqVO.setToLocationId(defaultArea.getLocationId());
            reqVO.setToAreaId(defaultArea.getId());
        }
        // æ ¡éªŒä»“库、库区、库位的关联关系
        warehouseAreaService.validateWarehouseAreaExists(
                reqVO.getToWarehouseId(), reqVO.getToLocationId(), reqVO.getToAreaId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseDefaultService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.mes.service.wm.warehouse;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
/**
 * ä»“库默认库区/库位 Service
 *
 * å®¢æˆ·æœªå¯ç”¨åº“区/库位管理时,业务单据不再要求用户选择库区/库位,
 * ç”±ç³»ç»ŸæŒ‰ä»“库自动落到「默认库区 / é»˜è®¤åº“位」(不存在时自动创建),
 * ä¿è¯åº“存记录仍具备完整的仓库-库区-库位维度。
 */
public interface MesWmWarehouseDefaultService {
    /**
     * èŽ·å–ä»“åº“çš„é»˜è®¤åº“åŒºï¼Œä¸å­˜åœ¨æ—¶è‡ªåŠ¨åˆ›å»º
     *
     * @param warehouseId ä»“库编号
     * @return é»˜è®¤åº“区
     */
    MesWmWarehouseLocationDO getOrCreateDefaultLocation(Long warehouseId);
    /**
     * èŽ·å–ä»“åº“çš„é»˜è®¤åº“ä½ï¼Œä¸å­˜åœ¨æ—¶è‡ªåŠ¨åˆ›å»ºï¼ˆåŒæ—¶ç¡®ä¿é»˜è®¤åº“åŒºå­˜åœ¨ï¼‰
     *
     * @param warehouseId ä»“库编号
     * @return é»˜è®¤åº“位
     */
    MesWmWarehouseAreaDO getOrCreateDefaultArea(Long warehouseId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseDefaultServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.mes.service.wm.warehouse;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo.area.MesWmWarehouseAreaSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo.location.MesWmWarehouseLocationSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
/**
 * ä»“库默认库区/库位 Service å®žçŽ°ç±»
 */
@Service
@Validated
public class MesWmWarehouseDefaultServiceImpl implements MesWmWarehouseDefaultService {
    /** é»˜è®¤åº“区编码前缀(按仓库隔离,保证编码全局唯一) */
    private static final String DEFAULT_LOCATION_CODE_PREFIX = "DEFAULT_KQ_";
    private static final String DEFAULT_LOCATION_NAME = "默认库区";
    /** é»˜è®¤åº“位编码前缀 */
    private static final String DEFAULT_AREA_CODE_PREFIX = "DEFAULT_KW_";
    private static final String DEFAULT_AREA_NAME = "默认库位";
    @Resource
    private MesWmWarehouseLocationService locationService;
    @Resource
    private MesWmWarehouseAreaService areaService;
    @Resource
    private MesWmWarehouseService warehouseService;
    @Override
    public MesWmWarehouseLocationDO getOrCreateDefaultLocation(Long warehouseId) {
        String code = DEFAULT_LOCATION_CODE_PREFIX + warehouseId;
        MesWmWarehouseLocationDO location = locationService.getWarehouseLocationByCode(code);
        if (location != null) {
            return location;
        }
        // ä»“库不存在时给出明确提示,避免建出没有归属的库区
        warehouseService.validateWarehouseExists(warehouseId);
        MesWmWarehouseLocationSaveReqVO createReqVO = new MesWmWarehouseLocationSaveReqVO();
        createReqVO.setWarehouseId(warehouseId);
        createReqVO.setCode(code);
        createReqVO.setName(DEFAULT_LOCATION_NAME);
        createReqVO.setFrozen(false);
        try {
            Long id = locationService.createWarehouseLocation(createReqVO);
            return locationService.getWarehouseLocation(id);
        } catch (ServiceException ex) {
            // å¹¶å‘首次使用时可能已被其他请求创建,回查一次
            MesWmWarehouseLocationDO created = locationService.getWarehouseLocationByCode(code);
            if (created != null) {
                return created;
            }
            throw ex;
        }
    }
    @Override
    public MesWmWarehouseAreaDO getOrCreateDefaultArea(Long warehouseId) {
        String code = DEFAULT_AREA_CODE_PREFIX + warehouseId;
        MesWmWarehouseAreaDO area = areaService.getWarehouseAreaByCode(code);
        if (area != null) {
            return area;
        }
        MesWmWarehouseLocationDO location = getOrCreateDefaultLocation(warehouseId);
        MesWmWarehouseAreaSaveReqVO createReqVO = new MesWmWarehouseAreaSaveReqVO();
        createReqVO.setCode(code);
        createReqVO.setName(DEFAULT_AREA_NAME);
        createReqVO.setLocationId(location.getId());
        createReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
        createReqVO.setFrozen(false);
        // é»˜è®¤åº“位需兼容所有物料/批次,避免在无库位管理的场景下触发混放校验
        createReqVO.setAllowItemMixing(true);
        createReqVO.setAllowBatchMixing(true);
        try {
            Long id = areaService.createWarehouseArea(createReqVO);
            return areaService.getWarehouseArea(id);
        } catch (ServiceException ex) {
            MesWmWarehouseAreaDO created = areaService.getWarehouseAreaByCode(code);
            if (created != null) {
                return created;
            }
            throw ex;
        }
    }
}