From eccad5a129106377a275be4f7cdc58e99e9b95d4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 21 七月 2026 13:45:14 +0800
Subject: [PATCH] feat(mes): 实现库存移库功能

---
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java                     |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockMoveReqVO.java |   36 ++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java           |   79 +++++++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java                     |   18 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                         |    1 
 docs/material_stock_move.md                                                                                                      |  120 ++++++++++++++++++++
 yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java                                             |   11 +
 docs/sale_order_production_status.md                                                                                             |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java   |   11 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java              |   22 +++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java               |    8 +
 11 files changed, 304 insertions(+), 12 deletions(-)

diff --git a/docs/material_stock_move.md b/docs/material_stock_move.md
new file mode 100644
index 0000000..1b0037d
--- /dev/null
+++ b/docs/material_stock_move.md
@@ -0,0 +1,120 @@
+# 搴撳瓨绉诲簱 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 搴撳瓨鍙拌处椤甸潰锛�/mes/wm/material-stock锛�
+
+## 涓氬姟鍦烘櫙
+
+鍦ㄥ簱瀛樺彴璐﹂〉闈紝鐢ㄦ埛鍙互閫夋嫨涓�鏉″簱瀛樿褰曪紝鐐瑰嚮"绉诲簱"鎸夐挳锛屽皢璇ュ簱瀛樹粠褰撳墠搴撲綅绉诲姩鍒扮洰鏍囧簱浣嶃�傜Щ搴撴搷浣滀細鐢熸垚涓ゆ潯鎴愬鐨勫簱瀛樹簨鍔℃祦姘达紙璋冩嫧绉诲嚭 + 璋冩嫧绉诲叆锛夛紝纭繚搴撳瓨鍙樺姩鍙拷婧��
+
+## 鏂板 API
+
+### 搴撳瓨绉诲簱
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mes/wm/material-stock/move | 搴撳瓨绉诲簱 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| materialStockId | Long | 鏄� | 搴撳瓨璁板綍ID |
+| toWarehouseId | Long | 鏄� | 鐩爣浠撳簱ID |
+| toLocationId | Long | 鏄� | 鐩爣搴撳尯ID |
+| toAreaId | Long | 鏄� | 鐩爣搴撲綅ID |
+| quantity | BigDecimal | 鏄� | 绉诲簱鏁伴噺 |
+| remark | String | 鍚� | 澶囨敞 |
+
+**璇锋眰绀轰緥锛�**
+
+```json
+{
+  "materialStockId": 1024,
+  "toWarehouseId": 1,
+  "toLocationId": 2,
+  "toAreaId": 3,
+  "quantity": 100,
+  "remark": "搴撲綅璋冩暣"
+}
+```
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": true
+}
+```
+
+## 鍓嶇淇敼鐐�
+
+### 1. 搴撳瓨鍙拌处鍒楄〃椤�
+
+- 鍦ㄦ搷浣滃垪娣诲姞"绉诲簱"鎸夐挳
+- 鐐瑰嚮鍚庢墦寮�绉诲簱寮圭獥
+
+### 2. 绉诲簱寮圭獥
+
+**琛ㄥ崟瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| 鐗╂枡缂栫爜 | 鏂囨湰 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 鐗╂枡鍚嶇О | 鏂囨湰 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 鎵规鍙� | 鏂囨湰 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 褰撳墠浠撳簱 | 鏂囨湰 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 褰撳墠搴撳尯 | 鏂囨湰 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 褰撳墠搴撲綅 | 鏂囨湰 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 褰撳墠鏁伴噺 | 鏁板瓧 | - | 鍙锛屼粠閫変腑琛屽甫鍏� |
+| 鐩爣浠撳簱 | 涓嬫媺 | 鏄� | 閫夋嫨鐩爣浠撳簱 |
+| 鐩爣搴撳尯 | 涓嬫媺 | 鏄� | 鏍规嵁鐩爣浠撳簱鑱斿姩 |
+| 鐩爣搴撲綅 | 涓嬫媺 | 鏄� | 鏍规嵁鐩爣搴撳尯鑱斿姩 |
+| 绉诲簱鏁伴噺 | 鏁板瓧 | 鏄� | 杈撳叆绉诲簱鏁伴噺锛屼笉鑳借秴杩囧綋鍓嶆暟閲� |
+| 澶囨敞 | 鏂囨湰 | 鍚� | 澶囨敞淇℃伅 |
+
+**鑱斿姩閫昏緫锛�**
+
+1. 閫夋嫨鐩爣浠撳簱鍚庯紝鍔犺浇璇ヤ粨搴撲笅鐨勫簱鍖哄垪琛�
+2. 閫夋嫨鐩爣搴撳尯鍚庯紝鍔犺浇璇ュ簱鍖轰笅鐨勫簱浣嶅垪琛�
+
+**鎻愪氦閫昏緫锛�**
+
+1. 鏍¢獙绉诲簱鏁伴噺涓嶈兘瓒呰繃褰撳墠搴撳瓨鏁伴噺
+2. 鏍¢獙绉诲簱鏁伴噺涓嶈兘瓒呰繃鍙敤閲忥紙褰撳墠鏁伴噺 - 鍐荤粨鏁伴噺 - 鍗犵敤閲忥級
+3. 璋冪敤绉诲簱鎺ュ彛
+4. 鎴愬姛鍚庡叧闂脊绐楋紝鍒锋柊鍒楄〃
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 搴撳瓨鏁伴噺鏍¢獙 | 绉诲簱鏁伴噺涓嶈兘瓒呰繃鍙敤閲忥紙鍦ㄥ簱鏁伴噺 - 鍐荤粨鏁伴噺 - 鍗犵敤閲忥級 |
+| 鍐荤粨鏍¢獙 | 搴撳瓨璁板綍宸插喕缁撴椂锛屼笉鍏佽绉诲簱 |
+| 搴撲綅娣锋斁瑙勫垯 | 鐩爣搴撲綅闇�婊¤冻娣锋斁瑙勫垯锛堢墿鏂欐贩鏀俱�佹壒娆℃贩鏀撅級 |
+| 鍚屽簱浣嶇Щ搴� | 婧愬簱浣嶅拰鐩爣搴撲綅鐩稿悓鏃讹紝涓嶅厑璁哥Щ搴� |
+
+## 搴撳瓨浜嬪姟娴佹按
+
+绉诲簱鎿嶄綔浼氱敓鎴愪袱鏉℃垚瀵圭殑搴撳瓨浜嬪姟娴佹按锛�
+
+| 瀛楁 | 璋冩嫧绉诲嚭 | 璋冩嫧绉诲叆 |
+|------|----------|----------|
+| type | 3 (MOVE_OUT) | 4 (MOVE_IN) |
+| bizType | 112 (WM_TRANSFER_OUT) | 113 (WM_TRANSFER_IN) |
+| bizCode | MOVE_xxx | MOVE_xxx锛堢浉鍚岋級 |
+| quantity | 璐熸暟锛堝 -100锛� | 姝f暟锛堝 100锛� |
+| warehouseId | 婧愪粨搴� | 鐩爣浠撳簱 |
+| locationId | 婧愬簱鍖� | 鐩爣搴撳尯 |
+| areaId | 婧愬簱浣� | 鐩爣搴撲綅 |
+| relatedTransactionId | null | 绉诲嚭浜嬪姟ID |
+
+## 娉ㄦ剰浜嬮」
+
+1. **鏉冮檺瑕佹眰**锛氶渶瑕� `mes:wm-material-stock:move` 鏉冮檺
+2. **浜嬪姟涓�鑷存��**锛氱Щ搴撴搷浣滃湪浜嬪姟涓墽琛岋紝纭繚鍑哄簱鍜屽叆搴撴搷浣滅殑鍘熷瓙鎬�
+3. **鎵规淇濈暀**锛氱Щ搴撴椂鎵规淇℃伅淇濈暀锛屼笉浼氭敼鍙樻壒娆″睘鎬�
+4. **渚涘簲鍟嗕繚鐣�**锛氱Щ搴撴椂渚涘簲鍟嗕俊鎭繚鐣�
diff --git a/docs/sale_order_production_status.md b/docs/sale_order_production_status.md
index a6c9d0b..40facd2 100644
--- a/docs/sale_order_production_status.md
+++ b/docs/sale_order_production_status.md
@@ -74,7 +74,7 @@
 | 鐢熶骇鐘舵�佸垵濮嬪�� | 閿�鍞鍗曞鏍搁�氳繃鏃讹紝productionStatus 榛樿涓� 0锛堟湭瀹屾垚锛� |
 | 鐢熶骇鐘舵�佹洿鏂版椂鏈� | 褰撴墍鏈夊叧鑱旂殑鐢熶骇璁㈠崟瀹屾垚鏃讹紝绯荤粺鑷姩鏇存柊涓� 1锛堝凡瀹屾垚锛� |
 | 鐢熶骇瀹屾垚鍒ゆ柇 | 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺 涓� 鎵�鏈夋姤宸ュ崟宸插畬鎴愶紙鍚川妫�瀹屾垚锛� |
-| 鍙戣揣閫氱煡鍗曠敓鎴� | 褰� productionStatus=1 鏃讹紝canCreateSalesNotice 涓� true |
+| 鍙戣揣閫氱煡鍗曠敓鎴� | 褰� productionStatus=1 涓旀湭鐢熸垚鍙戣揣閫氱煡鍗曟椂锛宑anCreateSalesNotice 涓� true |
 
 ## 灞曠ず閫昏緫璇存槑
 
@@ -99,7 +99,9 @@
 
 ## 娉ㄦ剰浜嬮」
 
-1. **鐘舵�佽仈鍔�**锛歚productionStatus` 涓� `canCreateSalesNotice` 鍏宠仈锛岀敓浜ф湭瀹屾垚鏃舵棤娉曠敓鎴愬彂璐ч�氱煡鍗�
+1. **鐘舵�佽仈鍔�**锛歚canCreateSalesNotice` 闇�鍚屾椂婊¤冻涓や釜鏉′欢锛氱敓浜у凡瀹屾垚锛坧roductionStatus=1锛変笖鏈敓鎴愬彂璐ч�氱煡鍗曪紙hasSalesNotice=false锛�
+
+2. **宸茬敓鎴愬彂璐ч�氱煡鍗�**锛氬綋 `hasSalesNotice=true` 鏃讹紝涓嶆樉绀�"鐢熸垚鍙戣揣閫氱煡鍗�"鎸夐挳
 
 2. **鏃犻渶鐢熶骇鐨勮鍗�**锛歚needProduction !== 1` 鐨勮鍗曪紝`productionStatus` 瀛楁涓� null锛屽墠绔簲鏄剧ず"鏃犻渶鐢熶骇"鎴栦笉鏄剧ず
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
index 1280ab6..488e818 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -218,8 +218,8 @@
             MapUtils.findAndThen(userMap, Long.parseLong(saleOrder.getCreator()), user -> saleOrder.setCreatorName(user.getNickname()));
             // 璁剧疆鏄惁宸茬敓鎴愬彂璐ч�氱煡鍗�
             saleOrder.setHasSalesNotice(salesNoticeMap.getOrDefault(saleOrder.getId(), false));
-            // 璁剧疆鏄惁鍙互鐢熸垚鍙戣揣閫氱煡鍗�
-            saleOrder.setCanCreateSalesNotice(canCreateSalesNoticeMap.getOrDefault(saleOrder.getId(), true));
+            // 璁剧疆鏄惁鍙互鐢熸垚鍙戣揣閫氱煡鍗曪細宸茬敓鎴愬彂璐ч�氱煡鍗曟椂涓嶆樉绀烘寜閽�
+            saleOrder.setCanCreateSalesNotice(!saleOrder.getHasSalesNotice() && canCreateSalesNoticeMap.getOrDefault(saleOrder.getId(), true));
             // 璁剧疆鏄惁鏈夐渶瑕佺敓浜х殑鏄庣粏
             boolean hasNeedProduction = saleOrder.getItems().stream()
                     .anyMatch(item -> item.getNeedProduction() != null && item.getNeedProduction() == 1);
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
index 621ebc8..4c519db 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -9,9 +9,10 @@
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockRespVO;
-import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;
 import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
@@ -114,6 +115,14 @@
         return success(materialStockService.adjustMaterialStockOut(reqVO));
     }
 
+    @PostMapping("/move")
+    @Operation(summary = "搴撳瓨绉诲簱")
+    @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:update')")
+    public CommonResult<Boolean> moveMaterialStock(@Valid @RequestBody MesWmMaterialStockMoveReqVO reqVO) {
+        materialStockService.moveMaterialStock(reqVO);
+        return success(true);
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "瀵煎嚭搴撳瓨鍙拌处 Excel")
     @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:export')")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockMoveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockMoveReqVO.java
new file mode 100644
index 0000000..d045090
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockMoveReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨绉诲簱 Request VO")
+@Data
+public class MesWmMaterialStockMoveReqVO {
+
+    @Schema(description = "搴撳瓨璁板綍ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "搴撳瓨璁板綍ID涓嶈兘涓虹┖")
+    private Long materialStockId;
+
+    @Schema(description = "鐩爣浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐩爣浠撳簱涓嶈兘涓虹┖")
+    private Long toWarehouseId;
+
+    @Schema(description = "鐩爣搴撳尯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐩爣搴撳尯涓嶈兘涓虹┖")
+    private Long toLocationId;
+
+    @Schema(description = "鐩爣搴撲綅ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐩爣搴撲綅涓嶈兘涓虹┖")
+    private Long toAreaId;
+
+    @Schema(description = "绉诲簱鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "绉诲簱鏁伴噺涓嶈兘涓虹┖")
+    private BigDecimal quantity;
+
+    @Schema(description = "澶囨敞", example = "搴撲綅璋冩暣")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
index 3086782..5769273 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -146,6 +146,28 @@
         return selectOne(wrapper);
     }
 
+    /**
+     * 鎸夌粍鍚堥敭鏌ヨ搴撳瓨璁板綍锛堝拷鐣ヤ緵搴斿晢缁村害锛�
+     *
+     * 鐢ㄤ簬绉诲簱鍦烘櫙锛氱Щ搴撴椂灏嗗簱瀛樺悎骞跺埌鐩爣搴撲綅宸叉湁搴撳瓨锛屼笉鍖哄垎渚涘簲鍟�
+     */
+    default MesWmMaterialStockDO selectByCompositeKeyIgnoreVendor(Long itemId, Long warehouseId, Long locationId,
+                                                                    Long areaId, Long batchId) {
+        LambdaQueryWrapperX<MesWmMaterialStockDO> wrapper = new LambdaQueryWrapperX<MesWmMaterialStockDO>()
+                .eq(MesWmMaterialStockDO::getItemId, itemId)
+                .eq(MesWmMaterialStockDO::getWarehouseId, warehouseId)
+                .eq(MesWmMaterialStockDO::getLocationId, locationId)
+                .eq(MesWmMaterialStockDO::getAreaId, areaId);
+        // batchId=null 鏃剁簿纭尮閰� is null
+        if (batchId != null) {
+            wrapper.eq(MesWmMaterialStockDO::getBatchId, batchId);
+        } else {
+            wrapper.isNull(MesWmMaterialStockDO::getBatchId);
+        }
+        wrapper.last("LIMIT 1");
+        return selectOne(wrapper);
+    }
+
     default List<MesWmMaterialStockDO> selectList(MesWmMaterialStockListReqVO reqVO) {
         return selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                 .eqIfPresent(MesWmMaterialStockDO::getWarehouseId, reqVO.getWarehouseId())
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index 2dc155a..a9040fb 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -460,6 +460,7 @@
     ErrorCode WM_MATERIAL_STOCK_REQUIRED = new ErrorCode(1_040_703_013, "搴撳瓨璁板綍涓嶈兘涓虹┖");
     ErrorCode WM_MATERIAL_STOCK_SELECTION_MISMATCH = new ErrorCode(1_040_703_014, "搴撳瓨璁板綍涓庢彁浜ょ殑鐗╂枡銆佹壒娆℃垨搴撲綅淇℃伅涓嶄竴鑷�");
     ErrorCode WM_MATERIAL_STOCK_QUANTITY_INVALID = new ErrorCode(1_040_703_015, "璋冩暣鏁伴噺涓嶈兘涓虹┖鎴栭浂");
+    ErrorCode WM_MATERIAL_STOCK_FROZEN = new ErrorCode(1_040_703_016, "搴撳瓨璁板綍宸插喕缁擄紝鏃犳硶绉诲簱");
 
     // ========== MES 浠撳簱绠$悊-鍒拌揣閫氱煡鍗曪紙1-040-704-000锛� ==========
     ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "鍒拌揣閫氱煡鍗曚笉瀛樺湪");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
index 2ae9a0f..8ad1a45 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -4,6 +4,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 import jakarta.validation.Valid;
@@ -179,4 +180,11 @@
      */
     Long adjustMaterialStockOut(@Valid MesWmMaterialStockAdjustReqVO reqVO);
 
+    /**
+     * 搴撳瓨绉诲簱
+     *
+     * @param reqVO 绉诲簱璇锋眰
+     */
+    void moveMaterialStock(@Valid MesWmMaterialStockMoveReqVO reqVO);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
index b62bbec..f8f4734 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -8,6 +8,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
@@ -415,4 +416,82 @@
         throw new IllegalArgumentException("鏃犳晥鐨勮川閲忕姸鎬佺被鍨�: " + qualityStatus.getClass().getName());
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void moveMaterialStock(MesWmMaterialStockMoveReqVO reqVO) {
+        // 1. 鏌ヨ鍘熷簱瀛樿褰�
+        MesWmMaterialStockDO sourceStock = validateMaterialStockExists(reqVO.getMaterialStockId());
+
+        // 2. 鏍¢獙搴撳瓨鏁伴噺鍏呰冻锛堝彲鐢ㄩ噺 = 鍦ㄥ簱鏁伴噺 - 鍐荤粨鏁伴噺 - 鍗犵敤閲忥級
+        BigDecimal availableQty = sourceStock.getQuantity() != null ? sourceStock.getQuantity() : BigDecimal.ZERO;
+        if (sourceStock.getFrozenQuantity() != null) {
+            availableQty = availableQty.subtract(sourceStock.getFrozenQuantity());
+        }
+        if (sourceStock.getReservedQuantity() != null) {
+            availableQty = availableQty.subtract(sourceStock.getReservedQuantity());
+        }
+        if (availableQty.compareTo(reqVO.getQuantity()) < 0) {
+            throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
+        }
+
+        // 3. 鏍¢獙搴撳瓨鏈喕缁�
+        if (Boolean.TRUE.equals(sourceStock.getFrozen())) {
+            throw exception(WM_MATERIAL_STOCK_FROZEN);
+        }
+
+        // 4. 鏍¢獙鐩爣搴撲綅娣锋斁瑙勫垯
+        checkAreaMixingRule(reqVO.getToAreaId(), sourceStock.getItemId(), sourceStock.getBatchId());
+
+        // 5. 鐢熸垚绉诲簱鍗曞彿
+        String moveCode = "MOVE_" + System.currentTimeMillis();
+
+        // 6. 鍒涘缓璋冩嫧绉诲嚭浜嬪姟锛堣礋鏁帮級
+        MesWmTransactionSaveReqDTO outTransaction = new MesWmTransactionSaveReqDTO()
+                .setType(MesWmTransactionTypeEnum.MOVE_OUT.getType())
+                .setBizType(MesBizTypeConstants.WM_TRANSFER_OUT)
+                .setBizId(sourceStock.getId())
+                .setBizCode(moveCode)
+                .setBizLineId(sourceStock.getId())
+                .setItemId(sourceStock.getItemId())
+                .setQuantity(reqVO.getQuantity().negate())
+                .setBatchId(sourceStock.getBatchId())
+                .setBatchCode(sourceStock.getBatchCode())
+                .setWarehouseId(sourceStock.getWarehouseId())
+                .setLocationId(sourceStock.getLocationId())
+                .setAreaId(sourceStock.getAreaId())
+                .setVendorId(sourceStock.getVendorId())
+                .setMaterialStockId(sourceStock.getId())
+                .setCheckFlag(true);
+        Long outTransactionId = wmTransactionService.createTransaction(outTransaction);
+
+        // 7. 鏌ユ壘鐩爣搴撲綅鐨勫簱瀛樿褰曪紙蹇界暐渚涘簲鍟嗭級锛岀敤浜庡悎骞跺簱瀛�
+        MesWmMaterialStockDO targetStock = materialStockMapper.selectByCompositeKeyIgnoreVendor(
+                sourceStock.getItemId(),
+                reqVO.getToWarehouseId(),
+                reqVO.getToLocationId(),
+                reqVO.getToAreaId(),
+                sourceStock.getBatchId()
+        );
+
+        // 8. 鍒涘缓璋冩嫧绉诲叆浜嬪姟锛堟鏁帮級锛屽叧鑱斿嚭搴撲簨鍔�
+        // 浣跨敤鐩爣搴撲綅宸叉湁搴撳瓨璁板綍鐨� vendorId锛堝鏋滄湁锛夛紝鍚﹀垯涓嶄紶
+        MesWmTransactionSaveReqDTO inTransaction = new MesWmTransactionSaveReqDTO()
+                .setType(MesWmTransactionTypeEnum.MOVE_IN.getType())
+                .setBizType(MesBizTypeConstants.WM_TRANSFER_IN)
+                .setBizId(sourceStock.getId())
+                .setBizCode(moveCode)
+                .setBizLineId(sourceStock.getId())
+                .setItemId(sourceStock.getItemId())
+                .setQuantity(reqVO.getQuantity())
+                .setBatchId(sourceStock.getBatchId())
+                .setBatchCode(sourceStock.getBatchCode())
+                .setWarehouseId(reqVO.getToWarehouseId())
+                .setLocationId(reqVO.getToLocationId())
+                .setAreaId(reqVO.getToAreaId())
+                .setVendorId(targetStock != null ? targetStock.getVendorId() : null)
+                .setRelatedTransactionId(outTransactionId)
+                .setCheckFlag(false);
+        wmTransactionService.createTransaction(inTransaction);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
index 9e40829..f9d5110 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
@@ -10,6 +10,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferDO;
 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.materialstock.MesWmMaterialStockDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.transfer.MesWmTransferMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
@@ -189,20 +190,33 @@
         // 2. 閬嶅巻琛屽拰瀵瑰簲鐨勬槑缁嗭紝鍒涘缓浜嬪姟
         for (MesWmTransferLineDO line : lines) {
             List<MesWmTransferDetailDO> details = detailMap.getOrDefault(line.getId(), Collections.emptyList());
+            // 浠� materialStockId 鑾峰彇婧愬簱瀛樹俊鎭紙鍖呮嫭 vendor_id, batch_id 绛夛級
+            Long vendorId = null;
+            String batchCode = null;
+            if (line.getMaterialStockId() != null) {
+                MesWmMaterialStockDO sourceStock = materialStockService.getMaterialStock(line.getMaterialStockId());
+                if (sourceStock != null) {
+                    vendorId = sourceStock.getVendorId();
+                    batchCode = sourceStock.getBatchCode();
+                }
+            }
             for (MesWmTransferDetailDO detail : details) {
                 // 2.1 鍏堟墽琛屽嚭搴擄細璋冩嫧绉诲嚭锛堜粠婧愪粨搴撴墸鍑忓簱瀛橈級锛屽嚭搴撴暟閲忓熀浜庡綋鍓嶆槑缁嗙殑鏁伴噺
                 Long outTransactionId = wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                         .setType(MesWmTransactionTypeEnum.MOVE_OUT.getType()).setItemId(line.getItemId())
                         .setQuantity(detail.getQuantity().negate()) // 搴撳瓨鍑忓皯锛堟寜 detail 鐨勬暟閲忓嚭锛�
-                        .setBatchId(line.getBatchId())
+                        .setBatchId(line.getBatchId()).setBatchCode(batchCode)
+                        .setVendorId(vendorId)
+                        .setMaterialStockId(line.getMaterialStockId())
                         .setWarehouseId(line.getFromWarehouseId()).setLocationId(line.getFromLocationId()).setAreaId(line.getFromAreaId())
                         .setBizType(MesBizTypeConstants.WM_TRANSFER_OUT).setBizId(transfer.getId())
                         .setBizCode(transfer.getCode()).setBizLineId(line.getId()));
                 // 2.2 鍐嶆墽琛屽叆搴擄細璋冩嫧绉诲叆锛堝悜鐩爣浠撳簱澧炲姞搴撳瓨锛夛紝鍏ュ簱鏁伴噺绛変簬鐩稿悓鏄庣粏鐨勬暟閲�
+                // 娉ㄦ剰锛氬叆搴撴椂涓嶄紶 vendorId锛屼互蹇界暐渚涘簲鍟嗙淮搴︼紝鍚堝苟鍒扮洰鏍囧簱浣嶅凡鏈夊簱瀛�
                 wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                         .setType(MesWmTransactionTypeEnum.MOVE_IN.getType()).setItemId(line.getItemId()) // 鐗╂枡淇℃伅鏉ヨ嚜 line
                         .setQuantity(detail.getQuantity()) // 搴撳瓨澧炲姞锛堟寜 detail 鐨勬暟閲忓叆锛�
-                        .setBatchId(line.getBatchId()) // 鎵规淇℃伅鏉ヨ嚜 line
+                        .setBatchId(line.getBatchId()).setBatchCode(batchCode) // 鎵规淇℃伅鏉ヨ嚜 line
                         .setWarehouseId(detail.getToWarehouseId()).setLocationId(detail.getToLocationId()).setAreaId(detail.getToAreaId())
                         .setBizType(MesBizTypeConstants.WM_TRANSFER_IN).setBizId(transfer.getId())
                         .setBizCode(transfer.getCode()).setBizLineId(line.getId())
diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
index f8ba5ec..650b72f 100644
--- a/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
+++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
@@ -58,11 +58,12 @@
                 "[CRM 妯″潡 yudao-module-crm - 宸茬鐢╙[鍙傝�� https://doc.iocoder.cn/crm/build/ 寮�鍚痌");
     }
 
-    @RequestMapping(value = { "/admin-api/mes/**"})
-    public CommonResult<Boolean> mes404() {
-        return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                "[MES 绯荤粺 yudao-module-mes - 宸茬鐢╙[鍙傝�� https://doc.iocoder.cn/mes/build/ 寮�鍚痌");
-    }
+    // MES 妯″潡宸插惎鐢紝绉婚櫎鍏滃簳鏄犲皠
+    // @RequestMapping(value = { "/admin-api/mes/**"})
+    // public CommonResult<Boolean> mes404() {
+    //     return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+    //             "[MES 绯荤粺 yudao-module-mes - 宸茬鐢╙[鍙傝�� https://doc.iocoder.cn/mes/build/ 寮�鍚痌");
+    // }
 
     @RequestMapping(value = { "/admin-api/im/**"})
     public CommonResult<Boolean> im404() {

--
Gitblit v1.9.3