From 940c8481d2fdcf51341db4ccd6fd6fcc2d43debb Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 29 六月 2026 15:32:58 +0800
Subject: [PATCH] 初始化项目
---
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java | 7
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java | 88
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java | 31
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java | 12
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandSaveReqVO.java | 25
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/WmsInventoryReserveController.java | 137
yudao-server/pom.xml | 7
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApiImpl.java | 130
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java | 18
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java | 16
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java | 12
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java | 24
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsInventoryBizTypeEnum.java | 68
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java | 10
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java | 107
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java | 9
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java | 36
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java | 12
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptResultDTO.java | 30
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/unit/MdmUnitMeasureMapper.java | 50
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java | 102
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java | 6
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java | 124
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryTransactionDO.java | 116
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApiImpl.java | 73
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java | 79
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApiImpl.java | 55
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseService.java | 97
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsTransactionTypeEnum.java | 43
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java | 153
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java | 177
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java | 10
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseSaveReqVO.java | 42
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApi.java | 42
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java | 6
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApi.java | 50
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseServiceImpl.java | 126
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/brand/MdmBrandDO.java | 52
docs/batch1/database_changes.sql | 166
yudao-module-wms/pom.xml | 7
yudao-module-mdm-api/pom.xml | 26
docs/batch6/frontend_changes.md | 163
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java | 93
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java | 84
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReservePageReqVO.java | 27
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/category/MdmItemCategoryMapper.java | 63
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java | 144
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryReserveMapper.java | 52
pom.xml | 2
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryRespVO.java | 39
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java | 12
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java | 143
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategorySaveReqVO.java | 34
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java | 6
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java | 75
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemBatchConfigMapper.java | 19
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryRespDTO.java | 61
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApiImpl.java | 55
docs/batch5/frontend_changes.md | 83
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java | 3
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApiImpl.java | 49
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java | 4
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveService.java | 74
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java | 69
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionService.java | 29
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java | 88
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java | 31
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java | 107
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApi.java | 50
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseRespVO.java | 46
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/dto/MdmUnitMeasureRespDTO.java | 33
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApi.java | 88
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/warehouse/MdmWarehouseMapper.java | 52
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApiImpl.java | 61
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/listener/MesProWorkOrderStatusListener.java | 31
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java | 14
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java | 16
docs/batch3/frontend_changes.md | 181
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java | 98
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java | 4
docs/batch4/frontend_changes.md | 93
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemSkuDO.java | 99
yudao-module-mdm/pom.xml | 63
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java | 69
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java | 6
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java | 10
docs/mdm_frontend_change.md | 241 +
yudao-module-mes/pom.xml | 14
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java | 10
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java | 114
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java | 266
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java | 9
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasurePageReqVO.java | 29
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptReqDTO.java | 92
docs/batch2/frontend_changes.md | 229 +
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemSkuMapper.java | 45
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java | 31
docs/batch6/database_changes.sql | 42
yudao-module-erp/pom.xml | 21
sql/mdm_tables.sql | 334 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java | 50
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveServiceImpl.java | 210
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/brand/MdmBrandMapper.java | 50
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java | 46
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReserveRespVO.java | 64
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionServiceImpl.java | 38
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandServiceImpl.java | 124
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApi.java | 83
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentResultDTO.java | 30
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java | 134
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOutStatusListener.java | 31
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryTransactionMapper.java | 29
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryPageReqVO.java | 30
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java | 63
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/dto/MdmItemCategoryRespDTO.java | 28
docs/batch4/database_changes.sql | 53
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java | 107
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSkuRespDTO.java | 51
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java | 42
docs/batch5/database_changes.sql | 53
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java | 16
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentReqDTO.java | 92
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java | 16
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java | 20
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java | 2
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java | 3
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsUseReserveReqDTO.java | 30
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveStatusEnum.java | 41
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java | 10
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java | 73
docs/batch1/frontend_changes.md | 321 +
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java | 6
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandPageReqVO.java | 29
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java | 66
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApiImpl.java | 266 +
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehousePageReqVO.java | 32
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java | 92
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuServiceImpl.java | 151
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java | 12
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java | 189
docs/batch3/database_changes.sql | 53
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java | 139
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemPageReqVO.java | 41
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandService.java | 102
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java | 75
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java | 279
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java | 102
docs/cross_module_integration_plan.md | 531 ++
sql/mdm_migration.sql | 405 +
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/dto/StockRecordCreateDTO.java | 81
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryReserveDO.java | 100
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/stock/StockBizTypeEnum.java | 119
docs/im_module.sql | 353 +
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java | 16
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveBizTypeEnum.java | 39
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java | 68
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuService.java | 130
docs/batch2/database_changes.sql | 92
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseInStatusListener.java | 31
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java | 3
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java | 3
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java | 87
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java | 10
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java | 79
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApi.java | 72
docs/im_module_unlock.md | 399 +
docs/workflow_guide.md | 639 +-
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/dto/MdmBrandRespDTO.java | 22
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java | 8
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryQueryReqDTO.java | 30
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReserveReqDTO.java | 64
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/dto/MdmWarehouseRespDTO.java | 39
172 files changed, 12,956 insertions(+), 519 deletions(-)
diff --git a/docs/batch1/database_changes.sql b/docs/batch1/database_changes.sql
new file mode 100644
index 0000000..985291c
--- /dev/null
+++ b/docs/batch1/database_changes.sql
@@ -0,0 +1,166 @@
+-- =====================================================
+-- Batch 1: WMS API 妯″潡寤鸿 - 鏁版嵁搴撳彉鏇磋剼鏈�
+-- 鐗堟湰: v1.0
+-- 鏃ユ湡: 2026-06-26
+-- 璇存槑: 鏂板搴撳瓨棰勭暀鐩稿叧琛紝鎵╁睍搴撳瓨瀛楁
+-- =====================================================
+
+-- =====================================================
+-- 1. 鏂板搴撳瓨棰勭暀琛�
+-- =====================================================
+
+-- 搴撳瓨棰勭暀琛紙鐢ㄤ簬閿�鍞鍗曠瓑涓氬姟棰勭暀搴撳瓨锛�
+CREATE TABLE IF NOT EXISTS `wms_inventory_reserve` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `reserve_no` VARCHAR(64) NOT NULL COMMENT '棰勭暀鍗曞彿',
+ `biz_type` INT NOT NULL COMMENT '涓氬姟绫诲瀷锛�10=閿�鍞鐣� 20=鐢熶骇棰勭暀锛�',
+ `biz_id` BIGINT NOT NULL COMMENT '涓氬姟鍗曟嵁ID',
+ `biz_no` VARCHAR(64) DEFAULT NULL COMMENT '涓氬姟鍗曟嵁鍙�',
+ `sku_id` BIGINT NOT NULL COMMENT 'SKU缂栧彿',
+ `warehouse_id` BIGINT NOT NULL COMMENT '浠撳簱缂栧彿',
+ `reserve_quantity` DECIMAL(24,6) NOT NULL COMMENT '棰勭暀鏁伴噺',
+ `used_quantity` DECIMAL(24,6) DEFAULT 0 COMMENT '宸蹭娇鐢ㄦ暟閲�',
+ `status` INT NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0=棰勭暀涓� 1=閮ㄥ垎浣跨敤 2=宸蹭娇鐢� 3=宸查噴鏀撅級',
+ `expire_time` DATETIME DEFAULT NULL COMMENT '杩囨湡鏃堕棿',
+ `remark` VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_reserve_no` (`reserve_no`),
+ KEY `idx_biz` (`biz_type`, `biz_id`),
+ KEY `idx_sku_warehouse` (`sku_id`, `warehouse_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='搴撳瓨棰勭暀琛�';
+
+-- =====================================================
+-- 2. 鏂板搴撳瓨浜嬪姟娴佹按琛�
+-- =====================================================
+
+CREATE TABLE IF NOT EXISTS `wms_inventory_transaction` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `transaction_no` VARCHAR(64) NOT NULL COMMENT '浜嬪姟娴佹按鍙�',
+ `transaction_type` INT NOT NULL COMMENT '浜嬪姟绫诲瀷锛�10=鍏ュ簱 20=鍑哄簱 30=棰勭暀 40=閲婃斁 50=鐩樼偣 60=璋冩嫧锛�',
+ `biz_type` INT NOT NULL COMMENT '涓氬姟绫诲瀷',
+ `biz_id` BIGINT NOT NULL COMMENT '涓氬姟鍗曟嵁ID',
+ `biz_no` VARCHAR(64) DEFAULT NULL COMMENT '涓氬姟鍗曟嵁鍙�',
+ `sku_id` BIGINT NOT NULL COMMENT 'SKU缂栧彿',
+ `warehouse_id` BIGINT NOT NULL COMMENT '浠撳簱缂栧彿',
+ `quantity` DECIMAL(24,6) NOT NULL COMMENT '鍙樺姩鏁伴噺锛堟鏁板叆搴擄紝璐熸暟鍑哄簱锛�',
+ `before_quantity` DECIMAL(24,6) DEFAULT NULL COMMENT '鍙樺姩鍓嶆暟閲�',
+ `after_quantity` DECIMAL(24,6) DEFAULT NULL COMMENT '鍙樺姩鍚庢暟閲�',
+ `reserve_id` BIGINT DEFAULT NULL COMMENT '鍏宠仈棰勭暀ID',
+ `reserve_no` VARCHAR(64) DEFAULT NULL COMMENT '鍏宠仈棰勭暀鍗曞彿',
+ `transaction_time` DATETIME NOT NULL COMMENT '浜嬪姟鏃堕棿',
+ `remark` VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_transaction_no` (`transaction_no`),
+ KEY `idx_biz` (`biz_type`, `biz_id`),
+ KEY `idx_sku` (`sku_id`),
+ KEY `idx_transaction_time` (`transaction_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='搴撳瓨浜嬪姟娴佹按琛�';
+
+-- =====================================================
+-- 3. 鏁版嵁瀛楀吀鏂板
+-- =====================================================
+
+-- 搴撳瓨棰勭暀涓氬姟绫诲瀷
+INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('WMS 搴撳瓨棰勭暀涓氬姟绫诲瀷', 'wms_reserve_biz_type', 0, '搴撳瓨棰勭暀鐨勪笟鍔℃潵婧愮被鍨�', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `name` = 'WMS 搴撳瓨棰勭暀涓氬姟绫诲瀷';
+
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(10, '閿�鍞鐣�', '10', 'wms_reserve_biz_type', 0, 'primary', '', '閿�鍞鍗曢鐣欏簱瀛�', '1', NOW(), '1', NOW(), b'0'),
+(20, '鐢熶骇棰勭暀', '20', 'wms_reserve_biz_type', 0, 'success', '', '鐢熶骇宸ュ崟棰勭暀搴撳瓨', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
+
+-- 搴撳瓨浜嬪姟绫诲瀷
+INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('WMS 搴撳瓨浜嬪姟绫诲瀷', 'wms_transaction_type', 0, '搴撳瓨浜嬪姟娴佹按绫诲瀷', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `name` = 'WMS 搴撳瓨浜嬪姟绫诲瀷';
+
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(10, '鍏ュ簱', '10', 'wms_transaction_type', 0, 'success', '', '搴撳瓨鍏ュ簱', '1', NOW(), '1', NOW(), b'0'),
+(20, '鍑哄簱', '20', 'wms_transaction_type', 0, 'warning', '', '搴撳瓨鍑哄簱', '1', NOW(), '1', NOW(), b'0'),
+(30, '棰勭暀', '30', 'wms_transaction_type', 0, 'primary', '', '搴撳瓨棰勭暀', '1', NOW(), '1', NOW(), b'0'),
+(40, '閲婃斁', '40', 'wms_transaction_type', 0, 'info', '', '棰勭暀閲婃斁', '1', NOW(), '1', NOW(), b'0'),
+(50, '鐩樼偣', '50', 'wms_transaction_type', 0, 'default', '', '搴撳瓨鐩樼偣', '1', NOW(), '1', NOW(), b'0'),
+(60, '璋冩嫧', '60', 'wms_transaction_type', 0, 'default', '', '搴撳瓨璋冩嫧', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
+
+-- 搴撳瓨棰勭暀鐘舵��
+INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('WMS 搴撳瓨棰勭暀鐘舵��', 'wms_reserve_status', 0, '搴撳瓨棰勭暀鐘舵��', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `name` = 'WMS 搴撳瓨棰勭暀鐘舵��';
+
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(0, '棰勭暀涓�', '0', 'wms_reserve_status', 0, 'primary', '', '搴撳瓨棰勭暀涓�', '1', NOW(), '1', NOW(), b'0'),
+(1, '閮ㄥ垎浣跨敤', '1', 'wms_reserve_status', 0, 'warning', '', '閮ㄥ垎宸蹭娇鐢�', '1', NOW(), '1', NOW(), b'0'),
+(2, '宸蹭娇鐢�', '2', 'wms_reserve_status', 0, 'success', '', '宸插叏閮ㄤ娇鐢�', '1', NOW(), '1', NOW(), b'0'),
+(3, '宸查噴鏀�', '3', 'wms_reserve_status', 0, 'info', '', '棰勭暀宸查噴鏀�', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
+
+-- =====================================================
+-- 4. 鑿滃崟鏂板锛堝簱瀛橀鐣欑鐞嗭級
+-- =====================================================
+
+-- 搴撳瓨棰勭暀鏀惧湪"搴撳瓨绠$悊"鑿滃崟涓嬶紙ID: 6440锛�
+-- 濡傛灉搴撳瓨绠$悊鑿滃崟涓嶅瓨鍦紝鍒欐斁鍦� WMS 绯荤粺鑿滃崟涓嬶紙ID: 6400锛�
+
+-- 鏂规涓�锛氫娇鐢ㄥ浐瀹氱殑搴撳瓨绠$悊鑿滃崟ID锛堟帹鑽愶紝濡傛灉鏁版嵁搴撶粨鏋勪竴鑷达級
+-- SET @inventory_menu_id = 6440;
+
+-- 鏂规浜岋細鍔ㄦ�佹煡璇㈠簱瀛樼鐞嗚彍鍗旾D锛堟洿瀹夊叏锛�
+SET @inventory_menu_id = (SELECT `id` FROM `system_menu` WHERE `name` = '搴撳瓨绠$悊' AND `parent_id` = 6400 AND `deleted` = b'0' LIMIT 1);
+
+-- 濡傛灉搴撳瓨绠$悊鑿滃崟涓嶅瓨鍦紝浣跨敤 WMS 绯荤粺鑿滃崟ID
+SET @inventory_menu_id = IFNULL(@inventory_menu_id, 6400);
+
+-- 鏂板搴撳瓨棰勭暀鑿滃崟
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('搴撳瓨棰勭暀', '', 2, 30, @inventory_menu_id, 'reserve', 'ep:lock', 'wms/inventory/reserve/index', 'WmsInventoryReserve', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `name` = '搴撳瓨棰勭暀';
+
+-- 鑾峰彇鑿滃崟ID
+SET @reserve_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'reserve' AND `parent_id` = @inventory_menu_id AND `deleted` = b'0');
+
+-- 鏌ヨ鎸夐挳
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('搴撳瓨棰勭暀鏌ヨ', 'wms:inventory-reserve:query', 3, 1, @reserve_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `permission` = 'wms:inventory-reserve:query';
+
+-- 閲婃斁鎸夐挳
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('搴撳瓨棰勭暀閲婃斁', 'wms:inventory-reserve:release', 3, 2, @reserve_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `permission` = 'wms:inventory-reserve:release';
+
+-- =====================================================
+-- 鍥炴粴鑴氭湰锛堝闇�鍥炴粴锛屾墽琛屼互涓嬭鍙ワ級
+-- =====================================================
+
+/*
+-- 鍒犻櫎鏂板琛�
+DROP TABLE IF EXISTS `wms_inventory_reserve`;
+DROP TABLE IF EXISTS `wms_inventory_transaction`;
+
+-- 鍒犻櫎瀛楀吀
+DELETE FROM `system_dict_type` WHERE `type` = 'wms_reserve_biz_type';
+DELETE FROM `system_dict_data` WHERE `dict_type` = 'wms_reserve_biz_type';
+DELETE FROM `system_dict_type` WHERE `type` = 'wms_transaction_type';
+DELETE FROM `system_dict_data` WHERE `dict_type` = 'wms_transaction_type';
+DELETE FROM `system_dict_type` WHERE `type` = 'wms_reserve_status';
+DELETE FROM `system_dict_data` WHERE `dict_type` = 'wms_reserve_status';
+
+-- 鍒犻櫎鑿滃崟
+DELETE FROM `system_menu` WHERE `path` = 'reserve' AND `name` = '搴撳瓨棰勭暀';
+DELETE FROM `system_menu` WHERE `permission` LIKE 'wms:inventory-reserve:%';
+*/
\ No newline at end of file
diff --git a/docs/batch1/frontend_changes.md b/docs/batch1/frontend_changes.md
new file mode 100644
index 0000000..8afd949
--- /dev/null
+++ b/docs/batch1/frontend_changes.md
@@ -0,0 +1,321 @@
+# Batch 1: WMS API 妯″潡寤鸿 - 鍓嶇淇敼鏂囨。
+
+## 娑夊強椤甸潰
+
+- 鏂板椤甸潰锛氬簱瀛橀鐣欑鐞嗛〉闈�
+- 淇敼椤甸潰锛氬簱瀛樻煡璇㈤〉闈紙鏂板棰勭暀鏁伴噺鏄剧ず锛�
+
+---
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /wms/inventory-reserve/page | 搴撳瓨棰勭暀鍒嗛〉鏌ヨ |
+| POST | /wms/inventory-reserve/release | 閲婃斁搴撳瓨棰勭暀 |
+| GET | /wms/inventory-reserve/get | 鑾峰彇棰勭暀璇︽儏 |
+
+**璇锋眰鍙傛暟锛堝垎椤垫煡璇級锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| pageNo | Integer | 鏄� | 椤电爜 |
+| pageSize | Integer | 鏄� | 姣忛〉鏁伴噺 |
+| reserveNo | String | 鍚� | 棰勭暀鍗曞彿 |
+| bizType | Integer | 鍚� | 涓氬姟绫诲瀷 |
+| bizNo | String | 鍚� | 涓氬姟鍗曟嵁鍙� |
+| status | Integer | 鍚� | 鐘舵�� |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "reserveNo": "RES202606260001",
+ "bizType": 10,
+ "bizTypeName": "閿�鍞鐣�",
+ "bizId": 100,
+ "bizNo": "SO202606260001",
+ "skuId": 1,
+ "skuName": "鍟嗗搧A-绾㈣壊-L",
+ "warehouseId": 1,
+ "warehouseName": "涓讳粨搴�",
+ "reserveQuantity": 100.00,
+ "usedQuantity": 0.00,
+ "status": 0,
+ "statusName": "棰勭暀涓�",
+ "expireTime": "2026-06-30 00:00:00",
+ "createTime": "2026-06-26 10:00:00"
+ }
+ ],
+ "total": 1
+ }
+}
+```
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鏂板搴撳瓨棰勭暀绠$悊椤甸潰
+
+**鏂囦欢璺緞锛�** `src/views/wms/inventory/reserve/index.vue`
+
+```vue
+<template>
+ <ContentWrap>
+ <!-- 鎼滅储 -->
+ <el-form
+ class="-mb-15px"
+ :model="queryParams"
+ :inline="true"
+ label-width="68px"
+ >
+ <el-form-item label="棰勭暀鍗曞彿" prop="reserveNo">
+ <el-input
+ v-model="queryParams.reserveNo"
+ placeholder="璇疯緭鍏ラ鐣欏崟鍙�"
+ clearable
+ @keyup.enter="handleQuery"
+ class="!w-240px"
+ />
+ </el-form-item>
+ <el-form-item label="涓氬姟绫诲瀷" prop="bizType">
+ <el-select
+ v-model="queryParams.bizType"
+ placeholder="璇烽�夋嫨涓氬姟绫诲瀷"
+ clearable
+ class="!w-240px"
+ >
+ <el-option
+ v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RESERVE_BIZ_TYPE)"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select
+ v-model="queryParams.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ clearable
+ class="!w-240px"
+ >
+ <el-option
+ v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RESERVE_STATUS)"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />鎼滅储</el-button>
+ <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </ContentWrap>
+
+ <!-- 鍒楄〃 -->
+ <ContentWrap class="mt-10px">
+ <el-table v-loading="loading" :data="list" :stripe="true">
+ <el-table-column label="棰勭暀鍗曞彿" align="center" prop="reserveNo" width="180" />
+ <el-table-column label="涓氬姟绫诲瀷" align="center" prop="bizTypeName" width="100" />
+ <el-table-column label="涓氬姟鍗曟嵁鍙�" align="center" prop="bizNo" width="180" />
+ <el-table-column label="鍟嗗搧SKU" align="center" prop="skuName" width="200" />
+ <el-table-column label="浠撳簱" align="center" prop="warehouseName" width="120" />
+ <el-table-column label="棰勭暀鏁伴噺" align="center" prop="reserveQuantity" width="100" />
+ <el-table-column label="宸蹭娇鐢ㄦ暟閲�" align="center" prop="usedQuantity" width="100" />
+ <el-table-column label="鐘舵��" align="center" prop="statusName" width="100">
+ <template #default="scope">
+ <dict-tag :type="DICT_TYPE.WMS_RESERVE_STATUS" :value="scope.row.status" />
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="杩囨湡鏃堕棿"
+ align="center"
+ prop="expireTime"
+ :formatter="dateFormatter"
+ width="180"
+ />
+ <el-table-column label="鎿嶄綔" align="center" fixed="right" width="100">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ @click="handleRelease(scope.row)"
+ v-hasPermi="['wms:inventory-reserve:release']"
+ v-if="scope.row.status === 0 || scope.row.status === 1"
+ >
+ 閲婃斁
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 鍒嗛〉 -->
+ <Pagination
+ :total="total"
+ v-model:page="queryParams.pageNo"
+ v-model:limit="queryParams.pageSize"
+ @pagination="getList"
+ />
+ </ContentWrap>
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as ReserveApi from '@/api/wms/inventory/reserve'
+
+defineOptions({ name: 'WmsInventoryReserve' })
+
+const loading = ref(true)
+const list = ref([])
+const total = ref(0)
+const queryParams = reactive({
+ pageNo: 1,
+ pageSize: 10,
+ reserveNo: undefined,
+ bizType: undefined,
+ bizNo: undefined,
+ status: undefined
+})
+
+/** 鏌ヨ鍒楄〃 */
+const getList = async () => {
+ loading.value = true
+ try {
+ const data = await ReserveApi.getInventoryReservePage(queryParams)
+ list.value = data.list
+ total.value = data.total
+ } finally {
+ loading.value = false
+ }
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ queryParams.pageNo = 1
+ getList()
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+ queryParams.reserveNo = undefined
+ queryParams.bizType = undefined
+ queryParams.bizNo = undefined
+ queryParams.status = undefined
+ handleQuery()
+}
+
+/** 閲婃斁棰勭暀 */
+const handleRelease = async (row: any) => {
+ try {
+ await message.delConfirm(`纭閲婃斁棰勭暀鍗曞彿銆�${row.reserveNo}銆戠殑搴撳瓨棰勭暀锛焋)
+ await ReserveApi.releaseInventoryReserve(row.id)
+ message.success('閲婃斁鎴愬姛')
+ await getList()
+ } catch {}
+}
+
+/** 鍒濆鍖� */
+onMounted(() => {
+ getList()
+})
+</script>
+```
+
+### 2. 鏂板 API 鎺ュ彛鏂囦欢
+
+**鏂囦欢璺緞锛�** `src/api/wms/inventory/reserve/index.ts`
+
+```typescript
+import request from '@/config/axios'
+
+// 搴撳瓨棰勭暀鍒嗛〉鏌ヨ
+export const getInventoryReservePage = async (params: any) => {
+ return await request.get({ url: '/wms/inventory-reserve/page', params })
+}
+
+// 鑾峰彇搴撳瓨棰勭暀璇︽儏
+export const getInventoryReserve = async (id: number) => {
+ return await request.get({ url: '/wms/inventory-reserve/get?id=' + id })
+}
+
+// 閲婃斁搴撳瓨棰勭暀
+export const releaseInventoryReserve = async (id: number) => {
+ return await request.post({ url: '/wms/inventory-reserve/release?id=' + id })
+}
+```
+
+### 3. 瀛楀吀绫诲瀷鏂板
+
+**鏂囦欢璺緞锛�** `src/utils/dict.ts`
+
+鍦� `DICT_TYPE` 涓柊澧烇細
+
+```typescript
+WMS_RESERVE_BIZ_TYPE = 'wms_reserve_biz_type',
+WMS_RESERVE_STATUS = 'wms_reserve_status',
+WMS_TRANSACTION_TYPE = 'wms_transaction_type'
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. 鏈壒娆′富瑕佹柊澧炲簱瀛橀鐣欏姛鑳斤紝鍓嶇鏀瑰姩杈冨皬
+2. 搴撳瓨棰勭暀椤甸潰鎻愪緵棰勭暀鏌ヨ鍜屾墜鍔ㄩ噴鏀惧姛鑳�
+3. 鍚庣画鎵规锛圗RP/MES 闆嗘垚锛変細鑷姩璋冪敤棰勭暀 API锛屾棤闇�鍓嶇骞查
+4. 寤鸿鍏堟墽琛屾暟鎹簱鑴氭湰锛屽啀閮ㄧ讲鍚庣浠g爜锛屾渶鍚庢洿鏂板墠绔�
+
+---
+
+## 鍚庣鏂板鏂囦欢娓呭崟
+
+```
+yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/
+鈹溾攢鈹� api/inventory/
+鈹� 鈹溾攢鈹� WmsInventoryApi.java
+鈹� 鈹溾攢鈹� WmsInventoryApiImpl.java
+鈹� 鈹斺攢鈹� dto/
+鈹� 鈹溾攢鈹� WmsInventoryQueryReqDTO.java
+鈹� 鈹溾攢鈹� WmsInventoryRespDTO.java
+鈹� 鈹溾攢鈹� WmsReceiptReqDTO.java
+鈹� 鈹溾攢鈹� WmsReceiptResultDTO.java
+鈹� 鈹溾攢鈹� WmsReserveReqDTO.java
+鈹� 鈹溾攢鈹� WmsShipmentReqDTO.java
+鈹� 鈹溾攢鈹� WmsShipmentResultDTO.java
+鈹� 鈹斺攢鈹� WmsUseReserveReqDTO.java
+鈹溾攢鈹� controller/admin/inventory/
+鈹� 鈹溾攢鈹� WmsInventoryReserveController.java
+鈹� 鈹斺攢鈹� vo/
+鈹� 鈹溾攢鈹� WmsInventoryReservePageReqVO.java
+鈹� 鈹斺攢鈹� WmsInventoryReserveRespVO.java
+鈹溾攢鈹� dal/dataobject/inventory/
+鈹� 鈹溾攢鈹� WmsInventoryReserveDO.java
+鈹� 鈹斺攢鈹� WmsInventoryTransactionDO.java
+鈹溾攢鈹� dal/mysql/inventory/
+鈹� 鈹溾攢鈹� WmsInventoryReserveMapper.java
+鈹� 鈹斺攢鈹� WmsInventoryTransactionMapper.java
+鈹溾攢鈹� enums/inventory/
+鈹� 鈹溾攢鈹� WmsInventoryBizTypeEnum.java
+鈹� 鈹溾攢鈹� WmsReserveBizTypeEnum.java
+鈹� 鈹溾攢鈹� WmsReserveStatusEnum.java
+鈹� 鈹斺攢鈹� WmsTransactionTypeEnum.java
+鈹斺攢鈹� service/inventory/
+ 鈹溾攢鈹� WmsInventoryReserveService.java
+ 鈹溾攢鈹� WmsInventoryReserveServiceImpl.java
+ 鈹溾攢鈹� WmsInventoryTransactionService.java
+ 鈹斺攢鈹� WmsInventoryTransactionServiceImpl.java
+```
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-26
diff --git a/docs/batch2/database_changes.sql b/docs/batch2/database_changes.sql
new file mode 100644
index 0000000..3b687c2
--- /dev/null
+++ b/docs/batch2/database_changes.sql
@@ -0,0 +1,92 @@
+-- =====================================================
+-- Batch 2: ERP 閲囪喘璁㈠崟瀹℃壒闆嗘垚 - 鏁版嵁搴撳彉鏇磋剼鏈�
+-- 鐗堟湰: v1.0
+-- 鏃ユ湡: 2026-06-26
+-- 璇存槑: 涓� ERP 閲囪喘璁㈠崟娣诲姞 BPM 娴佺▼瀹℃壒闆嗘垚
+-- =====================================================
+
+-- =====================================================
+-- 1. 淇敼閲囪喘璁㈠崟琛紝鏂板娴佺▼瀹炰緥瀛楁
+-- =====================================================
+
+-- 鏂板娴佺▼瀹炰緥缂栧彿瀛楁
+ALTER TABLE `erp_purchase_order`
+ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM 娴佺▼瀹炰緥缂栧彿' AFTER `remark`;
+
+-- =====================================================
+-- 2. 鏁版嵁瀛楀吀淇敼锛堝鏍哥姸鎬侊級
+-- =====================================================
+
+-- 娉ㄦ剰锛欵RP 瀹℃牳鐘舵�佸凡鏈夊瓧鍏告暟鎹紝杩欓噷浠呰ˉ鍏呮柊澧炵姸鎬�
+-- 鍘� PROCESS(10) 浠�"鏈鏍�"鏀逛负"瀹℃壒涓�"
+-- 鍘� APPROVE(20) 浠�"宸插鏍�"鏀逛负"瀹℃壒閫氳繃"
+-- 鏂板 DRAFT(0) 鑽夌鐘舵��
+-- 鏂板 REJECT(30) 瀹℃壒涓嶉�氳繃鐘舵��
+
+-- 鏇存柊鐜版湁瀛楀吀鏁版嵁鐨勬爣绛�
+UPDATE `system_dict_data`
+SET `label` = '瀹℃壒涓�'
+WHERE `dict_type` = 'erp_audit_status' AND `value` = '10';
+
+UPDATE `system_dict_data`
+SET `label` = '瀹℃壒閫氳繃'
+WHERE `dict_type` = 'erp_audit_status' AND `value` = '20';
+
+-- 鏂板鑽夌鐘舵�侊紙濡傛灉涓嶅瓨鍦級
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 0, '鑽夌', '0', 'erp_audit_status', 0, 'info', '', '璁㈠崟鑽夌鐘舵��', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '0');
+
+-- 鏂板瀹℃壒涓嶉�氳繃鐘舵�侊紙濡傛灉涓嶅瓨鍦級
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 30, '瀹℃壒涓嶉�氳繃', '30', 'erp_audit_status', 0, 'danger', '', '瀹℃壒涓嶉�氳繃', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '30');
+
+-- =====================================================
+-- 3. 鑿滃崟鏂板锛堟彁浜ゅ鎵规寜閽級
+-- =====================================================
+
+-- 鑾峰彇閲囪喘璁㈠崟鑿滃崟ID
+SET @purchase_order_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'purchase-order' AND `deleted` = b'0' LIMIT 1);
+
+-- 濡傛灉閲囪喘璁㈠崟鑿滃崟涓嶅瓨鍦紝灏濊瘯閫氳繃鍚嶇О鏌ユ壘
+SET @purchase_order_menu_id = IFNULL(@purchase_order_menu_id,
+ (SELECT `id` FROM `system_menu` WHERE `name` = '閲囪喘璁㈠崟' AND `deleted` = b'0' LIMIT 1));
+
+-- 鏂板鎻愪氦瀹℃壒鎸夐挳
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT '閲囪喘璁㈠崟鎻愪氦瀹℃壒', 'erp:purchase-order:submit', 3, 10, @purchase_order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE @purchase_order_menu_id IS NOT NULL
+ON DUPLICATE KEY UPDATE `permission` = 'erp:purchase-order:submit';
+
+-- =====================================================
+-- 4. BPM 娴佺▼瀹氫箟锛堥渶瑕佸湪鍓嶇鍒涘缓娴佺▼妯″瀷锛�
+-- =====================================================
+
+-- 鎻愮ず锛氳鍦� BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷
+-- 娴佺▼鏍囪瘑锛圞ey锛夛細erp-purchase-order-audit
+-- 娴佺▼鍚嶇О锛欵RP 閲囪喘璁㈠崟瀹℃壒娴佺▼
+-- 涓氬姟鍏宠仈锛氶噰璐鍗� ID
+
+-- =====================================================
+-- 鍥炴粴鑴氭湰锛堝闇�鍥炴粴锛屾墽琛屼互涓嬭鍙ワ級
+-- =====================================================
+
+/*
+-- 鍒犻櫎鏂板瀛楁
+ALTER TABLE `erp_purchase_order` DROP COLUMN `process_instance_id`;
+
+-- 鎭㈠瀛楀吀鏁版嵁鏍囩
+UPDATE `system_dict_data` SET `label` = '鏈鏍�' WHERE `dict_type` = 'erp_audit_status' AND `value` = '10';
+UPDATE `system_dict_data` SET `label` = '宸插鏍�' WHERE `dict_type` = 'erp_audit_status' AND `value` = '20';
+
+-- 鍒犻櫎鏂板瀛楀吀鏁版嵁
+DELETE FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '0';
+DELETE FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '30';
+
+-- 鍒犻櫎鏂板鑿滃崟
+DELETE FROM `system_menu` WHERE `permission` = 'erp:purchase-order:submit';
+*/
\ No newline at end of file
diff --git a/docs/batch2/frontend_changes.md b/docs/batch2/frontend_changes.md
new file mode 100644
index 0000000..a5370b8
--- /dev/null
+++ b/docs/batch2/frontend_changes.md
@@ -0,0 +1,229 @@
+# Batch 2: ERP 閲囪喘璁㈠崟瀹℃壒闆嗘垚 - 鍓嶇淇敼鏂囨。
+
+## 姒傝堪
+
+鏈壒娆′负 ERP 閲囪喘璁㈠崟妯″潡闆嗘垚 BPM 娴佺▼瀹℃壒鍔熻兘锛屽疄鐜伴噰璐鍗曠殑瀹℃壒娴佺▼鍖栫鐞嗐��
+
+## 娑夊強椤甸潰
+
+- 淇敼椤甸潰锛氶噰璐鍗曠鐞嗛〉闈紙鏂板鎻愪氦瀹℃壒銆佸鎵圭姸鎬佸睍绀猴級
+- 鏂板閰嶇疆锛欱PM 娴佺▼妯″瀷閰嶇疆
+
+---
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/purchase-order/submit | 鎻愪氦閲囪喘璁㈠崟瀹℃壒 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘璁㈠崟缂栧彿 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 閲囪喘璁㈠崟鍒楄〃椤甸潰淇敼
+
+**鏂囦欢璺緞锛�** `src/views/erp/purchase/order/index.vue`
+
+#### 1.1 鏂板鎻愪氦瀹℃壒鎸夐挳
+
+鍦ㄦ搷浣滃垪鏂板"鎻愪氦瀹℃壒"鎸夐挳锛屼粎鑽夌鐘舵�佸彲瑙侊細
+
+```vue
+<el-table-column label="鎿嶄綔" align="center" fixed="right" width="200">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ @click="handleUpdate(scope.row.id)"
+ v-hasPermi="['erp:purchase-order:update']"
+ v-if="scope.row.status === 0"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ link
+ type="primary"
+ @click="handleSubmit(scope.row.id)"
+ v-hasPermi="['erp:purchase-order:update']"
+ v-if="scope.row.status === 0"
+ >
+ 鎻愪氦瀹℃壒
+ </el-button>
+ <el-button
+ link
+ type="primary"
+ @click="handleApprove(scope.row.id)"
+ v-hasPermi="['erp:purchase-order:update-status']"
+ v-if="scope.row.status === 10"
+ >
+ 瀹℃壒閫氳繃
+ </el-button>
+ <el-button
+ link
+ type="danger"
+ @click="handleDelete(scope.row.id)"
+ v-hasPermi="['erp:purchase-order:delete']"
+ v-if="scope.row.status === 0 || scope.row.status === 30"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+</el-table-column>
+```
+
+#### 1.2 鏂板鎻愪氦瀹℃壒鏂规硶
+
+```typescript
+/** 鎻愪氦瀹℃壒 */
+const handleSubmit = async (id: number) => {
+ try {
+ await message.confirm('纭鎻愪氦璇ラ噰璐鍗曡繘琛屽鎵癸紵')
+ await PurchaseOrderApi.submitPurchaseOrder(id)
+ message.success('鎻愪氦鎴愬姛')
+ await getList()
+ } catch {}
+}
+```
+
+#### 1.3 淇敼鐘舵�佸垪鏄剧ず
+
+```vue
+<el-table-column label="鐘舵��" align="center" prop="status" width="100">
+ <template #default="scope">
+ <dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
+ </template>
+</el-table-column>
+```
+
+### 2. API 鎺ュ彛鏂囦欢淇敼
+
+**鏂囦欢璺緞锛�** `src/api/erp/purchase/order/index.ts`
+
+鏂板鎻愪氦瀹℃壒鎺ュ彛锛�
+
+```typescript
+// 鎻愪氦閲囪喘璁㈠崟瀹℃壒
+export const submitPurchaseOrder = async (id: number) => {
+ return await request.post({ url: '/erp/purchase-order/submit?id=' + id })
+}
+```
+
+### 3. 瀛楀吀绫诲瀷纭
+
+**鏂囦欢璺緞锛�** `src/utils/dict.ts`
+
+纭繚 `DICT_TYPE` 涓凡鍖呭惈锛�
+
+```typescript
+ERP_AUDIT_STATUS = 'erp_audit_status'
+```
+
+瀛楀吀鏁版嵁闇�瑕佸寘鍚互涓嬪�硷細
+- 0: 鑽夌锛坕nfo 棰滆壊锛�
+- 10: 瀹℃壒涓紙warning 棰滆壊锛�
+- 20: 瀹℃壒閫氳繃锛坰uccess 棰滆壊锛�
+- 30: 瀹℃壒涓嶉�氳繃锛坉anger 棰滆壊锛�
+
+---
+
+## BPM 娴佺▼妯″瀷閰嶇疆
+
+### 娴佺▼妯″瀷淇℃伅
+
+鍦� BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷锛�
+
+| 閰嶇疆椤� | 鍊� |
+|--------|-----|
+| 娴佺▼鏍囪瘑锛圞ey锛� | erp-purchase-order-audit |
+| 娴佺▼鍚嶇О | ERP 閲囪喘璁㈠崟瀹℃壒娴佺▼ |
+| 娴佺▼鍒嗙被 | 涓氬姟娴佺▼ |
+
+### 娴佺▼璁捐寤鸿
+
+```
+寮�濮嬭妭鐐� 鈫� 閮ㄩ棬涓荤瀹℃壒 鈫� 閲囪喘缁忕悊瀹℃壒 鈫� 缁撴潫鑺傜偣
+```
+
+瀹℃壒缁撴灉锛�
+- 閫氳繃锛氱姸鎬佸彉涓� 20锛堝鎵归�氳繃锛�
+- 涓嶉�氳繃锛氱姸鎬佸彉涓� 30锛堝鎵逛笉閫氳繃锛�
+- 鍙栨秷锛氱姸鎬佸彉涓� 0锛堣崏绋匡級
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. 鍒涘缓閲囪喘璁㈠崟鏃讹紝榛樿鐘舵�佷粠"鏈鏍�"鏀逛负"鑽夌"
+2. 鍙湁鑽夌鐘舵�佺殑璁㈠崟鎵嶈兘鎻愪氦瀹℃壒
+3. 瀹℃壒涓殑璁㈠崟涓嶈兘缂栬緫鍜屽垹闄�
+4. 瀹℃壒涓嶉�氳繃鐨勮鍗曞彲浠ラ噸鏂扮紪杈戝悗鍐嶆鎻愪氦
+5. 闇�瑕佸厛鎵ц鏁版嵁搴撹剼鏈紝鍐嶉儴缃插悗绔唬鐮�
+6. 闇�瑕佸湪 BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷锛圞ey: erp-purchase-order-audit锛�
+
+---
+
+## 鍚庣鏂板/淇敼鏂囦欢娓呭崟
+
+```
+yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/
+鈹溾攢鈹� enums/
+鈹� 鈹溾攢鈹� ErpAuditStatus.java (淇敼锛氭柊澧� DRAFT銆丷EJECT 鐘舵��)
+鈹� 鈹斺攢鈹� ErrorCodeConstants.java (淇敼锛氭柊澧為敊璇爜)
+鈹溾攢鈹� dal/dataobject/purchase/
+鈹� 鈹� ErpPurchaseOrderDO.java (淇敼锛氭柊澧� processInstanceId 瀛楁)
+鈹溾攢鈹� service/purchase/
+鈹� 鈹溾攢鈹� ErpPurchaseOrderService.java (淇敼锛氭柊澧炴帴鍙f柟娉�)
+鈹� 鈹溾攢鈹� ErpPurchaseOrderServiceImpl.java (淇敼锛氬疄鐜� BPM 闆嗘垚)
+鈹� 鈹斺攢鈹� listener/
+鈹� 鈹斺攢鈹� ErpPurchaseOrderStatusListener.java (鏂板锛氬鎵圭姸鎬佺洃鍚櫒)
+鈹斺攢鈹� controller/admin/purchase/
+ 鈹� ErpPurchaseOrderController.java (淇敼锛氭柊澧炴彁浜ゅ鎵规帴鍙�)
+```
+
+---
+
+## 鐘舵�佹祦杞浘
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鑽夌 鈹�
+鈹� (status=0)鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鎻愪氦瀹℃壒
+ 鈫�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 瀹℃壒涓� 鈹�
+鈹� (status=10)鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 瀹℃壒缁撴灉
+ 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈫� 鈫� 鈫�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 瀹℃壒閫氳繃 鈹� 鈹� 瀹℃壒涓嶉�氳繃 鈹� 鈹� 鍙栨秷 鈹�
+鈹� (status=20) 鈹� 鈹� (status=30) 鈹� 鈹� (status=0) 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 閲嶆柊缂栬緫
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-26
\ No newline at end of file
diff --git a/docs/batch3/database_changes.sql b/docs/batch3/database_changes.sql
new file mode 100644
index 0000000..2c111cf
--- /dev/null
+++ b/docs/batch3/database_changes.sql
@@ -0,0 +1,53 @@
+-- =====================================================
+-- Batch 3: ERP 閿�鍞鍗曞鎵归泦鎴� - 鏁版嵁搴撳彉鏇磋剼鏈�
+-- 鐗堟湰: v1.0
+-- 鏃ユ湡: 2026-06-26
+-- 璇存槑: 涓� ERP 閿�鍞鍗曟坊鍔� BPM 娴佺▼瀹℃壒闆嗘垚
+-- =====================================================
+
+-- =====================================================
+-- 1. 淇敼閿�鍞鍗曡〃锛屾柊澧炴祦绋嬪疄渚嬪瓧娈�
+-- =====================================================
+
+-- 鏂板娴佺▼瀹炰緥缂栧彿瀛楁
+ALTER TABLE `erp_sale_order`
+ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM 娴佺▼瀹炰緥缂栧彿' AFTER `remark`;
+
+-- =====================================================
+-- 2. 鑿滃崟鏂板锛堟彁浜ゅ鎵规寜閽級
+-- =====================================================
+
+-- 鑾峰彇閿�鍞鍗曡彍鍗旾D
+SET @sale_order_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'sale-order' AND `deleted` = b'0' LIMIT 1);
+
+-- 濡傛灉閿�鍞鍗曡彍鍗曚笉瀛樺湪锛屽皾璇曢�氳繃鍚嶇О鏌ユ壘
+SET @sale_order_menu_id = IFNULL(@sale_order_menu_id,
+ (SELECT `id` FROM `system_menu` WHERE `name` = '閿�鍞鍗�' AND `deleted` = b'0' LIMIT 1));
+
+-- 鏂板鎻愪氦瀹℃壒鎸夐挳
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT '閿�鍞鍗曟彁浜ゅ鎵�', 'erp:sale-order:submit', 3, 10, @sale_order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE @sale_order_menu_id IS NOT NULL
+ON DUPLICATE KEY UPDATE `permission` = 'erp:sale-order:submit';
+
+-- =====================================================
+-- 3. BPM 娴佺▼瀹氫箟锛堥渶瑕佸湪鍓嶇鍒涘缓娴佺▼妯″瀷锛�
+-- =====================================================
+
+-- 鎻愮ず锛氳鍦� BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷
+-- 娴佺▼鏍囪瘑锛圞ey锛夛細erp-sale-order-audit
+-- 娴佺▼鍚嶇О锛欵RP 閿�鍞鍗曞鎵规祦绋�
+-- 涓氬姟鍏宠仈锛氶攢鍞鍗� ID
+
+-- =====================================================
+-- 鍥炴粴鑴氭湰锛堝闇�鍥炴粴锛屾墽琛屼互涓嬭鍙ワ級
+-- =====================================================
+
+/*
+-- 鍒犻櫎鏂板瀛楁
+ALTER TABLE `erp_sale_order` DROP COLUMN `process_instance_id`;
+
+-- 鍒犻櫎鏂板鑿滃崟
+DELETE FROM `system_menu` WHERE `permission` = 'erp:sale-order:submit';
+*/
\ No newline at end of file
diff --git a/docs/batch3/frontend_changes.md b/docs/batch3/frontend_changes.md
new file mode 100644
index 0000000..d94f483
--- /dev/null
+++ b/docs/batch3/frontend_changes.md
@@ -0,0 +1,181 @@
+# Batch 3: ERP 閿�鍞鍗曞鎵归泦鎴� - 鍓嶇淇敼鏂囨。
+
+## 姒傝堪
+
+鏈壒娆′负 ERP 閿�鍞鍗曟ā鍧楅泦鎴� BPM 娴佺▼瀹℃壒鍔熻兘锛屽疄鐜伴攢鍞鍗曠殑瀹℃壒娴佺▼鍖栫鐞嗐�備笌 Batch 2 閲囪喘璁㈠崟瀹℃壒闆嗘垚绫讳技銆�
+
+## 娑夊強椤甸潰
+
+- 淇敼椤甸潰锛氶攢鍞鍗曠鐞嗛〉闈紙鏂板鎻愪氦瀹℃壒銆佸鎵圭姸鎬佸睍绀猴級
+- 鏂板閰嶇疆锛欱PM 娴佺▼妯″瀷閰嶇疆
+
+---
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/sale-order/submit | 鎻愪氦閿�鍞鍗曞鎵� |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閿�鍞鍗曠紪鍙� |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 閿�鍞鍗曞垪琛ㄩ〉闈慨鏀�
+
+**鏂囦欢璺緞锛�** `src/views/erp/sale/order/index.vue`
+
+#### 1.1 鏂板鎻愪氦瀹℃壒鎸夐挳
+
+鍦ㄦ搷浣滃垪鏂板"鎻愪氦瀹℃壒"鎸夐挳锛屼粎鑽夌鐘舵�佸彲瑙侊細
+
+```vue
+<el-table-column label="鎿嶄綔" align="center" fixed="right" width="200">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ @click="handleUpdate(scope.row.id)"
+ v-hasPermi="['erp:sale-order:update']"
+ v-if="scope.row.status === 0"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ link
+ type="primary"
+ @click="handleSubmit(scope.row.id)"
+ v-hasPermi="['erp:sale-order:update']"
+ v-if="scope.row.status === 0"
+ >
+ 鎻愪氦瀹℃壒
+ </el-button>
+ <el-button
+ link
+ type="primary"
+ @click="handleApprove(scope.row.id)"
+ v-hasPermi="['erp:sale-order:update-status']"
+ v-if="scope.row.status === 10"
+ >
+ 瀹℃壒閫氳繃
+ </el-button>
+ <el-button
+ link
+ type="danger"
+ @click="handleDelete(scope.row.id)"
+ v-hasPermi="['erp:sale-order:delete']"
+ v-if="scope.row.status === 0 || scope.row.status === 30"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+</el-table-column>
+```
+
+#### 1.2 鏂板鎻愪氦瀹℃壒鏂规硶
+
+```typescript
+/** 鎻愪氦瀹℃壒 */
+const handleSubmit = async (id: number) => {
+ try {
+ await message.confirm('纭鎻愪氦璇ラ攢鍞鍗曡繘琛屽鎵癸紵')
+ await SaleOrderApi.submitSaleOrder(id)
+ message.success('鎻愪氦鎴愬姛')
+ await getList()
+ } catch {}
+}
+```
+
+#### 1.3 淇敼鐘舵�佸垪鏄剧ず
+
+```vue
+<el-table-column label="鐘舵��" align="center" prop="status" width="100">
+ <template #default="scope">
+ <dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
+ </template>
+</el-table-column>
+```
+
+### 2. API 鎺ュ彛鏂囦欢淇敼
+
+**鏂囦欢璺緞锛�** `src/api/erp/sale/order/index.ts`
+
+鏂板鎻愪氦瀹℃壒鎺ュ彛锛�
+
+```typescript
+// 鎻愪氦閿�鍞鍗曞鎵�
+export const submitSaleOrder = async (id: number) => {
+ return await request.post({ url: '/erp/sale-order/submit?id=' + id })
+}
+```
+
+---
+
+## BPM 娴佺▼妯″瀷閰嶇疆
+
+### 娴佺▼妯″瀷淇℃伅
+
+鍦� BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷锛�
+
+| 閰嶇疆椤� | 鍊� |
+|--------|-----|
+| 娴佺▼鏍囪瘑锛圞ey锛� | erp-sale-order-audit |
+| 娴佺▼鍚嶇О | ERP 閿�鍞鍗曞鎵规祦绋� |
+| 娴佺▼鍒嗙被 | 涓氬姟娴佺▼ |
+
+### 娴佺▼璁捐寤鸿
+
+```
+寮�濮嬭妭鐐� 鈫� 閿�鍞富绠″鎵� 鈫� 璐㈠姟瀹℃壒 鈫� 缁撴潫鑺傜偣
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. 鍒涘缓閿�鍞鍗曟椂锛岄粯璁ょ姸鎬佷粠"鏈鏍�"鏀逛负"鑽夌"
+2. 鍙湁鑽夌鐘舵�佺殑璁㈠崟鎵嶈兘鎻愪氦瀹℃壒
+3. 瀹℃壒涓殑璁㈠崟涓嶈兘缂栬緫鍜屽垹闄�
+4. 瀹℃壒涓嶉�氳繃鐨勮鍗曞彲浠ラ噸鏂扮紪杈戝悗鍐嶆鎻愪氦
+5. 闇�瑕佸厛鎵ц鏁版嵁搴撹剼鏈紝鍐嶉儴缃插悗绔唬鐮�
+6. 闇�瑕佸湪 BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷锛圞ey: erp-sale-order-audit锛�
+
+---
+
+## 鍚庣鏂板/淇敼鏂囦欢娓呭崟
+
+```
+yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/
+鈹溾攢鈹� enums/
+鈹� 鈹斺攢鈹� ErrorCodeConstants.java (淇敼锛氭柊澧為敊璇爜)
+鈹溾攢鈹� dal/dataobject/sale/
+鈹� 鈹斺攢鈹� ErpSaleOrderDO.java (淇敼锛氭柊澧� processInstanceId 瀛楁)
+鈹溾攢鈹� service/sale/
+鈹� 鈹溾攢鈹� ErpSaleOrderService.java (淇敼锛氭柊澧炴帴鍙f柟娉�)
+鈹� 鈹溾攢鈹� ErpSaleOrderServiceImpl.java (淇敼锛氬疄鐜� BPM 闆嗘垚)
+鈹� 鈹斺攢鈹� listener/
+鈹� 鈹斺攢鈹� ErpSaleOrderStatusListener.java (鏂板锛氬鎵圭姸鎬佺洃鍚櫒)
+鈹斺攢鈹� controller/admin/sale/
+ 鈹斺攢鈹� ErpSaleOrderController.java (淇敼锛氭柊澧炴彁浜ゅ鎵规帴鍙�)
+```
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-26
\ No newline at end of file
diff --git a/docs/batch4/database_changes.sql b/docs/batch4/database_changes.sql
new file mode 100644
index 0000000..304e895
--- /dev/null
+++ b/docs/batch4/database_changes.sql
@@ -0,0 +1,53 @@
+-- =====================================================
+-- Batch 4: ERP 閲囪喘鍏ュ簱鍗曞鎵归泦鎴� - 鏁版嵁搴撳彉鏇磋剼鏈�
+-- 鐗堟湰: v1.0
+-- 鏃ユ湡: 2026-06-26
+-- 璇存槑: 涓� ERP 閲囪喘鍏ュ簱鍗曟坊鍔� BPM 娴佺▼瀹℃壒闆嗘垚
+-- =====================================================
+
+-- =====================================================
+-- 1. 淇敼閲囪喘鍏ュ簱琛紝鏂板娴佺▼瀹炰緥瀛楁
+-- =====================================================
+
+-- 鏂板娴佺▼瀹炰緥缂栧彿瀛楁
+ALTER TABLE `erp_purchase_in`
+ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM 娴佺▼瀹炰緥缂栧彿' AFTER `remark`;
+
+-- =====================================================
+-- 2. 鑿滃崟鏂板锛堟彁浜ゅ鎵规寜閽級
+-- =====================================================
+
+-- 鑾峰彇閲囪喘鍏ュ簱鑿滃崟ID
+SET @purchase_in_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'purchase-in' AND `deleted` = b'0' LIMIT 1);
+
+-- 濡傛灉閲囪喘鍏ュ簱鑿滃崟涓嶅瓨鍦紝灏濊瘯閫氳繃鍚嶇О鏌ユ壘
+SET @purchase_in_menu_id = IFNULL(@purchase_in_menu_id,
+ (SELECT `id` FROM `system_menu` WHERE `name` = '閲囪喘鍏ュ簱' AND `deleted` = b'0' LIMIT 1));
+
+-- 鏂板鎻愪氦瀹℃壒鎸夐挳
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT '閲囪喘鍏ュ簱鎻愪氦瀹℃壒', 'erp:purchase-in:submit', 3, 10, @purchase_in_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE @purchase_in_menu_id IS NOT NULL
+ON DUPLICATE KEY UPDATE `permission` = 'erp:purchase-in:submit';
+
+-- =====================================================
+-- 3. BPM 娴佺▼瀹氫箟锛堥渶瑕佸湪鍓嶇鍒涘缓娴佺▼妯″瀷锛�
+-- =====================================================
+
+-- 鎻愮ず锛氳鍦� BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷
+-- 娴佺▼鏍囪瘑锛圞ey锛夛細erp-purchase-in-audit
+-- 娴佺▼鍚嶇О锛欵RP 閲囪喘鍏ュ簱瀹℃壒娴佺▼
+-- 涓氬姟鍏宠仈锛氶噰璐叆搴撳崟 ID
+
+-- =====================================================
+-- 鍥炴粴鑴氭湰锛堝闇�鍥炴粴锛屾墽琛屼互涓嬭鍙ワ級
+-- =====================================================
+
+/*
+-- 鍒犻櫎鏂板瀛楁
+ALTER TABLE `erp_purchase_in` DROP COLUMN `process_instance_id`;
+
+-- 鍒犻櫎鏂板鑿滃崟
+DELETE FROM `system_menu` WHERE `permission` = 'erp:purchase-in:submit';
+*/
\ No newline at end of file
diff --git a/docs/batch4/frontend_changes.md b/docs/batch4/frontend_changes.md
new file mode 100644
index 0000000..98a04f5
--- /dev/null
+++ b/docs/batch4/frontend_changes.md
@@ -0,0 +1,93 @@
+# Batch 4: ERP 閲囪喘鍏ュ簱鍗曞鎵归泦鎴� - 鍓嶇淇敼鏂囨。
+
+## 姒傝堪
+
+鏈壒娆′负 ERP 閲囪喘鍏ュ簱鍗曟ā鍧楅泦鎴� BPM 娴佺▼瀹℃壒鍔熻兘銆傞噰璐叆搴撳崟瀹℃壒閫氳繃鍚庝細鑷姩澧炲姞搴撳瓨銆�
+
+## 娑夊強椤甸潰
+
+- 淇敼椤甸潰锛氶噰璐叆搴撶鐞嗛〉闈紙鏂板鎻愪氦瀹℃壒銆佸鎵圭姸鎬佸睍绀猴級
+
+---
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/purchase-in/submit | 鎻愪氦閲囪喘鍏ュ簱瀹℃壒 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘鍏ュ簱鍗曠紪鍙� |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 閲囪喘鍏ュ簱鍒楄〃椤甸潰淇敼
+
+**鏂囦欢璺緞锛�** `src/views/erp/purchase/in/index.vue`
+
+#### 1.1 鏂板鎻愪氦瀹℃壒鎸夐挳
+
+```vue
+<el-button
+ link
+ type="primary"
+ @click="handleSubmit(scope.row.id)"
+ v-hasPermi="['erp:purchase-in:update']"
+ v-if="scope.row.status === 0"
+>
+ 鎻愪氦瀹℃壒
+</el-button>
+```
+
+#### 1.2 鏂板鎻愪氦瀹℃壒鏂规硶
+
+```typescript
+/** 鎻愪氦瀹℃壒 */
+const handleSubmit = async (id: number) => {
+ try {
+ await message.confirm('纭鎻愪氦璇ラ噰璐叆搴撳崟杩涜瀹℃壒锛熷鎵归�氳繃鍚庡皢澧炲姞搴撳瓨')
+ await PurchaseInApi.submitPurchaseIn(id)
+ message.success('鎻愪氦鎴愬姛')
+ await getList()
+ } catch {}
+}
+```
+
+### 2. API 鎺ュ彛鏂囦欢淇敼
+
+**鏂囦欢璺緞锛�** `src/api/erp/purchase/in/index.ts`
+
+```typescript
+// 鎻愪氦閲囪喘鍏ュ簱瀹℃壒
+export const submitPurchaseIn = async (id: number) => {
+ return await request.post({ url: '/erp/purchase-in/submit?id=' + id })
+}
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. 閲囪喘鍏ュ簱鍗曞鎵归�氳繃鍚庝細鑷姩澧炲姞搴撳瓨
+2. 瀹℃壒涓嶉�氳繃鎴栧彇娑堜笉浼氬彉鏇村簱瀛�
+3. BPM 娴佺▼ Key锛歚erp-purchase-in-audit`
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-26
\ No newline at end of file
diff --git a/docs/batch5/database_changes.sql b/docs/batch5/database_changes.sql
new file mode 100644
index 0000000..364e2b5
--- /dev/null
+++ b/docs/batch5/database_changes.sql
@@ -0,0 +1,53 @@
+-- =====================================================
+-- Batch 5: ERP 閿�鍞嚭搴撳崟瀹℃壒闆嗘垚 - 鏁版嵁搴撳彉鏇磋剼鏈�
+-- 鐗堟湰: v1.0
+-- 鏃ユ湡: 2026-06-26
+-- 璇存槑: 涓� ERP 閿�鍞嚭搴撳崟娣诲姞 BPM 娴佺▼瀹℃壒闆嗘垚
+-- =====================================================
+
+-- =====================================================
+-- 1. 淇敼閿�鍞嚭搴撹〃锛屾柊澧炴祦绋嬪疄渚嬪瓧娈�
+-- =====================================================
+
+-- 鏂板娴佺▼瀹炰緥缂栧彿瀛楁
+ALTER TABLE `erp_sale_out`
+ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM 娴佺▼瀹炰緥缂栧彿' AFTER `remark`;
+
+-- =====================================================
+-- 2. 鑿滃崟鏂板锛堟彁浜ゅ鎵规寜閽級
+-- =====================================================
+
+-- 鑾峰彇閿�鍞嚭搴撹彍鍗旾D
+SET @sale_out_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'sale-out' AND `deleted` = b'0' LIMIT 1);
+
+-- 濡傛灉閿�鍞嚭搴撹彍鍗曚笉瀛樺湪锛屽皾璇曢�氳繃鍚嶇О鏌ユ壘
+SET @sale_out_menu_id = IFNULL(@sale_out_menu_id,
+ (SELECT `id` FROM `system_menu` WHERE `name` = '閿�鍞嚭搴�' AND `deleted` = b'0' LIMIT 1));
+
+-- 鏂板鎻愪氦瀹℃壒鎸夐挳
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT '閿�鍞嚭搴撴彁浜ゅ鎵�', 'erp:sale-out:submit', 3, 10, @sale_out_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE @sale_out_menu_id IS NOT NULL
+ON DUPLICATE KEY UPDATE `permission` = 'erp:sale-out:submit';
+
+-- =====================================================
+-- 3. BPM 娴佺▼瀹氫箟锛堥渶瑕佸湪鍓嶇鍒涘缓娴佺▼妯″瀷锛�
+-- =====================================================
+
+-- 鎻愮ず锛氳鍦� BPM 绠$悊鍚庡彴鍒涘缓娴佺▼妯″瀷
+-- 娴佺▼鏍囪瘑锛圞ey锛夛細erp-sale-out-audit
+-- 娴佺▼鍚嶇О锛欵RP 閿�鍞嚭搴撳鎵规祦绋�
+-- 涓氬姟鍏宠仈锛氶攢鍞嚭搴撳崟 ID
+
+-- =====================================================
+-- 鍥炴粴鑴氭湰锛堝闇�鍥炴粴锛屾墽琛屼互涓嬭鍙ワ級
+-- =====================================================
+
+/*
+-- 鍒犻櫎鏂板瀛楁
+ALTER TABLE `erp_sale_out` DROP COLUMN `process_instance_id`;
+
+-- 鍒犻櫎鏂板鑿滃崟
+DELETE FROM `system_menu` WHERE `permission` = 'erp:sale-out:submit';
+*/
\ No newline at end of file
diff --git a/docs/batch5/frontend_changes.md b/docs/batch5/frontend_changes.md
new file mode 100644
index 0000000..cba1796
--- /dev/null
+++ b/docs/batch5/frontend_changes.md
@@ -0,0 +1,83 @@
+# Batch 5: ERP 閿�鍞嚭搴撳崟瀹℃壒闆嗘垚 - 鍓嶇淇敼鏂囨。
+
+## 姒傝堪
+
+鏈壒娆′负 ERP 閿�鍞嚭搴撳崟妯″潡闆嗘垚 BPM 娴佺▼瀹℃壒鍔熻兘銆傞攢鍞嚭搴撳崟瀹℃壒閫氳繃鍚庝細鑷姩鍑忓皯搴撳瓨銆�
+
+## 娑夊強椤甸潰
+
+- 淇敼椤甸潰锛氶攢鍞嚭搴撶鐞嗛〉闈紙鏂板鎻愪氦瀹℃壒銆佸鎵圭姸鎬佸睍绀猴級
+
+---
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/sale-out/submit | 鎻愪氦閿�鍞嚭搴撳鎵� |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閿�鍞嚭搴撳崟缂栧彿 |
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 閿�鍞嚭搴撳垪琛ㄩ〉闈慨鏀�
+
+**鏂囦欢璺緞锛�** `src/views/erp/sale/out/index.vue`
+
+#### 1.1 鏂板鎻愪氦瀹℃壒鎸夐挳
+
+```vue
+<el-button
+ link
+ type="primary"
+ @click="handleSubmit(scope.row.id)"
+ v-hasPermi="['erp:sale-out:update']"
+ v-if="scope.row.status === 0"
+>
+ 鎻愪氦瀹℃壒
+</el-button>
+```
+
+#### 1.2 鏂板鎻愪氦瀹℃壒鏂规硶
+
+```typescript
+/** 鎻愪氦瀹℃壒 */
+const handleSubmit = async (id: number) => {
+ try {
+ await message.confirm('纭鎻愪氦璇ラ攢鍞嚭搴撳崟杩涜瀹℃壒锛熷鎵归�氳繃鍚庡皢鍑忓皯搴撳瓨')
+ await SaleOutApi.submitSaleOut(id)
+ message.success('鎻愪氦鎴愬姛')
+ await getList()
+ } catch {}
+}
+```
+
+### 2. API 鎺ュ彛鏂囦欢淇敼
+
+**鏂囦欢璺緞锛�** `src/api/erp/sale/out/index.ts`
+
+```typescript
+// 鎻愪氦閿�鍞嚭搴撳鎵�
+export const submitSaleOut = async (id: number) => {
+ return await request.post({ url: '/erp/sale-out/submit?id=' + id })
+}
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. 閿�鍞嚭搴撳崟瀹℃壒閫氳繃鍚庝細鑷姩鍑忓皯搴撳瓨锛堣礋鏁板彉鏇达級
+2. 瀹℃壒涓嶉�氳繃鎴栧彇娑堜笉浼氬彉鏇村簱瀛�
+3. BPM 娴佺▼ Key锛歚erp-sale-out-audit`
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-26
\ No newline at end of file
diff --git a/docs/batch6/database_changes.sql b/docs/batch6/database_changes.sql
new file mode 100644
index 0000000..2b80ab2
--- /dev/null
+++ b/docs/batch6/database_changes.sql
@@ -0,0 +1,42 @@
+-- =============================================
+-- 鎵规鍏細MES 鐢熶骇宸ュ崟 BPM 瀹℃壒闆嗘垚
+-- 鎵ц椤哄簭锛�1
+-- =============================================
+
+-- 1. 涓� MES 鐢熶骇宸ュ崟琛ㄦ坊鍔犳祦绋嬪疄渚嬬紪鍙峰瓧娈�
+ALTER TABLE `mes_pro_work_order` ADD COLUMN `process_instance_id` VARCHAR(64) NULL COMMENT 'BPM 娴佺▼瀹炰緥缂栧彿' AFTER `status`;
+
+-- 2. 涓烘祦绋嬪疄渚嬬紪鍙峰垱寤虹储寮曪紙鍙�夛紝鐢ㄤ簬鎸夋祦绋嬪疄渚嬫煡璇級
+-- ALTER TABLE `mes_pro_work_order` ADD INDEX `idx_process_instance_id` (`process_instance_id`);
+
+-- 3. 鏇存柊宸ュ崟鐘舵�佸瓧鍏告暟鎹紙娣诲姞瀹℃壒涓�佸鎵逛笉閫氳繃鐘舵�侊級
+-- 娉ㄦ剰锛氬鏋滃瓧鍏告暟鎹凡瀛樺湪鍒欏拷鐣�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+ (1, '瀹℃壒涓�', '1', 'mes_pro_work_order_status', 0, 'warning', '', 'MES鐢熶骇宸ュ崟鐘舵��-瀹℃壒涓�', '1', NOW(), '1', NOW(), b'0'),
+ (5, '瀹℃壒涓嶉�氳繃', '5', 'mes_pro_work_order_status', 0, 'danger', '', 'MES鐢熶骇宸ュ崟鐘舵��-瀹℃壒涓嶉�氳繃', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `updater` = '1', `update_time` = NOW();
+
+-- 4. 娣诲姞鑿滃崟鎸夐挳锛堟彁浜ゅ鎵癸級
+-- 鍏堟煡璇㈢敓浜у伐鍗曡彍鍗旾D锛堝亣璁捐彍鍗曡矾寰勪负 MES绠$悊 > 鐢熶骇绠$悊 > 鐢熶骇宸ュ崟锛�
+SET @workorder_menu_id = (SELECT `id` FROM `system_menu` WHERE `name` = '鐢熶骇宸ュ崟' AND `parent_id` IN (SELECT `id` FROM `system_menu` WHERE `name` = '鐢熶骇绠$悊') LIMIT 1);
+
+INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT '鐢熶骇宸ュ崟鎻愪氦瀹℃壒', 'mes:pro-work-order:update', 3, 6, @workorder_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE @workorder_menu_id IS NOT NULL
+ AND NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `name` = '鐢熶骇宸ュ崟鎻愪氦瀹℃壒' AND `parent_id` = @workorder_menu_id);
+
+-- 5. 涓鸿鑹插垎閰嶆彁浜ゅ鎵规寜閽潈闄愶紙鏍规嵁瀹為檯闇�瑕佽皟鏁达級
+-- 绀轰緥锛氫负 MES 绠$悊鍛樿鑹插垎閰嶆潈闄�
+-- INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+-- SELECT r.id, m.id, '1', NOW(), '1', NOW(), b'0', 1
+-- FROM `system_role` r, `system_menu` m
+-- WHERE r.name = 'MES绠$悊鍛�' AND m.name = '鐢熶骇宸ュ崟鎻愪氦瀹℃壒';
+
+-- =============================================
+-- 鍥炴粴鑴氭湰锛堝闇�鍥炴粴鎵ц浠ヤ笅璇彞锛�
+-- =============================================
+-- ALTER TABLE `mes_pro_work_order` DROP COLUMN `process_instance_id`;
+-- DELETE FROM `system_dict_data` WHERE `dict_type` = 'mes_pro_work_order_status' AND `value` IN ('1', '5');
+-- DELETE FROM `system_menu` WHERE `name` = '鐢熶骇宸ュ崟鎻愪氦瀹℃壒';
diff --git a/docs/batch6/frontend_changes.md b/docs/batch6/frontend_changes.md
new file mode 100644
index 0000000..7f22dbc
--- /dev/null
+++ b/docs/batch6/frontend_changes.md
@@ -0,0 +1,163 @@
+# MES 鐢熶骇宸ュ崟瀹℃壒闆嗘垚
+
+## 娑夊強椤甸潰
+
+- 鐢熶骇宸ュ崟鍒楄〃椤甸潰 (`views/mes/pro/workorder/index.vue`)
+- 鐢熶骇宸ュ崟璇︽儏/缂栬緫椤甸潰 (`views/mes/pro/workorder/WorkOrderForm.vue`)
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| PUT | /mes/pro/work-order/submit | 鎻愪氦宸ュ崟瀹℃壒 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+
+**鍝嶅簲锛�** `{ "code": 200, "msg": "鎿嶄綔鎴愬姛", "data": true }`
+
+## 宸ュ崟鐘舵�佸彉鏇磋鏄�
+
+| 鍘熺姸鎬� | 鏂扮姸鎬� | 瑙﹀彂鏉′欢 |
+|--------|--------|----------|
+| 鑽夌(0) | 瀹℃壒涓�(1) | 鎻愪氦瀹℃壒 |
+| 瀹℃壒涓�(1) | 宸茬‘璁�(2) | 瀹℃壒閫氳繃 |
+| 瀹℃壒涓�(1) | 瀹℃壒涓嶉�氳繃(5) | 瀹℃壒涓嶉�氳繃 |
+| 瀹℃壒涓�(1) | 鑽夌(0) | 瀹℃壒鍙栨秷 |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鍒楄〃椤甸潰 - 娣诲姞"鎻愪氦瀹℃壒"鎸夐挳
+
+**浣嶇疆锛�** `views/mes/pro/workorder/index.vue`
+
+```html
+<!-- 鍦ㄦ搷浣滃垪涓坊鍔犳彁浜ゅ鎵规寜閽� -->
+<el-table-column label="鎿嶄綔" align="center" width="200">
+ <template #default="scope">
+ <el-button
+ v-if="scope.row.status === 0"
+ link
+ type="primary"
+ @click="handleSubmit(scope.row.id)"
+ v-hasPermi="['mes:pro-work-order:update']"
+ >
+ 鎻愪氦瀹℃壒
+ </el-button>
+ <el-button
+ v-if="scope.row.status === 0"
+ link
+ type="primary"
+ @click="handleUpdate(scope.row.id)"
+ v-hasPermi="['mes:pro-work-order:update']"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ v-if="scope.row.status === 0"
+ link
+ type="danger"
+ @click="handleDelete(scope.row.id)"
+ v-hasPermi="['mes:pro-work-order:delete']"
+ >
+ 鍒犻櫎
+ </el-button>
+ <!-- 宸茬‘璁ょ姸鎬佹樉绀哄畬鎴愩�佸彇娑堟寜閽� -->
+ <el-button
+ v-if="scope.row.status === 2"
+ link
+ type="success"
+ @click="handleFinish(scope.row.id)"
+ v-hasPermi="['mes:pro-work-order:update']"
+ >
+ 瀹屾垚
+ </el-button>
+ <el-button
+ v-if="scope.row.status === 2"
+ link
+ type="warning"
+ @click="handleCancel(scope.row.id)"
+ v-hasPermi="['mes:pro-work-order:update']"
+ >
+ 鍙栨秷
+ </el-button>
+ </template>
+</el-table-column>
+```
+
+### 2. data 鏁版嵁
+
+```js
+// 鐘舵�佸瓧鍏�
+const statusDict = ref([
+ { label: '鑽夌', value: 0, tagType: 'info' },
+ { label: '瀹℃壒涓�', value: 1, tagType: 'warning' },
+ { label: '宸茬‘璁�', value: 2, tagType: 'success' },
+ { label: '宸插畬鎴�', value: 3, tagType: 'primary' },
+ { label: '宸插彇娑�', value: 4, tagType: 'danger' },
+ { label: '瀹℃壒涓嶉�氳繃', value: 5, tagType: 'danger' }
+])
+```
+
+### 3. 鏂规硶
+
+```js
+import { submitWorkOrder } from '@/api/mes/pro/workorder'
+
+// 鎻愪氦瀹℃壒
+const handleSubmit = async (id: number) => {
+ try {
+ await message.confirm('纭鎻愪氦璇ュ伐鍗曡繘琛屽鎵癸紵')
+ await submitWorkOrder(id)
+ message.success('鎻愪氦鎴愬姛')
+ await getList()
+ } catch (error) {
+ console.error(error)
+ }
+}
+```
+
+### 4. API 鏂规硶
+
+**鏂囦欢锛�** `api/mes/pro/workorder/index.ts`
+
+```ts
+// 鎻愪氦宸ュ崟瀹℃壒
+export const submitWorkOrder = (id: number) => {
+ return request.put({ url: '/mes/pro/work-order/submit', params: { id } })
+}
+```
+
+### 5. 鐘舵�佹爣绛炬樉绀�
+
+```html
+<dict-tag :type="DICT_TYPE.MES_PRO_WORK_ORDER_STATUS" :value="scope.row.status" />
+```
+
+### 6. 琛ㄥ崟鐘舵�佺鐢ㄩ�昏緫
+
+**缂栬緫琛ㄥ崟锛�** 鍙湁鑽夌鐘舵��(0)鍜屽鎵逛笉閫氳繃鐘舵��(5)鎵嶈兘缂栬緫
+
+```js
+// 鍒ゆ柇鏄惁鍙紪杈�
+const isEditable = computed(() => {
+ return formData.value.status === 0 || formData.value.status === 5
+})
+```
+
+```html
+<el-form-item label="宸ュ崟缂栫爜" prop="code">
+ <el-input v-model="formData.code" :disabled="!isEditable" />
+</el-form-item>
+```
+
+## 娉ㄦ剰浜嬮」
+
+1. **鐘舵�佹祦杞�**锛氳崏绋� 鈫� 鎻愪氦瀹℃壒 鈫� 瀹℃壒涓� 鈫� 瀹℃壒閫氳繃(宸茬‘璁�) / 瀹℃壒涓嶉�氳繃
+2. **瀹℃壒涓嶉�氳繃鍚�**锛氬伐鍗曠姸鎬佸彉涓�"瀹℃壒涓嶉�氳繃"(5)锛屽彲浠ラ噸鏂扮紪杈戝悗鍐嶆鎻愪氦瀹℃壒
+3. **鏉冮檺鎺у埗**锛氭彁浜ゅ鎵逛娇鐢� `mes:pro-work-order:update` 鏉冮檺锛屼笌缂栬緫鏉冮檺涓�鑷�
+4. **鎸夐挳鏄剧ず**锛氭牴鎹伐鍗曠姸鎬佸姩鎬佹樉绀烘搷浣滄寜閽紝閬垮厤璇搷浣�
+5. **瀛楀吀鏁版嵁**锛氱‘淇濈郴缁熷瓧鍏� `mes_pro_work_order_status` 宸叉坊鍔犲鎵逛腑(1)鍜屽鎵逛笉閫氳繃(5)鐘舵��
diff --git a/docs/cross_module_integration_plan.md b/docs/cross_module_integration_plan.md
new file mode 100644
index 0000000..30e10c0
--- /dev/null
+++ b/docs/cross_module_integration_plan.md
@@ -0,0 +1,531 @@
+# MES/ERP/WMS/CRM 璺ㄦā鍧楅泦鎴愭柟妗�
+
+## 涓�銆佹暣浣撴灦鏋勬杩�
+
+### 1.1 妯″潡瀹氫綅
+
+| 妯″潡 | 鏍稿績鑱岃矗 | 涓氬姟杈圭晫 |
+|------|---------|---------|
+| **ERP** | 璐㈠姟鏍哥畻銆侀噰璐攢鍞鍗曠鐞� | 瀵瑰涓氬姟寰�鏉ャ�佹垚鏈牳绠� |
+| **MES** | 鐢熶骇鎵ц銆佸伐鑹虹鐞嗐�佽川閲忔帶鍒� | 鐢熶骇杩囩▼绠$悊 |
+| **WMS** | 浠撳偍绠$悊銆佸簱瀛樻帶鍒� | 瀹炵墿搴撳瓨绠$悊 |
+| **CRM** | 瀹㈡埛鍏崇郴銆侀攢鍞満浼氥�佸悎鍚岀鐞� | 瀹㈡埛渚т笟鍔$鐞� |
+
+### 1.2 缁熶竴鍩虹鏁版嵁鏋舵瀯
+
+閲囩敤 **鍗曚竴浜у搧涓绘暟鎹�** 妯″紡锛屾墦鐮存ā鍧楀鍨掞細
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍩虹鏁版嵁灞傦紙鍏变韩锛� 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 浜у搧鐗╂枡涓绘暟鎹紙MD_ITEM锛� 鈫� 鎵�鏈夋ā鍧楀叡鐢ㄥ敮涓�浜у搧缂栫爜 鈹�
+鈹� 瀹㈡埛涓绘暟鎹紙MD_CLIENT锛� 鈫� ERP/CRM 鍏辩敤 鈹�
+鈹� 渚涘簲鍟嗕富鏁版嵁锛圡D_VENDOR锛� 鈫� ERP/MES 鍏辩敤 鈹�
+鈹� 浠撳簱涓绘暟鎹紙MD Warehouse锛� 鈫� ERP/WMS/MES 鍏辩敤 鈹�
+鈹� 璁¢噺鍗曚綅涓绘暟鎹紙MD Unit锛� 鈫� 鍏ㄥ眬鍏辩敤 鈹�
+鈹� BOM 涓绘暟鎹紙MD BOM锛� 鈫� MES/ERP 鍏辩敤 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈫� 鈫� 鈫� 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� ERP 鈹� 鈹� MES 鈹� 鈹� WMS 鈹� 鈹� CRM 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 1.3 鏍稿績璁捐鍘熷垯
+
+1. **鏁版嵁鍞竴鎬�**锛氫骇鍝佺墿鏂欑紪鐮佸叏灞�鍞竴锛屽悇妯″潡閫氳繃浜у搧ID鍏宠仈
+2. **娴佺▼椹卞姩**锛氭墍鏈夎法妯″潡鎿嶄綔閫氳繃 BPM 瀹℃壒娴佺▼瑙﹀彂
+3. **浜嬩欢瑙h��**锛氭ā鍧楅棿閫氳繃浜嬩欢鐩戝惉鏈哄埗瀹炵幇鑱斿姩锛岄伩鍏嶇洿鎺ヨ皟鐢�
+4. **搴撳瓨涓績鍖�**锛歐MS 浣滀负鍞竴搴撳瓨绠$悊鑰咃紝鎵�鏈夊簱瀛樺彉鍔ㄩ渶缁忚繃 WMS
+
+---
+
+## 浜屻�佺粺涓�鍩虹鏁版嵁璁捐
+
+### 2.1 浜у搧鐗╂枡涓绘暟鎹紙鏍稿績锛�
+
+**琛ㄧ粨鏋勶細** `md_item`
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 | 浣跨敤妯″潡 |
+|------|------|------|---------|
+| id | BIGINT | 浜у搧ID锛堝叏灞�鍞竴锛� | 鍏ㄩ儴 |
+| code | VARCHAR(64) | 浜у搧缂栫爜锛堝叏灞�鍞竴锛� | 鍏ㄩ儴 |
+| name | VARCHAR(255) | 浜у搧鍚嶇О | 鍏ㄩ儴 |
+| specification | VARCHAR(255) | 瑙勬牸鍨嬪彿 | 鍏ㄩ儴 |
+| unit_measure_id | BIGINT | 涓昏閲忓崟浣� | 鍏ㄩ儴 |
+| category_id | BIGINT | 浜у搧鍒嗙被 | 鍏ㄩ儴 |
+| item_type | INT | 鐗╂枡绫诲瀷锛堝師鏂�/鍗婃垚鍝�/鎴愬搧/杈呮枡锛� | MES/ERP |
+| is_batch_managed | BOOLEAN | 鏄惁鎵规绠$悊 | WMS/MES |
+| is_serial_managed | BOOLEAN | 鏄惁搴忓垪鍙风鐞� | WMS/MES |
+| barcode_rule_id | BIGINT | 鏉$爜瑙勫垯 | WMS/MES |
+| safety_stock | DECIMAL | 瀹夊叏搴撳瓨 | WMS |
+| min_order_qty | DECIMAL | 鏈�灏忚璐ч噺 | ERP |
+| lead_time | INT | 閲囪喘鎻愬墠鏈燂紙澶╋級 | ERP/MES |
+| purchase_price | DECIMAL | 閲囪喘浠� | ERP |
+| sales_price | DECIMAL | 閿�鍞环 | ERP/CRM |
+| cost_price | DECIMAL | 鎴愭湰浠� | ERP |
+| status | INT | 鐘舵�侊紙鍚敤/绂佺敤锛� | 鍏ㄩ儴 |
+
+### 2.2 妯″潡鏁版嵁褰掑睘鍒掑垎
+
+| 鏁版嵁绫诲瀷 | 鎵�灞炴ā鍧� | 鍏变韩鑼冨洿 |
+|---------|---------|---------|
+| 浜у搧鐗╂枡 | MES锛堜富锛夋垨鐙珛妯″潡 | ERP/WMS/CRM/MES 鍏ㄩ儴鍏变韩 |
+| 瀹㈡埛淇℃伅 | CRM锛堜富锛夋垨鐙珛妯″潡 | ERP/MES 鍏变韩锛堣鍗�/鍙戣揣锛� |
+| 渚涘簲鍟嗕俊鎭� | ERP锛堜富锛夋垨鐙珛妯″潡 | MES 鍏变韩锛堝鍗忓姞宸ワ級 |
+| 浠撳簱淇℃伅 | WMS锛堜富锛夋垨鐙珛妯″潡 | ERP/MES 鍏变韩锛堝嚭鍏ュ簱锛� |
+| BOM 淇℃伅 | MES锛堜富锛夋垨鐙珛妯″潡 | ERP 鍏变韩锛堟垚鏈牳绠楋級 |
+| 宸ヨ壓璺嚎 | MES 鐙湁 | 涓嶅叡浜� |
+| 閿�鍞満浼� | CRM 鐙湁 | 涓嶅叡浜� |
+| 璐ㄦ鏍囧噯 | MES 鐙湁 | WMS 鍙傝�� |
+
+### 2.3 鏁版嵁鍚屾绛栫暐
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� API/浜嬩欢 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 涓绘暟鎹ā鍧� 鈹� 鈫愨攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 涓氬姟妯″潡 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈫� 鈫�
+ 鏁版嵁鍙樻洿浜嬩欢 鏈湴缂撳瓨鏇存柊
+ 鈫� 鈫�
+ 鍙戝竷 DomainEvent 鐩戝惉骞跺悓姝�
+```
+
+**鍚屾鏂瑰紡锛�**
+1. **瀹炴椂鍚屾**锛氬叧閿暟鎹彉鏇撮�氳繃 Spring Event 瀹炴椂閫氱煡
+2. **API 鏌ヨ**锛氫笟鍔℃ā鍧楅渶瑕佽鎯呮椂璋冪敤涓绘暟鎹ā鍧� API
+3. **鏈湴缂撳瓨**锛氶珮棰戣闂暟鎹湪涓氬姟妯″潡寤虹珛缂撳瓨琛紙浠呭瓨 ID+鍚嶇О+缂栫爜锛�
+
+---
+
+## 涓夈�佽法妯″潡涓氬姟娴佺▼闆嗘垚
+
+### 3.1 閲囪喘涓氬姟娴佺▼锛圗RP 鈫� WMS 鈫� MES锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 閲囪喘涓氬姟瀹屾暣娴佺▼ 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+ERP閲囪喘璁㈠崟 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫� 鍙垱寤洪噰璐叆搴撳崟
+ 鈫� 鈫�
+ 鐘舵�佸彉鏇� ERP閲囪喘鍏ュ簱
+ 鈫� 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫�
+鏇存柊璁㈠崟鐘舵�� 鈫�
+ WMS搴撳瓨澧炲姞
+ 鈫�
+ MES鍘熸枡鍙敤
+ 鈫�
+ 鍙垱寤虹敓浜ч鏂欏崟
+```
+
+**鑱斿姩鐐硅璁★細**
+
+| 瑙﹀彂鐐� | 鑱斿姩鎿嶄綔 | 瀹炵幇鏂瑰紡 |
+|--------|---------|---------|
+| ERP閲囪喘璁㈠崟瀹℃壒閫氳繃 | 璁㈠崟鐘舵��=宸插鎵癸紝鍏佽鍒涘缓鍏ュ簱鍗� | 鐘舵�佹牎楠� |
+| ERP閲囪喘鍏ュ簱瀹℃壒閫氳繃 | WMS搴撳瓨澧炲姞銆佹洿鏂伴噰璐鍗曞凡鍏ュ簱鏁伴噺 | 浜嬩欢鐩戝惉 |
+| ERP閲囪喘鍏ュ簱鍏ㄩ儴瀹屾垚 | 閲囪喘璁㈠崟鐘舵��=宸插畬鎴� | 鏁伴噺鍒ゆ柇 |
+| MES鐢熶骇棰嗘枡瀹℃壒閫氳繃 | WMS鍘熸枡搴撳瓨鍑忓皯 | 浜嬩欢鐩戝惉 |
+
+### 3.2 閿�鍞笟鍔℃祦绋嬶紙CRM 鈫� ERP 鈫� WMS锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 閿�鍞笟鍔″畬鏁存祦绋� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+CRM閿�鍞悎鍚� 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫� 鍙垱寤篍RP閿�鍞鍗�
+ 鈫� 鈫�
+ 鐘舵�佸彉鏇� ERP閿�鍞鍗�
+ 鈫� 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫�
+鍚堝悓灞ヨ寮�濮� 鈫�
+ 鍙垱寤洪攢鍞嚭搴撳崟
+ 鈫�
+ ERP閿�鍞嚭搴�
+ 鈫�
+ 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫�
+ 鈫�
+ WMS搴撳瓨鍑忓皯
+ 鈫�
+ ERP鎴愭湰鏍哥畻
+ 鈫�
+ CRM鍚堝悓灞ヨ鏇存柊
+```
+
+**鑱斿姩鐐硅璁★細**
+
+| 瑙﹀彂鐐� | 鑱斿姩鎿嶄綔 | 瀹炵幇鏂瑰紡 |
+|--------|---------|---------|
+| CRM鍚堝悓瀹℃壒閫氳繃 | 鍙垱寤篍RP閿�鍞鍗曪紝鍏宠仈鍚堝悓ID | 鐘舵�佹牎楠� |
+| ERP閿�鍞鍗曞鎵归�氳繃 | 璁㈠崟鐘舵��=宸插鎵癸紝鍏佽鍒涘缓鍑哄簱鍗� | 鐘舵�佹牎楠� |
+| ERP閿�鍞嚭搴撳鎵归�氳繃 | WMS搴撳瓨鍑忓皯銆佹洿鏂伴攢鍞鍗曞凡鍑哄簱鏁伴噺 | 浜嬩欢鐩戝惉 |
+| ERP閿�鍞嚭搴撳鎵归�氳繃 | 鏇存柊CRM鍚堝悓灞ヨ杩涘害 | 浜嬩欢鐩戝惉 |
+| ERP閿�鍞嚭搴撳叏閮ㄥ畬鎴� | 閿�鍞鍗曠姸鎬�=宸插畬鎴� | 鏁伴噺鍒ゆ柇 |
+
+### 3.3 鐢熶骇涓氬姟娴佺▼锛圡ES 鈫� WMS 鈫� ERP锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鐢熶骇涓氬姟瀹屾暣娴佺▼ 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+MES鐢熶骇宸ュ崟 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫� 鐘舵��=宸茬‘璁�
+ 鈫� 鈫�
+ 鑷姩鐢熸垚BOM 鍙垱寤虹敓浜т换鍔�
+ 鈫� 鈫�
+ 鍙垱寤洪鏂欏崟 MES鐢熶骇浠诲姟鎵ц
+ 鈫� 鈹�鈹�(浠诲姟寮�濮�)鈹�鈹�鈫�
+MES鐢熶骇棰嗘枡 鈫�
+ 鈫� 鍘熸枡娑堣�楄褰�
+ 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫� 鈫�
+ 鈫� 鈹�鈹�(浠诲姟瀹屾垚)鈹�鈹�鈫�
+WMS鍘熸枡搴撳瓨鍑忓皯 鈫�
+ MES鐢熶骇鍏ュ簱
+ 鈫�
+ 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫�
+ 鈫�
+ WMS鎴愬搧搴撳瓨澧炲姞
+ 鈫�
+ ERP鎴愭湰鏍哥畻
+ 鈫�
+ MES宸ュ崟瀹屾垚
+```
+
+**鑱斿姩鐐硅璁★細**
+
+| 瑙﹀彂鐐� | 鑱斿姩鎿嶄綔 | 瀹炵幇鏂瑰紡 |
+|--------|---------|---------|
+| MES鐢熶骇宸ュ崟瀹℃壒閫氳繃 | 宸ュ崟鐘舵��=宸茬‘璁わ紝鍙垱寤轰换鍔�/棰嗘枡鍗� | 鐘舵�佹牎楠� |
+| MES鐢熶骇棰嗘枡瀹℃壒閫氳繃 | WMS鍘熸枡搴撳瓨鍑忓皯 | 浜嬩欢鐩戝惉 |
+| MES鐢熶骇浠诲姟瀹屾垚 | 绱鐢熶骇鏁伴噺銆佸彲鍒涘缓鍏ュ簱鍗� | 鐘舵�佹牎楠� |
+| MES鐢熶骇鍏ュ簱瀹℃壒閫氳繃 | WMS鎴愬搧搴撳瓨澧炲姞銆佸伐鍗曠敓浜ф暟閲忕疮鍔� | 浜嬩欢鐩戝惉 |
+| MES鐢熶骇鍏ュ簱瀹℃壒閫氳繃 | ERP鎴愭湰鏍哥畻锛堝彲閫夛級 | 浜嬩欢鐩戝惉 |
+| MES宸ュ崟鍏ㄩ儴瀹屾垚 | 宸ュ崟鐘舵��=宸插畬鎴� | 鏁伴噺鍒ゆ柇 |
+
+### 3.4 閫�璐т笟鍔℃祦绋�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 閫�璐т笟鍔℃祦绋� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+ERP閲囪喘閫�璐� 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫� WMS搴撳瓨鍑忓皯
+ 鈫�
+ 鏇存柊閲囪喘璁㈠崟閫�璐ф暟閲�
+
+ERP閿�鍞��璐� 鈹�鈹�(瀹℃壒閫氳繃)鈹�鈹�鈫� WMS搴撳瓨澧炲姞
+ 鈫�
+ 鏇存柊閿�鍞鍗曢��璐ф暟閲�
+```
+
+---
+
+## 鍥涖�佸簱瀛樼鐞嗙粺涓�璁捐
+
+### 4.1 搴撳瓨鏁版嵁妯″瀷
+
+**鏍稿績琛細** `wms_stock`
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭 |
+| item_id | BIGINT | 浜у搧ID锛堝叧鑱� md_item锛� |
+| warehouse_id | BIGINT | 浠撳簱ID |
+| location_id | BIGINT | 搴撲綅ID |
+| batch_code | VARCHAR(64) | 鎵规鍙� |
+| quantity | DECIMAL | 褰撳墠搴撳瓨鏁伴噺 |
+| quantity_locked | DECIMAL | 閿佸畾鏁伴噺 |
+| quantity_available | DECIMAL | 鍙敤鏁伴噺 = quantity - quantity_locked |
+| unit_measure_id | BIGINT | 璁¢噺鍗曚綅 |
+
+### 4.2 搴撳瓨鍙樺姩鏉ユ簮
+
+| 鍙樺姩绫诲瀷 | 澧炲噺鏂瑰悜 | 鏉ユ簮妯″潡 | 瑙﹀彂鏉′欢 |
+|---------|---------|---------|---------|
+| 閲囪喘鍏ュ簱 | + | ERP | 閲囪喘鍏ュ簱鍗曞鎵归�氳繃 |
+| 閿�鍞嚭搴� | - | ERP | 閿�鍞嚭搴撳崟瀹℃壒閫氳繃 |
+| 鐢熶骇鍏ュ簱 | + | MES | 鐢熶骇鍏ュ簱鍗曞鎵归�氳繃 |
+| 鐢熶骇棰嗘枡 | - | MES | 鐢熶骇棰嗘枡鍗曞鎵归�氳繃 |
+| 鐢熶骇閫�鏂� | + | MES | 鐢熶骇閫�鏂欏崟瀹℃壒閫氳繃 |
+| 閲囪喘閫�璐� | - | ERP | 閲囪喘閫�璐у崟瀹℃壒閫氳繃 |
+| 閿�鍞��璐� | + | ERP | 閿�鍞��璐у崟瀹℃壒閫氳繃 |
+| 璋冩嫧鍏ュ簱 | + | WMS | 璋冩嫧鍗曞鎵归�氳繃锛堢洰鏍囦粨锛� |
+| 璋冩嫧鍑哄簱 | - | WMS | 璋冩嫧鍗曞鎵归�氳繃锛堟簮浠擄級 |
+| 鐩樼泩 | + | WMS | 鐩樼偣鍗曞鎵归�氳繃 |
+| 鐩樹簭 | - | WMS | 鐩樼偣鍗曞鎵归�氳繃 |
+
+### 4.3 搴撳瓨鏈嶅姟 API 璁捐
+
+```java
+/**
+ * 搴撳瓨鏈嶅姟鎺ュ彛 - WMS 妯″潡鎻愪緵锛屽叾浠栨ā鍧楄皟鐢�
+ */
+public interface WmsStockApi {
+
+ /**
+ * 鍒涘缓搴撳瓨璁板綍锛堝叆搴擄級
+ */
+ Long createStockRecord(StockRecordCreateDTO dto);
+
+ /**
+ * 閿佸畾搴撳瓨
+ */
+ void lockStock(Long itemId, Long warehouseId, BigDecimal quantity);
+
+ /**
+ * 瑙i攣搴撳瓨
+ */
+ void unlockStock(Long itemId, Long warehouseId, BigDecimal quantity);
+
+ /**
+ * 鏌ヨ鍙敤搴撳瓨
+ */
+ BigDecimal getAvailableStock(Long itemId, Long warehouseId);
+
+ /**
+ * 鎵归噺鏌ヨ搴撳瓨
+ */
+ Map<Long, BigDecimal> getAvailableStockMap(List<Long> itemIds, Long warehouseId);
+
+ /**
+ * 鏍¢獙搴撳瓨鍏呰冻
+ */
+ void validateStockEnough(Long itemId, Long warehouseId, BigDecimal quantity);
+}
+```
+
+### 4.4 搴撳瓨棰勮鏈哄埗
+
+| 棰勮绫诲瀷 | 瑙﹀彂鏉′欢 | 閫氱煡瀵硅薄 |
+|---------|---------|---------|
+| 搴撳瓨涓嶈冻 | 鍙敤搴撳瓨 < 瀹夊叏搴撳瓨 | ERP閲囪喘銆丮ES璁″垝 |
+| 搴撳瓨绉帇 | 搴撳瓨 > 鏈�楂樺簱瀛橀槇鍊� | ERP閿�鍞� |
+| 鏁堟湡棰勮 | 鎵规鏁堟湡 < 30澶� | WMS銆丮ES璐ㄩ噺 |
+| 閿佸畾瓒呮椂 | 閿佸畾鏃堕棿 > 閰嶇疆闃堝�� | 鐩稿叧涓氬姟浜哄憳 |
+
+---
+
+## 浜斻�丅PM 瀹℃壒娴佺▼缁熶竴璁捐
+
+### 5.1 瀹℃壒娴佺▼鏍囪瘑瑙勮寖
+
+| 妯″潡 | 涓氬姟鍗曟嵁 | 娴佺▼ KEY |
+|------|---------|---------|
+| ERP | 閲囪喘璁㈠崟 | `erp-purchase-order-audit` |
+| ERP | 閿�鍞鍗� | `erp-sale-order-audit` |
+| ERP | 閲囪喘鍏ュ簱 | `erp-purchase-in-audit` |
+| ERP | 閿�鍞嚭搴� | `erp-sale-out-audit` |
+| ERP | 閲囪喘閫�璐� | `erp-purchase-return-audit` |
+| ERP | 閿�鍞��璐� | `erp-sale-return-audit` |
+| MES | 鐢熶骇宸ュ崟 | `mes-pro-work-order-audit` |
+| MES | 鐢熶骇棰嗘枡 | `mes-material-issue-audit` |
+| MES | 鐢熶骇鍏ュ簱 | `mes-material-receive-audit` |
+| WMS | 搴撳瓨璋冩嫧 | `wms-stock-transfer-audit` |
+| WMS | 搴撳瓨鐩樼偣 | `wms-stock-check-audit` |
+| CRM | 閿�鍞悎鍚� | `crm-sale-contract-audit` |
+| CRM | 閿�鍞姤浠� | `crm-sale-quotation-audit` |
+
+### 5.2 瀹℃壒鐘舵�佹灇涓剧粺涓�
+
+```java
+/**
+ * 缁熶竴瀹℃壒鐘舵�佹灇涓�
+ */
+public enum AuditStatusEnum {
+ DRAFT(0, "鑽夌"), // 鏂板缓鐘舵��
+ AUDITING(1, "瀹℃壒涓�"), // 宸叉彁浜ゅ鎵�
+ APPROVED(20, "宸插鎵�"), // 瀹℃壒閫氳繃
+ REJECTED(30, "宸叉嫆缁�"), // 瀹℃壒涓嶉�氳繃
+ CANCELLED(40, "宸插彇娑�"); // 瀹℃壒鍙栨秷
+
+ // BPM 娴佺▼鐘舵�佸搴旓細
+ // BPM_RESULT_PASS = 2 鈫� APPROVED
+ // BPM_RESULT_REJECT = 3 鈫� REJECTED
+ // BPM_RESULT_CANCEL = 4 鈫� DRAFT锛堥��鍥炶崏绋匡級
+}
+```
+
+### 5.3 瀹℃壒鐩戝惉鍣ㄧ粺涓�妯℃澘
+
+```java
+/**
+ * 璺ㄦā鍧楀鎵圭粨鏋滅洃鍚櫒鍩虹被
+ */
+public abstract class CrossModuleAuditListener extends BpmProcessInstanceStatusEventListener {
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ // 1. 鏇存柊鏈ā鍧楀崟鎹姸鎬�
+ updateAuditStatus(event.getBusinessKey(), event.getStatus());
+
+ // 2. 瑙﹀彂璺ㄦā鍧楄仈鍔紙閫氳繃浜嬩欢鍙戝竷锛�
+ if (event.getStatus() == 2) { // 瀹℃壒閫氳繃
+ publishCrossModuleEvent(event.getBusinessKey());
+ }
+ }
+
+ /**
+ * 鍙戝竷璺ㄦā鍧楄仈鍔ㄤ簨浠�
+ */
+ protected abstract void publishCrossModuleEvent(String businessKey);
+}
+```
+
+---
+
+## 鍏�佽法妯″潡浜嬩欢鏈哄埗璁捐
+
+### 6.1 浜嬩欢绫诲瀷瀹氫箟
+
+| 浜嬩欢绫诲瀷 | 鍙戝竷妯″潡 | 鐩戝惉妯″潡 | 璇存槑 |
+|---------|---------|---------|---------|
+| `PurchaseInApprovedEvent` | ERP | WMS, ERP-PO | 閲囪喘鍏ュ簱瀹℃壒閫氳繃 |
+| `SaleOutApprovedEvent` | ERP | WMS, ERP-SO, CRM | 閿�鍞嚭搴撳鎵归�氳繃 |
+| `MaterialIssueApprovedEvent` | MES | WMS | 鐢熶骇棰嗘枡瀹℃壒閫氳繃 |
+| `MaterialReceiveApprovedEvent` | MES | WMS, MES-WO | 鐢熶骇鍏ュ簱瀹℃壒閫氳繃 |
+| `WorkOrderApprovedEvent` | MES | MES-Task | 鐢熶骇宸ュ崟瀹℃壒閫氳繃 |
+| `ContractApprovedEvent` | CRM | ERP | 閿�鍞悎鍚屽鎵归�氳繃 |
+
+### 6.2 浜嬩欢鏁版嵁缁撴瀯
+
+```java
+/**
+ * 璺ㄦā鍧椾簨浠跺熀绫�
+ */
+@Data
+public abstract class CrossModuleEvent {
+ /** 涓氬姟鍗曟嵁ID */
+ private Long businessId;
+ /** 涓氬姟鍗曟嵁缂栫爜 */
+ private String businessCode;
+ /** 浜у搧ID鍒楄〃 */
+ private List<Long> itemIds;
+ /** 浠撳簱ID */
+ private Long warehouseId;
+ /** 鎿嶄綔鏃堕棿 */
+ private LocalDateTime eventTime;
+ /** 鎿嶄綔鐢ㄦ埛ID */
+ private Long userId;
+ /** 绉熸埛ID */
+ private Long tenantId;
+}
+
+/**
+ * 閲囪喘鍏ュ簱瀹℃壒閫氳繃浜嬩欢
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PurchaseInApprovedEvent extends CrossModuleEvent {
+ /** 鍏ュ簱鏁伴噺锛堟寜浜у搧锛� */
+ private Map<Long, BigDecimal> quantityMap;
+ /** 鍏宠仈閲囪喘璁㈠崟ID */
+ private Long purchaseOrderId;
+}
+```
+
+### 6.3 浜嬩欢鍙戝竷涓庣洃鍚�
+
+```java
+// ERP 妯″潡鍙戝竷浜嬩欢
+@Component
+public class ErpPurchaseInStatusListener extends CrossModuleAuditListener {
+
+ @Resource
+ private ApplicationEventPublisher eventPublisher;
+
+ @Override
+ protected void publishCrossModuleEvent(String businessKey) {
+ ErpPurchaseInDO purchaseIn = purchaseInService.getPurchaseIn(Long.parseLong(businessKey));
+
+ // 鍙戝竷浜嬩欢渚涘叾浠栨ā鍧楃洃鍚�
+ eventPublisher.publishEvent(new PurchaseInApprovedEvent()
+ .setBusinessId(purchaseIn.getId())
+ .setItemIds(extractItemIds(purchaseIn))
+ .setQuantityMap(extractQuantityMap(purchaseIn))
+ .setWarehouseId(purchaseIn.getWarehouseId()));
+ }
+}
+
+// WMS 妯″潡鐩戝惉浜嬩欢
+@Component
+public class WmsStockEventListener {
+
+ @EventListener
+ public void onPurchaseInApproved(PurchaseInApprovedEvent event) {
+ // 澧炲姞搴撳瓨
+ event.getQuantityMap().forEach((itemId, quantity) -> {
+ stockService.createStockRecord(new StockRecordCreateDTO()
+ .setItemId(itemId)
+ .setWarehouseId(event.getWarehouseId())
+ .setQuantity(quantity)
+ .setBizType(StockBizTypeEnum.PURCHASE_IN));
+ });
+ }
+}
+
+// ERP 閲囪喘璁㈠崟妯″潡鐩戝惉浜嬩欢
+@Component
+public class ErpPurchaseOrderEventListener {
+
+ @EventListener
+ public void onPurchaseInApproved(PurchaseInApprovedEvent event) {
+ // 鏇存柊閲囪喘璁㈠崟宸插叆搴撴暟閲�
+ purchaseOrderService.updateInQuantity(event.getPurchaseOrderId(), event.getQuantityMap());
+ }
+}
+```
+
+---
+
+## 涓冦�佸疄鏂借鍒�
+
+### 7.1 鍒嗘壒娆″疄鏂介『搴�
+
+| 鎵规 | 鍐呭 | 妯″潡 | 棰勪及宸ヤ綔閲� |
+|------|------|------|-----------|
+| 1 | 鍩虹鏁版嵁缁熶竴 | 鍏ㄩ儴 | 3澶� |
+| 2 | 搴撳瓨鏈嶅姟 API | WMS | 2澶� |
+| 3 | 閲囪喘涓氬姟鑱斿姩 | ERP/WMS | 2澶� |
+| 4 | 閿�鍞笟鍔¤仈鍔� | ERP/WMS/CRM | 2澶� |
+| 5 | 鐢熶骇涓氬姟鑱斿姩 | MES/WMS | 3澶� |
+| 6 | 閫�璐т笟鍔¤仈鍔� | ERP/WMS | 1澶� |
+| 7 | 浜嬩欢鏈哄埗瀹屽杽 | 鍏ㄩ儴 | 2澶� |
+| 8 | 娴佺▼瀹氫箟閰嶇疆 | BPM | 2澶� |
+| 9 | 鍓嶇椤甸潰璋冩暣 | 鍏ㄩ儴 | 3澶� |
+| 10 | 鑱旇皟娴嬭瘯 | 鍏ㄩ儴 | 3澶� |
+
+### 7.2 鎶�鏈鐐�
+
+1. **Spring Event 寮傛澶勭悊**锛氳法妯″潡浜嬩欢浣跨敤 `@Async` 寮傛鎵ц锛岄伩鍏嶄簨鍔″祵濂�
+2. **鍒嗗竷寮忎簨鍔�**锛氬叧閿搷浣滆�冭檻浣跨敤 Seata 鎴栨湰鍦版秷鎭〃淇濊瘉涓�鑷存��
+3. **骞傜瓑璁捐**锛氫簨浠剁洃鍚櫒闇�鏀寔骞傜瓑锛岄槻姝㈤噸澶嶆墽琛�
+4. **绉熸埛闅旂**锛氫簨浠舵暟鎹惡甯� tenantId锛岀洃鍚椂杩囨护
+
+---
+
+## 鍏�侀闄╀笌瀵圭瓥
+
+| 椋庨櫓鐐� | 褰卞搷 | 瀵圭瓥 |
+|--------|------|------|
+| 鏁版嵁涓嶄竴鑷� | 搴撳瓨涓庡疄闄呬笉绗� | 浜嬩欢骞傜瓑+琛ュ伩鏈哄埗+瀹氭湡鐩樼偣 |
+| 娴佺▼姝婚攣 | 瀹℃壒鍗′綇 | 璁剧疆瀹℃壒瓒呮椂鑷姩澶勭悊 |
+| 鎬ц兘鐡堕 | 浜嬩欢闃熷垪闃诲 | 寮傛闃熷垪+鎵归噺澶勭悊 |
+| 璺ㄧ鎴锋暟鎹硠闇� | 瀹夊叏椋庨櫓 | 浜嬩欢鎼哄甫绉熸埛ID+涓ユ牸鏍¢獙 |
+
+---
+
+## 涔濄�佹�荤粨
+
+鏈柟妗堥�氳繃浠ヤ笅鏈哄埗瀹炵幇妯″潡闂存墦閫氾細
+
+1. **缁熶竴鍩虹鏁版嵁**锛氫骇鍝�/瀹㈡埛/渚涘簲鍟�/浠撳簱绛変富鏁版嵁鍏ㄥ眬鍏变韩
+2. **浜嬩欢椹卞姩鑱斿姩**锛氬鎵归�氳繃瑙﹀彂璺ㄦā鍧椾簨浠讹紝瑙h�︽ā鍧椾緷璧�
+3. **搴撳瓨涓績鍖�**锛歐MS 浣滀负鍞竴搴撳瓨绠$悊鑰咃紝鎵�鏈夊彉鍔ㄩ�氳繃 API/浜嬩欢
+4. **娴佺▼鏍囧噯鍖�**锛氱粺涓�鐨� BPM 娴佺▼ KEY銆佺姸鎬佹灇涓俱�佺洃鍚櫒妯℃澘
+
+涓嬩竴姝ラ渶纭锛�
+- 涓绘暟鎹ā鍧楀綊灞烇紙鏄惁鏂板缓鐙珛妯″潡锛�
+- 鍒嗗竷寮忎簨鍔℃柟妗堥�夋嫨
+- 鍏蜂綋瀹炴柦浼樺厛绾ф帓搴�
\ No newline at end of file
diff --git a/docs/im_module.sql b/docs/im_module.sql
new file mode 100644
index 0000000..875d5d1
--- /dev/null
+++ b/docs/im_module.sql
@@ -0,0 +1,353 @@
+-- =====================================================
+-- IM 鍗虫椂閫氳妯″潡鏁版嵁搴撹〃缁撴瀯
+-- 鐢熸垚鏃ユ湡: 2026-06-29
+-- 閫傜敤椤圭洰: mom-pro2-after
+-- 鍖呭惈: 濂藉弸绠$悊銆佺兢鑱婄鐞嗐�佺鑱婃秷鎭�佺兢鑱婃秷鎭�
+-- =====================================================
+
+-- =====================================================
+-- 绗竴閮ㄥ垎: 瀛楀吀绫诲瀷瀹氫箟
+-- =====================================================
+
+-- IM 绉佽亰娑堟伅鐘舵��
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (600, 'IM 绉佽亰娑堟伅鐘舵��', 'im_private_message_status', 0, '绉佽亰娑堟伅鐘舵��', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇よ亰娑堟伅鐘舵��
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (601, 'IM 缇よ亰娑堟伅鐘舵��', 'im_group_message_status', 0, '缇よ亰娑堟伅鐘舵��', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇よ亰娑堟伅鍥炴墽鐘舵��
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (602, 'IM 缇よ亰娑堟伅鍥炴墽鐘舵��', 'im_group_message_receipt_status', 0, '缇よ亰娑堟伅鍥炴墽鐘舵��', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 濂藉弸鐘舵��
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (603, 'IM 濂藉弸鐘舵��', 'im_friend_status', 0, '濂藉弸鐘舵��', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇ょ姸鎬�
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (604, 'IM 缇ょ姸鎬�', 'im_group_status', 0, '缇ょ姸鎬�', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇ゆ垚鍛樿鑹�
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (605, 'IM 缇ゆ垚鍛樿鑹�', 'im_group_member_role', 0, '缇ゆ垚鍛樿鑹�', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 濂藉弸娣诲姞鏉ユ簮
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (606, 'IM 濂藉弸娣诲姞鏉ユ簮', 'im_friend_add_source', 0, '濂藉弸娣诲姞鏉ユ簮', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 濂藉弸鐢宠澶勭悊缁撴灉
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (607, 'IM 濂藉弸鐢宠澶勭悊缁撴灉', 'im_friend_request_handle_result', 0, '濂藉弸鐢宠澶勭悊缁撴灉', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 娑堟伅绫诲瀷
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (608, 'IM 娑堟伅绫诲瀷', 'im_message_type', 0, '娑堟伅绫诲瀷', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- =====================================================
+-- 绗簩閮ㄥ垎: 瀛楀吀鏁版嵁瀹氫箟
+-- =====================================================
+
+-- IM 绉佽亰娑堟伅鐘舵�佸瓧鍏告暟鎹�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '鏈', '0', 'im_private_message_status', 0, 'warning', '', '绉佽亰=鏈', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '宸叉挙鍥�', '2', 'im_private_message_status', 0, 'danger', '', 'RECALL', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3, '宸茶', '3', 'im_private_message_status', 0, 'success', '', 'READ', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇よ亰娑堟伅鐘舵�佸瓧鍏告暟鎹�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '姝e父', '0', 'im_group_message_status', 0, 'success', '', '缇よ亰姝e父', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '宸叉挙鍥�', '2', 'im_group_message_status', 0, 'danger', '', '缇よ亰宸叉挙鍥�', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 濂藉弸鐘舵�佸瓧鍏告暟鎹�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '姝e父', '0', 'im_friend_status', 0, 'success', '', '姝e父濂藉弸鍏崇郴', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '宸插垹闄�', '1', 'im_friend_status', 0, 'danger', '', '宸插垹闄ゅソ鍙嬪叧绯�', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇ょ姸鎬佸瓧鍏告暟鎹�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '姝e父', '0', 'im_group_status', 0, 'success', '', '缇ゆ甯�', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '宸茶В鏁�', '1', 'im_group_status', 0, 'info', '', '缇ゅ凡瑙f暎', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇ゆ垚鍛樿鑹插瓧鍏告暟鎹�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '缇や富', '1', 'im_group_member_role', 0, 'primary', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '绠$悊鍛�', '2', 'im_group_member_role', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3, '鏅�氭垚鍛�', '3', 'im_group_member_role', 0, 'info', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 濂藉弸娣诲姞鏉ユ簮瀛楀吀鏁版嵁
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '鎼滅储', '1', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '缇よ亰', '2', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3, '鎵爜', '3', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (4, '鍚嶇墖', '4', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 濂藉弸鐢宠澶勭悊缁撴灉瀛楀吀鏁版嵁
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '绛夊緟楠岃瘉', '0', 'im_friend_request_handle_result', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '宸叉坊鍔�', '1', 'im_friend_request_handle_result', 0, 'success', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3, '宸叉嫆缁�', '2', 'im_friend_request_handle_result', 0, 'info', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 娑堟伅绫诲瀷瀛楀吀鏁版嵁
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (101, '鏂囨湰', '101', 'im_message_type', 0, '', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (102, '鍥剧墖', '102', 'im_message_type', 0, 'success', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (103, '璇煶', '103', 'im_message_type', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (104, '瑙嗛', '104', 'im_message_type', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (105, '鏂囦欢', '105', 'im_message_type', 0, 'info', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2101, '鎾ゅ洖', '2101', 'im_message_type', 0, 'danger', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2200, '鍥炴墽', '2200', 'im_message_type', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2201, '宸茶', '2201', 'im_message_type', 0, 'success', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- IM 缇ゆ秷鎭洖鎵х姸鎬�
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, '涓嶉渶瑕佸洖鎵�', '0', 'im_group_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, '寰呭畬鎴�', '1', 'im_group_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', NOW(), 'admin', NOW(), b'0');
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3, '宸插畬鎴�', '2', 'im_group_message_receipt_status', 0, 'success', '', 'DONE', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- =====================================================
+-- 绗笁閮ㄥ垎: IM 妯″潡鏍稿績琛ㄧ粨鏋� (11 寮犺〃)
+-- =====================================================
+
+-- 3.1 濂藉弸鍏崇郴琛�
+DROP TABLE IF EXISTS `im_friend`;
+CREATE TABLE `im_friend` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `user_id` bigint NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `friend_user_id` bigint NOT NULL COMMENT '濂藉弸鐢ㄦ埛缂栧彿',
+ `remark` varchar(255) DEFAULT NULL COMMENT '濂藉弸澶囨敞',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '濂藉弸鐘舵�侊紙0姝e父 1宸插垹闄わ級',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_user_id` (`user_id`),
+ KEY `idx_friend_user_id` (`friend_user_id`),
+ UNIQUE KEY `uk_user_friend` (`user_id`, `friend_user_id`)
+) ENGINE=InnoDB COMMENT='濂藉弸鍏崇郴琛�';
+
+-- 3.2 濂藉弸鐢宠琛�
+DROP TABLE IF EXISTS `im_friend_request`;
+CREATE TABLE `im_friend_request` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `user_id` bigint NOT NULL COMMENT '鐢宠浜虹紪鍙�',
+ `target_user_id` bigint NOT NULL COMMENT '鐩爣鐢ㄦ埛缂栧彿',
+ `content` varchar(500) DEFAULT NULL COMMENT '鐢宠鍐呭',
+ `source` tinyint NOT NULL DEFAULT 1 COMMENT '娣诲姞鏉ユ簮锛�1鎼滅储 2缇よ亰 3鎵爜 4鍚嶇墖锛�',
+ `handle_result` tinyint NOT NULL DEFAULT 0 COMMENT '澶勭悊缁撴灉锛�0绛夊緟 1宸叉坊鍔� 2宸叉嫆缁濓級',
+ `handle_time` datetime DEFAULT NULL COMMENT '澶勭悊鏃堕棿',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_user_id` (`user_id`),
+ KEY `idx_target_user_id` (`target_user_id`)
+) ENGINE=InnoDB COMMENT='濂藉弸鐢宠琛�';
+
+-- 3.3 缇ょ粍琛�
+DROP TABLE IF EXISTS `im_group`;
+CREATE TABLE `im_group` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缇ょ紪鍙�',
+ `name` varchar(100) NOT NULL COMMENT '缇ゅ悕绉�',
+ `avatar` varchar(255) DEFAULT NULL COMMENT '缇ゅご鍍�',
+ `owner_user_id` bigint NOT NULL COMMENT '缇や富鐢ㄦ埛缂栧彿',
+ `notice` varchar(500) DEFAULT NULL COMMENT '缇ゅ叕鍛�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '缇ょ姸鎬侊紙0姝e父 1宸茶В鏁o級',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_owner_user_id` (`owner_user_id`)
+) ENGINE=InnoDB COMMENT='缇ょ粍琛�';
+
+-- 3.4 缇ゆ垚鍛樿〃
+DROP TABLE IF EXISTS `im_group_member`;
+CREATE TABLE `im_group_member` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `group_id` bigint NOT NULL COMMENT '缇ょ紪鍙�',
+ `user_id` bigint NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `nickname` varchar(100) DEFAULT NULL COMMENT '缇ゅ唴鏄电О',
+ `role` tinyint NOT NULL DEFAULT 3 COMMENT '缇よ鑹诧紙1缇や富 2绠$悊鍛� 3鏅�氭垚鍛橈級',
+ `mute_flag` bit(1) DEFAULT b'0' COMMENT '鏄惁绂佽█',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_group_id` (`group_id`),
+ KEY `idx_user_id` (`user_id`),
+ UNIQUE KEY `uk_group_user` (`group_id`, `user_id`)
+) ENGINE=InnoDB COMMENT='缇ゆ垚鍛樿〃';
+
+-- 3.5 绉佽亰娑堟伅琛�
+DROP TABLE IF EXISTS `im_private_message`;
+CREATE TABLE `im_private_message` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '娑堟伅缂栧彿',
+ `from_user_id` bigint NOT NULL COMMENT '鍙戦�佷汉缂栧彿',
+ `to_user_id` bigint NOT NULL COMMENT '鎺ユ敹浜虹紪鍙�',
+ `content` text COMMENT '娑堟伅鍐呭',
+ `message_type` int NOT NULL DEFAULT 101 COMMENT '娑堟伅绫诲瀷锛�101鏂囨湰 102鍥剧墖 103璇煶 104瑙嗛 105鏂囦欢锛�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '娑堟伅鐘舵�侊紙0鏈 2宸叉挙鍥� 3宸茶锛�',
+ `send_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍙戦�佹椂闂�',
+ `read_time` datetime DEFAULT NULL COMMENT '宸茶鏃堕棿',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_from_user_id` (`from_user_id`),
+ KEY `idx_to_user_id` (`to_user_id`)
+) ENGINE=InnoDB COMMENT='绉佽亰娑堟伅琛�';
+
+-- 3.6 缇よ亰娑堟伅琛�
+DROP TABLE IF EXISTS `im_group_message`;
+CREATE TABLE `im_group_message` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '娑堟伅缂栧彿',
+ `group_id` bigint NOT NULL COMMENT '缇ょ紪鍙�',
+ `from_user_id` bigint NOT NULL COMMENT '鍙戦�佷汉缂栧彿',
+ `content` text COMMENT '娑堟伅鍐呭',
+ `message_type` int NOT NULL DEFAULT 101 COMMENT '娑堟伅绫诲瀷',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '娑堟伅鐘舵�侊紙0姝e父 2宸叉挙鍥烇級',
+ `receipt_status` tinyint NOT NULL DEFAULT 0 COMMENT '鍥炴墽鐘舵�侊紙0涓嶉渶瑕� 1寰呭畬鎴� 2宸插畬鎴愶級',
+ `send_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍙戦�佹椂闂�',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_group_id` (`group_id`),
+ KEY `idx_from_user_id` (`from_user_id`)
+) ENGINE=InnoDB COMMENT='缇よ亰娑堟伅琛�';
+
+-- 3.7 缇ゆ秷鎭洖鎵ц〃
+DROP TABLE IF EXISTS `im_group_message_receipt`;
+CREATE TABLE `im_group_message_receipt` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `message_id` bigint NOT NULL COMMENT '缇ゆ秷鎭紪鍙�',
+ `user_id` bigint NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `read_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '宸茶鏃堕棿',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_message_id` (`message_id`),
+ KEY `idx_user_id` (`user_id`),
+ UNIQUE KEY `uk_message_user` (`message_id`, `user_id`)
+) ENGINE=InnoDB COMMENT='缇ゆ秷鎭洖鎵ц〃';
+
+-- 3.8 浼氳瘽琛紙绉佽亰+缇よ亰锛�
+DROP TABLE IF EXISTS `im_conversation`;
+CREATE TABLE `im_conversation` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '浼氳瘽缂栧彿',
+ `user_id` bigint NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `conversation_type` tinyint NOT NULL COMMENT '浼氳瘽绫诲瀷锛�1绉佽亰 2缇よ亰锛�',
+ `target_id` bigint NOT NULL COMMENT '鐩爣缂栧彿锛堢鑱婁负鐢ㄦ埛ID锛岀兢鑱婁负缇D锛�',
+ `last_message_id` bigint DEFAULT NULL COMMENT '鏈�鍚庢秷鎭紪鍙�',
+ `last_message_time` datetime DEFAULT NULL COMMENT '鏈�鍚庢秷鎭椂闂�',
+ `unread_count` int NOT NULL DEFAULT 0 COMMENT '鏈娑堟伅鏁�',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_user_id` (`user_id`),
+ UNIQUE KEY `uk_user_target_type` (`user_id`, `target_id`, `conversation_type`)
+) ENGINE=InnoDB COMMENT='浼氳瘽琛�';
+
+-- 3.9 鐢ㄦ埛鍦ㄧ嚎鐘舵�佽〃
+DROP TABLE IF EXISTS `im_user_online`;
+CREATE TABLE `im_user_online` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `user_id` bigint NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `online_status` tinyint NOT NULL DEFAULT 0 COMMENT '鍦ㄧ嚎鐘舵�侊紙0绂荤嚎 1鍦ㄧ嚎锛�',
+ `last_online_time` datetime DEFAULT NULL COMMENT '鏈�鍚庡湪绾挎椂闂�',
+ `last_offline_time` datetime DEFAULT NULL COMMENT '鏈�鍚庣绾挎椂闂�',
+ `device_type` tinyint DEFAULT NULL COMMENT '璁惧绫诲瀷锛�1Web 2Android 3iOS锛�',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_user_id` (`user_id`),
+ UNIQUE KEY `uk_user_id` (`user_id`)
+) ENGINE=InnoDB COMMENT='鐢ㄦ埛鍦ㄧ嚎鐘舵�佽〃';
+
+-- 3.10 缇ょ敵璇疯〃
+DROP TABLE IF EXISTS `im_group_request`;
+CREATE TABLE `im_group_request` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `group_id` bigint NOT NULL COMMENT '缇ょ紪鍙�',
+ `user_id` bigint NOT NULL COMMENT '鐢宠浜虹紪鍙�',
+ `content` varchar(500) DEFAULT NULL COMMENT '鐢宠鍐呭',
+ `handle_result` tinyint NOT NULL DEFAULT 0 COMMENT '澶勭悊缁撴灉锛�0绛夊緟 1宸查�氳繃 2宸叉嫆缁濓級',
+ `handle_user_id` bigint DEFAULT NULL COMMENT '澶勭悊浜虹紪鍙�',
+ `handle_time` datetime DEFAULT NULL COMMENT '澶勭悊鏃堕棿',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_group_id` (`group_id`),
+ KEY `idx_user_id` (`user_id`)
+) ENGINE=InnoDB COMMENT='缇ょ敵璇疯〃';
+
+-- 3.11 娑堟伅鏂囦欢琛�
+DROP TABLE IF EXISTS `im_message_file`;
+CREATE TABLE `im_message_file` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `message_id` bigint NOT NULL COMMENT '娑堟伅缂栧彿',
+ `message_type` tinyint NOT NULL COMMENT '娑堟伅绫诲瀷锛�102鍥剧墖 103璇煶 104瑙嗛 105鏂囦欢锛�',
+ `file_url` varchar(500) NOT NULL COMMENT '鏂囦欢URL',
+ `file_name` varchar(255) DEFAULT NULL COMMENT '鏂囦欢鍚�',
+ `file_size` bigint DEFAULT NULL COMMENT '鏂囦欢澶у皬锛堝瓧鑺傦級',
+ `file_duration` int DEFAULT NULL COMMENT '鏂囦欢鏃堕暱锛堢锛岃闊�/瑙嗛锛�',
+ `width` int DEFAULT NULL COMMENT '瀹藉害锛堝浘鐗�/瑙嗛锛�',
+ `height` int DEFAULT NULL COMMENT '楂樺害锛堝浘鐗�/瑙嗛锛�',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_message_id` (`message_id`)
+) ENGINE=InnoDB COMMENT='娑堟伅鏂囦欢琛�';
+
+-- =====================================================
+-- IM 妯″潡琛ㄧ粺璁�: 11 寮犺〃
+-- =====================================================
\ No newline at end of file
diff --git a/docs/im_module_unlock.md b/docs/im_module_unlock.md
new file mode 100644
index 0000000..8f6c422
--- /dev/null
+++ b/docs/im_module_unlock.md
@@ -0,0 +1,399 @@
+# IM 鍗虫椂閫氳妯″潡瑙i攣鎸囧崡
+
+## 涓�銆佹ā鍧楁杩�
+
+IM锛圛nstant Messaging锛夊嵆鏃堕�氳妯″潡鎻愪緵浼佷笟鍐呴儴鐨勫嵆鏃堕�氳鑳藉姏锛屾敮鎸佺鑱娿�佺兢鑱娿�佸ソ鍙嬬鐞嗙瓑鏍稿績鍔熻兘銆�
+
+### 1.1 鍔熻兘娓呭崟
+
+| 鍔熻兘 | 璇存槑 |
+|------|------|
+| 濂藉弸绠$悊 | 娣诲姞濂藉弸銆佸垹闄ゅソ鍙嬨�佸ソ鍙嬬敵璇峰鏍� |
+| 绉佽亰娑堟伅 | 涓�瀵逛竴鑱婂ぉ銆佹秷鎭挙鍥炪�佸凡璇诲洖鎵� |
+| 缇よ亰绠$悊 | 鍒涘缓缇ゃ�佽В鏁g兢銆佺兢鎴愬憳绠$悊銆佺兢鍏憡 |
+| 缇よ亰娑堟伅 | 缇ゅ唴鑱婂ぉ銆佹秷鎭挙鍥炪�佸洖鎵х‘璁� |
+| 浼氳瘽绠$悊 | 浼氳瘽鍒楄〃銆佹湭璇昏鏁般�佸巻鍙叉秷鎭� |
+| 鍦ㄧ嚎鐘舵�� | 鐢ㄦ埛鍦ㄧ嚎/绂荤嚎鐘舵�佹樉绀� |
+
+### 1.2 鏁版嵁搴撹〃娓呭崟
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| im_friend | 濂藉弸鍏崇郴琛� |
+| im_friend_request | 濂藉弸鐢宠琛� |
+| im_group | 缇ょ粍琛� |
+| im_group_member | 缇ゆ垚鍛樿〃 |
+| im_private_message | 绉佽亰娑堟伅琛� |
+| im_group_message | 缇よ亰娑堟伅琛� |
+| im_group_message_receipt | 缇ゆ秷鎭洖鎵ц〃 |
+| im_conversation | 浼氳瘽琛� |
+| im_user_online | 鐢ㄦ埛鍦ㄧ嚎鐘舵�佽〃 |
+| im_group_request | 缇ょ敵璇疯〃 |
+| im_message_file | 娑堟伅鏂囦欢琛� |
+
+---
+
+## 浜屻�佽В閿佹楠�
+
+### 2.1 鍒涘缓妯″潡鐩綍缁撴瀯
+
+```bash
+mkdir -p yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/api
+mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/{controller,service,dal}
+mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/{friend,group,message}
+mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/{friend,group,message}
+mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/{dataobject,mysql}
+mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/{friend,group,message}
+mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/{friend,group,message}
+```
+
+### 2.2 鍒涘缓 pom.xml
+
+**yudao-module-im-api/pom.xml:**
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-im</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-im-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>IM 鍗虫椂閫氳妯″潡 API</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-framework-common</artifactId>
+ </dependency>
+ </dependencies>
+</project>
+```
+
+**yudao-module-im-biz/pom.xml:**
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-im</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-im-biz</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>IM 鍗虫椂閫氳妯″潡涓氬姟瀹炵幇</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-im-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-system-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- Web 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-security</artifactId>
+ </dependency>
+
+ <!-- DB 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-redis</artifactId>
+ </dependency>
+
+ <!-- WebSocket -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-websocket</artifactId>
+ </dependency>
+
+ <!-- Test 娴嬭瘯鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-test</artifactId>
+ </dependency>
+ </dependencies>
+</project>
+```
+
+### 2.3 鎵ц鏁版嵁搴� SQL
+
+```bash
+# 鎵ц IM 妯″潡琛ㄧ粨鏋� SQL
+mysql -u root -p mom_pro < docs/im_module.sql
+```
+
+### 2.4 鍦� yudao-server/pom.xml 涓坊鍔犱緷璧�
+
+```xml
+<!-- IM 鍗虫椂閫氳妯″潡 -->
+<dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-im-biz</artifactId>
+ <version>${revision}</version>
+</dependency>
+```
+
+---
+
+## 涓夈�佹牳蹇冨疄浣撶被绀轰緥
+
+### 3.1 ImFriendDO.java
+
+```java
+package cn.iocoder.yudao.module.im.dal.dataobject.friend;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 濂藉弸鍏崇郴 DO
+ */
+@TableName("im_friend")
+@KeySequence("im_friend_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImFriendDO extends BaseDO {
+
+ @TableId
+ private Long id;
+
+ /** 鐢ㄦ埛缂栧彿 */
+ private Long userId;
+
+ /** 濂藉弸鐢ㄦ埛缂栧彿 */
+ private Long friendUserId;
+
+ /** 濂藉弸澶囨敞 */
+ private String remark;
+
+ /** 濂藉弸鐘舵�侊紙0姝e父 1宸插垹闄わ級 */
+ private Integer status;
+
+}
+```
+
+### 3.2 ImGroupDO.java
+
+```java
+package cn.iocoder.yudao.module.im.dal.dataobject.group;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 缇ょ粍 DO
+ */
+@TableName("im_group")
+@KeySequence("im_group_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImGroupDO extends BaseDO {
+
+ @TableId
+ private Long id;
+
+ /** 缇ゅ悕绉� */
+ private String name;
+
+ /** 缇ゅご鍍� */
+ private String avatar;
+
+ /** 缇や富鐢ㄦ埛缂栧彿 */
+ private Long ownerUserId;
+
+ /** 缇ゅ叕鍛� */
+ private String notice;
+
+ /** 缇ょ姸鎬侊紙0姝e父 1宸茶В鏁o級 */
+ private Integer status;
+
+}
+```
+
+### 3.3 ImPrivateMessageDO.java
+
+```java
+package cn.iocoder.yudao.module.im.dal.dataobject.message;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 绉佽亰娑堟伅 DO
+ */
+@TableName("im_private_message")
+@KeySequence("im_private_message_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImPrivateMessageDO extends BaseDO {
+
+ @TableId
+ private Long id;
+
+ /** 鍙戦�佷汉缂栧彿 */
+ private Long fromUserId;
+
+ /** 鎺ユ敹浜虹紪鍙� */
+ private Long toUserId;
+
+ /** 娑堟伅鍐呭 */
+ private String content;
+
+ /** 娑堟伅绫诲瀷 */
+ private Integer messageType;
+
+ /** 娑堟伅鐘舵�侊紙0鏈 2宸叉挙鍥� 3宸茶锛� */
+ private Integer status;
+
+ /** 鍙戦�佹椂闂� */
+ private LocalDateTime sendTime;
+
+ /** 宸茶鏃堕棿 */
+ private LocalDateTime readTime;
+
+}
+```
+
+---
+
+## 鍥涖�佷笟鍔℃祦绋嬭鏄�
+
+### 4.1 濂藉弸娣诲姞娴佺▼
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍙戦�佸ソ鍙� 鈹� -> 鈹� 瀵规柟鏀跺埌 鈹� -> 鈹� 鍚屾剰/鎷掔粷 鈹� -> 鈹� 寤虹珛濂藉弸 鈹�
+鈹� 鐢宠 鈹� 鈹� 鐢宠閫氱煡 鈹� 鈹� 鐢宠 鈹� 鈹� 鍏崇郴 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
+ v v v
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� im_friend_ 鈹� 鈹� WebSocket 鈹� 鈹� im_friend 鈹�
+鈹� request 鈹� 鈹� 鎺ㄩ�侀�氱煡 鈹� 鈹� 琛ㄦ柊澧� 鈹�
+鈹� 琛ㄦ柊澧� 鈹� 鈹� 鈹� 鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 4.2 绉佽亰娑堟伅娴佺▼
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍙戦�佹秷鎭� 鈹� -> 鈹� WebSocket 鈹� -> 鈹� 鎺ユ敹娑堟伅 鈹� -> 鈹� 宸茶鍥炴墽 鈹�
+鈹� 鈹� 鈹� 鎺ㄩ�� 鈹� 鈹� 鈹� 鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹� 鈹�
+ v v v v
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� im_private_ 鈹� 鈹� 瀹炴椂鎺ㄩ�� 鈹� 鈹� 娑堟伅鍏ュ簱 鈹� 鈹� 鏇存柊宸茶 鈹�
+鈹� message 鈹� 鈹� 缁欐帴鏀朵汉 鈹� 鈹� 鐘舵�佹湭璇� 鈹� 鈹� 鐘舵�� 鈹�
+鈹� 鍏ュ簱 鈹� 鈹� 鈹� 鈹� 鈹� 鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 4.3 缇よ亰绠$悊娴佺▼
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍒涘缓缇よ亰 鈹� -> 鈹� 娣诲姞鎴愬憳 鈹� -> 鈹� 缇ゅ唴鑱婂ぉ 鈹� -> 鈹� 瑙f暎缇よ亰 鈹�
+鈹� 鈹� 鈹� 鈹� 鈹� 鈹� 鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹� 鈹�
+ v v v v
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� im_group 鈹� 鈹� im_group_ 鈹� 鈹� im_group_ 鈹� 鈹� 鏇存柊缇ょ姸鎬� 鈹�
+鈹� 琛ㄦ柊澧� 鈹� 鈹� member 鈹� 鈹� message 鈹� 鈹� 涓哄凡瑙f暎 鈹�
+鈹� 鈹� 鈹� 琛ㄦ柊澧� 鈹� 鈹� 鍏ュ簱 鈹� 鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 浜斻�佽彍鍗曢厤缃�
+
+闇�瑕佸湪 system_menu 琛ㄤ腑娣诲姞 IM 妯″潡鐨勮彍鍗曪細
+
+```sql
+-- IM 妯″潡涓�绾ц彍鍗�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2100, '鍗虫椂閫氳', '', 1, 30, 0, '/im', 'message', NULL, 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 濂藉弸绠$悊
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2101, '濂藉弸绠$悊', '', 2, 1, 2100, 'friend', '', 'im/friend/index', 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 缇よ亰绠$悊
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2102, '缇よ亰绠$悊', '', 2, 2, 2100, 'group', '', 'im/group/index', 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 娑堟伅绠$悊
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2103, '娑堟伅绠$悊', '', 2, 3, 2100, 'message', '', 'im/message/index', 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+```
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. **WebSocket 閰嶇疆**: IM 妯″潡渚濊禆 WebSocket 瀹炵幇瀹炴椂閫氳锛岄渶瑕侀厤缃� WebSocket 绔偣
+2. **Redis 娑堟伅闃熷垪**: 寤鸿浣跨敤 Redis Pub/Sub 瀹炵幇娑堟伅骞挎挱
+3. **绂荤嚎娑堟伅瀛樺偍**: 鐢ㄦ埛绂荤嚎鏃剁殑娑堟伅闇�瑕佹寔涔呭寲锛屼笂绾垮悗鎺ㄩ��
+4. **娑堟伅瀹夊叏**: 娑堟伅鍐呭闇�瑕佽�冭檻鏁忔劅璇嶈繃婊ゃ�� XSS 闃叉姢
+5. **鎬ц兘浼樺寲**: 楂樺苟鍙戝満鏅缓璁娇鐢ㄦ秷鎭槦鍒楀墛宄板~璋�
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-29
+**閫傜敤椤圭洰**: mom-pro2-after
+**琛ㄦ暟閲�**: 11 寮�
\ No newline at end of file
diff --git a/docs/mdm_frontend_change.md b/docs/mdm_frontend_change.md
new file mode 100644
index 0000000..bb8589b
--- /dev/null
+++ b/docs/mdm_frontend_change.md
@@ -0,0 +1,241 @@
+# MDM 鐗╂枡涓绘暟鎹ā鍧楁敼閫� - 鍓嶇鍙樻洿鏂囨。
+
+## 姒傝堪
+
+鏈鏀归�犲皢 ERP銆丮ES銆乄MS 涓変釜妯″潡鐨勭墿鏂�/浜у搧鏁版嵁缁熶竴鍒� MDM锛堜富鏁版嵁绠$悊锛夋ā鍧楋紝娑堥櫎鏁版嵁瀛ゅ矝銆�
+
+## 妯″潡鍙樻洿
+
+### 1. MDM 妯″潡锛堟柊澧烇級
+
+鏂板 MDM 鐗╂枡涓绘暟鎹鐞嗘ā鍧楋紝鍖呭惈浠ヤ笅椤甸潰锛�
+
+| 椤甸潰 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 鐗╂枡绠$悊 | /mdm/item | 鐗╂枡涓绘暟鎹鐞� |
+| 鐗╂枡鍒嗙被 | /mdm/category | 鐗╂枡鍒嗙被绠$悊 |
+| 璁¢噺鍗曚綅 | /mdm/unit | 璁¢噺鍗曚綅绠$悊 |
+| 鍝佺墝绠$悊 | /mdm/brand | 鍝佺墝绠$悊 |
+| 浠撳簱绠$悊 | /mdm/warehouse | 浠撳簱涓绘暟鎹鐞� |
+
+### 2. ERP 妯″潡
+
+ERP 浜у搧鏁版嵁鏉ユ簮浜� MDM锛屽彉鏇村涓嬶細
+
+| 鍘熼〉闈� | 鍙樺寲 |
+|--------|------|
+| /erp/product | 浜у搧鏁版嵁浠� MDM 鑾峰彇锛岄〉闈繚鐣欎絾鏁版嵁婧愬彉鏇� |
+
+### 3. MES 妯″潡
+
+MES 鐗╂枡鏁版嵁鏉ユ簮浜� MDM锛屽彉鏇村涓嬶細
+
+| 鍘熼〉闈� | 鍙樺寲 |
+|--------|------|
+| /mes/md/item | 鐗╂枡鏁版嵁浠� MDM 鑾峰彇锛岄〉闈繚鐣欎絾鏁版嵁婧愬彉鏇� |
+
+### 4. WMS 妯″潡
+
+WMS 鍟嗗搧鍜� SKU 鏁版嵁鏉ユ簮浜� MDM锛屽彉鏇村涓嬶細
+
+| 鍘熼〉闈� | 鍙樺寲 |
+|--------|------|
+| /wms/item | 鍟嗗搧鏁版嵁浠� MDM 鑾峰彇锛岄〉闈繚鐣欎絾鏁版嵁婧愬彉鏇� |
+| /wms/item-sku | SKU 鏁版嵁浠� MDM 鑾峰彇锛岄〉闈繚鐣欎絾鏁版嵁婧愬彉鏇� |
+
+---
+
+## API 鍙樻洿璇存槑
+
+### MDM 鐗╂枡 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mdm/item/create | 鍒涘缓鐗╂枡 |
+| PUT | /mdm/item/update | 鏇存柊鐗╂枡 |
+| PUT | /mdm/item/update-status | 鏇存柊鐗╂枡鐘舵�� |
+| DELETE | /mdm/item/delete | 鍒犻櫎鐗╂枡锛堝疄闄呬负绂佺敤锛� |
+| GET | /mdm/item/get | 鑾峰彇鐗╂枡 |
+| GET | /mdm/item/page | 鑾峰彇鐗╂枡鍒嗛〉 |
+| GET | /mdm/item/list-by-ids | 鑾峰彇鐗╂枡鍒楄〃 |
+
+**璇锋眰鍙傛暟锛堝垱寤猴級锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| code | String | 鏄� | 鐗╂枡缂栫爜 |
+| name | String | 鏄� | 鐗╂枡鍚嶇О |
+| barCode | String | 鍚� | 鐗╂枡鏉$爜 |
+| specification | String | 鍚� | 瑙勬牸鍨嬪彿 |
+| categoryId | Long | 鏄� | 鐗╂枡鍒嗙被缂栧彿 |
+| unitMeasureId | Long | 鏄� | 璁¢噺鍗曚綅缂栧彿 |
+| brandId | Long | 鍚� | 鍝佺墝缂栧彿 |
+| itemType | Integer | 鏄� | 鐗╂枡绫诲瀷锛�1:鐗╂枡, 2:浜у搧, 3:鍗婃垚鍝侊級 |
+| status | Integer | 鍚� | 鐘舵�侊紙0:鍚敤, 1:绂佺敤锛� |
+| purchasePrice | BigDecimal | 鍚� | 閲囪喘浠锋牸 |
+| salesPrice | BigDecimal | 鍚� | 閿�鍞环鏍� |
+| costPrice | BigDecimal | 鍚� | 鎴愭湰浠锋牸 |
+| safetyStock | BigDecimal | 鍚� | 瀹夊叏搴撳瓨 |
+| expiryDays | Integer | 鍚� | 淇濊川鏈熷ぉ鏁� |
+| isBatchManaged | Boolean | 鍚� | 鏄惁鎵规绠$悊 |
+| remark | String | 鍚� | 澶囨敞 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": 1,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+### MDM 鐗╂枡鍒嗙被 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mdm/category/create | 鍒涘缓鍒嗙被 |
+| PUT | /mdm/category/update | 鏇存柊鍒嗙被 |
+| DELETE | /mdm/category/delete | 鍒犻櫎鍒嗙被 |
+| GET | /mdm/category/get | 鑾峰彇鍒嗙被 |
+| GET | /mdm/category/list | 鑾峰彇鍒嗙被鍒楄〃 |
+| GET | /mdm/category/list-all-simple | 鑾峰彇鍚敤鐨勫垎绫诲垪琛� |
+
+### MDM 璁¢噺鍗曚綅 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mdm/unit/create | 鍒涘缓鍗曚綅 |
+| PUT | /mdm/unit/update | 鏇存柊鍗曚綅 |
+| DELETE | /mdm/unit/delete | 鍒犻櫎鍗曚綅 |
+| GET | /mdm/unit/get | 鑾峰彇鍗曚綅 |
+| GET | /mdm/unit/page | 鑾峰彇鍗曚綅鍒嗛〉 |
+| GET | /mdm/unit/list-by-ids | 鑾峰彇鍗曚綅鍒楄〃 |
+
+### MDM 鍝佺墝 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mdm/brand/create | 鍒涘缓鍝佺墝 |
+| PUT | /mdm/brand/update | 鏇存柊鍝佺墝 |
+| DELETE | /mdm/brand/delete | 鍒犻櫎鍝佺墝 |
+| GET | /mdm/brand/get | 鑾峰彇鍝佺墝 |
+| GET | /mdm/brand/page | 鑾峰彇鍝佺墝鍒嗛〉 |
+| GET | /mdm/brand/list-by-ids | 鑾峰彇鍝佺墝鍒楄〃 |
+
+### MDM 浠撳簱 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mdm/warehouse/create | 鍒涘缓浠撳簱 |
+| PUT | /mdm/warehouse/update | 鏇存柊浠撳簱 |
+| PUT | /mdm/warehouse/update-status | 鏇存柊浠撳簱鐘舵�� |
+| DELETE | /mdm/warehouse/delete | 鍒犻櫎浠撳簱 |
+| GET | /mdm/warehouse/get | 鑾峰彇浠撳簱 |
+| GET | /mdm/warehouse/page | 鑾峰彇浠撳簱鍒嗛〉 |
+| GET | /mdm/warehouse/list-by-ids | 鑾峰彇浠撳簱鍒楄〃 |
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. ERP 浜у搧椤甸潰
+
+ERP 浜у搧椤甸潰鏀逛负浠� MDM 鑾峰彇鏁版嵁锛屽墠绔渶瑕佷慨鏀癸細
+
+**API 璋冪敤鍙樻洿锛�**
+
+```js
+// 鍘熻皟鐢� ERP 浜у搧 API
+import { getProductPage, getProduct } from '@/api/erp/product'
+
+// 鏀逛负璋冪敤 MDM 鐗╂枡 API锛坕temType=2 浜у搧锛�
+import { getItemPage, getItem } from '@/api/mdm/item'
+
+// 鏌ヨ鏃堕渶瑕佷紶鍏� itemType=2锛堜骇鍝佺被鍨嬶級
+getItemPage({ itemType: 2, pageNo: 1, pageSize: 10 })
+```
+
+**瀛楁鏄犲皠锛�**
+
+| ERP 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 缂栧彿 |
+| barCode | code | 缂栫爜锛堝師鏉$爜鏀逛负缂栫爜锛� |
+| name | name | 鍚嶇О |
+| spec | specification | 瑙勬牸鍨嬪彿 |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| unitId | unitMeasureId | 鍗曚綅缂栧彿 |
+| brandId | brandId | 鍝佺墝缂栧彿 |
+| status | status | 鐘舵�� |
+| purchasePrice | purchasePrice | 閲囪喘浠锋牸 |
+| salePrice | salesPrice | 閿�鍞环鏍� |
+| minPrice | costPrice | 鏈�浣庝环鏍兼敼涓烘垚鏈环 |
+
+### 2. MES 鐗╂枡椤甸潰
+
+MES 鐗╂枡椤甸潰鏀逛负浠� MDM 鑾峰彇鏁版嵁锛�
+
+**API 璋冪敤鍙樻洿锛�**
+
+```js
+// 鍘熻皟鐢� MES 鐗╂枡 API
+import { getItemPage, getItem } from '@/api/mes/md/item'
+
+// 鏀逛负璋冪敤 MDM 鐗╂枡 API锛坕temType=1 鐗╂枡锛�
+import { getItemPage, getItem } from '@/api/mdm/item'
+
+// 鏌ヨ鏃堕渶瑕佷紶鍏� itemType=1锛堢墿鏂欑被鍨嬶級
+getItemPage({ itemType: 1, pageNo: 1, pageSize: 10 })
+```
+
+**MES 鐗规湁鍔熻兘淇濈暀锛�**
+- 鎵规灞炴�ч厤缃細浠嶇敱 MES 妯″潡绠$悊
+- BOM 閰嶇疆锛氫粛鐢� MES 妯″潡绠$悊
+- SOP/SIP 閰嶇疆锛氫粛鐢� MES 妯″潡绠$悊
+
+### 3. WMS 鍟嗗搧/SKU 椤甸潰
+
+WMS 鍟嗗搧鍜� SKU 椤甸潰鏀逛负浠� MDM 鑾峰彇鏁版嵁锛�
+
+**API 璋冪敤鍙樻洿锛�**
+
+```js
+// 鍘熻皟鐢� WMS 鍟嗗搧 API
+import { getItemPage, getItem } from '@/api/wms/item'
+
+// 鏀逛负璋冪敤 MDM 鐗╂枡 API
+import { getItemPage, getItem } from '@/api/mdm/item'
+
+// SKU 鏁版嵁
+import { getItemSkuList } from '@/api/mdm/item-sku'
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. **鐗╂枡绫诲瀷鍖哄垎**锛�
+ - itemType=1锛氱墿鏂欙紙MES 浣跨敤锛�
+ - itemType=2锛氫骇鍝侊紙ERP 浣跨敤锛�
+ - itemType=3锛氬崐鎴愬搧
+
+2. **鍒犻櫎鎿嶄綔**锛歁DM 鐗╂枡鍒犻櫎瀹為檯涓虹鐢ㄦ搷浣滐紝涓嶅仛鐗╃悊鍒犻櫎
+
+3. **SKU 妯″紡**锛氭墍鏈夌墿鏂欏繀椤婚厤缃� SKU锛孍RP/MES 鏁版嵁杩佺Щ鏃惰嚜鍔ㄥ垱寤洪粯璁� SKU
+
+4. **鍚戝悗鍏煎**锛氬師妯″潡鐨� API 鎺ュ彛淇濈暀锛屽唴閮ㄨ皟鐢� MDM API
+
+---
+
+## 鏁版嵁搴撹〃
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| mdm_item | 鐗╂枡涓绘暟鎹〃 |
+| mdm_item_sku | 鐗╂枡 SKU 琛� |
+| mdm_item_category | 鐗╂枡鍒嗙被琛� |
+| mdm_unit_measure | 璁¢噺鍗曚綅琛� |
+| mdm_brand | 鍝佺墝琛� |
+| mdm_warehouse | 浠撳簱琛� |
+| mdm_item_batch_config | 鎵规灞炴�ч厤缃〃 |
diff --git a/docs/workflow_guide.md b/docs/workflow_guide.md
index 72f4642..9c1888a 100644
--- a/docs/workflow_guide.md
+++ b/docs/workflow_guide.md
@@ -4,7 +4,7 @@
### 1.1 椤圭洰绠�浠�
-鑺嬮亾 ruoyi-vue-pro 鏄竴濂楀叏閮ㄥ紑婧愮殑浼佷笟绾у揩閫熷紑鍙戝钩鍙帮紝鍩轰簬 Spring Boot 4.0 + Vue 3 鏋勫缓锛屽寘鍚郴缁熺鐞嗐�佸伐浣滄祦銆丆RM銆丒RP銆丮ES銆乄MS 绛夊涓笟鍔℃ā鍧椼��
+鑺嬮亾 ruoyi-vue-pro 鏄竴濂楀叏閮ㄥ紑婧愮殑浼佷笟绾у揩閫熷紑鍙戝钩鍙帮紝鍩轰簬 Spring Boot 4.0 + Vue 3 鏋勫缓锛屽寘鍚郴缁熺鐞嗐�佸伐浣滄祦銆丆RM銆丒RP銆丮ES銆乄MS銆丮DM 绛夊涓笟鍔℃ā鍧椼��
### 1.2 宸茶В閿佹ā鍧�
@@ -14,9 +14,45 @@
| yudao-module-infra | 鍩虹璁炬柦锛堜唬鐮佺敓鎴愩�佹枃浠躲�佸畾鏃朵换鍔$瓑锛� | 11 |
| yudao-module-bpm | 宸ヤ綔娴佸紩鎿� | 8 |
| yudao-module-crm | 瀹㈡埛鍏崇郴绠$悊 | 19 |
+| yudao-module-mdm | 涓绘暟鎹鐞嗭紙缁熶竴鐗╂枡銆佸垎绫汇�佸崟浣嶇瓑锛� | 6 |
| yudao-module-erp | 浼佷笟璧勬簮璁″垝 | 33 |
| yudao-module-mes | 鍒堕�犳墽琛岀郴缁� | 71 |
-| yudao-module-wms | 浠撳簱绠$悊绯荤粺 | 16 |
+| yudao-module-wms | 浠撳簱绠$悊绯荤粺 | 18 |
+
+### 1.3 妯″潡闆嗘垚鏋舵瀯锛堟牳蹇冩敼閫狅級
+
+鏈」鐩凡瀹炵幇 **MDM 涓绘暟鎹鐞�** 妯″潡锛屾墦閫� ERP銆丮ES銆乄MS 涓変釜涓氬姟妯″潡鐨勬暟鎹宀涳細
+
+```
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� MDM 涓绘暟鎹鐞嗘ā鍧� 鈹�
+ 鈹� (鐗╂枡銆丼KU銆佸垎绫汇�佸崟浣嶃�佸搧鐗�) 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
+ 鈻� 鈻� 鈻�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� ERP 妯″潡 鈹� 鈹� WMS 妯″潡 鈹� 鈹� MES 妯″潡 鈹�
+ 鈹� 浜у搧 = 鎴愬搧 鈹� 鈹� 鍟嗗搧 = 鐗╂枡 鈹� 鈹� 鐗╂枡 = 鍘熸枡 鈹�
+ 鈹� itemType=2 鈹� 鈹� itemType=1 鈹� 鈹� itemType=1 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈻尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� WMS 搴撳瓨绠$悊 API 鈹�
+ 鈹� (缁熶竴搴撳瓨鍏ュ簱/鍑哄簱/鏌ヨ) 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**鏁版嵁缁熶竴璇存槑锛�**
+
+| 涓氬姟妯″潡 | 鍘熸暟鎹〃 | 鐜版暟鎹簮 | 鐗╂枡绫诲瀷鏄犲皠 |
+|----------|----------|----------|--------------|
+| ERP | erp_product | MDM Item | itemType=2锛堟垚鍝侊級 |
+| WMS | wms_item + wms_item_sku | MDM Item + SKU | itemType=1锛堢墿鏂欙級 |
+| MES | mes_md_item | MDM Item | itemType=1锛堢墿鏂欙級 |
---
@@ -72,11 +108,95 @@
---
-## 涓夈�佸姛鑳芥ā鍧楀伐浣滄祦绋�
+## 涓夈�佷富鏁版嵁绠$悊妯″潡 (MDM)
-### 3.1 绯荤粺绠$悊妯″潡 (System)
+### 3.1 MDM 妯″潡姒傝堪
-#### 3.1.1 鐢ㄦ埛绠$悊娴佺▼
+MDM锛圡aster Data Management锛変富鏁版嵁绠$悊妯″潡鏄暣涓郴缁熺殑鏁版嵁鏍稿績锛岃礋璐g粺涓�绠$悊锛�
+
+- **鐗╂枡涓绘暟鎹�** - 浜у搧銆佺墿鏂欍�佸崐鎴愬搧鐨勭粺涓�妗f
+- **鐗╂枡 SKU** - 鐗╂枡鐨勮鏍煎彉浣擄紙灏哄銆侀鑹茬瓑锛�
+- **鐗╂枡鍒嗙被** - 鐗╂枡鐨勫垎绫讳綋绯�
+- **璁¢噺鍗曚綅** - 缁熶竴鐨勮閲忓崟浣嶇鐞�
+- **鍝佺墝绠$悊** - 鍝佺墝妗f绠$悊
+
+### 3.2 鐗╂枡绫诲瀷璇存槑
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� MDM 鐗╂枡绫诲瀷 (itemType) 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 1 - 鐗╂枡/鍘熸枡 鈫� 鐢ㄤ簬 WMS 鍟嗗搧銆丮ES 鐗╂枡 鈹�
+鈹� 2 - 鎴愬搧/浜у搧 鈫� 鐢ㄤ簬 ERP 浜у搧 鈹�
+鈹� 3 - 鍗婃垚鍝� 鈫� 鐢ㄤ簬 MES 鐢熶骇涓棿浠� 鈹�
+鈹� 4 - 杈呮枡 鈫� 鐢ㄤ簬鐢熶骇杈呭姪鏉愭枡 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 3.3 鐗╂枡绠$悊娴佺▼
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍒涘缓鐗╂枡 鈹� -> 鈹� 璁剧疆鍒嗙被 鈹� -> 鈹� 閰嶇疆 SKU 鈹� -> 鈹� 鍚敤鐗╂枡 鈹�
+鈹� 鍩烘湰淇℃伅 鈹� 鈹� 鍗曚綅鍝佺墝 鈹� 鈹� 瑙勬牸浠锋牸 鈹� 鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
+ v v v
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 缂栫爜鐢熸垚 鈹� 鈹� 鍒嗙被鍏宠仈 鈹� 鈹� 澶氳鏍兼敮鎸� 鈹�
+鈹� 鏉$爜绠$悊 鈹� 鈹� 鍗曚綅鎹㈢畻 鈹� 鈹� 浠锋牸浣撶郴 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**鎿嶄綔姝ラ锛�**
+
+1. 杩涘叆銆怣DM銆�->銆愮墿鏂欑鐞嗐��
+2. 鐐瑰嚮銆愭柊澧炪�戯紝濉啓鐗╂枡鍩烘湰淇℃伅
+ - 鐗╂枡缂栫爜锛堝敮涓�锛�
+ - 鐗╂枡鍚嶇О
+ - 鐗╂枡鏉$爜
+ - 瑙勬牸鍨嬪彿
+3. 閫夋嫨鐗╂枡鍒嗙被銆佽閲忓崟浣嶃�佸搧鐗�
+4. 璁剧疆鐗╂枡绫诲瀷锛堢墿鏂�/鎴愬搧/鍗婃垚鍝侊級
+5. 閰嶇疆浠锋牸锛堥噰璐环銆侀攢鍞环銆佹垚鏈环锛�
+6. 淇濆瓨鍚庯紝鍙坊鍔� SKU 瑙勬牸
+
+**娑夊強琛細**
+- `mdm_item` - 鐗╂枡涓昏〃
+- `mdm_item_sku` - 鐗╂枡 SKU 琛�
+- `mdm_item_category` - 鐗╂枡鍒嗙被琛�
+- `mdm_unit_measure` - 璁¢噺鍗曚綅琛�
+- `mdm_brand` - 鍝佺墝琛�
+
+### 3.4 SKU 绠$悊璇存槑
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鐗╂枡 (Item) 鈹�
+鈹� id: 1001 code: ITEM001 name: T鎭� 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� SKU 鍒楄〃 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� SKU-001 鈹� SKU-002 鈹� SKU-003 鈹�
+鈹� 绾㈣壊 / L 鈹� 钃濊壊 / M 鈹� 榛戣壊 / XL 鈹�
+鈹� 鏉$爜: 001REDL 鈹� 鏉$爜: 001BLUEM 鈹� 鏉$爜: 001BLACKXL 鈹�
+鈹� 閿�鍞环: 99 鈹� 閿�鍞环: 99 鈹� 閿�鍞环: 109 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**SKU 瀛楁锛�**
+- SKU 缂栫爜銆丼KU 鍚嶇О銆丼KU 鏉$爜
+- 灏哄锛堥暱/瀹�/楂橈級
+- 閲嶉噺锛堟瘺閲�/鍑�閲嶏級
+- 浠锋牸锛堟垚鏈环/閿�鍞环锛�
+
+---
+
+## 鍥涖�佸姛鑳芥ā鍧楀伐浣滄祦绋�
+
+### 4.1 绯荤粺绠$悊妯″潡 (System)
+
+#### 4.1.1 鐢ㄦ埛绠$悊娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -104,7 +224,7 @@
- `system_user_role` - 鐢ㄦ埛瑙掕壊鍏宠仈琛�
- `system_user_post` - 鐢ㄦ埛宀椾綅鍏宠仈琛�
-#### 3.1.2 瑙掕壊鏉冮檺閰嶇疆娴佺▼
+#### 4.1.2 瑙掕壊鏉冮檺閰嶇疆娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -127,9 +247,9 @@
---
-### 3.2 宸ヤ綔娴佹ā鍧� (BPM)
+### 4.2 宸ヤ綔娴佹ā鍧� (BPM)
-#### 3.2.1 娴佺▼璁捐娴佺▼
+#### 4.2.1 娴佺▼璁捐娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -153,7 +273,7 @@
- `bpm_form` - 琛ㄥ崟瀹氫箟琛�
- `bpm_process_definition_info` - 娴佺▼瀹氫箟鎵╁睍琛�
-#### 3.2.2 娴佺▼瀹℃壒娴佺▼
+#### 4.2.2 娴佺▼瀹℃壒娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -177,9 +297,9 @@
---
-### 3.3 瀹㈡埛鍏崇郴绠$悊妯″潡 (CRM)
+### 4.3 瀹㈡埛鍏崇郴绠$悊妯″潡 (CRM)
-#### 3.3.1 绾跨储杞鎴锋祦绋�
+#### 4.3.1 绾跨储杞鎴锋祦绋�
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -208,31 +328,39 @@
- `crm_follow_up_record` - 璺熻繘璁板綍琛�
- `crm_permission` - 鏁版嵁鏉冮檺琛�
-#### 3.3.2 鍟嗘満閿�鍞祦绋�
-
-```
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 鍒涘缓鍟嗘満 鈹� -> 鈹� 鍏宠仈浜у搧 鈹� -> 鈹� 闃舵鎺ㄨ繘 鈹� -> 鈹� 鐢熸垚鍚堝悓 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� 鈹� 鈹�
- v v v
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 鍟嗘満闃舵 鈹� 鈹� 浜у搧鎶ヤ环 鈹� 鈹� 璧㈠崟/杈撳崟 鈹�
-鈹� 绠$悊 鈹� 鈹� 鈹� 鈹� 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-```
-
-**娑夊強琛細**
-- `crm_business` - 鍟嗘満琛�
-- `crm_business_product` - 鍟嗘満浜у搧鍏宠仈琛�
-- `crm_business_status` - 鍟嗘満鐘舵�佽〃
-- `crm_contract` - 鍚堝悓琛�
-
---
-### 3.4 浼佷笟璧勬簮璁″垝妯″潡 (ERP)
+### 4.4 浼佷笟璧勬簮璁″垝妯″潡 (ERP)
-#### 3.4.1 閲囪喘涓氬姟娴佺▼
+#### 4.4.1 浜у搧绠$悊锛圡DM 闆嗘垚锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� ERP 浜у搧 = MDM 鐗╂枡 (itemType=2) 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� ErpProductService 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈻� 鈹�
+鈹� MdmItemApi.createItem() 鈹�鈹�鈫� MDM 鍒涘缓鐗╂枡 鈹�
+鈹� MdmItemApi.updateItem() 鈹�鈹�鈫� MDM 鏇存柊鐗╂枡 鈹�
+鈹� MdmItemApi.getItem() 鈹�鈹�鈫� MDM 鏌ヨ鐗╂枡 鈹�
+鈹� MdmItemApi.getItemPage() 鈹�鈹�鈫� MDM 鍒嗛〉鏌ヨ 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**瀛楁鏄犲皠锛�**
+
+| ERP 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 缁熶竴 ID |
+| name | name | 浜у搧鍚嶇О |
+| barCode | barCode | 浜у搧鏉$爜 |
+| standard | specification | 瑙勬牸鍨嬪彿 |
+| unitId | unitMeasureId | 鍗曚綅缂栧彿 |
+| salePrice | salesPrice | 閿�鍞环鏍� |
+| minPrice | costPrice | 鏈�浣庝环鏍�/鎴愭湰浠� |
+
+#### 4.4.2 閲囪喘涓氬姟娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -241,37 +369,33 @@
鈹� 鈹� 鈹� 鈹�
v v v v
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 闇�姹傞儴闂� 鈹� 鈹� 渚涘簲鍟� 鈹� 鈹� 浠撳簱 鈹� 鈹� 璐㈠姟 鈹�
-鈹� 鍙戣捣 鈹� 鈹� 纭 鈹� 鈹� 楠屾敹 鈹� 鈹� 缁撶畻 鈹�
+鈹� 闇�姹傞儴闂� 鈹� 鈹� 渚涘簲鍟� 鈹� 鈹� WMS鍏ュ簱 鈹� 鈹� 璐㈠姟 鈹�
+鈹� 鍙戣捣 鈹� 鈹� 纭 鈹� 鈹� API璋冪敤 鈹� 鈹� 缁撶畻 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-**閲囪喘璁㈠崟鎿嶄綔姝ラ锛�**
+**閲囪喘鍏ュ簱闆嗘垚锛�**
-1. 杩涘叆銆怑RP銆�->銆愰噰璐鐞嗐��->銆愰噰璐鍗曘��
-2. 鐐瑰嚮銆愭柊澧炪�戯紝閫夋嫨渚涘簲鍟�
-3. 娣诲姞閲囪喘浜у搧鏄庣粏锛堜骇鍝併�佹暟閲忋�佸崟浠凤級
-4. 璁剧疆浜よ揣鏃ユ湡銆佺粨绠楄处鎴�
-5. 鐐瑰嚮銆愭彁浜ゃ�戞垨鐩存帴銆愬鏍搞��
-6. 璁㈠崟瀹℃牳鍚庯紝鍙繘琛屽叆搴撴搷浣�
-
-**閲囪喘鍏ュ簱鎿嶄綔姝ラ锛�**
-
-1. 杩涘叆銆愰噰璐叆搴撱�戦〉闈�
-2. 閫夋嫨宸插鏍哥殑閲囪喘璁㈠崟
-3. 绯荤粺鑷姩甯﹀叆浜у搧鏄庣粏
-4. 閫夋嫨鍏ュ簱浠撳簱
-5. 纭鍏ュ簱鏁伴噺锛岀偣鍑汇�愭彁浜ゃ��
-6. 瀹℃牳閫氳繃鍚庯紝搴撳瓨鑷姩澧炲姞
+```
+ERP 閲囪喘鍏ュ簱
+ 鈹�
+ 鈻�
+ErpStockRecordService.createStockRecord()
+ 鈹�
+ 鈻�
+WmsInventoryApi.receipt() 鈹�鈹�鈫� WMS 鍏ュ簱鎿嶄綔
+ 鈹�
+ 鈻�
+WMS 搴撳瓨澧炲姞
+```
**娑夊強琛細**
- `erp_supplier` - 渚涘簲鍟嗚〃
- `erp_purchase_order` - 閲囪喘璁㈠崟琛�
-- `erp_purchase_order_items` - 閲囪喘璁㈠崟椤硅〃
- `erp_purchase_in` - 閲囪喘鍏ュ簱琛�
-- `erp_stock` - 搴撳瓨琛�
+- `erp_stock_record` - 搴撳瓨娴佹按琛紙ERP 鏈湴璁板綍锛�
-#### 3.4.2 閿�鍞笟鍔℃祦绋�
+#### 4.4.3 閿�鍞笟鍔℃祦绋�
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -280,8 +404,24 @@
鈹� 鈹� 鈹� 鈹�
v v v v
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 瀹㈡埛璇环 鈹� 鈹� 鍚堝悓绛捐 鈹� 鈹� 鍙戣揣 鈹� 鈹� 瀵硅处寮�绁� 鈹�
+鈹� 瀹㈡埛璇环 鈹� 鈹� 鍚堝悓绛捐 鈹� 鈹� WMS鍑哄簱 鈹� 鈹� 瀵硅处寮�绁� 鈹�
+鈹� 鈹� 鈹� 鈹� 鈹� API璋冪敤 鈹� 鈹� 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**閿�鍞嚭搴撻泦鎴愶細**
+
+```
+ERP 閿�鍞嚭搴�
+ 鈹�
+ 鈻�
+ErpStockRecordService.createStockRecord()
+ 鈹�
+ 鈻�
+WmsInventoryApi.shipment() 鈹�鈹�鈫� WMS 鍑哄簱鎿嶄綔
+ 鈹�
+ 鈻�
+WMS 搴撳瓨鍑忓皯
```
**娑夊強琛細**
@@ -290,70 +430,42 @@
- `erp_sale_out` - 閿�鍞嚭搴撹〃
- `erp_finance_receipt` - 鏀舵鍗曡〃
-#### 3.4.3 搴撳瓨绠$悊娴佺▼
+#### 4.4.4 搴撳瓨鏌ヨ锛圵MS 闆嗘垚锛�
```
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 搴撳瓨鏌ヨ 鈹� -> 鈹� 搴撳瓨鐩樼偣 鈹� -> 鈹� 搴撳瓨璋冩嫧 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� 鈹� 鈹�
- v v v
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 瀹炴椂搴撳瓨 鈹� 鈹� 鐩樼泩/鐩樹簭 鈹� 鈹� 璺ㄤ粨璋冩嫧 鈹�
-鈹� 鏄庣粏 鈹� 鈹� 澶勭悊 鈹� 鈹� 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� ERP 搴撳瓨 = WMS 搴撳瓨 API 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� ErpStockService 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈻� 鈹�
+鈹� MdmItemSkuApi.getItemSkuListByItemId() 鈹�鈹�鈫� 鑾峰彇榛樿 SKU 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈻� 鈹�
+鈹� WmsInventoryApi.getAvailableQuantity() 鈹�鈹�鈫� 鏌ヨ鍙敤搴撳瓨 鈹�
+鈹� WmsInventoryApi.getInventoryList() 鈹�鈹�鈫� 鏌ヨ搴撳瓨鍒楄〃 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-
-**娑夊強琛細**
-- `erp_warehouse` - 浠撳簱琛�
-- `erp_stock` - 搴撳瓨琛�
-- `erp_stock_record` - 搴撳瓨娴佹按琛�
-- `erp_stock_check` - 鐩樼偣鍗曡〃
-- `erp_stock_move` - 璋冩嫧鍗曡〃
---
-### 3.5 鍒堕�犳墽琛岀郴缁熸ā鍧� (MES)
+### 4.5 鍒堕�犳墽琛岀郴缁熸ā鍧� (MES)
-#### 3.5.1 鐢熶骇涓绘暟鎹鐞嗘祦绋�
+#### 4.5.1 鐗╂枡绠$悊锛圡DM 闆嗘垚锛�
```
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 鐗╂枡绠$悊 鈹� -> 鈹� BOM 绠$悊 鈹� -> 鈹� 宸ヨ壓璺嚎 鈹� -> 鈹� 宸ヤ綔绔� 鈹�
-鈹� 鈹� 鈹� 鈹� 鈹� 鈹� 鈹� 閰嶇疆 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� 鈹� 鈹� 鈹�
- v v v v
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 鐗╂枡鍒嗙被 鈹� 鈹� 浜у搧缁撴瀯 鈹� 鈹� 宸ュ簭瀹氫箟 鈹� 鈹� 璁惧/浜哄憳 鈹�
-鈹� 璁¢噺鍗曚綅 鈹� 鈹� 鏇夸唬鏂� 鈹� 鈹� 椤哄簭閰嶇疆 鈹� 鈹� 宸ュ叿澶瑰叿 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� MES 鐗╂枡 = MDM 鐗╂枡 (itemType=1) 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� MesMdItemService 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈻� 鈹�
+鈹� MdmItemApi.getItem() 鈹�鈹�鈫� MDM 鏌ヨ鐗╂枡 鈹�
+鈹� MdmItemApi.getItemList() 鈹�鈹�鈫� MDM 鏌ヨ鐗╂枡鍒楄〃 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-**鐗╂枡绠$悊鎿嶄綔锛�**
-
-1. 杩涘叆銆怣ES銆�->銆愪富鏁版嵁銆�->銆愮墿鏂欑鐞嗐��
-2. 鏂板鐗╂枡鍒嗙被锛堟爲褰㈢粨鏋勶級
-3. 鍒涘缓鐗╂枡妗f锛堢紪鐮併�佸悕绉般�佽鏍笺�佸崟浣嶇瓑锛�
-4. 閰嶇疆鐗╂枡鎵规灞炴��
-
-**宸ヨ壓璺嚎鎿嶄綔锛�**
-
-1. 杩涘叆銆愬伐鑹鸿矾绾裤�戠鐞�
-2. 鍏堝畾涔夈�愬伐搴忋�戯紙鍔犲伐銆佹楠屻�佽閰嶇瓑锛�
-3. 鍒涘缓宸ヨ壓璺嚎锛屽叧鑱斾骇鍝�
-4. 閰嶇疆宸ュ簭椤哄簭銆佸伐鏃跺畾棰�
-5. 鍏宠仈宸ヤ綔涓績/璁惧
-
-**娑夊強琛細**
-- `mes_md_item` - 鐗╂枡琛�
-- `mes_md_item_type` - 鐗╂枡鍒嗙被琛�
-- `mes_md_product_bom` - BOM 琛�
-- `mes_pro_process` - 宸ュ簭琛�
-- `mes_pro_route` - 宸ヨ壓璺嚎琛�
-- `mes_md_workstation` - 宸ヤ綔绔欒〃
-
-#### 3.5.2 鐢熶骇鎵ц娴佺▼
+#### 4.5.2 鐢熶骇鎵ц娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -363,32 +475,17 @@
鈹� 鈹� 鈹� 鈹�
v v v v
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 璁″垝鎺掔▼ 鈹� 鈹� 浠诲姟鍒嗛厤 鈹� 鈹� 鐗╂枡娑堣�� 鈹� 鈹� 杩涘害鍙嶉 鈹�
-鈹� 鐗╂枡妫�鏌� 鈹� 鈹� 宸ヤ綔绔欐寚娲� 鈹� 鈹� 鎵规杩芥函 鈹� 鈹� 璐ㄩ噺璁板綍 鈹�
+鈹� 璁″垝鎺掔▼ 鈹� 鈹� 浠诲姟鍒嗛厤 鈹� 鈹� WMS鍑哄簱 鈹� 鈹� 杩涘害鍙嶉 鈹�
+鈹� 鐗╂枡妫�鏌� 鈹� 鈹� 宸ヤ綔绔欐寚娲� 鈹� 鈹� API璋冪敤 鈹� 鈹� 璐ㄩ噺璁板綍 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
鈹�
v
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
鈹� 鐢熶骇鍏ュ簱 鈹�
+鈹� WMS鍏ュ簱 鈹�
+鈹� API璋冪敤 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-
-**鐢熶骇宸ュ崟鎿嶄綔姝ラ锛�**
-
-1. 杩涘叆銆怣ES銆�->銆愮敓浜х鐞嗐��->銆愮敓浜у伐鍗曘��
-2. 鐐瑰嚮銆愭柊澧炪�戯紝閫夋嫨浜у搧
-3. 绯荤粺鑷姩甯﹀叆 BOM 鍜屽伐鑹鸿矾绾�
-4. 璁剧疆璁″垝鏁伴噺銆佽鍒掑紑濮�/缁撴潫鏃堕棿
-5. 鐐瑰嚮銆愪笅杈俱�戝伐鍗�
-6. 宸ュ崟涓嬭揪鍚庡彲杩涜娲惧伐銆侀鏂欐搷浣�
-
-**鐢熶骇鎶ュ伐鎿嶄綔锛�**
-
-1. 杩涘叆銆愭姤宸ャ�戦〉闈�
-2. 鎵爜鎴栭�夋嫨宸ュ崟銆佸伐搴�
-3. 濉啓鎶ュ伐鏁伴噺锛堝悎鏍�/鎶ュ簾锛�
-4. 璁板綍宸ユ椂銆佽澶囦娇鐢ㄦ儏鍐�
-5. 鎻愪氦鎶ュ伐鏁版嵁
**娑夊強琛細**
- `mes_pro_work_order` - 鐢熶骇宸ュ崟琛�
@@ -396,78 +493,66 @@
- `mes_pro_work_record` - 鎶ュ伐璁板綍琛�
- `mes_wm_item_consume` - 鐗╂枡娑堣�楄〃
-#### 3.5.3 璁惧绠$悊娴佺▼
-
-```
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 璁惧鍙拌处 鈹� -> 鈹� 鐐规淇濆吇 鈹� -> 鈹� 缁翠慨绠$悊 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� 鈹� 鈹�
- v v v
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 璁惧妗f 鈹� 鈹� 鐐规璁″垝 鈹� 鈹� 缁翠慨宸ュ崟 鈹�
-鈹� 璁惧鐘舵�� 鈹� 鈹� 淇濆吇璁″垝 鈹� 鈹� 鏁呴殰璁板綍 鈹�
-鈹� 杞﹂棿鍒嗗竷 鈹� 鈹� 鎵ц璁板綍 鈹� 鈹� 楠屾敹纭 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-```
-
-**璁惧鐐规鎿嶄綔锛�**
-
-1. 杩涘叆銆愯澶囩鐞嗐��->銆愮偣妫�鏂规銆�
-2. 鍒涘缓鐐规鏂规锛岄厤缃偣妫�椤圭洰
-3. 鍏宠仈璁惧鍜屽懆鏈�
-4. 鎵ц鐐规鏃讹紝杩涘叆銆愮偣妫�璁板綍銆�
-5. 閫愰」妫�鏌ワ紝璁板綍缁撴灉
-6. 鍙戠幇寮傚父锛岃嚜鍔ㄨЕ鍙戞姤淇�
-
-**娑夊強琛細**
-- `mes_dv_machinery` - 璁惧鍙拌处琛�
-- `mes_dv_check_plan` - 鐐规鏂规琛�
-- `mes_dv_check_record` - 鐐规璁板綍琛�
-- `mes_dv_repair` - 缁翠慨宸ュ崟琛�
-
-#### 3.5.4 璐ㄩ噺绠$悊娴佺▼
-
-```
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 鏉ユ枡妫�楠� 鈹� -> 鈹� 杩囩▼妫�楠� 鈹� -> 鈹� 鎴愬搧妫�楠� 鈹� -> 鈹� 鍑鸿揣妫�楠� 鈹�
-鈹� (IQC) 鈹� 鈹� (IPQC) 鈹� 鈹� (FQC) 鈹� 鈹� (OQC) 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� 鈹� 鈹� 鈹�
- v v v v
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 閲囪喘鍏ュ簱 鈹� 鈹� 鐢熶骇宸ュ簭 鈹� 鈹� 鐢熶骇瀹屾垚 鈹� 鈹� 閿�鍞嚭搴� 鈹�
-鈹� 鍓嶆楠� 鈹� 鈹� 鎶芥/宸℃ 鈹� 鈹� 鍏ㄦ/鎶芥 鈹� 鈹� 鍓嶆楠� 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-```
-
-**璐ㄦ鏂规閰嶇疆锛�**
-
-1. 杩涘叆銆愯川閲忕鐞嗐��->銆愯川妫�鏂规銆�
-2. 鍒涘缓璐ㄦ妯℃澘锛岄厤缃娴嬮」鐩�
-3. 璁剧疆鎶芥牱鏂规锛堝叏妫�/鎶芥姣斾緥锛�
-4. 閰嶇疆妫�楠屾寚鏍囧拰鏍囧噯鍊�
-
-**妫�楠屾搷浣滐細**
-
-1. 閫夋嫨寰呮鍗曟嵁锛堥噰璐叆搴撳崟/鐢熶骇宸ュ崟绛夛級
-2. 鍒涘缓妫�楠屽崟
-3. 閫愰」妫�楠岋紝璁板綍鏁版嵁
-4. 鍒ゅ畾缁撴灉锛堝悎鏍�/涓嶅悎鏍�/璁╂鎺ユ敹锛�
-5. 涓嶅悎鏍煎搧杩涘叆澶勭悊娴佺▼
-
-**娑夊強琛細**
-- `mes_qc_template` - 璐ㄦ鏂规琛�
-- `mes_qc_iqc` - 鏉ユ枡妫�楠岃〃
-- `mes_qc_ipqc` - 杩囩▼妫�楠岃〃
-- `mes_qc_oqc` - 鍑鸿揣妫�楠岃〃
-- `mes_qc_defect_record` - 缂洪櫡璁板綍琛�
-
---
-### 3.6 浠撳簱绠$悊绯荤粺妯″潡 (WMS)
+### 4.6 浠撳簱绠$悊绯荤粺妯″潡 (WMS)
-#### 3.6.1 鍏ュ簱涓氬姟娴佺▼
+#### 4.6.1 鍟嗗搧绠$悊锛圡DM 闆嗘垚锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� WMS 鍟嗗搧 = MDM 鐗╂枡 (itemType=1) 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� WmsItemService 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈻� 鈹�
+鈹� MdmItemApi.createItem() 鈹�鈹�鈫� MDM 鍒涘缓鐗╂枡 鈹�
+鈹� MdmItemApi.updateItem() 鈹�鈹�鈫� MDM 鏇存柊鐗╂枡 鈹�
+鈹� MdmItemApi.getItemPage() 鈹�鈹�鈫� MDM 鍒嗛〉鏌ヨ 鈹�
+鈹� MdmItemApi.getItemList() 鈹�鈹�鈫� MDM 鏌ヨ鍒楄〃 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**瀛楁鏄犲皠锛�**
+
+| WMS 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 缁熶竴 ID |
+| code | code | 鍟嗗搧缂栫爜 |
+| name | name | 鍟嗗搧鍚嶇О |
+| unit | unitMeasure.name | 鍗曚綅鍚嶇О锛堜粠璁¢噺鍗曚綅鑾峰彇锛� |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| brandId | brandId | 鍝佺墝缂栧彿 |
+
+#### 4.6.2 搴撳瓨绠$悊鏍稿績
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� WMS 搴撳瓨绠$悊 API 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� WmsInventoryApi 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈹溾攢 receipt() 鍏ュ簱鎿嶄綔 鈹�
+鈹� 鈹� 鈹斺攢 鍙傛暟: bizType, bizId, warehouseId, items[] 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈹溾攢 shipment() 鍑哄簱鎿嶄綔 鈹�
+鈹� 鈹� 鈹斺攢 鍙傛暟: bizType, bizId, warehouseId, items[] 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈹溾攢 reserve() 搴撳瓨棰勭暀锛堥攣搴撳瓨锛� 鈹�
+鈹� 鈹� 鈹斺攢 鍙傛暟: bizType, bizId, warehouseId, items[] 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈹溾攢 releaseReserve() 閲婃斁棰勭暀 鈹�
+鈹� 鈹� 鈹斺攢 鍙傛暟: reserveNo 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈹溾攢 getInventoryList() 鏌ヨ搴撳瓨鍒楄〃 鈹�
+鈹� 鈹� 鈹斺攢 鍙傛暟: skuIds, warehouseIds, onlyPositive 鈹�
+鈹� 鈹� 鈹�
+鈹� 鈹斺攢 getAvailableQuantity() 鏌ヨ鍙敤搴撳瓨 鈹�
+鈹� 鈹斺攢 鍙傛暟: skuId, warehouseId 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+#### 4.6.3 鍏ュ簱涓氬姟娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -477,26 +562,26 @@
v v v v
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
鈹� ERP閲囪喘鍗� 鈹� 鈹� 閫佽揣鍗曟嵁 鈹� 鈹� 璐ㄩ噺妫�楠� 鈹� 鈹� 搴撲綅鍒嗛厤 鈹�
-鈹� 鐢熸垚棰勭害 鈹� 鈹� 杞﹁締淇℃伅 鈹� 鈹� 鏁伴噺纭 鈹� 鈹� 鎵规绠$悊 鈹�
+鈹� MES鐢熶骇鍏ュ簱 鈹� 鈹� 杞﹁締淇℃伅 鈹� 鈹� 鏁伴噺纭 鈹� 鈹� 鎵规绠$悊 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-**鍏ュ簱鎿嶄綔姝ラ锛�**
+**API 璋冪敤绀轰緥锛�**
-1. 杩涘叆銆怶MS銆�->銆愬叆搴撶鐞嗐��->銆愬叆搴撳崟銆�
-2. 鏂板缓鍏ュ簱鍗曪紝閫夋嫨鍏ュ簱绫诲瀷锛堥噰璐叆搴�/鐢熶骇鍏ュ簱/鍏朵粬鍏ュ簱锛�
-3. 娣诲姞鍏ュ簱鍟嗗搧鏄庣粏
-4. 閫夋嫨浠撳簱
-5. 鎻愪氦瀹℃牳
-6. 瀹℃牳閫氳繃鍚庯紝搴撳瓨澧炲姞
+```java
+// ERP/MES 璋冪敤 WMS 鍏ュ簱
+WmsReceiptReqDTO req = new WmsReceiptReqDTO();
+req.setBizType(WmsInventoryBizTypeEnum.PURCHASE_RECEIPT.getType());
+req.setBizId(purchaseInId);
+req.setBizNo(purchaseInNo);
+req.setWarehouseId(warehouseId);
+req.setItems(List.of(
+ new WmsReceiptReqDTO.Item(skuId, quantity, price)
+));
+wmsInventoryApi.receipt(req);
+```
-**娑夊強琛細**
-- `wms_receipt_order` - 鍏ュ簱鍗曡〃
-- `wms_receipt_order_detail` - 鍏ュ簱鍗曟槑缁嗚〃
-- `wms_inventory` - 搴撳瓨琛�
-- `wms_inventory_history` - 搴撳瓨娴佹按琛�
-
-#### 3.6.2 鍑哄簱涓氬姟娴佺▼
+#### 4.6.4 鍑哄簱涓氬姟娴佺▼
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
@@ -510,73 +595,84 @@
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-**娑夊強琛細**
-- `wms_shipment_order` - 鍑哄簱鍗曡〃
-- `wms_shipment_order_detail` - 鍑哄簱鍗曟槑缁嗚〃
+**API 璋冪敤绀轰緥锛�**
-#### 3.6.3 搴撳瓨绠$悊娴佺▼
-
-```
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 搴撳瓨鏌ヨ 鈹� -> 鈹� 搴撳瓨鐩樼偣 鈹� -> 鈹� 绉诲簱浣滀笟 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� 鈹� 鈹�
- v v v
-鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
-鈹� 瀹炴椂搴撳瓨 鈹� 鈹� 鐩樼泩鐩樹簭 鈹� 鈹� 搴撲綅璋冩暣 鈹�
-鈹� 鎵规杩芥函 鈹� 鈹� 宸紓澶勭悊 鈹� 鈹� 搴撳瓨浼樺寲 鈹�
-鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```java
+// ERP/MES 璋冪敤 WMS 鍑哄簱
+WmsShipmentReqDTO req = new WmsShipmentReqDTO();
+req.setBizType(WmsInventoryBizTypeEnum.SALE_SHIPMENT.getType());
+req.setBizId(saleOutId);
+req.setBizNo(saleOutNo);
+req.setWarehouseId(warehouseId);
+req.setItems(List.of(
+ new WmsShipmentReqDTO.Item(skuId, quantity)
+));
+wmsInventoryApi.shipment(req);
```
**娑夊強琛細**
- `wms_inventory` - 搴撳瓨琛�
-- `wms_check_order` - 鐩樼偣鍗曡〃
-- `wms_movement_order` - 绉诲簱鍗曡〃
-- `wms_item_sku` - 鍟嗗搧 SKU 琛�
+- `wms_inventory_history` - 搴撳瓨娴佹按琛�
+- `wms_inventory_reserve` - 搴撳瓨棰勭暀琛�
---
-## 鍥涖�佺郴缁熼泦鎴愯鏄�
+## 浜斻�佺郴缁熼泦鎴愯鏄�
-### 4.1 妯″潡闂存暟鎹祦杞�
+### 5.1 妯″潡闂存暟鎹祦杞�
```
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� System 妯″潡 鈹�
- 鈹� (鐢ㄦ埛/鏉冮檺) 鈹�
+ 鈹� MDM 妯″潡 鈹�
+ 鈹� (鐗╂枡涓绘暟鎹�) 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
鈹�
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
鈹� 鈹� 鈹�
v v v
鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� CRM 妯″潡 鈹� 鈹� ERP 妯″潡 鈹� 鈹� MES 妯″潡 鈹�
- 鈹� (瀹㈡埛) 鈹� 鈹� (閲囪喘/閿�鍞�)鈹� 鈹� (鐢熶骇) 鈹�
+ 鈹� ERP 妯″潡 鈹� 鈹� WMS 妯″潡 鈹� 鈹� MES 妯″潡 鈹�
+ 鈹� (鎴愬搧) 鈹� 鈹� (鐗╂枡+SKU) 鈹� 鈹� (鐗╂枡) 鈹�
+ 鈹� itemType=2 鈹� 鈹� itemType=1 鈹� 鈹� itemType=1 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
鈹� 鈹� 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
鈹�
鈹屸攢鈹�鈹�鈹�鈹�鈹�v鈹�鈹�鈹�鈹�鈹�鈹�鈹�
- 鈹� WMS 妯″潡 鈹�
- 鈹� (搴撳瓨) 鈹�
+ 鈹� WMS 搴撳瓨 鈹�
+ 鈹� API 鈹�
鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
```
-### 4.2 涓氬姟鏁版嵁鍏宠仈
+### 5.2 API 鎺ュ彛娓呭崟
-| 涓氬姟鍦烘櫙 | 娑夊強妯″潡 | 鏁版嵁娴佸悜 |
-|----------|----------|----------|
-| 閿�鍞鍗� -> 鍙戣揣 | CRM 鈫� WMS | 瀹㈡埛璁㈠崟瑙﹀彂鍑哄簱 |
-| 閲囪喘璁㈠崟 -> 鍏ュ簱 | ERP 鈫� WMS | 閲囪喘璁㈠崟瑙﹀彂鍏ュ簱 |
-| 鐢熶骇棰嗘枡 | MES 鈫� WMS | 宸ュ崟瑙﹀彂棰嗘枡鍑哄簱 |
-| 鐢熶骇鍏ュ簱 | MES 鈫� WMS | 瀹屽伐瑙﹀彂鎴愬搧鍏ュ簱 |
-| 璐ㄩ噺妫�楠� | WMS 鈫� MES | 鍏ュ簱瑙﹀彂鏉ユ枡妫�楠� |
+| API 鎺ュ彛 | 鎻愪緵鏂� | 璋冪敤鏂� | 璇存槑 |
+|----------|--------|--------|------|
+| MdmItemApi | MDM | ERP/WMS/MES | 鐗╂枡涓绘暟鎹� CRUD |
+| MdmItemSkuApi | MDM | ERP/WMS/MES | SKU 绠$悊 |
+| MdmItemCategoryApi | MDM | ERP/WMS/MES | 鐗╂枡鍒嗙被 |
+| MdmUnitMeasureApi | MDM | ERP/WMS/MES | 璁¢噺鍗曚綅 |
+| WmsInventoryApi | WMS | ERP/MES | 搴撳瓨鍏ュ簱/鍑哄簱/鏌ヨ |
+
+### 5.3 涓氬姟鏁版嵁鍏宠仈
+
+| 涓氬姟鍦烘櫙 | 娑夊強妯″潡 | 鏁版嵁娴佸悜 | API 璋冪敤 |
+|----------|----------|----------|----------|
+| ERP 浜у搧绠$悊 | ERP 鈫� MDM | 浜у搧 CRUD | MdmItemApi |
+| WMS 鍟嗗搧绠$悊 | WMS 鈫� MDM | 鍟嗗搧 CRUD | MdmItemApi |
+| MES 鐗╂枡绠$悊 | MES 鈫� MDM | 鐗╂枡鏌ヨ | MdmItemApi |
+| 閲囪喘鍏ュ簱 | ERP 鈫� WMS | 搴撳瓨澧炲姞 | WmsInventoryApi.receipt() |
+| 閿�鍞嚭搴� | ERP 鈫� WMS | 搴撳瓨鍑忓皯 | WmsInventoryApi.shipment() |
+| 鐢熶骇棰嗘枡 | MES 鈫� WMS | 搴撳瓨鍑忓皯 | WmsInventoryApi.shipment() |
+| 鐢熶骇鍏ュ簱 | MES 鈫� WMS | 搴撳瓨澧炲姞 | WmsInventoryApi.receipt() |
+| 搴撳瓨鏌ヨ | ERP/MES 鈫� WMS | 搴撳瓨鏌ヨ | WmsInventoryApi.getAvailableQuantity() |
---
-## 浜斻�佸父瑙侀棶棰樺鐞�
+## 鍏�佸父瑙侀棶棰樺鐞�
-### 5.1 鐢ㄦ埛鏃犳硶鐧诲綍
+### 6.1 鐢ㄦ埛鏃犳硶鐧诲綍
**鎺掓煡姝ラ锛�**
@@ -586,7 +682,7 @@
4. 妫�鏌ヨ处鎴锋槸鍚﹁閿佸畾锛堝瘑鐮侀敊璇鏁拌繃澶氾級
5. 鏌ョ湅鐧诲綍鏃ュ織 `system_login_log`
-### 5.2 鏁版嵁鏉冮檺闂
+### 6.2 鏁版嵁鏉冮檺闂
**鎺掓煡姝ラ锛�**
@@ -594,7 +690,7 @@
2. CRM 妯″潡妫�鏌� `crm_permission` 琛�
3. 纭鏁版嵁鏄惁姝g‘鍒嗛厤璐熻矗浜�
-### 5.3 娴佺▼瀹℃壒寮傚父
+### 6.3 娴佺▼瀹℃壒寮傚父
**鎺掓煡姝ラ锛�**
@@ -603,20 +699,29 @@
3. 鏌ョ湅娴佺▼瀹炰緥鐘舵��
4. 妫�鏌ュ�欓�夌敤鎴�/鐢ㄦ埛缁勯厤缃�
-### 5.4 搴撳瓨鏁版嵁涓嶄竴鑷�
+### 6.4 鐗╂枡鏁版嵁涓嶄竴鑷�
**鎺掓煡姝ラ锛�**
-1. 妫�鏌� `wms_inventory_history` 鎴� `erp_stock_record` 娴佹按璁板綍
+1. 纭鐗╂枡鏄惁鍦� MDM 涓纭垱寤�
+2. 妫�鏌ョ墿鏂欑被鍨嬶紙itemType锛夋槸鍚︽纭�
+3. 纭 SKU 鏄惁姝g‘閰嶇疆
+4. 楠岃瘉鍒嗙被銆佸崟浣嶅叧鑱旀槸鍚︽纭�
+
+### 6.5 搴撳瓨鏁版嵁涓嶄竴鑷�
+
+**鎺掓煡姝ラ锛�**
+
+1. 妫�鏌� `wms_inventory_history` 搴撳瓨娴佹按璁板綍
2. 鏍稿鍑哄叆搴撳崟鎹槸鍚﹀叏閮ㄥ鏍�
-3. 鎵ц搴撳瓨鐩樼偣鏍℃
-4. 妫�鏌ユ槸鍚︽湁鏈畬鎴愮殑璋冩嫧鍗�
+3. 纭 WMS API 璋冪敤鏄惁鎴愬姛
+4. 鎵ц搴撳瓨鐩樼偣鏍℃
---
-## 鍏�侀檮褰�
+## 涓冦�侀檮褰�
-### 6.1 妯″潡璺緞瀵圭収琛�
+### 7.1 妯″潡璺緞瀵圭収琛�
| 妯″潡 | 鍚庣璺緞 | 鍓嶇璺緞 |
|------|----------|----------|
@@ -624,11 +729,12 @@
| Infra | yudao-module-infra | views/infra |
| BPM | yudao-module-bpm | views/bpm |
| CRM | yudao-module-crm | views/crm |
+| MDM | yudao-module-mdm | views/mdm |
| ERP | yudao-module-erp | views/erp |
| MES | yudao-module-mes | views/mes |
| WMS | yudao-module-wms | views/wms |
-### 6.2 鎺ュ彛閴存潈璇存槑
+### 7.2 鎺ュ彛閴存潈璇存槑
鎵�鏈変笟鍔℃帴鍙i渶瑕佹惡甯� Token 璁块棶锛�
@@ -638,15 +744,28 @@
Token 鑾峰彇鏂瑰紡锛氳皟鐢ㄧ櫥褰曟帴鍙� `/system/auth/login`
-### 6.3 澶氱鎴疯鏄�
+### 7.3 澶氱鎴疯鏄�
绯荤粺鏀寔澶氱鎴锋ā寮忥紝绉熸埛闅旂閫氳繃 `tenant_id` 瀛楁瀹炵幇锛�
- System 妯″潡澶ч儴鍒嗚〃鍖呭惈 `tenant_id`
-- 涓氬姟妯″潡锛圕RM銆丒RP銆丮ES銆乄MS锛変笉鍚鎴峰瓧娈碉紝闇�鑷瀹炵幇闅旂閫昏緫
+- 涓氬姟妯″潡锛圕RM銆丒RP銆丮ES銆乄MS銆丮DM锛変笉鍚鎴峰瓧娈碉紝闇�鑷瀹炵幇闅旂閫昏緫
+
+### 7.4 鏍稿績鏀归�犳枃浠舵竻鍗�
+
+| 鏂囦欢 | 鏀归�犺鏄� |
+|------|----------|
+| ErpProductServiceImpl | 浜у搧 CRUD 璋冪敤 MdmItemApi |
+| ErpStockServiceImpl | 搴撳瓨鏌ヨ璋冪敤 WmsInventoryApi |
+| ErpStockRecordServiceImpl | 搴撳瓨鍙樻洿璋冪敤 WmsInventoryApi |
+| WmsItemServiceImpl | 鍟嗗搧 CRUD 璋冪敤 MdmItemApi |
+| MdmItemApi | MDM 鐗╂枡 API 鎺ュ彛 |
+| MdmItemSkuApi | MDM SKU API 鎺ュ彛 |
+| WmsInventoryApi | WMS 搴撳瓨 API 鎺ュ彛 |
---
-**鏂囨。鐗堟湰**: v1.0
-**鏇存柊鏃ユ湡**: 2026-06-23
+**鏂囨。鐗堟湰**: v2.0
+**鏇存柊鏃ユ湡**: 2026-06-29
**閫傜敤鐗堟湰**: ruoyi-vue-pro 2026.04-SNAPSHOT
+**鏍稿績鏀归��**: MDM 涓绘暟鎹鐞嗘ā鍧楅泦鎴愶紝鎵撻�� ERP/MES/WMS 鏁版嵁瀛ゅ矝
diff --git a/pom.xml b/pom.xml
index 0a8e49c..f2aa5e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,8 @@
<module>yudao-module-iot</module>
<module>yudao-module-mes</module>
<module>yudao-module-wms</module>
+ <module>yudao-module-mdm</module>
+ <module>yudao-module-mdm-api</module>
<!-- 璇峰弬鑰� https://doc.iocoder.cn/ai/build/ 鏂囨。锛屽畬鎴� AI 妯″潡鐨勫惎鍔紒锛侊紒 -->
<module>yudao-module-ai</module>
</modules>
diff --git a/sql/mdm_migration.sql b/sql/mdm_migration.sql
new file mode 100644
index 0000000..54402b2
--- /dev/null
+++ b/sql/mdm_migration.sql
@@ -0,0 +1,405 @@
+-- =============================================
+-- MDM 鏁版嵁杩佺Щ SQL 鑴氭湰
+-- 鍒涘缓鏃ユ湡: 2026-06-29
+-- 璇存槑: 灏� MES/ERP/WMS 鐨勭墿鏂欐暟鎹縼绉诲埌缁熶竴涓绘暟鎹〃 md_item
+-- 娉ㄦ剰: 鎵ц鍓嶈澶囦唤鏁版嵁搴擄紒
+-- =============================================
+
+-- ---------------------------------------------
+-- 绗竴闃舵锛氳縼绉诲熀纭�鏁版嵁锛堝垎绫汇�佸崟浣嶃�佸搧鐗岋級
+-- ---------------------------------------------
+
+-- 1. 杩佺Щ MES 璁¢噺鍗曚綅鍒� md_unit_measure
+INSERT INTO `md_unit_measure` (`id`, `code`, `name`, `primary_flag`, `primary_id`, `change_rate`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id,
+ LOWER(REPLACE(name, ' ', '_')) AS code, -- 浣跨敤鍚嶇О鐢熸垚缂栫爜
+ name,
+ COALESCE(primary_flag, 0) AS primary_flag,
+ primary_id,
+ change_rate,
+ COALESCE(status, 0) AS status,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `mes_md_unit_measure`
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- 2. 杩佺Щ ERP 璁¢噺鍗曚綅鍒� md_unit_measure锛堜娇鐢ㄦ柊ID鑼冨洿 10000-19999锛�
+INSERT INTO `md_unit_measure` (`id`, `code`, `name`, `primary_flag`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id + 10000, -- ERP 鍗曚綅 ID 鍋忕Щ 10000
+ LOWER(REPLACE(name, ' ', '_')) AS code,
+ name,
+ 0 AS primary_flag,
+ COALESCE(status, 0) AS status,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `erp_product_unit`
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- 3. 杩佺Щ MES 鐗╂枡鍒嗙被鍒� md_item_category
+INSERT INTO `md_item_category` (`id`, `parent_id`, `code`, `name`, `item_type`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id,
+ parent_id,
+ code,
+ name,
+ CASE item_or_product
+ WHEN 'ITEM' THEN 1 -- 鍘熸枡
+ WHEN 'PRODUCT' THEN 3 -- 鎴愬搧
+ ELSE NULL
+ END AS item_type,
+ sort,
+ COALESCE(status, 0) AS status,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `mes_md_item_type`
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- 4. 杩佺Щ ERP 浜у搧鍒嗙被鍒� md_item_category锛堜娇鐢ㄦ柊ID鑼冨洿 10000-19999锛�
+INSERT INTO `md_item_category` (`id`, `parent_id`, `code`, `name`, `item_type`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id + 10000,
+ CASE WHEN parent_id = 0 THEN 0 ELSE parent_id + 10000 END,
+ CONCAT('ERP_', code) AS code, -- 娣诲姞鍓嶇紑閬垮厤鍐茬獊
+ name,
+ 3 AS item_type, -- ERP 浜у搧榛樿涓烘垚鍝�
+ COALESCE(sort, 0) AS sort,
+ COALESCE(status, 0) AS status,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `erp_product_category`
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- 5. 杩佺Щ WMS 鍟嗗搧鍒嗙被鍒� md_item_category锛堜娇鐢ㄦ柊ID鑼冨洿 20000-29999锛�
+INSERT INTO `md_item_category` (`id`, `parent_id`, `code`, `name`, `item_type`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id + 20000,
+ CASE WHEN parent_id = 0 THEN 0 ELSE parent_id + 20000 END,
+ CONCAT('WMS_', code) AS code,
+ name,
+ NULL AS item_type, -- WMS 鍒嗙被涓嶅尯鍒嗙被鍨�
+ COALESCE(sort, 0) AS sort,
+ COALESCE(status, 0) AS status,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `wms_item_category`
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- 6. 杩佺Щ WMS 鍝佺墝鍒� md_brand
+INSERT INTO `md_brand` (`id`, `code`, `name`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id,
+ code,
+ name,
+ 0 AS status,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `wms_item_brand`
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- ---------------------------------------------
+-- 绗簩闃舵锛氳縼绉荤墿鏂欎富鏁版嵁
+-- ---------------------------------------------
+
+-- 7. 杩佺Щ MES 鐗╂枡鍒� md_item
+INSERT INTO `md_item` (
+ `id`, `code`, `name`, `bar_code`, `specification`,
+ `category_id`, `unit_measure_id`, `brand_id`, `item_type`, `status`,
+ `purchase_price`, `sales_price`, `cost_price`, `min_price`,
+ `safety_stock`, `min_stock`, `max_stock`, `min_order_qty`, `lead_time`,
+ `is_batch_managed`, `is_serial_managed`, `barcode_rule_id`,
+ `expiry_day`, `high_value`, `weight`, `remark`,
+ `creator`, `create_time`, `updater`, `update_time`, `deleted`
+)
+SELECT
+ id,
+ code,
+ name,
+ NULL AS bar_code, -- MES 娌℃湁鏉$爜
+ specification,
+ itemTypeId,
+ unitMeasureId,
+ NULL AS brand_id,
+ 1 AS item_type, -- MES 鐗╂枡榛樿涓哄師鏂�
+ COALESCE(status, 0),
+ NULL, NULL, NULL, NULL, -- 浠锋牸淇℃伅 MES 娌℃湁
+ NULL AS safety_stock,
+ min_stock,
+ max_stock,
+ NULL, NULL, -- 鏈�灏忚璐ч噺銆佹彁鍓嶆湡 MES 娌℃湁
+ COALESCE(batchFlag, 0),
+ 0 AS is_serial_managed,
+ NULL AS barcode_rule_id,
+ NULL AS expiry_day,
+ COALESCE(highValue, 0),
+ NULL AS weight,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `mes_md_item`
+WHERE deleted = 0;
+
+-- 8. 璁板綍 MES 鐗╂枡杩佺Щ鏄犲皠
+INSERT INTO `md_item_migration_map` (`source_module`, `source_table`, `source_id`, `target_item_id`, `create_time`)
+SELECT 'MES', 'mes_md_item', id, id, NOW()
+FROM `mes_md_item`
+WHERE deleted = 0;
+
+-- 9. 杩佺Щ ERP 浜у搧鍒� md_item锛堜娇鐢ㄦ柊ID鑼冨洿 100000-199999锛�
+INSERT INTO `md_item` (
+ `id`, `code`, `name`, `bar_code`, `specification`,
+ `category_id`, `unit_measure_id`, `brand_id`, `item_type`, `status`,
+ `purchase_price`, `sales_price`, `cost_price`, `min_price`,
+ `safety_stock`, `min_stock`, `max_stock`, `min_order_qty`, `lead_time`,
+ `is_batch_managed`, `is_serial_managed`, `barcode_rule_id`,
+ `expiry_day`, `high_value`, `weight`, `remark`,
+ `creator`, `create_time`, `updater`, `update_time`, `deleted`
+)
+SELECT
+ id + 100000, -- ERP 浜у搧 ID 鍋忕Щ 100000
+ COALESCE(bar_code, CONCAT('ERP_', id)) AS code, -- 浣跨敤鏉$爜浣滀负缂栫爜锛屾病鏈夊垯鐢熸垚
+ name,
+ bar_code,
+ standard AS specification,
+ categoryId + 10000, -- 浣跨敤 ERP 鍒嗙被鏄犲皠鍚庣殑ID
+ unitId + 10000, -- 浣跨敤 ERP 鍗曚綅鏄犲皠鍚庣殑ID
+ NULL AS brand_id,
+ 3 AS item_type, -- ERP 浜у搧榛樿涓烘垚鍝�
+ COALESCE(status, 0),
+ purchase_price,
+ sale_price,
+ NULL AS cost_price,
+ min_price,
+ NULL, NULL, NULL, NULL, NULL, -- 搴撳瓨绠$悊 ERP 娌℃湁
+ 0 AS is_batch_managed,
+ 0 AS is_serial_managed,
+ NULL AS barcode_rule_id,
+ expiry_day,
+ 0 AS high_value,
+ weight,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `erp_product`
+WHERE deleted = 0;
+
+-- 10. 璁板綍 ERP 浜у搧杩佺Щ鏄犲皠
+INSERT INTO `md_item_migration_map` (`source_module`, `source_table`, `source_id`, `target_item_id`, `create_time`)
+SELECT 'ERP', 'erp_product', id, id + 100000, NOW()
+FROM `erp_product`
+WHERE deleted = 0;
+
+-- 11. 杩佺Щ WMS 鍟嗗搧鍒� md_item锛堜娇鐢ㄦ柊ID鑼冨洿 200000-299999锛�
+INSERT INTO `md_item` (
+ `id`, `code`, `name`, `bar_code`, `specification`,
+ `category_id`, `unit_measure_id`, `brand_id`, `item_type`, `status`,
+ `purchase_price`, `sales_price`, `cost_price`, `min_price`,
+ `safety_stock`, `min_stock`, `max_stock`, `min_order_qty`, `lead_time`,
+ `is_batch_managed`, `is_serial_managed`, `barcode_rule_id`,
+ `expiry_day`, `high_value`, `weight`, `remark`,
+ `creator`, `create_time`, `updater`, `update_time`, `deleted`
+)
+SELECT
+ id + 200000, -- WMS 鍟嗗搧 ID 鍋忕Щ 200000
+ code,
+ name,
+ NULL AS bar_code, -- WMS 鏉$爜鍦� SKU 灞�
+ NULL AS specification,
+ categoryId + 20000, -- 浣跨敤 WMS 鍒嗙被鏄犲皠鍚庣殑ID
+ NULL AS unit_measure_id, -- WMS 鍗曚綅鏄瓧绗︿覆锛岄渶瑕佸悗缁鐞�
+ brandId,
+ 1 AS item_type, -- WMS 鍟嗗搧榛樿涓哄師鏂�
+ 0 AS status,
+ NULL, NULL, NULL, NULL, -- 浠锋牸鍦� SKU 灞�
+ NULL, NULL, NULL, NULL, NULL,
+ 0, 0, NULL,
+ NULL, 0, NULL,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `wms_item`
+WHERE deleted = 0;
+
+-- 12. 璁板綍 WMS 鍟嗗搧杩佺Щ鏄犲皠
+INSERT INTO `md_item_migration_map` (`source_module`, `source_table`, `source_id`, `target_item_id`, `create_time`)
+SELECT 'WMS', 'wms_item', id, id + 200000, NOW()
+FROM `wms_item`
+WHERE deleted = 0;
+
+-- ---------------------------------------------
+-- 绗笁闃舵锛氳縼绉绘壒娆″睘鎬ч厤缃�
+-- ---------------------------------------------
+
+-- 13. 杩佺Щ MES 鎵规灞炴�ч厤缃埌 md_item_batch_config
+INSERT INTO `md_item_batch_config` (
+ `id`, `item_id`,
+ `produce_date_flag`, `expire_date_flag`, `receipt_date_flag`,
+ `vendor_flag`, `client_flag`, `sales_order_code_flag`, `purchase_order_code_flag`,
+ `work_order_flag`, `task_flag`, `workstation_flag`, `tool_flag`, `mold_flag`,
+ `lot_number_flag`, `quality_status_flag`,
+ `creator`, `create_time`, `updater`, `update_time`, `deleted`
+)
+SELECT
+ id,
+ itemId,
+ COALESCE(produce_date_flag, 0),
+ COALESCE(expire_date_flag, 0),
+ COALESCE(receipt_date_flag, 0),
+ COALESCE(vendor_flag, 0),
+ COALESCE(client_flag, 0),
+ COALESCE(sales_order_code_flag, 0),
+ COALESCE(purchase_order_code_flag, 0),
+ COALESCE(work_order_flag, 0),
+ COALESCE(task_flag, 0),
+ COALESCE(workstation_flag, 0),
+ COALESCE(tool_flag, 0),
+ COALESCE(mold_flag, 0),
+ COALESCE(lot_number_flag, 0),
+ COALESCE(quality_status_flag, 0),
+ creator, create_time, updater, update_time, deleted
+FROM `mes_md_item_batch_config`
+WHERE deleted = 0;
+
+-- ---------------------------------------------
+-- 绗洓闃舵锛氳縼绉讳粨搴撴暟鎹�
+-- ---------------------------------------------
+
+-- 14. 杩佺Щ MES 浠撳簱鍒� md_warehouse
+INSERT INTO `md_warehouse` (`id`, `code`, `name`, `address`, `area`, `type`, `charge_user_id`, `status`, `frozen`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id,
+ code,
+ name,
+ address,
+ area,
+ NULL AS type, -- MES 浠撳簱娌℃湁绫诲瀷
+ chargeUserId,
+ 0 AS status,
+ COALESCE(frozen, 0),
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `mes_wm_warehouse`
+WHERE deleted = 0;
+
+-- 15. 杩佺Щ ERP 浠撳簱鍒� md_warehouse锛堜娇鐢ㄦ柊ID鑼冨洿 10000-19999锛�
+INSERT INTO `md_warehouse` (`id`, `code`, `name`, `address`, `area`, `type`, `charge_user_id`, `status`, `frozen`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id + 10000,
+ CONCAT('ERP_', id) AS code, -- ERP 浠撳簱娌℃湁缂栫爜锛岀敓鎴愪竴涓�
+ name,
+ address,
+ NULL AS area,
+ NULL AS type,
+ NULL AS charge_user_id, -- ERP 浣跨敤 principal 瀛楃涓诧紝鏃犳硶鏄犲皠
+ COALESCE(status, 0),
+ 0 AS frozen,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `erp_warehouse`
+WHERE deleted = 0;
+
+-- 16. 杩佺Щ WMS 浠撳簱鍒� md_warehouse锛堜娇鐢ㄦ柊ID鑼冨洿 20000-29999锛�
+INSERT INTO `md_warehouse` (`id`, `code`, `name`, `address`, `area`, `type`, `charge_user_id`, `status`, `frozen`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ id + 20000,
+ code,
+ name,
+ address,
+ area,
+ NULL AS type,
+ chargeUserId,
+ COALESCE(status, 0),
+ 0 AS frozen,
+ remark,
+ creator, create_time, updater, update_time, deleted
+FROM `wms_warehouse`
+WHERE deleted = 0;
+
+-- ---------------------------------------------
+-- 绗簲闃舵锛氳縼绉诲簱瀛樻暟鎹�
+-- ---------------------------------------------
+
+-- 17. 杩佺Щ MES 搴撳瓨鍒� wms_stock
+INSERT INTO `wms_stock` (`item_id`, `warehouse_id`, `location_id`, `batch_code`, `quantity`, `quantity_locked`, `quantity_available`, `unit_measure_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ m.target_item_id AS item_id, -- 浣跨敤鏄犲皠鍚庣殑鐗╂枡ID
+ s.warehouseId AS warehouse_id,
+ s.locationId AS location_id,
+ s.batchCode AS batch_code,
+ s.quantity,
+ 0 AS quantity_locked,
+ s.quantity AS quantity_available,
+ i.unitMeasureId AS unit_measure_id,
+ s.creator, s.create_time, s.updater, s.update_time, s.deleted
+FROM `mes_wm_material_stock` s
+LEFT JOIN `mes_md_item` i ON i.id = s.itemId
+LEFT JOIN `md_item_migration_map` m ON m.source_module = 'MES' AND m.source_table = 'mes_md_item' AND m.source_id = s.itemId
+WHERE s.deleted = 0 AND m.target_item_id IS NOT NULL;
+
+-- 18. 杩佺Щ ERP 搴撳瓨鍒� wms_stock
+INSERT INTO `wms_stock` (`item_id`, `warehouse_id`, `batch_code`, `quantity`, `quantity_locked`, `quantity_available`, `unit_measure_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ m.target_item_id AS item_id,
+ s.warehouseId + 10000 AS warehouse_id, -- 浣跨敤 ERP 浠撳簱鏄犲皠鍚庣殑ID
+ NULL AS batch_code,
+ s.count AS quantity,
+ 0 AS quantity_locked,
+ s.count AS quantity_available,
+ p.unitId + 10000 AS unit_measure_id, -- 浣跨敤 ERP 鍗曚綅鏄犲皠鍚庣殑ID
+ s.creator, s.create_time, s.updater, s.update_time, s.deleted
+FROM `erp_stock` s
+LEFT JOIN `erp_product` p ON p.id = s.productId
+LEFT JOIN `md_item_migration_map` m ON m.source_module = 'ERP' AND m.source_table = 'erp_product' AND m.source_id = s.productId
+WHERE s.deleted = 0 AND m.target_item_id IS NOT NULL;
+
+-- 19. 杩佺Щ WMS 搴撳瓨鍒� wms_stock
+INSERT INTO `wms_stock` (`item_id`, `warehouse_id`, `batch_code`, `quantity`, `quantity_locked`, `quantity_available`, `unit_measure_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT
+ m.target_item_id AS item_id,
+ s.warehouseId + 20000 AS warehouse_id, -- 浣跨敤 WMS 浠撳簱鏄犲皠鍚庣殑ID
+ NULL AS batch_code,
+ s.quantity,
+ 0 AS quantity_locked,
+ s.quantity AS quantity_available,
+ NULL AS unit_measure_id, -- WMS 搴撳瓨娌℃湁鍗曚綅
+ s.creator, s.create_time, s.updater, s.update_time, s.deleted
+FROM `wms_inventory` s
+LEFT JOIN `md_item_migration_map` m ON m.source_module = 'WMS' AND m.source_table = 'wms_item' AND m.source_id = s.skuId -- 娉ㄦ剰锛歐MS 搴撳瓨鍏宠仈鐨勬槸 SKU
+WHERE s.deleted = 0 AND m.target_item_id IS NOT NULL;
+
+-- =============================================
+-- 杩佺Щ瀹屾垚
+-- =============================================
+
+-- 鏌ヨ杩佺Щ缁撴灉缁熻
+SELECT
+ 'md_item' AS table_name,
+ (SELECT COUNT(*) FROM md_item WHERE deleted = 0) AS count
+UNION ALL
+SELECT
+ 'md_item_category',
+ (SELECT COUNT(*) FROM md_item_category WHERE deleted = 0)
+UNION ALL
+SELECT
+ 'md_unit_measure',
+ (SELECT COUNT(*) FROM md_unit_measure WHERE deleted = 0)
+UNION ALL
+SELECT
+ 'md_brand',
+ (SELECT COUNT(*) FROM md_brand WHERE deleted = 0)
+UNION ALL
+SELECT
+ 'md_warehouse',
+ (SELECT COUNT(*) FROM md_warehouse WHERE deleted = 0)
+UNION ALL
+SELECT
+ 'wms_stock',
+ (SELECT COUNT(*) FROM wms_stock WHERE deleted = 0)
+UNION ALL
+SELECT
+ 'md_item_migration_map',
+ (SELECT COUNT(*) FROM md_item_migration_map);
diff --git a/sql/mdm_tables.sql b/sql/mdm_tables.sql
new file mode 100644
index 0000000..6efdc42
--- /dev/null
+++ b/sql/mdm_tables.sql
@@ -0,0 +1,334 @@
+-- =============================================
+-- 缁熶竴鐗╂枡涓绘暟鎹ā鍧� (MDM) 寤鸿〃 SQL
+-- 鍒涘缓鏃ユ湡: 2026-06-29
+-- 璇存槑: 鏁村悎 MES/ERP/WMS 鐨勭墿鏂欐暟鎹紝瀹炵幇璺ㄦā鍧楁暟鎹墦閫�
+-- 娉ㄦ剰: 涓嶅紑鍚绉熸埛妯″紡
+-- =============================================
+
+-- ---------------------------------------------
+-- 1. 鐗╂枡涓绘暟鎹〃 (md_item)
+-- 鏍稿績琛紝鎵�鏈夋ā鍧楀叡鐢ㄥ敮涓�浜у搧缂栫爜
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_item`;
+CREATE TABLE `md_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐗╂枡ID锛堝叏灞�鍞竴锛�',
+ `code` varchar(64) NOT NULL COMMENT '鐗╂枡缂栫爜锛堝叏灞�鍞竴锛�',
+ `name` varchar(255) NOT NULL COMMENT '鐗╂枡鍚嶇О',
+ `bar_code` varchar(64) DEFAULT NULL COMMENT '鐗╂枡鏉$爜',
+ `specification` varchar(500) DEFAULT NULL COMMENT '瑙勬牸鍨嬪彿',
+
+ -- 鍒嗙被涓庡崟浣�
+ `category_id` bigint NOT NULL COMMENT '鐗╂枡鍒嗙被缂栧彿',
+ `unit_measure_id` bigint NOT NULL COMMENT '涓昏閲忓崟浣嶇紪鍙�',
+ `brand_id` bigint DEFAULT NULL COMMENT '鍝佺墝缂栧彿',
+
+ -- 鐗╂枡绫诲瀷
+ `item_type` tinyint NOT NULL DEFAULT 1 COMMENT '鐗╂枡绫诲瀷(1:鍘熸枡, 2:鍗婃垚鍝�, 3:鎴愬搧, 4:杈呮枡)',
+
+ -- 鐘舵��
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��(0:鍚敤, 1:绂佺敤)',
+
+ -- 浠锋牸淇℃伅
+ `purchase_price` decimal(24,6) DEFAULT NULL COMMENT '閲囪喘浠�',
+ `sales_price` decimal(24,6) DEFAULT NULL COMMENT '閿�鍞环',
+ `cost_price` decimal(24,6) DEFAULT NULL COMMENT '鎴愭湰浠�',
+ `min_price` decimal(24,6) DEFAULT NULL COMMENT '鏈�浣庝环',
+
+ -- 搴撳瓨绠$悊
+ `safety_stock` decimal(24,6) DEFAULT NULL COMMENT '瀹夊叏搴撳瓨',
+ `min_stock` decimal(24,6) DEFAULT NULL COMMENT '鏈�浣庡簱瀛橀噺',
+ `max_stock` decimal(24,6) DEFAULT NULL COMMENT '鏈�楂樺簱瀛橀噺',
+ `min_order_qty` decimal(24,6) DEFAULT NULL COMMENT '鏈�灏忚璐ч噺',
+ `lead_time` int DEFAULT NULL COMMENT '閲囪喘鎻愬墠鏈燂紙澶╋級',
+
+ -- 鎵规涓庡簭鍒楀彿绠$悊
+ `is_batch_managed` bit(1) DEFAULT b'0' COMMENT '鏄惁鎵规绠$悊',
+ `is_serial_managed` bit(1) DEFAULT b'0' COMMENT '鏄惁搴忓垪鍙风鐞�',
+ `barcode_rule_id` bigint DEFAULT NULL COMMENT '鏉$爜瑙勫垯ID',
+
+ -- 璐ㄩ噺绠$悊
+ `expiry_day` int DEFAULT NULL COMMENT '淇濊川鏈熷ぉ鏁�',
+ `high_value` bit(1) DEFAULT b'0' COMMENT '鏄惁楂樺�肩墿鏂�',
+
+ -- 鐗╃悊灞炴��
+ `weight` decimal(24,6) DEFAULT NULL COMMENT '閲嶉噺(kg)',
+
+ -- 澶囨敞
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_code` (`code`),
+ KEY `idx_category_id` (`category_id`),
+ KEY `idx_unit_measure_id` (`unit_measure_id`),
+ KEY `idx_brand_id` (`brand_id`),
+ KEY `idx_item_type` (`item_type`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鐗╂枡涓绘暟鎹〃';
+
+-- ---------------------------------------------
+-- 2. 鐗╂枡鍒嗙被琛� (md_item_category)
+-- 缁熶竴鍒嗙被锛屾敮鎸佹爲褰㈢粨鏋�
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_item_category`;
+CREATE TABLE `md_item_category` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍒嗙被缂栧彿',
+ `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '鐖跺垎绫荤紪鍙�(鏍硅妭鐐�=0)',
+ `code` varchar(64) NOT NULL COMMENT '鍒嗙被缂栫爜',
+ `name` varchar(255) NOT NULL COMMENT '鍒嗙被鍚嶇О',
+ `item_type` tinyint DEFAULT NULL COMMENT '閫傜敤鐗╂枡绫诲瀷(1:鍘熸枡, 2:鍗婃垚鍝�, 3:鎴愬搧, 4:杈呮枡, NULL:鍏ㄩ儴)',
+ `sort` int NOT NULL DEFAULT 0 COMMENT '鎺掑簭',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��(0:鍚敤, 1:绂佺敤)',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_code` (`code`),
+ KEY `idx_parent_id` (`parent_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鐗╂枡鍒嗙被琛�';
+
+-- ---------------------------------------------
+-- 3. 璁¢噺鍗曚綅琛� (md_unit_measure)
+-- 缁熶竴璁¢噺鍗曚綅
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_unit_measure`;
+CREATE TABLE `md_unit_measure` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍗曚綅缂栧彿',
+ `code` varchar(64) NOT NULL COMMENT '鍗曚綅缂栫爜',
+ `name` varchar(64) NOT NULL COMMENT '鍗曚綅鍚嶇О',
+ `primary_flag` bit(1) DEFAULT b'0' COMMENT '鏄惁涓诲崟浣�',
+ `primary_id` bigint DEFAULT NULL COMMENT '涓诲崟浣嶇紪鍙�',
+ `change_rate` decimal(24,6) DEFAULT NULL COMMENT '涓庝富鍗曚綅鎹㈢畻姣斾緥',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��(0:鍚敤, 1:绂佺敤)',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='璁¢噺鍗曚綅琛�';
+
+-- ---------------------------------------------
+-- 4. 鍝佺墝琛� (md_brand)
+-- 缁熶竴鍝佺墝鏁版嵁
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_brand`;
+CREATE TABLE `md_brand` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍝佺墝缂栧彿',
+ `code` varchar(64) NOT NULL COMMENT '鍝佺墝缂栫爜',
+ `name` varchar(255) NOT NULL COMMENT '鍝佺墝鍚嶇О',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��(0:鍚敤, 1:绂佺敤)',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍝佺墝琛�';
+
+-- ---------------------------------------------
+-- 5. 鐗╂枡鎵规灞炴�ч厤缃〃 (md_item_batch_config)
+-- 鏉ヨ嚜 MES 鐨勬壒娆$鐞嗛厤缃�
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_item_batch_config`;
+CREATE TABLE `md_item_batch_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `item_id` bigint NOT NULL COMMENT '鐗╂枡缂栧彿',
+
+ -- 鎵规灞炴�у紑鍏�
+ `produce_date_flag` bit(1) DEFAULT b'0' COMMENT '鐢熶骇鏃ユ湡',
+ `expire_date_flag` bit(1) DEFAULT b'0' COMMENT '鏈夋晥鏈�',
+ `receipt_date_flag` bit(1) DEFAULT b'0' COMMENT '鍏ュ簱鏃ユ湡',
+ `vendor_flag` bit(1) DEFAULT b'0' COMMENT '渚涘簲鍟�',
+ `client_flag` bit(1) DEFAULT b'0' COMMENT '瀹㈡埛',
+ `sales_order_code_flag` bit(1) DEFAULT b'0' COMMENT '閿�鍞鍗曠紪鍙�',
+ `purchase_order_code_flag` bit(1) DEFAULT b'0' COMMENT '閲囪喘璁㈠崟缂栧彿',
+ `work_order_flag` bit(1) DEFAULT b'0' COMMENT '鐢熶骇宸ュ崟',
+ `task_flag` bit(1) DEFAULT b'0' COMMENT '鐢熶骇浠诲姟',
+ `workstation_flag` bit(1) DEFAULT b'0' COMMENT '宸ヤ綔绔�',
+ `tool_flag` bit(1) DEFAULT b'0' COMMENT '宸ュ叿',
+ `mold_flag` bit(1) DEFAULT b'0' COMMENT '妯″叿',
+ `lot_number_flag` bit(1) DEFAULT b'0' COMMENT '鐢熶骇鎵瑰彿',
+ `quality_status_flag` bit(1) DEFAULT b'0' COMMENT '璐ㄩ噺鐘舵��',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_item_id` (`item_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鐗╂枡鎵规灞炴�ч厤缃〃';
+
+-- ---------------------------------------------
+-- 6. 浠撳簱涓绘暟鎹〃 (md_warehouse)
+-- 缁熶竴浠撳簱鏁版嵁锛學MS 涓轰富鏁版嵁婧�
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_warehouse`;
+CREATE TABLE `md_warehouse` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '浠撳簱ID',
+ `code` varchar(64) NOT NULL COMMENT '浠撳簱缂栫爜',
+ `name` varchar(255) NOT NULL COMMENT '浠撳簱鍚嶇О',
+ `address` varchar(500) DEFAULT NULL COMMENT '浠撳簱鍦板潃',
+ `area` decimal(24,6) DEFAULT NULL COMMENT '闈㈢Н(骞虫柟绫�)',
+ `type` tinyint DEFAULT NULL COMMENT '浠撳簱绫诲瀷(1:鍘熸枡浠�, 2:鎴愬搧浠�, 3:鍗婃垚鍝佷粨, 4:绾胯竟浠�)',
+ `charge_user_id` bigint DEFAULT NULL COMMENT '璐熻矗浜虹敤鎴稩D',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��(0:鍚敤, 1:绂佺敤)',
+ `frozen` bit(1) DEFAULT b'0' COMMENT '鏄惁鍐荤粨',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='浠撳簱涓绘暟鎹〃';
+
+-- ---------------------------------------------
+-- 7. 搴撳瓨琛� (wms_stock)
+-- WMS 浣滀负鍞竴搴撳瓨绠$悊鑰�
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `wms_stock`;
+CREATE TABLE `wms_stock` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '搴撳瓨ID',
+ `item_id` bigint NOT NULL COMMENT '鐗╂枡ID锛堝叧鑱� md_item锛�',
+ `warehouse_id` bigint NOT NULL COMMENT '浠撳簱ID锛堝叧鑱� md_warehouse锛�',
+ `location_id` bigint DEFAULT NULL COMMENT '搴撲綅ID',
+ `batch_code` varchar(64) DEFAULT NULL COMMENT '鎵规鍙�',
+
+ -- 搴撳瓨鏁伴噺
+ `quantity` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '褰撳墠搴撳瓨鏁伴噺',
+ `quantity_locked` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '閿佸畾鏁伴噺',
+ `quantity_available` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '鍙敤鏁伴噺 = quantity - quantity_locked',
+
+ `unit_measure_id` bigint NOT NULL COMMENT '璁¢噺鍗曚綅ID',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_item_warehouse_batch` (`item_id`, `warehouse_id`, `batch_code`),
+ KEY `idx_item_id` (`item_id`),
+ KEY `idx_warehouse_id` (`warehouse_id`),
+ KEY `idx_batch_code` (`batch_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='搴撳瓨琛�';
+
+-- ---------------------------------------------
+-- 8. 搴撳瓨浜嬪姟娴佹按琛� (wms_stock_transaction)
+-- 璁板綍鎵�鏈夊簱瀛樺彉鍔�
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `wms_stock_transaction`;
+CREATE TABLE `wms_stock_transaction` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '娴佹按ID',
+ `item_id` bigint NOT NULL COMMENT '鐗╂枡ID',
+ `warehouse_id` bigint NOT NULL COMMENT '浠撳簱ID',
+ `location_id` bigint DEFAULT NULL COMMENT '搴撲綅ID',
+ `batch_code` varchar(64) DEFAULT NULL COMMENT '鎵规鍙�',
+
+ -- 鍙樺姩淇℃伅
+ `quantity` decimal(24,6) NOT NULL COMMENT '鍙樺姩鏁伴噺(姝f暟鍏ュ簱,璐熸暟鍑哄簱)',
+ `biz_type` tinyint NOT NULL COMMENT '涓氬姟绫诲瀷(瑙佹灇涓�)',
+ `biz_id` bigint DEFAULT NULL COMMENT '涓氬姟鍗曟嵁ID',
+ `biz_no` varchar(64) DEFAULT NULL COMMENT '涓氬姟鍗曟嵁鍙�',
+
+ -- 鍏宠仈搴撳瓨璁板綍
+ `stock_id` bigint DEFAULT NULL COMMENT '鍏宠仈搴撳瓨璁板綍ID',
+
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ -- 绯荤粺瀛楁
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+
+ PRIMARY KEY (`id`),
+ KEY `idx_item_id` (`item_id`),
+ KEY `idx_warehouse_id` (`warehouse_id`),
+ KEY `idx_biz_type` (`biz_type`),
+ KEY `idx_biz_id` (`biz_id`),
+ KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='搴撳瓨浜嬪姟娴佹按琛�';
+
+-- ---------------------------------------------
+-- 9. 鐗╂枡杩佺Щ鏄犲皠琛� (md_item_migration_map)
+-- 鐢ㄤ簬璁板綍鏃ц〃鏁版嵁鍒版柊琛ㄧ殑鏄犲皠鍏崇郴
+-- ---------------------------------------------
+DROP TABLE IF EXISTS `md_item_migration_map`;
+CREATE TABLE `md_item_migration_map` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鏄犲皠ID',
+ `source_module` varchar(20) NOT NULL COMMENT '鏉ユ簮妯″潡(ERP/MES/WMS)',
+ `source_table` varchar(64) NOT NULL COMMENT '鏉ユ簮琛ㄥ悕',
+ `source_id` bigint NOT NULL COMMENT '鍘烮D',
+ `target_item_id` bigint NOT NULL COMMENT 'MDM鐗╂枡ID',
+
+ -- 绯荤粺瀛楁
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_source` (`source_module`, `source_table`, `source_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鐗╂枡杩佺Щ鏄犲皠琛�';
+
+-- ---------------------------------------------
+-- 10. 搴撳瓨鍙樺姩涓氬姟绫诲瀷鏋氫妇鍊�
+-- ---------------------------------------------
+-- 涓氬姟绫诲瀷璇存槑锛�
+-- 10: 閲囪喘鍏ュ簱 (ERP)
+-- 11: 閲囪喘鍏ュ簱浣滃簾 (ERP)
+-- 20: 閿�鍞嚭搴� (ERP)
+-- 21: 閿�鍞嚭搴撲綔搴� (ERP)
+-- 30: 鐢熶骇鍏ュ簱 (MES)
+-- 31: 鐢熶骇鍏ュ簱浣滃簾 (MES)
+-- 40: 鐢熶骇棰嗘枡 (MES)
+-- 41: 鐢熶骇棰嗘枡浣滃簾 (MES)
+-- 50: 鐢熶骇閫�鏂� (MES)
+-- 51: 鐢熶骇閫�鏂欎綔搴� (MES)
+-- 60: 閲囪喘閫�璐у嚭搴� (ERP)
+-- 61: 閲囪喘閫�璐у嚭搴撲綔搴� (ERP)
+-- 70: 閿�鍞��璐у叆搴� (ERP)
+-- 71: 閿�鍞��璐у叆搴撲綔搴� (ERP)
+-- 80: 璋冩嫧鍏ュ簱 (WMS)
+-- 81: 璋冩嫧鍏ュ簱浣滃簾 (WMS)
+-- 90: 璋冩嫧鍑哄簱 (WMS)
+-- 91: 璋冩嫧鍑哄簱浣滃簾 (WMS)
+-- 100: 鐩樼泩鍏ュ簱 (WMS)
+-- 101: 鐩樼泩鍏ュ簱浣滃簾 (WMS)
+-- 110: 鐩樹簭鍑哄簱 (WMS)
+-- 111: 鐩樹簭鍑哄簱浣滃簾 (WMS)
+
+-- =============================================
+-- 瀹屾垚寤鸿〃 SQL
+-- =============================================
\ No newline at end of file
diff --git a/yudao-module-erp/pom.xml b/yudao-module-erp/pom.xml
index ce377ff..30f01bf 100644
--- a/yudao-module-erp/pom.xml
+++ b/yudao-module-erp/pom.xml
@@ -24,6 +24,27 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞嗘ā鍧� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- WMS 浠撳簱绠$悊妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-wms</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- BPM 娴佺▼妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-bpm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java
index de4f814..ffe7b2f 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java
@@ -20,6 +20,9 @@
@Schema(description = "浜у搧鍒嗙被缂栧彿", example = "11161")
private Long categoryId;
+ @Schema(description = "浜у搧鐘舵��", example = "0")
+ private Integer status;
+
@Schema(description = "鍒涘缓鏃堕棿")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java
index 3a63540..8d5168c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java
@@ -41,6 +41,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "绠$悊鍚庡彴 - ERP 閲囪喘鍏ュ簱")
@RestController
@@ -84,6 +85,15 @@
return success(true);
}
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦閲囪喘鍏ュ簱瀹℃壒")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-in:update')")
+ public CommonResult<Boolean> submitPurchaseIn(@RequestParam("id") Long id) {
+ purchaseInService.submitPurchaseIn(id, getLoginUserId());
+ return success(true);
+ }
+
@DeleteMapping("/delete")
@Operation(summary = "鍒犻櫎閲囪喘鍏ュ簱")
@Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
index a560906..5ced436 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -40,6 +40,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "绠$悊鍚庡彴 - ERP 閲囪喘璁㈠崟")
@RestController
@@ -83,6 +84,15 @@
return success(true);
}
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦閲囪喘璁㈠崟瀹℃壒")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-order:update')")
+ public CommonResult<Boolean> submitPurchaseOrder(@RequestParam("id") Long id) {
+ purchaseOrderService.submitPurchaseOrder(id, getLoginUserId());
+ return success(true);
+ }
+
@DeleteMapping("/delete")
@Operation(summary = "鍒犻櫎閲囪喘璁㈠崟")
@Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
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 b0ec991..274fac3 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
@@ -40,6 +40,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "绠$悊鍚庡彴 - ERP 閿�鍞鍗�")
@RestController
@@ -83,6 +84,15 @@
return success(true);
}
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦閿�鍞鍗曞鎵�")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('erp:sale-out:update')")
+ public CommonResult<Boolean> submitSaleOrder(@RequestParam("id") Long id) {
+ saleOrderService.submitSaleOrder(id, getLoginUserId());
+ return success(true);
+ }
+
@DeleteMapping("/delete")
@Operation(summary = "鍒犻櫎閿�鍞鍗�")
@Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java
index f67398b..cb45f2e 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java
@@ -41,6 +41,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "绠$悊鍚庡彴 - ERP 閿�鍞嚭搴�")
@RestController
@@ -84,6 +85,15 @@
return success(true);
}
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦閿�鍞嚭搴撳鎵�")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('erp:sale-out:update')")
+ public CommonResult<Boolean> submitSaleOut(@RequestParam("id") Long id) {
+ saleOutService.submitSaleOut(id, getLoginUserId());
+ return success(true);
+ }
+
@DeleteMapping("/delete")
@Operation(summary = "鍒犻櫎閿�鍞嚭搴�")
@Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java
index b8186f5..5849257 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java
@@ -119,4 +119,10 @@
*/
private String remark;
+ // ========== BPM 娴佺▼瀹℃壒 ==========
+ /**
+ * BPM 娴佺▼瀹炰緥缂栧彿
+ */
+ private String processInstanceId;
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
index bba1542..7b28cad 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
@@ -100,6 +100,12 @@
*/
private String remark;
+ // ========== BPM 娴佺▼瀹℃壒 ==========
+ /**
+ * BPM 娴佺▼瀹炰緥缂栧彿
+ */
+ private String processInstanceId;
+
// ========== 閲囪喘鍏ュ簱 ==========
/**
* 閲囪喘鍏ュ簱鏁伴噺
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
index 5cdd434..8a9be24 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
@@ -106,6 +106,12 @@
*/
private String remark;
+ // ========== BPM 娴佺▼瀹℃壒 ==========
+ /**
+ * BPM 娴佺▼瀹炰緥缂栧彿
+ */
+ private String processInstanceId;
+
// ========== 閿�鍞嚭搴� ==========
/**
* 閿�鍞嚭搴撴暟閲�
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java
index 65b9d94..9149f77 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java
@@ -125,4 +125,10 @@
*/
private String remark;
+ // ========== BPM 娴佺▼瀹℃壒 ==========
+ /**
+ * BPM 娴佺▼瀹炰緥缂栧彿
+ */
+ private String processInstanceId;
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
index 42c0174..f19f26c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
@@ -17,8 +17,10 @@
@Getter
public enum ErpAuditStatus implements ArrayValuable<Integer> {
- PROCESS(10, "鏈鏍�"), // 瀹℃牳涓�
- APPROVE(20, "宸插鏍�"); // 瀹℃牳閫氳繃
+ DRAFT(0, "鑽夌"), // 鑽夌鐘舵�侊紝鏈彁浜ゅ鎵�
+ PROCESS(10, "瀹℃壒涓�"), // 瀹℃壒涓�
+ APPROVE(20, "瀹℃壒閫氳繃"), // 瀹℃壒閫氳繃
+ REJECT(30, "瀹℃壒涓嶉�氳繃"); // 瀹℃壒涓嶉�氳繃
public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpAuditStatus::getStatus).toArray(Integer[]::new);
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
index 65f64c2..d8c825a 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -25,6 +25,8 @@
ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_IN = new ErrorCode(1_030_101_008, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑閲囪喘鍏ュ簱鍗�");
ErrorCode PURCHASE_ORDER_ITEM_RETURN_FAIL_IN_EXCEED = new ErrorCode(1_030_101_009, "閲囪喘璁㈠崟椤�({})瓒呰繃鏈�澶у厑璁搁��璐ф暟閲�({})");
ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_030_101_010, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑閲囪喘閫�璐у崟");
+ ErrorCode PURCHASE_ORDER_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_101_011, "鎻愪氦瀹℃壒澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑閲囪喘璁㈠崟鎵嶈兘鎻愪氦瀹℃壒");
+ ErrorCode PURCHASE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_101_012, "鏇存柊瀹℃壒鐘舵�佸け璐ワ紝鍙湁瀹℃壒涓殑閲囪喘璁㈠崟鎵嶈兘鏇存柊瀹℃壒鐘舵��");
// ========== ERP 閲囪喘鍏ュ簱锛�1-030-102-000锛� ==========
ErrorCode PURCHASE_IN_NOT_EXISTS = new ErrorCode(1_030_102_000, "閲囪喘鍏ュ簱鍗曚笉瀛樺湪");
@@ -36,6 +38,8 @@
ErrorCode PURCHASE_IN_NOT_APPROVE = new ErrorCode(1_030_102_006, "閲囪喘鍏ュ簱鍗曟湭瀹℃牳锛屾棤娉曟搷浣�");
ErrorCode PURCHASE_IN_FAIL_PAYMENT_PRICE_EXCEED = new ErrorCode(1_030_102_007, "浠樻閲戦({})瓒呰繃閲囪喘鍏ュ簱鍗曟�婚噾棰�({})");
ErrorCode PURCHASE_IN_PROCESS_FAIL_EXISTS_PAYMENT = new ErrorCode(1_030_102_008, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑浠樻鍗�");
+ ErrorCode PURCHASE_IN_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_102_009, "鎻愪氦瀹℃壒澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑閲囪喘鍏ュ簱鍗曟墠鑳芥彁浜ゅ鎵�");
+ ErrorCode PURCHASE_IN_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_102_010, "鏇存柊瀹℃壒鐘舵�佸け璐ワ紝鍙湁瀹℃壒涓殑閲囪喘鍏ュ簱鍗曟墠鑳芥洿鏂板鎵圭姸鎬�");
// ========== ERP 閲囪喘閫�璐э紙1-030-103-000锛� ==========
ErrorCode PURCHASE_RETURN_NOT_EXISTS = new ErrorCode(1_030_103_000, "閲囪喘閫�璐у崟涓嶅瓨鍦�");
@@ -64,6 +68,8 @@
ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_OUT = new ErrorCode(1_020_201_008, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑閿�鍞嚭搴撳崟");
ErrorCode SALE_ORDER_ITEM_RETURN_FAIL_OUT_EXCEED = new ErrorCode(1_020_201_009, "閿�鍞鍗曢」({})瓒呰繃鏈�澶у厑璁搁��璐ф暟閲�({})");
ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_020_201_010, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑閿�鍞��璐у崟");
+ ErrorCode SALE_ORDER_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_201_011, "鎻愪氦瀹℃壒澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑閿�鍞鍗曟墠鑳芥彁浜ゅ鎵�");
+ ErrorCode SALE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_201_012, "鏇存柊瀹℃壒鐘舵�佸け璐ワ紝鍙湁瀹℃壒涓殑閿�鍞鍗曟墠鑳芥洿鏂板鎵圭姸鎬�");
// ========== ERP 閿�鍞嚭搴擄紙1-030-202-000锛� ==========
ErrorCode SALE_OUT_NOT_EXISTS = new ErrorCode(1_020_202_000, "閿�鍞嚭搴撳崟涓嶅瓨鍦�");
@@ -75,6 +81,8 @@
ErrorCode SALE_OUT_NOT_APPROVE = new ErrorCode(1_020_202_006, "閿�鍞嚭搴撳崟鏈鏍革紝鏃犳硶鎿嶄綔");
ErrorCode SALE_OUT_FAIL_RECEIPT_PRICE_EXCEED = new ErrorCode(1_020_202_007, "鏀舵閲戦({})瓒呰繃閿�鍞嚭搴撳崟鎬婚噾棰�({})");
ErrorCode SALE_OUT_PROCESS_FAIL_EXISTS_RECEIPT = new ErrorCode(1_020_202_008, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑鏀舵鍗�");
+ ErrorCode SALE_OUT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_202_009, "鎻愪氦瀹℃壒澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑閿�鍞嚭搴撳崟鎵嶈兘鎻愪氦瀹℃壒");
+ ErrorCode SALE_OUT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_202_010, "鏇存柊瀹℃壒鐘舵�佸け璐ワ紝鍙湁瀹℃壒涓殑閿�鍞嚭搴撳崟鎵嶈兘鏇存柊瀹℃壒鐘舵��");
// ========== ERP 閿�鍞��璐э紙1-030-203-000锛� ==========
ErrorCode SALE_RETURN_NOT_EXISTS = new ErrorCode(1_020_203_000, "閿�鍞��璐у崟涓嶅瓨鍦�");
@@ -130,6 +138,7 @@
// ========== ERP 浜у搧 1-030-500-000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "浜у搧涓嶅瓨鍦�");
ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_030_500_001, "浜у搧({})鏈惎鐢�");
+ ErrorCode PRODUCT_BAR_CODE_EXISTS = new ErrorCode(1_030_500_002, "浜у搧鏉$爜宸插瓨鍦�");
// ========== ERP 浜у搧鍒嗙被 1-030-501-000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_501_000, "浜у搧鍒嗙被涓嶅瓨鍦�");
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java
index 88599a8..cf7cb5c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java
@@ -8,10 +8,15 @@
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO;
-import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
-import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
-import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemUpdateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.category.MdmItemCategoryApi;
+import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategoryRespDTO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -25,46 +30,57 @@
/**
* ERP 浜у搧 Service 瀹炵幇绫�
*
+ * 鏀归�犺鏄庯細ERP 浜у搧鏁版嵁鏉ユ簮浜� MDM 鐗╂枡涓绘暟鎹�
+ * - 鎵�鏈変骇鍝佹搷浣滃潎璋冪敤 MDM API
+ * - ERP 浜у搧瀵瑰簲 MDM 鐗╂枡绫诲瀷 = 鎴愬搧锛坕temType = 2锛�
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@Validated
public class ErpProductServiceImpl implements ErpProductService {
- @Resource
- private ErpProductMapper productMapper;
+ /** ERP 浜у搧瀵瑰簲鐨� MDM 鐗╂枡绫诲瀷锛氭垚鍝� */
+ private static final int MDM_ITEM_TYPE_PRODUCT = 2;
@Resource
- private ErpProductCategoryService productCategoryService;
+ private MdmItemApi mdmItemApi;
+
@Resource
- private ErpProductUnitService productUnitService;
+ private MdmItemCategoryApi mdmItemCategoryApi;
+
+ @Resource
+ private MdmUnitMeasureApi mdmUnitMeasureApi;
@Override
public Long createProduct(ProductSaveReqVO createReqVO) {
- // TODO 鑺嬭壙锛氭牎楠屽垎绫�
- // 鎻掑叆
- ErpProductDO product = BeanUtils.toBean(createReqVO, ErpProductDO.class);
- productMapper.insert(product);
- // 杩斿洖
- return product.getId();
+ // 1. 鏍¢獙鏉$爜鍞竴
+ validateProductBarCodeUnique(null, createReqVO.getBarCode());
+
+ // 2. 鏋勫缓鍒涘缓璇锋眰骞惰皟鐢� MDM API
+ MdmItemCreateReqDTO createReqDTO = buildCreateReqDTO(createReqVO);
+ return mdmItemApi.createItem(createReqDTO).getCheckedData();
}
@Override
public void updateProduct(ProductSaveReqVO updateReqVO) {
- // TODO 鑺嬭壙锛氭牎楠屽垎绫�
- // 鏍¢獙瀛樺湪
+ // 1. 鏍¢獙瀛樺湪
validateProductExists(updateReqVO.getId());
- // 鏇存柊
- ErpProductDO updateObj = BeanUtils.toBean(updateReqVO, ErpProductDO.class);
- productMapper.updateById(updateObj);
+
+ // 2. 鏍¢獙鏉$爜鍞竴
+ validateProductBarCodeUnique(updateReqVO.getId(), updateReqVO.getBarCode());
+
+ // 3. 鏋勫缓鏇存柊璇锋眰骞惰皟鐢� MDM API
+ MdmItemUpdateReqDTO updateReqDTO = buildUpdateReqDTO(updateReqVO);
+ mdmItemApi.updateItem(updateReqDTO);
}
@Override
public void deleteProduct(Long id) {
// 鏍¢獙瀛樺湪
validateProductExists(id);
- // 鍒犻櫎
- productMapper.deleteById(id);
+ // ERP 浜у搧鍒犻櫎瀹為檯涓虹鐢紙MDM 鐗╂枡涓嶈兘鐗╃悊鍒犻櫎锛�
+ mdmItemApi.updateItemStatus(id, CommonStatusEnum.DISABLE.getStatus());
}
@Override
@@ -72,35 +88,36 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- List<ErpProductDO> list = productMapper.selectByIds(ids);
- Map<Long, ErpProductDO> productMap = convertMap(list, ErpProductDO::getId);
+
+ // 1. 璋冪敤 MDM API 鑾峰彇鐗╂枡鍒楄〃
+ List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
+ Map<Long, MdmItemRespDTO> itemMap = convertMap(itemList, MdmItemRespDTO::getId);
+
+ // 2. 鏍¢獙姣忎釜浜у搧
for (Long id : ids) {
- ErpProductDO product = productMap.get(id);
- if (productMap.get(id) == null) {
+ MdmItemRespDTO item = itemMap.get(id);
+ if (item == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
- if (CommonStatusEnum.isDisable(product.getStatus())) {
- throw exception(PRODUCT_NOT_ENABLE, product.getName());
+ if (CommonStatusEnum.isDisable(item.getStatus())) {
+ throw exception(PRODUCT_NOT_ENABLE, item.getName());
}
}
- return list;
- }
- private void validateProductExists(Long id) {
- if (productMapper.selectById(id) == null) {
- throw exception(PRODUCT_NOT_EXISTS);
- }
+ // 3. 杞崲涓� ERP Product DO锛堟墜鍔ㄦ槧灏勫瓧娈碉級
+ return convertToErpProductDOList(itemList);
}
@Override
public ErpProductDO getProduct(Long id) {
- return productMapper.selectById(id);
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ return convertToErpProductDO(item);
}
@Override
public List<ErpProductRespVO> getProductVOListByStatus(Integer status) {
- List<ErpProductDO> list = productMapper.selectListByStatus(status);
- return buildProductVOList(list);
+ List<MdmItemRespDTO> itemList = mdmItemApi.getItemListByStatus(status).getCheckedData();
+ return buildProductVOList(itemList);
}
@Override
@@ -108,40 +125,188 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- List<ErpProductDO> list = productMapper.selectByIds(ids);
- return buildProductVOList(list);
+ List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
+ return buildProductVOList(itemList);
}
@Override
public PageResult<ErpProductRespVO> getProductVOPage(ErpProductPageReqVO pageReqVO) {
- PageResult<ErpProductDO> pageResult = productMapper.selectPage(pageReqVO);
- return new PageResult<>(buildProductVOList(pageResult.getList()), pageResult.getTotal());
- }
+ // 璋冪敤 MDM API 鍒嗛〉鏌ヨ
+ PageResult<MdmItemRespDTO> mdmPageResult = mdmItemApi.getItemPage(
+ MDM_ITEM_TYPE_PRODUCT,
+ pageReqVO.getStatus(),
+ pageReqVO.getPageNo(),
+ pageReqVO.getPageSize()
+ ).getCheckedData();
- private List<ErpProductRespVO> buildProductVOList(List<ErpProductDO> list) {
- if (CollUtil.isEmpty(list)) {
- return Collections.emptyList();
- }
- Map<Long, ErpProductCategoryDO> categoryMap = productCategoryService.getProductCategoryMap(
- convertSet(list, ErpProductDO::getCategoryId));
- Map<Long, ErpProductUnitDO> unitMap = productUnitService.getProductUnitMap(
- convertSet(list, ErpProductDO::getUnitId));
- return BeanUtils.toBean(list, ErpProductRespVO.class, product -> {
- MapUtils.findAndThen(categoryMap, product.getCategoryId(),
- category -> product.setCategoryName(category.getName()));
- MapUtils.findAndThen(unitMap, product.getUnitId(),
- unit -> product.setUnitName(unit.getName()));
- });
+ List<ErpProductRespVO> productVOList = buildProductVOList(mdmPageResult.getList());
+ return new PageResult<>(productVOList, mdmPageResult.getTotal());
}
@Override
public Long getProductCountByCategoryId(Long categoryId) {
- return productMapper.selectCountByCategoryId(categoryId);
+ return mdmItemApi.getItemCountByCategoryId(categoryId).getCheckedData();
}
@Override
public Long getProductCountByUnitId(Long unitId) {
- return productMapper.selectCountByUnitId(unitId);
+ return mdmItemApi.getItemCountByUnitMeasureId(unitId).getCheckedData();
}
-}
+ // ==================== 绉佹湁鏂规硶 ====================
+
+ /**
+ * 鏍¢獙浜у搧瀛樺湪
+ */
+ private void validateProductExists(Long id) {
+ mdmItemApi.validateItemExists(id).getCheckedData();
+ }
+
+ /**
+ * 鏍¢獙浜у搧鏉$爜鍞竴
+ */
+ private void validateProductBarCodeUnique(Long id, String barCode) {
+ if (barCode == null) {
+ return;
+ }
+ MdmItemRespDTO existItem = mdmItemApi.getItemByBarCode(barCode).getCheckedData();
+ if (existItem != null && !existItem.getId().equals(id)) {
+ throw exception(PRODUCT_BAR_CODE_EXISTS);
+ }
+ }
+
+ /**
+ * 鏋勫缓鍒涘缓璇锋眰 DTO
+ * 娉ㄦ剰锛歅roductSaveReqVO 浣跨敤 standard 瀛楁浣滀负瑙勬牸锛孧DM 浣跨敤 specification
+ */
+ private MdmItemCreateReqDTO buildCreateReqDTO(ProductSaveReqVO saveReqVO) {
+ MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
+ createReqDTO.setCode(saveReqVO.getBarCode()); // 浣跨敤鏉$爜浣滀负缂栫爜
+ createReqDTO.setName(saveReqVO.getName());
+ createReqDTO.setBarCode(saveReqVO.getBarCode());
+ createReqDTO.setSpecification(saveReqVO.getStandard()); // standard -> specification
+ createReqDTO.setCategoryId(saveReqVO.getCategoryId());
+ createReqDTO.setUnitMeasureId(saveReqVO.getUnitId()); // unitId -> unitMeasureId
+ createReqDTO.setItemType(MDM_ITEM_TYPE_PRODUCT);
+ createReqDTO.setStatus(saveReqVO.getStatus() != null ? saveReqVO.getStatus() : CommonStatusEnum.ENABLE.getStatus());
+ createReqDTO.setPurchasePrice(saveReqVO.getPurchasePrice());
+ createReqDTO.setSalesPrice(saveReqVO.getSalePrice());
+ createReqDTO.setCostPrice(saveReqVO.getMinPrice()); // minPrice -> costPrice
+ createReqDTO.setExpiryDay(saveReqVO.getExpiryDay());
+ createReqDTO.setWeight(saveReqVO.getWeight());
+ createReqDTO.setRemark(saveReqVO.getRemark());
+ return createReqDTO;
+ }
+
+ /**
+ * 鏋勫缓鏇存柊璇锋眰 DTO
+ */
+ private MdmItemUpdateReqDTO buildUpdateReqDTO(ProductSaveReqVO saveReqVO) {
+ MdmItemUpdateReqDTO updateReqDTO = new MdmItemUpdateReqDTO();
+ updateReqDTO.setId(saveReqVO.getId());
+ updateReqDTO.setCode(saveReqVO.getBarCode());
+ updateReqDTO.setName(saveReqVO.getName());
+ updateReqDTO.setBarCode(saveReqVO.getBarCode());
+ updateReqDTO.setSpecification(saveReqVO.getStandard()); // standard -> specification
+ updateReqDTO.setCategoryId(saveReqVO.getCategoryId());
+ updateReqDTO.setUnitMeasureId(saveReqVO.getUnitId()); // unitId -> unitMeasureId
+ updateReqDTO.setStatus(saveReqVO.getStatus());
+ updateReqDTO.setPurchasePrice(saveReqVO.getPurchasePrice());
+ updateReqDTO.setSalesPrice(saveReqVO.getSalePrice());
+ updateReqDTO.setCostPrice(saveReqVO.getMinPrice());
+ updateReqDTO.setExpiryDay(saveReqVO.getExpiryDay());
+ updateReqDTO.setWeight(saveReqVO.getWeight());
+ updateReqDTO.setRemark(saveReqVO.getRemark());
+ return updateReqDTO;
+ }
+
+ /**
+ * 鏋勫缓浜у搧 VO 鍒楄〃锛堝寘鍚垎绫诲拰鍗曚綅鍚嶇О锛�
+ * 娉ㄦ剰锛歁DM 鍜� ERP 瀛楁鍚嶆湁宸紓锛岄渶瑕佹墜鍔ㄦ槧灏�
+ */
+ private List<ErpProductRespVO> buildProductVOList(List<MdmItemRespDTO> itemList) {
+ if (CollUtil.isEmpty(itemList)) {
+ return Collections.emptyList();
+ }
+
+ // 1. 鑾峰彇鍒嗙被鍜屽崟浣嶄俊鎭�
+ Set<Long> categoryIds = convertSet(itemList, MdmItemRespDTO::getCategoryId);
+ Set<Long> unitIds = convertSet(itemList, MdmItemRespDTO::getUnitMeasureId);
+
+ Map<Long, MdmItemCategoryRespDTO> categoryMap = CollUtil.isEmpty(categoryIds)
+ ? Collections.emptyMap()
+ : convertMap(mdmItemCategoryApi.getItemCategoryList(categoryIds).getCheckedData(), MdmItemCategoryRespDTO::getId);
+
+ Map<Long, MdmUnitMeasureRespDTO> unitMap = CollUtil.isEmpty(unitIds)
+ ? Collections.emptyMap()
+ : convertMap(mdmUnitMeasureApi.getUnitMeasureList(unitIds).getCheckedData(), MdmUnitMeasureRespDTO::getId);
+
+ // 2. 鎵嬪姩杞崲骞跺~鍏呭叧鑱斾俊鎭紙澶勭悊瀛楁鍚嶅樊寮傦級
+ List<ErpProductRespVO> result = new ArrayList<>();
+ for (MdmItemRespDTO mdmItem : itemList) {
+ ErpProductRespVO product = new ErpProductRespVO();
+ product.setId(mdmItem.getId());
+ product.setName(mdmItem.getName());
+ product.setBarCode(mdmItem.getBarCode());
+ product.setStandard(mdmItem.getSpecification()); // specification -> standard
+ product.setCategoryId(mdmItem.getCategoryId());
+ product.setUnitId(mdmItem.getUnitMeasureId()); // unitMeasureId -> unitId
+ product.setStatus(mdmItem.getStatus());
+ product.setPurchasePrice(mdmItem.getPurchasePrice());
+ product.setSalePrice(mdmItem.getSalesPrice()); // salesPrice -> salePrice
+ product.setMinPrice(mdmItem.getCostPrice()); // costPrice -> minPrice
+ product.setExpiryDay(mdmItem.getExpiryDay());
+ product.setWeight(mdmItem.getWeight());
+ product.setRemark(mdmItem.getRemark());
+
+ // 璁剧疆鍒嗙被鍜屽崟浣嶅悕绉�
+ MapUtils.findAndThen(categoryMap, mdmItem.getCategoryId(),
+ category -> product.setCategoryName(category.getName()));
+ MapUtils.findAndThen(unitMap, mdmItem.getUnitMeasureId(),
+ unit -> product.setUnitName(unit.getName()));
+
+ result.add(product);
+ }
+ return result;
+ }
+
+ /**
+ * 杞崲 MDM 鐗╂枡涓� ERP 浜у搧 DO
+ * 娉ㄦ剰锛氬瓧娈靛悕宸紓闇�瑕佹墜鍔ㄦ槧灏�
+ */
+ private ErpProductDO convertToErpProductDO(MdmItemRespDTO mdmItem) {
+ if (mdmItem == null) {
+ return null;
+ }
+ ErpProductDO product = new ErpProductDO();
+ product.setId(mdmItem.getId());
+ product.setName(mdmItem.getName());
+ product.setBarCode(mdmItem.getBarCode());
+ product.setStandard(mdmItem.getSpecification()); // specification -> standard
+ product.setCategoryId(mdmItem.getCategoryId());
+ product.setUnitId(mdmItem.getUnitMeasureId()); // unitMeasureId -> unitId
+ product.setStatus(mdmItem.getStatus());
+ product.setPurchasePrice(mdmItem.getPurchasePrice());
+ product.setSalePrice(mdmItem.getSalesPrice()); // salesPrice -> salePrice
+ product.setMinPrice(mdmItem.getCostPrice()); // costPrice -> minPrice
+ product.setExpiryDay(mdmItem.getExpiryDay());
+ product.setWeight(mdmItem.getWeight());
+ product.setRemark(mdmItem.getRemark());
+ return product;
+ }
+
+ /**
+ * 鎵归噺杞崲 MDM 鐗╂枡鍒楄〃涓� ERP 浜у搧鍒楄〃
+ */
+ private List<ErpProductDO> convertToErpProductDOList(List<MdmItemRespDTO> mdmItemList) {
+ if (CollUtil.isEmpty(mdmItemList)) {
+ return Collections.emptyList();
+ }
+ List<ErpProductDO> result = new ArrayList<>();
+ for (MdmItemRespDTO mdmItem : mdmItemList) {
+ result.add(convertToErpProductDO(mdmItem));
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java
index 37aa452..6bc25ff 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java
@@ -42,6 +42,22 @@
void updatePurchaseInStatus(Long id, Integer status);
/**
+ * 鎻愪氦閲囪喘鍏ュ簱瀹℃壒
+ *
+ * @param id 缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitPurchaseIn(Long id, Long userId);
+
+ /**
+ * 鏇存柊閲囪喘鍏ュ簱鐨勫鎵圭姸鎬�
+ *
+ * @param id 缂栧彿
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updatePurchaseInAuditStatus(Long id, Integer bpmResult);
+
+ /**
* 鏇存柊閲囪喘鍏ュ簱鐨勪粯娆鹃噾棰�
*
* @param id 缂栧彿
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
index adc2f40..8481a93 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -22,6 +24,7 @@
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -37,8 +40,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
-// TODO 鑺嬭壙锛氳褰曟搷浣滄棩蹇�
-
/**
* ERP 閲囪喘鍏ュ簱 Service 瀹炵幇绫�
*
@@ -46,7 +47,13 @@
*/
@Service
@Validated
+@Slf4j
public class ErpPurchaseInServiceImpl implements ErpPurchaseInService {
+
+ /**
+ * BPM 閲囪喘鍏ュ簱瀹℃壒娴佺▼鏍囪瘑
+ */
+ public static final String BPM_PROCESS_DEFINITION_KEY = "erp-purchase-in-audit";
@Resource
private ErpPurchaseInMapper purchaseInMapper;
@@ -69,6 +76,9 @@
@Resource
private AdminUserApi adminUserApi;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+
@Override
@Transactional(rollbackFor = Exception.class)
public Long createPurchaseIn(ErpPurchaseInSaveReqVO createReqVO) {
@@ -86,7 +96,7 @@
// 2.1 鎻掑叆鍏ュ簱
ErpPurchaseInDO purchaseIn = BeanUtils.toBean(createReqVO, ErpPurchaseInDO.class, in -> in
- .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
+ .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()))
.setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId());
calculateTotalPrice(purchaseIn, purchaseInItems);
purchaseInMapper.insert(purchaseIn);
@@ -181,9 +191,15 @@
: ErpStockRecordBizTypeEnum.PURCHASE_IN_CANCEL.getType();
purchaseInItems.forEach(purchaseInItem -> {
BigDecimal count = approve ? purchaseInItem.getCount() : purchaseInItem.getCount().negate();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- purchaseInItem.getProductId(), purchaseInItem.getWarehouseId(), count,
- bizType, purchaseInItem.getInId(), purchaseInItem.getId(), purchaseIn.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(purchaseInItem.getProductId());
+ stockRecordBO.setWarehouseId(purchaseInItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(purchaseInItem.getInId());
+ stockRecordBO.setBizItemId(purchaseInItem.getId());
+ stockRecordBO.setBizNo(purchaseIn.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
@@ -305,4 +321,83 @@
return purchaseInItemMapper.selectListByInIds(inIds);
}
+ // ==================== BPM 娴佺▼瀹℃壒 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitPurchaseIn(Long id, Long userId) {
+ // 1. 鏍¢獙閲囪喘鍏ュ簱澶勪簬鑽夌鐘舵��
+ ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
+ if (ObjectUtil.notEqual(purchaseIn.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
+ throw exception(PURCHASE_IN_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鍒涘缓閲囪喘鍏ュ簱瀹℃壒娴佺▼瀹炰緥
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊閲囪喘鍏ュ簱宸ヤ綔娴佺紪鍙枫�佺姸鎬�
+ purchaseInMapper.updateById(new ErpPurchaseInDO()
+ .setId(id)
+ .setProcessInstanceId(processInstanceId)
+ .setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updatePurchaseInAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙閲囪喘鍏ュ簱瀛樺湪
+ ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
+
+ // 2. 鏍¢獙閲囪喘鍏ュ簱澶勪簬瀹℃壒涓�
+ if (ObjectUtil.notEqual(purchaseIn.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
+ log.error("[updatePurchaseInAuditStatus][purchaseIn({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ purchaseIn.getId(), bpmResult);
+ throw exception(PURCHASE_IN_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+
+ // 3. 鏇存柊閲囪喘鍏ュ簱瀹℃壒缁撴灉
+ Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+ purchaseInMapper.updateById(new ErpPurchaseInDO()
+ .setId(id)
+ .setStatus(auditStatus));
+
+ // 4. 瀹℃壒閫氳繃鏃跺彉鏇村簱瀛�
+ if (ErpAuditStatus.APPROVE.getStatus().equals(auditStatus)) {
+ List<ErpPurchaseInItemDO> purchaseInItems = purchaseInItemMapper.selectListByInId(id);
+ purchaseInItems.forEach(purchaseInItem -> {
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(purchaseInItem.getProductId());
+ stockRecordBO.setWarehouseId(purchaseInItem.getWarehouseId());
+ stockRecordBO.setCount(purchaseInItem.getCount());
+ stockRecordBO.setBizType(ErpStockRecordBizTypeEnum.PURCHASE_IN.getType());
+ stockRecordBO.setBizId(purchaseInItem.getInId());
+ stockRecordBO.setBizItemId(purchaseInItem.getId());
+ stockRecordBO.setBizNo(purchaseIn.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
+ });
+ }
+ }
+
+ /**
+ * 灏� BPM 瀹℃壒缁撴灉杞崲涓� ERP 瀹℃壒鐘舵��
+ *
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ * @return ERP 瀹℃壒鐘舵��
+ */
+ private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ // BPM 瀹℃壒缁撴灉锛�2=閫氳繃锛�3=涓嶉�氳繃锛�4=宸插彇娑�
+ if (bpmResult == 2) {
+ return ErpAuditStatus.APPROVE.getStatus();
+ } else if (bpmResult == 3) {
+ return ErpAuditStatus.REJECT.getStatus();
+ } else if (bpmResult == 4) {
+ return ErpAuditStatus.DRAFT.getStatus();
+ } else {
+ log.warn("[convertBpmResultToAuditStatus][鏈煡鐨� BPM 瀹℃壒缁撴灉({})]", bpmResult);
+ return ErpAuditStatus.PROCESS.getStatus();
+ }
+ }
+
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
index bfea076..15f998b 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -43,6 +43,22 @@
void updatePurchaseOrderStatus(Long id, Integer status);
/**
+ * 鎻愪氦閲囪喘璁㈠崟瀹℃壒
+ *
+ * @param id 缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitPurchaseOrder(Long id, Long userId);
+
+ /**
+ * 鏇存柊閲囪喘璁㈠崟鐨勫鎵圭姸鎬�
+ *
+ * @param id 缂栧彿
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult);
+
+ /**
* 鏇存柊閲囪喘璁㈠崟鐨勫叆搴撴暟閲�
*
* @param id 缂栧彿
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
index b93ca55..ccd1dd1 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -17,6 +19,7 @@
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -31,8 +34,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
-// TODO 鑺嬭壙锛氳褰曟搷浣滄棩蹇�
-
/**
* ERP 閲囪喘璁㈠崟 Service 瀹炵幇绫�
*
@@ -40,7 +41,13 @@
*/
@Service
@Validated
+@Slf4j
public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService {
+
+ /**
+ * BPM 閲囪喘璁㈠崟瀹℃壒娴佺▼鏍囪瘑
+ */
+ public static final String BPM_PROCESS_DEFINITION_KEY = "erp-purchase-order-audit";
@Resource
private ErpPurchaseOrderMapper purchaseOrderMapper;
@@ -56,6 +63,9 @@
private ErpSupplierService supplierService;
@Resource
private ErpAccountService accountService;
+
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -76,7 +86,7 @@
// 2.1 鎻掑叆璁㈠崟
ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
- .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()));
calculateTotalPrice(purchaseOrder, purchaseOrderItems);
purchaseOrderMapper.insert(purchaseOrder);
// 2.2 鎻掑叆璁㈠崟椤�
@@ -292,4 +302,67 @@
return purchaseOrderItemMapper.selectListByOrderIds(orderIds);
}
+ // ==================== BPM 娴佺▼瀹℃壒 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitPurchaseOrder(Long id, Long userId) {
+ // 1. 鏍¢獙閲囪喘璁㈠崟澶勪簬鑽夌鐘舵��
+ ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
+ if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
+ throw exception(PURCHASE_ORDER_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鍒涘缓閲囪喘璁㈠崟瀹℃壒娴佺▼瀹炰緥
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊閲囪喘璁㈠崟宸ヤ綔娴佺紪鍙枫�佺姸鎬�
+ purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
+ .setId(id)
+ .setProcessInstanceId(processInstanceId)
+ .setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙閲囪喘璁㈠崟瀛樺湪
+ ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
+
+ // 2. 鏍¢獙閲囪喘璁㈠崟澶勪簬瀹℃壒涓�
+ if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
+ log.error("[updatePurchaseOrderAuditStatus][purchaseOrder({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ purchaseOrder.getId(), bpmResult);
+ throw exception(PURCHASE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+
+ // 3. 鏇存柊閲囪喘璁㈠崟瀹℃壒缁撴灉
+ Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+ purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
+ .setId(id)
+ .setStatus(auditStatus));
+ }
+
+ /**
+ * 灏� BPM 瀹℃壒缁撴灉杞崲涓� ERP 瀹℃壒鐘舵��
+ *
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ * @return ERP 瀹℃壒鐘舵��
+ */
+ private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ // BPM 瀹℃壒缁撴灉锛�2=閫氳繃锛�3=涓嶉�氳繃锛�4=宸插彇娑�
+ if (bpmResult == 2) {
+ return ErpAuditStatus.APPROVE.getStatus();
+ } else if (bpmResult == 3) {
+ return ErpAuditStatus.REJECT.getStatus();
+ } else if (bpmResult == 4) {
+ return ErpAuditStatus.DRAFT.getStatus();
+ } else {
+ log.warn("[convertBpmResultToAuditStatus][鏈煡鐨� BPM 瀹℃壒缁撴灉({})]", bpmResult);
+ return ErpAuditStatus.PROCESS.getStatus();
+ }
+ }
+
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java
index 18fe5ce..f7fa72f 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java
@@ -177,9 +177,15 @@
: ErpStockRecordBizTypeEnum.PURCHASE_RETURN_CANCEL.getType();
purchaseReturnItems.forEach(purchaseReturnItem -> {
BigDecimal count = approve ? purchaseReturnItem.getCount().negate() : purchaseReturnItem.getCount();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- purchaseReturnItem.getProductId(), purchaseReturnItem.getWarehouseId(), count,
- bizType, purchaseReturnItem.getReturnId(), purchaseReturnItem.getId(), purchaseReturn.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(purchaseReturnItem.getProductId());
+ stockRecordBO.setWarehouseId(purchaseReturnItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(purchaseReturnItem.getReturnId());
+ stockRecordBO.setBizItemId(purchaseReturnItem.getId());
+ stockRecordBO.setBizNo(purchaseReturn.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseInStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseInStatusListener.java
new file mode 100644
index 0000000..3c52a85
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseInStatusListener.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.service.purchase.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閲囪喘鍏ュ簱瀹℃壒鐨勭粨鏋滅殑鐩戝惉鍣ㄥ疄鐜扮被
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+public class ErpPurchaseInStatusListener extends BpmProcessInstanceStatusEventListener {
+
+ @Resource
+ private ErpPurchaseInService purchaseInService;
+
+ @Override
+ public String getProcessDefinitionKey() {
+ return ErpPurchaseInServiceImpl.BPM_PROCESS_DEFINITION_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ purchaseInService.updatePurchaseInAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java
new file mode 100644
index 0000000..21b8109
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.service.purchase.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閲囪喘璁㈠崟瀹℃壒鐨勭粨鏋滅殑鐩戝惉鍣ㄥ疄鐜扮被
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+public class ErpPurchaseOrderStatusListener extends BpmProcessInstanceStatusEventListener {
+
+ @Resource
+ private ErpPurchaseOrderService purchaseOrderService;
+
+ @Override
+ public String getProcessDefinitionKey() {
+ return ErpPurchaseOrderServiceImpl.BPM_PROCESS_DEFINITION_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ purchaseOrderService.updatePurchaseOrderAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
index c75f201..530faa9 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
@@ -43,6 +43,22 @@
void updateSaleOrderStatus(Long id, Integer status);
/**
+ * 鎻愪氦閿�鍞鍗曞鎵�
+ *
+ * @param id 缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitSaleOrder(Long id, Long userId);
+
+ /**
+ * 鏇存柊閿�鍞鍗曠殑瀹℃壒鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updateSaleOrderAuditStatus(Long id, Integer bpmResult);
+
+ /**
* 鏇存柊閿�鍞鍗曠殑鍑哄簱鏁伴噺
*
* @param id 缂栧彿
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
index 0edd206..4abe8c6 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -18,6 +20,7 @@
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -32,8 +35,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
-// TODO 鑺嬭壙锛氳褰曟搷浣滄棩蹇�
-
/**
* ERP 閿�鍞鍗� Service 瀹炵幇绫�
*
@@ -41,7 +42,13 @@
*/
@Service
@Validated
+@Slf4j
public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
+
+ /**
+ * BPM 閿�鍞鍗曞鎵规祦绋嬫爣璇�
+ */
+ public static final String BPM_PROCESS_DEFINITION_KEY = "erp-sale-order-audit";
@Resource
private ErpSaleOrderMapper saleOrderMapper;
@@ -60,6 +67,9 @@
@Resource
private AdminUserApi adminUserApi;
+
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -84,7 +94,7 @@
// 2.1 鎻掑叆璁㈠崟
ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class, in -> in
- .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()));
calculateTotalPrice(saleOrder, saleOrderItems);
saleOrderMapper.insert(saleOrder);
// 2.2 鎻掑叆璁㈠崟椤�
@@ -304,4 +314,67 @@
return saleOrderItemMapper.selectListByOrderIds(orderIds);
}
+ // ==================== BPM 娴佺▼瀹℃壒 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitSaleOrder(Long id, Long userId) {
+ // 1. 鏍¢獙閿�鍞鍗曞浜庤崏绋跨姸鎬�
+ ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
+ if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
+ throw exception(SALE_ORDER_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鍒涘缓閿�鍞鍗曞鎵规祦绋嬪疄渚�
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊閿�鍞鍗曞伐浣滄祦缂栧彿銆佺姸鎬�
+ saleOrderMapper.updateById(new ErpSaleOrderDO()
+ .setId(id)
+ .setProcessInstanceId(processInstanceId)
+ .setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateSaleOrderAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙閿�鍞鍗曞瓨鍦�
+ ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
+
+ // 2. 鏍¢獙閿�鍞鍗曞浜庡鎵逛腑
+ if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
+ log.error("[updateSaleOrderAuditStatus][saleOrder({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ saleOrder.getId(), bpmResult);
+ throw exception(SALE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+
+ // 3. 鏇存柊閿�鍞鍗曞鎵圭粨鏋�
+ Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+ saleOrderMapper.updateById(new ErpSaleOrderDO()
+ .setId(id)
+ .setStatus(auditStatus));
+ }
+
+ /**
+ * 灏� BPM 瀹℃壒缁撴灉杞崲涓� ERP 瀹℃壒鐘舵��
+ *
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ * @return ERP 瀹℃壒鐘舵��
+ */
+ private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ // BPM 瀹℃壒缁撴灉锛�2=閫氳繃锛�3=涓嶉�氳繃锛�4=宸插彇娑�
+ if (bpmResult == 2) {
+ return ErpAuditStatus.APPROVE.getStatus();
+ } else if (bpmResult == 3) {
+ return ErpAuditStatus.REJECT.getStatus();
+ } else if (bpmResult == 4) {
+ return ErpAuditStatus.DRAFT.getStatus();
+ } else {
+ log.warn("[convertBpmResultToAuditStatus][鏈煡鐨� BPM 瀹℃壒缁撴灉({})]", bpmResult);
+ return ErpAuditStatus.PROCESS.getStatus();
+ }
+ }
+
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java
index 616f778..892f177 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java
@@ -42,6 +42,22 @@
void updateSaleOutStatus(Long id, Integer status);
/**
+ * 鎻愪氦閿�鍞嚭搴撳鎵�
+ *
+ * @param id 缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitSaleOut(Long id, Long userId);
+
+ /**
+ * 鏇存柊閿�鍞嚭搴撶殑瀹℃壒鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updateSaleOutAuditStatus(Long id, Integer bpmResult);
+
+ /**
* 鏇存柊閿�鍞嚭搴撶殑鏀舵閲戦
*
* @param id 缂栧彿
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
index f8af202..45aad13 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -22,6 +24,7 @@
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -37,8 +40,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
-// TODO 鑺嬭壙锛氳褰曟搷浣滄棩蹇�
-
/**
* ERP 閿�鍞嚭搴� Service 瀹炵幇绫�
*
@@ -46,7 +47,13 @@
*/
@Service
@Validated
+@Slf4j
public class ErpSaleOutServiceImpl implements ErpSaleOutService {
+
+ /**
+ * BPM 閿�鍞嚭搴撳鎵规祦绋嬫爣璇�
+ */
+ public static final String BPM_PROCESS_DEFINITION_KEY = "erp-sale-out-audit";
@Resource
private ErpSaleOutMapper saleOutMapper;
@@ -69,6 +76,9 @@
@Resource
private AdminUserApi adminUserApi;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+
@Override
@Transactional(rollbackFor = Exception.class)
public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) {
@@ -90,7 +100,7 @@
// 2.1 鎻掑叆鍑哄簱
ErpSaleOutDO saleOut = BeanUtils.toBean(createReqVO, ErpSaleOutDO.class, in -> in
- .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
+ .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()))
.setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId());
calculateTotalPrice(saleOut, saleOutItems);
saleOutMapper.insert(saleOut);
@@ -189,9 +199,15 @@
: ErpStockRecordBizTypeEnum.SALE_OUT_CANCEL.getType();
saleOutItems.forEach(saleOutItem -> {
BigDecimal count = approve ? saleOutItem.getCount().negate() : saleOutItem.getCount();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- saleOutItem.getProductId(), saleOutItem.getWarehouseId(), count,
- bizType, saleOutItem.getOutId(), saleOutItem.getId(), saleOut.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(saleOutItem.getProductId());
+ stockRecordBO.setWarehouseId(saleOutItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(saleOutItem.getOutId());
+ stockRecordBO.setBizItemId(saleOutItem.getId());
+ stockRecordBO.setBizNo(saleOut.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
@@ -313,4 +329,83 @@
return saleOutItemMapper.selectListByOutIds(outIds);
}
+ // ==================== BPM 娴佺▼瀹℃壒 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitSaleOut(Long id, Long userId) {
+ // 1. 鏍¢獙閿�鍞嚭搴撳浜庤崏绋跨姸鎬�
+ ErpSaleOutDO saleOut = validateSaleOutExists(id);
+ if (ObjectUtil.notEqual(saleOut.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
+ throw exception(SALE_OUT_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鍒涘缓閿�鍞嚭搴撳鎵规祦绋嬪疄渚�
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊閿�鍞嚭搴撳伐浣滄祦缂栧彿銆佺姸鎬�
+ saleOutMapper.updateById(new ErpSaleOutDO()
+ .setId(id)
+ .setProcessInstanceId(processInstanceId)
+ .setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateSaleOutAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙閿�鍞嚭搴撳瓨鍦�
+ ErpSaleOutDO saleOut = validateSaleOutExists(id);
+
+ // 2. 鏍¢獙閿�鍞嚭搴撳浜庡鎵逛腑
+ if (ObjectUtil.notEqual(saleOut.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
+ log.error("[updateSaleOutAuditStatus][saleOut({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ saleOut.getId(), bpmResult);
+ throw exception(SALE_OUT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+
+ // 3. 鏇存柊閿�鍞嚭搴撳鎵圭粨鏋�
+ Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+ saleOutMapper.updateById(new ErpSaleOutDO()
+ .setId(id)
+ .setStatus(auditStatus));
+
+ // 4. 瀹℃壒閫氳繃鏃跺彉鏇村簱瀛橈紙鍑忓皯搴撳瓨锛�
+ if (ErpAuditStatus.APPROVE.getStatus().equals(auditStatus)) {
+ List<ErpSaleOutItemDO> saleOutItems = saleOutItemMapper.selectListByOutId(id);
+ saleOutItems.forEach(saleOutItem -> {
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(saleOutItem.getProductId());
+ stockRecordBO.setWarehouseId(saleOutItem.getWarehouseId());
+ stockRecordBO.setCount(saleOutItem.getCount().negate());
+ stockRecordBO.setBizType(ErpStockRecordBizTypeEnum.SALE_OUT.getType());
+ stockRecordBO.setBizId(saleOutItem.getOutId());
+ stockRecordBO.setBizItemId(saleOutItem.getId());
+ stockRecordBO.setBizNo(saleOut.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
+ });
+ }
+ }
+
+ /**
+ * 灏� BPM 瀹℃壒缁撴灉杞崲涓� ERP 瀹℃壒鐘舵��
+ *
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ * @return ERP 瀹℃壒鐘舵��
+ */
+ private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ // BPM 瀹℃壒缁撴灉锛�2=閫氳繃锛�3=涓嶉�氳繃锛�4=宸插彇娑�
+ if (bpmResult == 2) {
+ return ErpAuditStatus.APPROVE.getStatus();
+ } else if (bpmResult == 3) {
+ return ErpAuditStatus.REJECT.getStatus();
+ } else if (bpmResult == 4) {
+ return ErpAuditStatus.DRAFT.getStatus();
+ } else {
+ log.warn("[convertBpmResultToAuditStatus][鏈煡鐨� BPM 瀹℃壒缁撴灉({})]", bpmResult);
+ return ErpAuditStatus.PROCESS.getStatus();
+ }
+ }
+
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java
index 6b3976c..ea97f1c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java
@@ -189,9 +189,15 @@
: ErpStockRecordBizTypeEnum.SALE_RETURN_CANCEL.getType();
saleReturnItems.forEach(saleReturnItem -> {
BigDecimal count = approve ? saleReturnItem.getCount() : saleReturnItem.getCount().negate();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- saleReturnItem.getProductId(), saleReturnItem.getWarehouseId(), count,
- bizType, saleReturnItem.getReturnId(), saleReturnItem.getId(), saleReturn.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(saleReturnItem.getProductId());
+ stockRecordBO.setWarehouseId(saleReturnItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(saleReturnItem.getReturnId());
+ stockRecordBO.setBizItemId(saleReturnItem.getId());
+ stockRecordBO.setBizNo(saleReturn.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java
new file mode 100644
index 0000000..6faaed3
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.service.sale.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
+import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閿�鍞鍗曞鎵圭殑缁撴灉鐨勭洃鍚櫒瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+public class ErpSaleOrderStatusListener extends BpmProcessInstanceStatusEventListener {
+
+ @Resource
+ private ErpSaleOrderService saleOrderService;
+
+ @Override
+ public String getProcessDefinitionKey() {
+ return ErpSaleOrderServiceImpl.BPM_PROCESS_DEFINITION_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ saleOrderService.updateSaleOrderAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOutStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOutStatusListener.java
new file mode 100644
index 0000000..8e01981
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOutStatusListener.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.service.sale.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutService;
+import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閿�鍞嚭搴撳鎵圭殑缁撴灉鐨勭洃鍚櫒瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+public class ErpSaleOutStatusListener extends BpmProcessInstanceStatusEventListener {
+
+ @Resource
+ private ErpSaleOutService saleOutService;
+
+ @Override
+ public String getProcessDefinitionKey() {
+ return ErpSaleOutServiceImpl.BPM_PROCESS_DEFINITION_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ saleOutService.updateSaleOutAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java
index bbe08ae..11b9f73 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java
@@ -135,9 +135,15 @@
bizType = count.compareTo(BigDecimal.ZERO) > 0 ? ErpStockRecordBizTypeEnum.CHECK_MORE_IN_CANCEL.getType()
: ErpStockRecordBizTypeEnum.CHECK_LESS_OUT_CANCEL.getType();
}
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- stockCheckItem.getProductId(), stockCheckItem.getWarehouseId(), count,
- bizType, stockCheckItem.getCheckId(), stockCheckItem.getId(), stockCheck.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(stockCheckItem.getProductId());
+ stockRecordBO.setWarehouseId(stockCheckItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(stockCheckItem.getCheckId());
+ stockRecordBO.setBizItemId(stockCheckItem.getId());
+ stockRecordBO.setBizNo(stockCheck.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
index 38967de..837240d 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
@@ -130,9 +130,15 @@
: ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType();
stockInItems.forEach(stockInItem -> {
BigDecimal count = approve ? stockInItem.getCount() : stockInItem.getCount().negate();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- stockInItem.getProductId(), stockInItem.getWarehouseId(), count,
- bizType, stockInItem.getInId(), stockInItem.getId(), stockIn.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(stockInItem.getProductId());
+ stockRecordBO.setWarehouseId(stockInItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(stockInItem.getInId());
+ stockRecordBO.setBizItemId(stockInItem.getId());
+ stockRecordBO.setBizNo(stockIn.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java
index f039227..9f20497 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java
@@ -128,12 +128,24 @@
stockMoveItems.forEach(stockMoveItem -> {
BigDecimal fromCount = approve ? stockMoveItem.getCount().negate() : stockMoveItem.getCount();
BigDecimal toCount = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- stockMoveItem.getProductId(), stockMoveItem.getFromWarehouseId(), fromCount,
- fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- stockMoveItem.getProductId(), stockMoveItem.getToWarehouseId(), toCount,
- toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
+ ErpStockRecordCreateReqBO fromStockRecordBO = new ErpStockRecordCreateReqBO();
+ fromStockRecordBO.setProductId(stockMoveItem.getProductId());
+ fromStockRecordBO.setWarehouseId(stockMoveItem.getFromWarehouseId());
+ fromStockRecordBO.setCount(fromCount);
+ fromStockRecordBO.setBizType(fromBizType);
+ fromStockRecordBO.setBizId(stockMoveItem.getMoveId());
+ fromStockRecordBO.setBizItemId(stockMoveItem.getId());
+ fromStockRecordBO.setBizNo(stockMove.getNo());
+ stockRecordService.createStockRecord(fromStockRecordBO);
+ ErpStockRecordCreateReqBO toStockRecordBO = new ErpStockRecordCreateReqBO();
+ toStockRecordBO.setProductId(stockMoveItem.getProductId());
+ toStockRecordBO.setWarehouseId(stockMoveItem.getToWarehouseId());
+ toStockRecordBO.setCount(toCount);
+ toStockRecordBO.setBizType(toBizType);
+ toStockRecordBO.setBizId(stockMoveItem.getMoveId());
+ toStockRecordBO.setBizItemId(stockMoveItem.getId());
+ toStockRecordBO.setBizNo(stockMove.getNo());
+ stockRecordService.createStockRecord(toStockRecordBO);
});
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java
index 5a229fc..1058533 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java
@@ -131,9 +131,15 @@
: ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
stockOutItems.forEach(stockOutItem -> {
BigDecimal count = approve ? stockOutItem.getCount().negate() : stockOutItem.getCount();
- stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
- stockOutItem.getProductId(), stockOutItem.getWarehouseId(), count,
- bizType, stockOutItem.getOutId(), stockOutItem.getId(), stockOut.getNo()));
+ ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
+ stockRecordBO.setProductId(stockOutItem.getProductId());
+ stockRecordBO.setWarehouseId(stockOutItem.getWarehouseId());
+ stockRecordBO.setCount(count);
+ stockRecordBO.setBizType(bizType);
+ stockRecordBO.setBizId(stockOutItem.getOutId());
+ stockRecordBO.setBizItemId(stockOutItem.getId());
+ stockRecordBO.setBizNo(stockOut.getNo());
+ stockRecordService.createStockRecord(stockRecordBO);
});
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java
index d84951c..0bfaed8 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java
@@ -1,20 +1,33 @@
package cn.iocoder.yudao.module.erp.service.stock;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockRecordMapper;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemSkuApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
+import cn.iocoder.yudao.module.wms.api.inventory.WmsInventoryApi;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsReceiptReqDTO;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsShipmentReqDTO;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryBizTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
+import java.util.List;
/**
* ERP 浜у搧搴撳瓨鏄庣粏 Service 瀹炵幇绫�
+ *
+ * 鏀归�犺鏄庯細搴撳瓨鍙樻洿璋冪敤 WMS 搴撳瓨 API
+ * - 鍏ュ簱鎿嶄綔璋冪敤 WmsInventoryApi.receipt()
+ * - 鍑哄簱鎿嶄綔璋冪敤 WmsInventoryApi.shipment()
+ * - productId 瀵瑰簲 MDM itemId锛岄�氳繃 SKU API 鑾峰彇榛樿 SKU
*
* @author 鑺嬮亾婧愮爜
*/
@@ -27,6 +40,12 @@
@Resource
private ErpStockService stockService;
+
+ @Resource
+ private WmsInventoryApi wmsInventoryApi;
+
+ @Resource
+ private MdmItemSkuApi mdmItemSkuApi;
@Override
public ErpStockRecordDO getStockRecord(Long id) {
@@ -41,13 +60,98 @@
@Override
@Transactional(rollbackFor = Exception.class)
public void createStockRecord(ErpStockRecordCreateReqBO createReqBO) {
- // 1. 鏇存柊搴撳瓨
- BigDecimal totalCount = stockService.updateStockCountIncrement(
- createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount());
- // 2. 鍒涘缓搴撳瓨鏄庣粏
+ // 1. 鑾峰彇浜у搧鐨勯粯璁� SKU ID锛圗RP productId = MDM itemId锛�
+ Long skuId = getDefaultSkuId(createReqBO.getProductId());
+ if (skuId == null) {
+ // 濡傛灉娌℃湁 SKU锛屼娇鐢ㄥ師鏈夐�昏緫鏇存柊搴撳瓨
+ BigDecimal totalCount = stockService.updateStockCountIncrement(
+ createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount());
+ ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class)
+ .setTotalCount(totalCount);
+ stockRecordMapper.insert(stockRecord);
+ return;
+ }
+
+ // 2. 鏍规嵁 count 姝h礋鍒ゆ柇鍏ュ簱杩樻槸鍑哄簱
+ BigDecimal count = createReqBO.getCount();
+ if (count.compareTo(BigDecimal.ZERO) > 0) {
+ // 鍏ュ簱鎿嶄綔
+ WmsReceiptReqDTO receiptReq = buildReceiptReqDTO(createReqBO, skuId);
+ wmsInventoryApi.receipt(receiptReq);
+ } else if (count.compareTo(BigDecimal.ZERO) < 0) {
+ // 鍑哄簱鎿嶄綔锛堟暟閲忚浆涓烘鏁帮級
+ WmsShipmentReqDTO shipmentReq = buildShipmentReqDTO(createReqBO, skuId);
+ wmsInventoryApi.shipment(shipmentReq);
+ }
+
+ // 3. 鍒涘缓搴撳瓨鏄庣粏锛堣褰曟搷浣滐級
+ BigDecimal currentStock = wmsInventoryApi.getAvailableQuantity(skuId, createReqBO.getWarehouseId()).getCheckedData();
ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class)
- .setTotalCount(totalCount);
+ .setTotalCount(currentStock != null ? currentStock : BigDecimal.ZERO);
stockRecordMapper.insert(stockRecord);
}
+ /**
+ * 鑾峰彇浜у搧鐨勯粯璁� SKU ID
+ */
+ private Long getDefaultSkuId(Long productId) {
+ if (productId == null) {
+ return null;
+ }
+ List<MdmItemSkuRespDTO> skuList = mdmItemSkuApi.getItemSkuListByItemId(productId).getCheckedData();
+ if (CollUtil.isEmpty(skuList)) {
+ return null;
+ }
+ return skuList.get(0).getId();
+ }
+
+ /**
+ * 鏋勫缓鍏ュ簱璇锋眰
+ */
+ private WmsReceiptReqDTO buildReceiptReqDTO(ErpStockRecordCreateReqBO createReqBO, Long skuId) {
+ WmsReceiptReqDTO receiptReq = new WmsReceiptReqDTO();
+ receiptReq.setBizType(convertBizType(createReqBO.getBizType()));
+ receiptReq.setBizId(createReqBO.getBizId());
+ receiptReq.setBizNo(createReqBO.getBizNo());
+ receiptReq.setWarehouseId(createReqBO.getWarehouseId());
+ receiptReq.setRemark(createReqBO.getRemark());
+
+ WmsReceiptReqDTO.Item item = new WmsReceiptReqDTO.Item();
+ item.setSkuId(skuId);
+ item.setQuantity(createReqBO.getCount());
+ receiptReq.setItems(List.of(item));
+ return receiptReq;
+ }
+
+ /**
+ * 鏋勫缓鍑哄簱璇锋眰
+ */
+ private WmsShipmentReqDTO buildShipmentReqDTO(ErpStockRecordCreateReqBO createReqBO, Long skuId) {
+ WmsShipmentReqDTO shipmentReq = new WmsShipmentReqDTO();
+ shipmentReq.setBizType(convertBizType(createReqBO.getBizType()));
+ shipmentReq.setBizId(createReqBO.getBizId());
+ shipmentReq.setBizNo(createReqBO.getBizNo());
+ shipmentReq.setWarehouseId(createReqBO.getWarehouseId());
+ shipmentReq.setRemark(createReqBO.getRemark());
+
+ WmsShipmentReqDTO.Item item = new WmsShipmentReqDTO.Item();
+ item.setSkuId(skuId);
+ item.setQuantity(createReqBO.getCount().negate()); // 鍑哄簱鏁伴噺涓烘鏁�
+ shipmentReq.setItems(List.of(item));
+ return shipmentReq;
+ }
+
+ /**
+ * 杞崲涓氬姟绫诲瀷锛圗RP 涓氬姟绫诲瀷鏄犲皠鍒� WMS 涓氬姟绫诲瀷锛�
+ */
+ private Integer convertBizType(Integer erpBizType) {
+ // ERP 涓氬姟绫诲瀷鏄犲皠鍒� WMS 涓氬姟绫诲瀷
+ // 鍙互鏍规嵁瀹為檯涓氬姟闇�姹傛墿灞曟槧灏勫叧绯�
+ if (erpBizType == null) {
+ return WmsInventoryBizTypeEnum.OTHER.getType();
+ }
+ // 榛樿杩斿洖 OTHER 绫诲瀷锛屽悗缁彲鏍规嵁 ERP 涓氬姟绫诲瀷鏋氫妇鎵╁睍
+ return WmsInventoryBizTypeEnum.OTHER.getType();
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
index 6541170..31dee0e 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
@@ -1,22 +1,34 @@
package cn.iocoder.yudao.module.erp.service.stock;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemSkuApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
+import cn.iocoder.yudao.module.wms.api.inventory.WmsInventoryApi;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryQueryReqDTO;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
+import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE;
-import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
/**
* ERP 浜у搧搴撳瓨 Service 瀹炵幇绫�
+ *
+ * 鏀归�犺鏄庯細ERP 搴撳瓨璋冪敤 WMS 搴撳瓨 API
+ * - productId 瀵瑰簲 MDM itemId
+ * - 閫氳繃 MDM SKU API 鑾峰彇鐗╂枡鐨勯粯璁� SKU
+ * - 搴撳瓨鎿嶄綔璋冪敤 WMS Inventory API
*
* @author 鑺嬮亾婧愮爜
*/
@@ -26,18 +38,24 @@
/**
* 鍏佽搴撳瓨涓鸿礋鏁�
- *
* TODO 鑺嬭壙锛氬悗缁仛鎴� db 閰嶇疆
*/
private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false;
@Resource
private ErpProductService productService;
+
@Resource
private ErpWarehouseService warehouseService;
@Resource
private ErpStockMapper stockMapper;
+
+ @Resource
+ private WmsInventoryApi wmsInventoryApi;
+
+ @Resource
+ private MdmItemSkuApi mdmItemSkuApi;
@Override
public ErpStockDO getStock(Long id) {
@@ -46,44 +64,117 @@
@Override
public ErpStockDO getStock(Long productId, Long warehouseId) {
- return stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
+ // 閫氳繃 MDM 鑾峰彇浜у搧鐨勯粯璁� SKU锛岀劧鍚庢煡璇� WMS 搴撳瓨
+ Long skuId = getDefaultSkuId(productId);
+ if (skuId == null) {
+ return null;
+ }
+
+ // 鏌ヨ WMS 搴撳瓨
+ BigDecimal quantity = wmsInventoryApi.getAvailableQuantity(skuId, warehouseId).getCheckedData();
+ if (quantity == null || quantity.compareTo(BigDecimal.ZERO) == 0) {
+ return null;
+ }
+
+ // 杞崲涓� ERP Stock DO
+ ErpStockDO stock = new ErpStockDO();
+ stock.setProductId(productId);
+ stock.setWarehouseId(warehouseId);
+ stock.setCount(quantity);
+ return stock;
}
@Override
public BigDecimal getStockCount(Long productId) {
- BigDecimal count = stockMapper.selectSumByProductId(productId);
- return count != null ? count : BigDecimal.ZERO;
+ // 閫氳繃 MDM 鑾峰彇浜у搧鐨勯粯璁� SKU锛岀劧鍚庢煡璇� WMS 搴撳瓨鎬婚噺
+ Long skuId = getDefaultSkuId(productId);
+ if (skuId == null) {
+ return BigDecimal.ZERO;
+ }
+
+ // 鏌ヨ璇� SKU 鍦ㄦ墍鏈変粨搴撶殑搴撳瓨
+ WmsInventoryQueryReqDTO queryReq = new WmsInventoryQueryReqDTO();
+ queryReq.setSkuIds(List.of(skuId));
+ queryReq.setOnlyPositive(true);
+
+ List<WmsInventoryRespDTO> inventories = wmsInventoryApi.getInventoryList(queryReq).getCheckedData();
+ if (CollUtil.isEmpty(inventories)) {
+ return BigDecimal.ZERO;
+ }
+
+ return inventories.stream()
+ .map(WmsInventoryRespDTO::getQuantity)
+ .filter(q -> q != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public PageResult<ErpStockDO> getStockPage(ErpStockPageReqVO pageReqVO) {
+ // 鏆傛椂淇濈暀鍘熼�昏緫锛屽悗缁彲鏀逛负璋冪敤 WMS 鍒嗛〉 API
return stockMapper.selectPage(pageReqVO);
}
@Override
public BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count) {
- // 1.1 鏌ヨ褰撳墠搴撳瓨
- ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
- if (stock == null) {
- stock = new ErpStockDO().setProductId(productId).setWarehouseId(warehouseId).setCount(BigDecimal.ZERO);
- stockMapper.insert(stock);
- }
- // 1.2 鏍¢獙搴撳瓨鏄惁鍏呰冻
- if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) {
- throw exception(STOCK_COUNT_NEGATIVE, productService.getProduct(productId).getName(),
- warehouseService.getWarehouse(warehouseId).getName(), stock.getCount(), count);
- }
-
- // 2. 搴撳瓨鍙樻洿
- int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
- if (updateCount == 0) {
- // 姝ゆ椂涓嶅ソ鍘绘煡璇㈡渶鏂板簱瀛橈紝鎵�浠ョ洿鎺ユ姏鍑鸿鎻愮ず锛屼笉鎻愪緵鍏蜂綋搴撳瓨鏁板瓧
- throw exception(STOCK_COUNT_NEGATIVE2, productService.getProduct(productId).getName(),
+ // 閫氳繃 MDM 鑾峰彇浜у搧鐨勯粯璁� SKU
+ Long skuId = getDefaultSkuId(productId);
+ if (skuId == null) {
+ throw exception(STOCK_COUNT_NEGATIVE2,
+ productService.getProduct(productId).getName(),
warehouseService.getWarehouse(warehouseId).getName());
}
- // 3. 杩斿洖鏈�鏂板簱瀛�
+ // 鏌ヨ褰撳墠搴撳瓨
+ BigDecimal currentStock = wmsInventoryApi.getAvailableQuantity(skuId, warehouseId).getCheckedData();
+ if (currentStock == null) {
+ currentStock = BigDecimal.ZERO;
+ }
+
+ // 鏍¢獙搴撳瓨鏄惁鍏呰冻锛堝嚭搴撴椂锛�
+ if (!NEGATIVE_STOCK_COUNT_ENABLE && count.compareTo(BigDecimal.ZERO) < 0) {
+ BigDecimal afterStock = currentStock.add(count);
+ if (afterStock.compareTo(BigDecimal.ZERO) < 0) {
+ throw exception(STOCK_COUNT_NEGATIVE,
+ productService.getProduct(productId).getName(),
+ warehouseService.getWarehouse(warehouseId).getName(),
+ currentStock, count.abs());
+ }
+ }
+
+ // TODO: 璋冪敤 WMS 搴撳瓨鍙樻洿 API锛堥渶瑕� WMS 鎻愪緵 increment/decrement 鏂规硶锛�
+ // 鏆傛椂淇濈暀鍘熼�昏緫锛屼娇鐢ㄦ湰鍦板簱瀛樿〃
+ ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
+ if (stock == null) {
+ stock = new ErpStockDO()
+ .setProductId(productId)
+ .setWarehouseId(warehouseId)
+ .setCount(BigDecimal.ZERO);
+ stockMapper.insert(stock);
+ }
+
+ int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
+ if (updateCount == 0) {
+ throw exception(STOCK_COUNT_NEGATIVE2,
+ productService.getProduct(productId).getName(),
+ warehouseService.getWarehouse(warehouseId).getName());
+ }
+
return stock.getCount().add(count);
}
+ /**
+ * 鑾峰彇浜у搧鐨勯粯璁� SKU ID
+ * ERP productId 瀵瑰簲 MDM itemId锛岄粯璁や娇鐢ㄧ涓�涓� SKU
+ */
+ private Long getDefaultSkuId(Long productId) {
+ if (productId == null) {
+ return null;
+ }
+ List<MdmItemSkuRespDTO> skuList = mdmItemSkuApi.getItemSkuListByItemId(productId).getCheckedData();
+ if (CollUtil.isEmpty(skuList)) {
+ return null;
+ }
+ return skuList.get(0).getId();
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java
index 7006f10..acab88a 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.erp.service.stock.bo;
import jakarta.validation.constraints.NotNull;
-import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -14,7 +13,6 @@
*/
@Data
@NoArgsConstructor
-@AllArgsConstructor
public class ErpStockRecordCreateReqBO {
/**
@@ -56,4 +54,9 @@
@NotNull(message = "涓氬姟鍗曞彿涓嶈兘涓虹┖")
private String bizNo;
-}
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm-api/pom.xml b/yudao-module-mdm-api/pom.xml
new file mode 100644
index 0000000..ccd7c49
--- /dev/null
+++ b/yudao-module-mdm-api/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-mdm-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ mdm 妯″潡涓嬶紝涓绘暟鎹鐞嗙殑 API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/yudao-module-mdm/pom.xml b/yudao-module-mdm/pom.xml
new file mode 100644
index 0000000..9190b17
--- /dev/null
+++ b/yudao-module-mdm/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-mdm</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ mdm 妯″潡涓嬶紝涓绘暟鎹鐞嗭紙Master Data Management锛夈��
+ 渚嬪璇达細鐗╂枡銆佸垎绫汇�佸崟浣嶃�佸搧鐗屻�佷粨搴撶瓑涓绘暟鎹鐞�
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-system</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- Web 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-security</artifactId>
+ </dependency>
+
+ <!-- DB 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-redis</artifactId>
+ </dependency>
+
+ <!-- 宸ュ叿绫荤浉鍏� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-excel</artifactId>
+ </dependency>
+
+ <!-- Test 娴嬭瘯鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-test</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApi.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApi.java
new file mode 100644
index 0000000..8b9ff0b
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApi.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.mdm.api.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mdm.api.brand.dto.MdmBrandRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 鍝佺墝 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmBrandApi {
+
+ String PREFIX = "/rpc-api/mdm/brand";
+
+ /**
+ * 鑾峰彇鍝佺墝
+ *
+ * @param id 鍝佺墝缂栧彿
+ * @return 鍝佺墝
+ */
+ CommonResult<MdmBrandRespDTO> getBrand(Long id);
+
+ /**
+ * 鑾峰彇鍝佺墝鍒楄〃
+ *
+ * @param ids 鍝佺墝缂栧彿鍒楄〃
+ * @return 鍝佺墝鍒楄〃
+ */
+ CommonResult<List<MdmBrandRespDTO>> getBrandList(Collection<Long> ids);
+
+ /**
+ * 鏍¢獙鍝佺墝瀛樺湪
+ *
+ * @param id 鍝佺墝缂栧彿
+ * @return 鍝佺墝
+ */
+ CommonResult<MdmBrandRespDTO> validateBrandExists(Long id);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇鍝佺墝
+ *
+ * @param code 鍝佺墝缂栫爜
+ * @return 鍝佺墝
+ */
+ CommonResult<MdmBrandRespDTO> getBrandByCode(String code);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApiImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApiImpl.java
new file mode 100644
index 0000000..70f54e1
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApiImpl.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.mdm.api.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.brand.dto.MdmBrandRespDTO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
+import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MDM 鍝佺墝 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MdmBrandApi.PREFIX)
+@Validated
+public class MdmBrandApiImpl implements MdmBrandApi {
+
+ @Resource
+ private MdmBrandService brandService;
+
+ @Override
+ public CommonResult<MdmBrandRespDTO> getBrand(Long id) {
+ MdmBrandDO brand = brandService.getBrand(id);
+ return success(BeanUtils.toBean(brand, MdmBrandRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmBrandRespDTO>> getBrandList(Collection<Long> ids) {
+ List<MdmBrandDO> list = brandService.getBrandList(ids);
+ return success(BeanUtils.toBean(list, MdmBrandRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmBrandRespDTO> validateBrandExists(Long id) {
+ MdmBrandDO brand = brandService.validateBrandExists(id);
+ return success(BeanUtils.toBean(brand, MdmBrandRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmBrandRespDTO> getBrandByCode(String code) {
+ MdmBrandDO brand = brandService.getBrandByCode(code);
+ return success(BeanUtils.toBean(brand, MdmBrandRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/dto/MdmBrandRespDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/dto/MdmBrandRespDTO.java
new file mode 100644
index 0000000..70779f8
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/dto/MdmBrandRespDTO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.mdm.api.brand.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 鍝佺墝 Response DTO")
+@Data
+public class MdmBrandRespDTO {
+
+ @Schema(description = "鍝佺墝缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鍝佺墝缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "BRAND001")
+ private String code;
+
+ @Schema(description = "鍝佺墝鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍝佺墝A")
+ private String name;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApi.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApi.java
new file mode 100644
index 0000000..17f4686
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApi.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.mdm.api.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategoryRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 鐗╂枡鍒嗙被 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmItemCategoryApi {
+
+ String PREFIX = "/rpc-api/mdm/category";
+
+ /**
+ * 鑾峰彇鐗╂枡鍒嗙被
+ *
+ * @param id 鍒嗙被缂栧彿
+ * @return 鐗╂枡鍒嗙被
+ */
+ CommonResult<MdmItemCategoryRespDTO> getItemCategory(Long id);
+
+ /**
+ * 鑾峰彇鐗╂枡鍒嗙被鍒楄〃
+ *
+ * @param ids 鍒嗙被缂栧彿鍒楄〃
+ * @return 鐗╂枡鍒嗙被鍒楄〃
+ */
+ CommonResult<List<MdmItemCategoryRespDTO>> getItemCategoryList(Collection<Long> ids);
+
+ /**
+ * 鏍¢獙鐗╂枡鍒嗙被瀛樺湪
+ *
+ * @param id 鍒嗙被缂栧彿
+ * @return 鐗╂枡鍒嗙被
+ */
+ CommonResult<MdmItemCategoryRespDTO> validateItemCategoryExists(Long id);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApiImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApiImpl.java
new file mode 100644
index 0000000..8f70f31
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApiImpl.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.mdm.api.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategoryRespDTO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
+import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MDM 鐗╂枡鍒嗙被 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MdmItemCategoryApi.PREFIX)
+@Validated
+public class MdmItemCategoryApiImpl implements MdmItemCategoryApi {
+
+ @Resource
+ private MdmItemCategoryService itemCategoryService;
+
+ @Override
+ public CommonResult<MdmItemCategoryRespDTO> getItemCategory(Long id) {
+ MdmItemCategoryDO itemCategory = itemCategoryService.getItemCategory(id);
+ return success(BeanUtils.toBean(itemCategory, MdmItemCategoryRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmItemCategoryRespDTO>> getItemCategoryList(Collection<Long> ids) {
+ List<MdmItemCategoryDO> list = itemCategoryService.getItemCategoryList(ids);
+ return success(BeanUtils.toBean(list, MdmItemCategoryRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemCategoryRespDTO> validateItemCategoryExists(Long id) {
+ MdmItemCategoryDO itemCategory = itemCategoryService.validateItemCategoryExists(id);
+ return success(BeanUtils.toBean(itemCategory, MdmItemCategoryRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/dto/MdmItemCategoryRespDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/dto/MdmItemCategoryRespDTO.java
new file mode 100644
index 0000000..edc0135
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/dto/MdmItemCategoryRespDTO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mdm.api.category.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 鐗╂枡鍒嗙被 Response DTO")
+@Data
+public class MdmItemCategoryRespDTO {
+
+ @Schema(description = "鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鐖跺垎绫荤紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long parentId;
+
+ @Schema(description = "鍒嗙被缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "CAT001")
+ private String code;
+
+ @Schema(description = "鍒嗙被鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜旈噾閰嶄欢")
+ private String name;
+
+ @Schema(description = "鐗╂枡绫诲瀷", example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java
new file mode 100644
index 0000000..597cf14
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java
@@ -0,0 +1,143 @@
+package cn.iocoder.yudao.module.mdm.api.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemUpdateReqDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 鐗╂枡 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmItemApi {
+
+ String PREFIX = "/rpc-api/mdm/item";
+
+ /**
+ * 鍒涘缓鐗╂枡
+ *
+ * @param createReqDTO 鍒涘缓淇℃伅
+ * @return 鐗╂枡缂栧彿
+ */
+ CommonResult<Long> createItem(MdmItemCreateReqDTO createReqDTO);
+
+ /**
+ * 鏇存柊鐗╂枡
+ *
+ * @param updateReqDTO 鏇存柊淇℃伅
+ * @return 鏄惁鎴愬姛
+ */
+ CommonResult<Boolean> updateItem(MdmItemUpdateReqDTO updateReqDTO);
+
+ /**
+ * 鑾峰彇鐗╂枡
+ *
+ * @param id 鐗╂枡缂栧彿
+ * @return 鐗╂枡
+ */
+ CommonResult<MdmItemRespDTO> getItem(Long id);
+
+ /**
+ * 鑾峰彇鐗╂枡鍒楄〃
+ *
+ * @param ids 鐗╂枡缂栧彿鍒楄〃
+ * @return 鐗╂枡鍒楄〃
+ */
+ CommonResult<List<MdmItemRespDTO>> getItemList(Collection<Long> ids);
+
+ /**
+ * 鑾峰彇鐗╂枡 Map
+ *
+ * @param ids 鐗╂枡缂栧彿鍒楄〃
+ * @return 鐗╂枡 Map
+ */
+ default Map<Long, MdmItemRespDTO> getItemMap(Collection<Long> ids) {
+ List<MdmItemRespDTO> list = getItemList(ids).getCheckedData();
+ return convertMap(list, MdmItemRespDTO::getId);
+ }
+
+ /**
+ * 鑾峰彇鐗╂枡鍒嗛〉
+ *
+ * @param itemType 鐗╂枡绫诲瀷
+ * @param status 鐘舵��
+ * @param pageNo 椤电爜
+ * @param pageSize 姣忛〉鏁伴噺
+ * @return 鐗╂枡鍒嗛〉
+ */
+ CommonResult<PageResult<MdmItemRespDTO>> getItemPage(Integer itemType, Integer status, Integer pageNo, Integer pageSize);
+
+ /**
+ * 鏍规嵁鐘舵�佽幏鍙栫墿鏂欏垪琛�
+ *
+ * @param status 鐘舵��
+ * @return 鐗╂枡鍒楄〃
+ */
+ CommonResult<List<MdmItemRespDTO>> getItemListByStatus(Integer status);
+
+ /**
+ * 鏍¢獙鐗╂枡瀛樺湪
+ *
+ * @param id 鐗╂枡缂栧彿
+ * @return 鐗╂枡
+ */
+ CommonResult<MdmItemRespDTO> validateItemExists(Long id);
+
+ /**
+ * 鏍¢獙鐗╂枡瀛樺湪涓斿惎鐢�
+ *
+ * @param id 鐗╂枡缂栧彿
+ * @return 鐗╂枡
+ */
+ CommonResult<MdmItemRespDTO> validateItemExistsAndEnable(Long id);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇鐗╂枡
+ *
+ * @param code 鐗╂枡缂栫爜
+ * @return 鐗╂枡
+ */
+ CommonResult<MdmItemRespDTO> getItemByCode(String code);
+
+ /**
+ * 鏍规嵁鏉$爜鑾峰彇鐗╂枡
+ *
+ * @param barCode 鐗╂枡鏉$爜
+ * @return 鐗╂枡
+ */
+ CommonResult<MdmItemRespDTO> getItemByBarCode(String barCode);
+
+ /**
+ * 鏍规嵁鍒嗙被缂栧彿缁熻鐗╂枡鏁伴噺
+ *
+ * @param categoryId 鍒嗙被缂栧彿
+ * @return 鐗╂枡鏁伴噺
+ */
+ CommonResult<Long> getItemCountByCategoryId(Long categoryId);
+
+ /**
+ * 鏍规嵁鍗曚綅缂栧彿缁熻鐗╂枡鏁伴噺
+ *
+ * @param unitMeasureId 鍗曚綅缂栧彿
+ * @return 鐗╂枡鏁伴噺
+ */
+ CommonResult<Long> getItemCountByUnitMeasureId(Long unitMeasureId);
+
+ /**
+ * 鏇存柊鐗╂枡鐘舵��
+ *
+ * @param id 鐗╂枡缂栧彿
+ * @param status 鐘舵��
+ * @return 鏄惁鎴愬姛
+ */
+ CommonResult<Boolean> updateItemStatus(Long id, Integer status);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApiImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApiImpl.java
new file mode 100644
index 0000000..9ec6300
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApiImpl.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.mdm.api.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemUpdateReqDTO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MDM 鐗╂枡 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MdmItemApi.PREFIX)
+@Validated
+public class MdmItemApiImpl implements MdmItemApi {
+
+ @Resource
+ private MdmItemService itemService;
+
+ @Resource
+ private MdmItemMapper itemMapper;
+
+ @Override
+ public CommonResult<Long> createItem(MdmItemCreateReqDTO createReqDTO) {
+ MdmItemSaveReqVO saveReqVO = BeanUtils.toBean(createReqDTO, MdmItemSaveReqVO.class);
+ // 璁剧疆榛樿鐘舵��
+ if (saveReqVO.getStatus() == null) {
+ saveReqVO.setStatus(0);
+ }
+ return success(itemService.createItem(saveReqVO));
+ }
+
+ @Override
+ public CommonResult<Boolean> updateItem(MdmItemUpdateReqDTO updateReqDTO) {
+ MdmItemSaveReqVO saveReqVO = BeanUtils.toBean(updateReqDTO, MdmItemSaveReqVO.class);
+ itemService.updateItem(saveReqVO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult<MdmItemRespDTO> getItem(Long id) {
+ MdmItemDO item = itemService.getItem(id);
+ return success(BeanUtils.toBean(item, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmItemRespDTO>> getItemList(Collection<Long> ids) {
+ List<MdmItemDO> list = itemService.getItemList(ids);
+ return success(BeanUtils.toBean(list, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<PageResult<MdmItemRespDTO>> getItemPage(Integer itemType, Integer status, Integer pageNo, Integer pageSize) {
+ MdmItemPageReqVO pageReqVO = new MdmItemPageReqVO();
+ pageReqVO.setItemType(itemType);
+ pageReqVO.setStatus(status);
+ pageReqVO.setPageNo(pageNo);
+ pageReqVO.setPageSize(pageSize);
+ PageResult<MdmItemDO> pageResult = itemService.getItemPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmItemRespDTO>> getItemListByStatus(Integer status) {
+ MdmItemPageReqVO pageReqVO = new MdmItemPageReqVO();
+ pageReqVO.setStatus(status);
+ pageReqVO.setPageNo(1);
+ pageReqVO.setPageSize(Integer.MAX_VALUE);
+ PageResult<MdmItemDO> pageResult = itemService.getItemPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult.getList(), MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemRespDTO> validateItemExists(Long id) {
+ MdmItemDO item = itemService.validateItemExists(id);
+ return success(BeanUtils.toBean(item, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemRespDTO> validateItemExistsAndEnable(Long id) {
+ MdmItemDO item = itemService.validateItemExistsAndEnable(id);
+ return success(BeanUtils.toBean(item, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemRespDTO> getItemByCode(String code) {
+ MdmItemDO item = itemService.getItemByCode(code);
+ return success(BeanUtils.toBean(item, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemRespDTO> getItemByBarCode(String barCode) {
+ MdmItemDO item = itemMapper.selectByBarCode(barCode);
+ return success(BeanUtils.toBean(item, MdmItemRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<Long> getItemCountByCategoryId(Long categoryId) {
+ return success(itemService.getItemCountByCategoryId(categoryId));
+ }
+
+ @Override
+ public CommonResult<Long> getItemCountByUnitMeasureId(Long unitMeasureId) {
+ return success(itemService.getItemCountByUnitMeasureId(unitMeasureId));
+ }
+
+ @Override
+ public CommonResult<Boolean> updateItemStatus(Long id, Integer status) {
+ itemService.updateItemStatus(id, status);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApi.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApi.java
new file mode 100644
index 0000000..d6f633e
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApi.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.mdm.api.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 鐗╂枡 SKU API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmItemSkuApi {
+
+ String PREFIX = "/rpc-api/mdm/item-sku";
+
+ /**
+ * 鑾峰彇 SKU
+ *
+ * @param id SKU 缂栧彿
+ * @return SKU
+ */
+ CommonResult<MdmItemSkuRespDTO> getItemSku(Long id);
+
+ /**
+ * 鑾峰彇 SKU 鍒楄〃
+ *
+ * @param ids SKU 缂栧彿鍒楄〃
+ * @return SKU 鍒楄〃
+ */
+ CommonResult<List<MdmItemSkuRespDTO>> getItemSkuList(Collection<Long> ids);
+
+ /**
+ * 鑾峰彇 SKU Map
+ *
+ * @param ids SKU 缂栧彿鍒楄〃
+ * @return SKU Map
+ */
+ default Map<Long, MdmItemSkuRespDTO> getItemSkuMap(Collection<Long> ids) {
+ List<MdmItemSkuRespDTO> list = getItemSkuList(ids).getCheckedData();
+ return convertMap(list, MdmItemSkuRespDTO::getId);
+ }
+
+ /**
+ * 鑾峰彇鐗╂枡鐨� SKU 鍒楄〃
+ *
+ * @param itemId 鐗╂枡缂栧彿
+ * @return SKU 鍒楄〃
+ */
+ CommonResult<List<MdmItemSkuRespDTO>> getItemSkuListByItemId(Long itemId);
+
+ /**
+ * 鏍¢獙 SKU 瀛樺湪
+ *
+ * @param id SKU 缂栧彿
+ * @return SKU
+ */
+ CommonResult<MdmItemSkuRespDTO> validateItemSkuExists(Long id);
+
+ /**
+ * 鏍¢獙 SKU 瀛樺湪涓斿惎鐢�
+ *
+ * @param id SKU 缂栧彿
+ * @return SKU
+ */
+ CommonResult<MdmItemSkuRespDTO> validateItemSkuExistsAndEnable(Long id);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇 SKU
+ *
+ * @param code SKU 缂栫爜
+ * @return SKU
+ */
+ CommonResult<MdmItemSkuRespDTO> getItemSkuByCode(String code);
+
+ /**
+ * 鏍规嵁鏉$爜鑾峰彇 SKU
+ *
+ * @param barCode SKU 鏉$爜
+ * @return SKU
+ */
+ CommonResult<MdmItemSkuRespDTO> getItemSkuByBarCode(String barCode);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApiImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApiImpl.java
new file mode 100644
index 0000000..7b0e0d9
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApiImpl.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.mdm.api.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemSkuDO;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemSkuService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MDM 鐗╂枡 SKU API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MdmItemSkuApi.PREFIX)
+@Validated
+public class MdmItemSkuApiImpl implements MdmItemSkuApi {
+
+ @Resource
+ private MdmItemSkuService itemSkuService;
+
+ @Override
+ public CommonResult<MdmItemSkuRespDTO> getItemSku(Long id) {
+ MdmItemSkuDO sku = itemSkuService.getItemSku(id);
+ return success(BeanUtils.toBean(sku, MdmItemSkuRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmItemSkuRespDTO>> getItemSkuList(Collection<Long> ids) {
+ List<MdmItemSkuDO> list = itemSkuService.getItemSkuList(ids);
+ return success(BeanUtils.toBean(list, MdmItemSkuRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmItemSkuRespDTO>> getItemSkuListByItemId(Long itemId) {
+ List<MdmItemSkuDO> list = itemSkuService.getItemSkuListByItemId(itemId);
+ return success(BeanUtils.toBean(list, MdmItemSkuRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemSkuRespDTO> validateItemSkuExists(Long id) {
+ MdmItemSkuDO sku = itemSkuService.validateItemSkuExists(id);
+ return success(BeanUtils.toBean(sku, MdmItemSkuRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemSkuRespDTO> validateItemSkuExistsAndEnable(Long id) {
+ MdmItemSkuDO sku = itemSkuService.validateItemSkuExistsAndEnable(id);
+ return success(BeanUtils.toBean(sku, MdmItemSkuRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemSkuRespDTO> getItemSkuByCode(String code) {
+ MdmItemSkuDO sku = itemSkuService.getItemSkuByCode(code);
+ return success(BeanUtils.toBean(sku, MdmItemSkuRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmItemSkuRespDTO> getItemSkuByBarCode(String barCode) {
+ MdmItemSkuDO sku = itemSkuService.getItemSkuByBarCode(barCode);
+ return success(BeanUtils.toBean(sku, MdmItemSkuRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
new file mode 100644
index 0000000..995cfe8
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.mdm.api.item.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 鐗╂枡鍒涘缓 Request DTO")
+@Data
+public class MdmItemCreateReqDTO {
+
+ @Schema(description = "鐗╂枡缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
+ private String specification;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long categoryId;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long unitMeasureId;
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long brandId;
+
+ @Schema(description = "鐗╂枡绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "閲囪喘浠锋牸", example = "10.00")
+ private BigDecimal purchasePrice;
+
+ @Schema(description = "閿�鍞环鏍�", example = "15.00")
+ private BigDecimal salesPrice;
+
+ @Schema(description = "鎴愭湰浠锋牸", example = "8.00")
+ private BigDecimal costPrice;
+
+ @Schema(description = "瀹夊叏搴撳瓨", example = "100")
+ private BigDecimal safetyStock;
+
+ @Schema(description = "淇濊川鏈熷ぉ鏁�", example = "365")
+ private Integer expiryDay;
+
+ @Schema(description = "鏄惁鎵规绠$悊", example = "false")
+ private Boolean isBatchManaged;
+
+ @Schema(description = "閲嶉噺(kg)", example = "0.5")
+ private BigDecimal weight;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
new file mode 100644
index 0000000..65364ca
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.mdm.api.item.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 鐗╂枡 Response DTO")
+@Data
+public class MdmItemRespDTO {
+
+ @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鐗╂枡缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
+ private String specification;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long categoryId;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long unitMeasureId;
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long brandId;
+
+ @Schema(description = "鐗╂枡绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "閲囪喘浠锋牸", example = "10.00")
+ private BigDecimal purchasePrice;
+
+ @Schema(description = "閿�鍞环鏍�", example = "15.00")
+ private BigDecimal salesPrice;
+
+ @Schema(description = "鎴愭湰浠锋牸", example = "8.00")
+ private BigDecimal costPrice;
+
+ @Schema(description = "瀹夊叏搴撳瓨", example = "100")
+ private BigDecimal safetyStock;
+
+ @Schema(description = "淇濊川鏈熷ぉ鏁�", example = "365")
+ private Integer expiryDay;
+
+ @Schema(description = "鏄惁鎵规绠$悊", example = "false")
+ private Boolean isBatchManaged;
+
+ @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
+ private Boolean highValue;
+
+ @Schema(description = "閲嶉噺", example = "0.5")
+ private BigDecimal weight;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSkuRespDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSkuRespDTO.java
new file mode 100644
index 0000000..92264a4
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSkuRespDTO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.mdm.api.item.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 鐗╂枡 SKU Response DTO")
+@Data
+public class MdmItemSkuRespDTO {
+
+ @Schema(description = "SKU 缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long itemId;
+
+ @Schema(description = "SKU 缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "SKU001")
+ private String code;
+
+ @Schema(description = "SKU 鍚嶇О", example = "瑙勬牸A")
+ private String name;
+
+ @Schema(description = "SKU 鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "闀垮害锛坈m锛�", example = "10")
+ private BigDecimal length;
+
+ @Schema(description = "瀹藉害锛坈m锛�", example = "5")
+ private BigDecimal width;
+
+ @Schema(description = "楂樺害锛坈m锛�", example = "2")
+ private BigDecimal height;
+
+ @Schema(description = "姣涢噸锛坘g锛�", example = "0.5")
+ private BigDecimal grossWeight;
+
+ @Schema(description = "鍑�閲嶏紙kg锛�", example = "0.4")
+ private BigDecimal netWeight;
+
+ @Schema(description = "鎴愭湰浠�", example = "8.00")
+ private BigDecimal costPrice;
+
+ @Schema(description = "閿�鍞环", example = "15.00")
+ private BigDecimal salePrice;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java
new file mode 100644
index 0000000..1f1fdf1
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.mdm.api.item.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 鐗╂枡鏇存柊 Request DTO")
+@Data
+public class MdmItemUpdateReqDTO {
+
+ @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鐗╂枡缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
+ private String specification;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long categoryId;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long unitMeasureId;
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long brandId;
+
+ @Schema(description = "鐗╂枡绫诲瀷", example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "閲囪喘浠锋牸", example = "10.00")
+ private BigDecimal purchasePrice;
+
+ @Schema(description = "閿�鍞环鏍�", example = "15.00")
+ private BigDecimal salesPrice;
+
+ @Schema(description = "鎴愭湰浠锋牸", example = "8.00")
+ private BigDecimal costPrice;
+
+ @Schema(description = "瀹夊叏搴撳瓨", example = "100")
+ private BigDecimal safetyStock;
+
+ @Schema(description = "淇濊川鏈熷ぉ鏁�", example = "365")
+ private Integer expiryDay;
+
+ @Schema(description = "鏄惁鎵规绠$悊", example = "false")
+ private Boolean isBatchManaged;
+
+ @Schema(description = "閲嶉噺(kg)", example = "0.5")
+ private BigDecimal weight;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApi.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApi.java
new file mode 100644
index 0000000..ab82a8a
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApi.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.mdm.api.unit;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 璁¢噺鍗曚綅 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmUnitMeasureApi {
+
+ String PREFIX = "/rpc-api/mdm/unit";
+
+ /**
+ * 鑾峰彇璁¢噺鍗曚綅
+ *
+ * @param id 鍗曚綅缂栧彿
+ * @return 璁¢噺鍗曚綅
+ */
+ CommonResult<MdmUnitMeasureRespDTO> getUnitMeasure(Long id);
+
+ /**
+ * 鑾峰彇璁¢噺鍗曚綅鍒楄〃
+ *
+ * @param ids 鍗曚綅缂栧彿鍒楄〃
+ * @return 璁¢噺鍗曚綅鍒楄〃
+ */
+ CommonResult<List<MdmUnitMeasureRespDTO>> getUnitMeasureList(Collection<Long> ids);
+
+ /**
+ * 鏍¢獙璁¢噺鍗曚綅瀛樺湪
+ *
+ * @param id 鍗曚綅缂栧彿
+ * @return 璁¢噺鍗曚綅
+ */
+ CommonResult<MdmUnitMeasureRespDTO> validateUnitMeasureExists(Long id);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇璁¢噺鍗曚綅
+ *
+ * @param code 鍗曚綅缂栫爜
+ * @return 璁¢噺鍗曚綅
+ */
+ CommonResult<MdmUnitMeasureRespDTO> getUnitMeasureByCode(String code);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApiImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApiImpl.java
new file mode 100644
index 0000000..905db39
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApiImpl.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.mdm.api.unit;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MDM 璁¢噺鍗曚綅 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MdmUnitMeasureApi.PREFIX)
+@Validated
+public class MdmUnitMeasureApiImpl implements MdmUnitMeasureApi {
+
+ @Resource
+ private MdmUnitMeasureService unitMeasureService;
+
+ @Override
+ public CommonResult<MdmUnitMeasureRespDTO> getUnitMeasure(Long id) {
+ MdmUnitMeasureDO unitMeasure = unitMeasureService.getUnitMeasure(id);
+ return success(BeanUtils.toBean(unitMeasure, MdmUnitMeasureRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmUnitMeasureRespDTO>> getUnitMeasureList(Collection<Long> ids) {
+ List<MdmUnitMeasureDO> list = unitMeasureService.getUnitMeasureList(ids);
+ return success(BeanUtils.toBean(list, MdmUnitMeasureRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmUnitMeasureRespDTO> validateUnitMeasureExists(Long id) {
+ MdmUnitMeasureDO unitMeasure = unitMeasureService.validateUnitMeasureExists(id);
+ return success(BeanUtils.toBean(unitMeasure, MdmUnitMeasureRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmUnitMeasureRespDTO> getUnitMeasureByCode(String code) {
+ MdmUnitMeasureDO unitMeasure = unitMeasureService.getUnitMeasureByCode(code);
+ return success(BeanUtils.toBean(unitMeasure, MdmUnitMeasureRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/dto/MdmUnitMeasureRespDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/dto/MdmUnitMeasureRespDTO.java
new file mode 100644
index 0000000..a9144f6
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/dto/MdmUnitMeasureRespDTO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.mdm.api.unit.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 璁¢噺鍗曚綅 Response DTO")
+@Data
+public class MdmUnitMeasureRespDTO {
+
+ @Schema(description = "鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鍗曚綅缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "PCS")
+ private String code;
+
+ @Schema(description = "鍗曚綅鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "涓�")
+ private String name;
+
+ @Schema(description = "鏄惁涓诲崟浣�", example = "true")
+ private Boolean primaryFlag;
+
+ @Schema(description = "涓诲崟浣嶇紪鍙�", example = "1")
+ private Long primaryId;
+
+ @Schema(description = "涓庝富鍗曚綅鎹㈢畻姣斾緥", example = "1.0")
+ private BigDecimal changeRate;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApi.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApi.java
new file mode 100644
index 0000000..d582986
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApi.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.mdm.api.warehouse;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mdm.api.warehouse.dto.MdmWarehouseRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 浠撳簱 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmWarehouseApi {
+
+ String PREFIX = "/rpc-api/mdm/warehouse";
+
+ /**
+ * 鑾峰彇浠撳簱
+ *
+ * @param id 浠撳簱缂栧彿
+ * @return 浠撳簱
+ */
+ CommonResult<MdmWarehouseRespDTO> getWarehouse(Long id);
+
+ /**
+ * 鑾峰彇浠撳簱鍒楄〃
+ *
+ * @param ids 浠撳簱缂栧彿鍒楄〃
+ * @return 浠撳簱鍒楄〃
+ */
+ CommonResult<List<MdmWarehouseRespDTO>> getWarehouseList(Collection<Long> ids);
+
+ /**
+ * 鑾峰彇浠撳簱 Map
+ *
+ * @param ids 浠撳簱缂栧彿鍒楄〃
+ * @return 浠撳簱 Map
+ */
+ default Map<Long, MdmWarehouseRespDTO> getWarehouseMap(Collection<Long> ids) {
+ List<MdmWarehouseRespDTO> list = getWarehouseList(ids).getCheckedData();
+ return convertMap(list, MdmWarehouseRespDTO::getId);
+ }
+
+ /**
+ * 鏍¢獙浠撳簱瀛樺湪
+ *
+ * @param id 浠撳簱缂栧彿
+ * @return 浠撳簱
+ */
+ CommonResult<MdmWarehouseRespDTO> validateWarehouseExists(Long id);
+
+ /**
+ * 鏍¢獙浠撳簱瀛樺湪涓斿惎鐢�
+ *
+ * @param id 浠撳簱缂栧彿
+ * @return 浠撳簱
+ */
+ CommonResult<MdmWarehouseRespDTO> validateWarehouseExistsAndEnable(Long id);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇浠撳簱
+ *
+ * @param code 浠撳簱缂栫爜
+ * @return 浠撳簱
+ */
+ CommonResult<MdmWarehouseRespDTO> getWarehouseByCode(String code);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApiImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApiImpl.java
new file mode 100644
index 0000000..0e3a6a6
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApiImpl.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.mdm.api.warehouse;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.warehouse.dto.MdmWarehouseRespDTO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse.MdmWarehouseDO;
+import cn.iocoder.yudao.module.mdm.service.warehouse.MdmWarehouseService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MDM 浠撳簱 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MdmWarehouseApi.PREFIX)
+@Validated
+public class MdmWarehouseApiImpl implements MdmWarehouseApi {
+
+ @Resource
+ private MdmWarehouseService warehouseService;
+
+ @Override
+ public CommonResult<MdmWarehouseRespDTO> getWarehouse(Long id) {
+ MdmWarehouseDO warehouse = warehouseService.getWarehouse(id);
+ return success(BeanUtils.toBean(warehouse, MdmWarehouseRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MdmWarehouseRespDTO>> getWarehouseList(Collection<Long> ids) {
+ List<MdmWarehouseDO> list = warehouseService.getWarehouseList(ids);
+ return success(BeanUtils.toBean(list, MdmWarehouseRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmWarehouseRespDTO> validateWarehouseExists(Long id) {
+ MdmWarehouseDO warehouse = warehouseService.validateWarehouseExists(id);
+ return success(BeanUtils.toBean(warehouse, MdmWarehouseRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmWarehouseRespDTO> validateWarehouseExistsAndEnable(Long id) {
+ MdmWarehouseDO warehouse = warehouseService.validateWarehouseExistsAndEnable(id);
+ return success(BeanUtils.toBean(warehouse, MdmWarehouseRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MdmWarehouseRespDTO> getWarehouseByCode(String code) {
+ MdmWarehouseDO warehouse = warehouseService.getWarehouseByCode(code);
+ return success(BeanUtils.toBean(warehouse, MdmWarehouseRespDTO.class));
+ }
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/dto/MdmWarehouseRespDTO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/dto/MdmWarehouseRespDTO.java
new file mode 100644
index 0000000..6e13e64
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/dto/MdmWarehouseRespDTO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mdm.api.warehouse.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - MDM 浠撳簱 Response DTO")
+@Data
+public class MdmWarehouseRespDTO {
+
+ @Schema(description = "浠撳簱缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "浠撳簱缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "WH001")
+ private String code;
+
+ @Schema(description = "浠撳簱鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍘熸枡浠�")
+ private String name;
+
+ @Schema(description = "浠撳簱鍦板潃", example = "涓婃捣甯傛郸涓滄柊鍖�")
+ private String address;
+
+ @Schema(description = "闈㈢Н锛堝钩鏂圭背锛�", example = "1000")
+ private BigDecimal area;
+
+ @Schema(description = "浠撳簱绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer type;
+
+ @Schema(description = "璐熻矗浜虹敤鎴稩D", example = "1")
+ private Long chargeUserId;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "鏄惁鍐荤粨", example = "false")
+ private Boolean frozen;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java
new file mode 100644
index 0000000..f55edf0
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
+import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MDM 鍝佺墝")
+@RestController
+@RequestMapping("/mdm/brand")
+@Validated
+public class MdmBrandController {
+
+ @Resource
+ private MdmBrandService brandService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓鍝佺墝")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:create')")
+ public CommonResult<Long> createBrand(@Valid @RequestBody MdmBrandSaveReqVO createReqVO) {
+ return success(brandService.createBrand(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鍝佺墝")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:update')")
+ public CommonResult<Boolean> updateBrand(@Valid @RequestBody MdmBrandSaveReqVO updateReqVO) {
+ brandService.updateBrand(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "鏇存柊鍝佺墝鐘舵��")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @Parameter(name = "status", description = "鐘舵��", required = true, example = "0")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:update')")
+ public CommonResult<Boolean> updateBrandStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
+ brandService.updateBrandStatus(id, status);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鍝佺墝")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:delete')")
+ public CommonResult<Boolean> deleteBrand(@RequestParam("id") Long id) {
+ brandService.deleteBrand(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鍝佺墝")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
+ public CommonResult<MdmBrandDO> getBrand(@RequestParam("id") Long id) {
+ return success(brandService.getBrand(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鍝佺墝鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
+ public CommonResult<PageResult<MdmBrandDO>> getBrandPage(@Valid MdmBrandPageReqVO pageReqVO) {
+ return success(brandService.getBrandPage(pageReqVO));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰緱鍝佺墝鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
+ public CommonResult<List<MdmBrandDO>> getBrandList() {
+ return success(brandService.getBrandList(null));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandPageReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandPageReqVO.java
new file mode 100644
index 0000000..39f8b6a
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandPageReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.brand.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鍝佺墝鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MdmBrandPageReqVO extends PageParam {
+
+ @Schema(description = "鍝佺墝缂栫爜", example = "BRAND001")
+ private String code;
+
+ @Schema(description = "鍝佺墝鍚嶇О", example = "鍝佺墝A")
+ private String name;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandSaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandSaveReqVO.java
new file mode 100644
index 0000000..39761e2
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandSaveReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.brand.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鍝佺墝鏂板/淇敼 Request VO")
+@Data
+public class MdmBrandSaveReqVO {
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "鍝佺墝缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "BRAND001")
+ private String code;
+
+ @Schema(description = "鍝佺墝鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍝佺墝A")
+ private String name;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java
new file mode 100644
index 0000000..715ec5d
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryRespVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategorySaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
+import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MDM 鐗╂枡鍒嗙被")
+@RestController
+@RequestMapping("/mdm/item-category")
+@Validated
+public class MdmItemCategoryController {
+
+ @Resource
+ private MdmItemCategoryService itemCategoryService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓鐗╂枡鍒嗙被")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:create')")
+ public CommonResult<Long> createItemCategory(@Valid @RequestBody MdmItemCategorySaveReqVO createReqVO) {
+ return success(itemCategoryService.createItemCategory(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鐗╂枡鍒嗙被")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:update')")
+ public CommonResult<Boolean> updateItemCategory(@Valid @RequestBody MdmItemCategorySaveReqVO updateReqVO) {
+ itemCategoryService.updateItemCategory(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "鏇存柊鐗╂枡鍒嗙被鐘舵��")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @Parameter(name = "status", description = "鐘舵��", required = true, example = "0")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:update')")
+ public CommonResult<Boolean> updateItemCategoryStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
+ itemCategoryService.updateItemCategoryStatus(id, status);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鐗╂枡鍒嗙被")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:delete')")
+ public CommonResult<Boolean> deleteItemCategory(@RequestParam("id") Long id) {
+ itemCategoryService.deleteItemCategory(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鐗╂枡鍒嗙被")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:query')")
+ public CommonResult<MdmItemCategoryRespVO> getItemCategory(@RequestParam("id") Long id) {
+ MdmItemCategoryDO itemCategory = itemCategoryService.getItemCategory(id);
+ return success(BeanUtils.toBean(itemCategory, MdmItemCategoryRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鐗╂枡鍒嗙被鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:query')")
+ public CommonResult<PageResult<MdmItemCategoryRespVO>> getItemCategoryPage(@Valid MdmItemCategoryPageReqVO pageReqVO) {
+ PageResult<MdmItemCategoryDO> pageResult = itemCategoryService.getItemCategoryPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, MdmItemCategoryRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰緱鐗╂枡鍒嗙被鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('mdm:item-category:query')")
+ public CommonResult<List<MdmItemCategoryRespVO>> getItemCategoryList() {
+ List<MdmItemCategoryDO> list = itemCategoryService.getItemCategoryList(null);
+ return success(BeanUtils.toBean(list, MdmItemCategoryRespVO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryPageReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryPageReqVO.java
new file mode 100644
index 0000000..dcc1ede
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.category.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡鍒嗙被鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MdmItemCategoryPageReqVO extends PageParam {
+
+ @Schema(description = "鍒嗙被缂栫爜", example = "CAT001")
+ private String code;
+
+ @Schema(description = "鍒嗙被鍚嶇О", example = "浜旈噾閰嶄欢")
+ private String name;
+
+ @Schema(description = "鐖跺垎绫荤紪鍙�", example = "0")
+ private Long parentId;
+
+ @Schema(description = "鐗╂枡绫诲瀷", example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryRespVO.java
new file mode 100644
index 0000000..ede6b3e
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡鍒嗙被 Response VO")
+@Data
+public class MdmItemCategoryRespVO {
+
+ @Schema(description = "鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鐖跺垎绫荤紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long parentId;
+
+ @Schema(description = "鍒嗙被缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "CAT001")
+ private String code;
+
+ @Schema(description = "鍒嗙被鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜旈噾閰嶄欢")
+ private String name;
+
+ @Schema(description = "鐗╂枡绫诲瀷", example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鎺掑簭", example = "1")
+ private Integer sort;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategorySaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategorySaveReqVO.java
new file mode 100644
index 0000000..7df8691
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategorySaveReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡鍒嗙被鏂板/淇敼 Request VO")
+@Data
+public class MdmItemCategorySaveReqVO {
+
+ @Schema(description = "鍒嗙被缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "鐖跺垎绫荤紪鍙�", example = "0")
+ private Long parentId;
+
+ @Schema(description = "鍒嗙被缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "CAT001")
+ private String code;
+
+ @Schema(description = "鍒嗙被鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜旈噾閰嶄欢")
+ private String name;
+
+ @Schema(description = "鐗╂枡绫诲瀷", example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鎺掑簭", example = "1")
+ private Integer sort;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
new file mode 100644
index 0000000..736e3bd
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemRespVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MDM 鐗╂枡涓绘暟鎹�")
+@RestController
+@RequestMapping("/mdm/item")
+@Validated
+public class MdmItemController {
+
+ @Resource
+ private MdmItemService itemService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓鐗╂枡")
+ @PreAuthorize("@ss.hasPermission('mdm:item:create')")
+ public CommonResult<Long> createItem(@Valid @RequestBody MdmItemSaveReqVO createReqVO) {
+ return success(itemService.createItem(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鐗╂枡")
+ @PreAuthorize("@ss.hasPermission('mdm:item:update')")
+ public CommonResult<Boolean> updateItem(@Valid @RequestBody MdmItemSaveReqVO updateReqVO) {
+ itemService.updateItem(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "鏇存柊鐗╂枡鐘舵��")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @Parameter(name = "status", description = "鐘舵��", required = true, example = "0")
+ @PreAuthorize("@ss.hasPermission('mdm:item:update')")
+ public CommonResult<Boolean> updateItemStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
+ itemService.updateItemStatus(id, status);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鐗╂枡")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:item:delete')")
+ public CommonResult<Boolean> deleteItem(@RequestParam("id") Long id) {
+ itemService.deleteItem(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鐗╂枡")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:item:query')")
+ public CommonResult<MdmItemRespVO> getItem(@RequestParam("id") Long id) {
+ MdmItemDO item = itemService.getItem(id);
+ return success(BeanUtils.toBean(item, MdmItemRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鐗╂枡鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('mdm:item:query')")
+ public CommonResult<PageResult<MdmItemRespVO>> getItemPage(@Valid MdmItemPageReqVO pageReqVO) {
+ PageResult<MdmItemDO> pageResult = itemService.getItemPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, MdmItemRespVO.class));
+ }
+
+ @GetMapping("/list-by-ids")
+ @Operation(summary = "鑾峰緱鐗╂枡鍒楄〃")
+ @Parameter(name = "ids", description = "缂栧彿鍒楄〃", required = true, example = "1,2,3")
+ @PreAuthorize("@ss.hasPermission('mdm:item:query')")
+ public CommonResult<List<MdmItemRespVO>> getItemList(@RequestParam("ids") List<Long> ids) {
+ List<MdmItemDO> list = itemService.getItemList(ids);
+ return success(BeanUtils.toBean(list, MdmItemRespVO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemPageReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemPageReqVO.java
new file mode 100644
index 0000000..93e87b5
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemPageReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MdmItemPageReqVO extends PageParam {
+
+ @Schema(description = "鐗╂枡缂栫爜", example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿", example = "1")
+ private Long categoryId;
+
+ @Schema(description = "鐗╂枡绫诲瀷", example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long brandId;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
new file mode 100644
index 0000000..cc07aa9
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡 Response VO")
+@Data
+public class MdmItemRespVO {
+
+ @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鐗╂枡缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
+ private String specification;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long categoryId;
+
+ @Schema(description = "鐗╂枡鍒嗙被鍚嶇О", example = "浜旈噾閰嶄欢")
+ private String categoryName;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long unitMeasureId;
+
+ @Schema(description = "璁¢噺鍗曚綅鍚嶇О", example = "涓�")
+ private String unitMeasureName;
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long brandId;
+
+ @Schema(description = "鍝佺墝鍚嶇О", example = "鍝佺墝A")
+ private String brandName;
+
+ @Schema(description = "鐗╂枡绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "閲囪喘浠锋牸", example = "10.00")
+ private BigDecimal purchasePrice;
+
+ @Schema(description = "閿�鍞环鏍�", example = "15.00")
+ private BigDecimal salesPrice;
+
+ @Schema(description = "鎴愭湰浠锋牸", example = "8.00")
+ private BigDecimal costPrice;
+
+ @Schema(description = "瀹夊叏搴撳瓨", example = "100")
+ private BigDecimal safetyStock;
+
+ @Schema(description = "鏈�浣庡簱瀛�", example = "50")
+ private BigDecimal minStock;
+
+ @Schema(description = "鏈�楂樺簱瀛�", example = "500")
+ private BigDecimal maxStock;
+
+ @Schema(description = "淇濊川鏈熷ぉ鏁�", example = "365")
+ private Integer expiryDays;
+
+ @Schema(description = "鏄惁鎵规绠$悊", example = "false")
+ private Boolean isBatchManaged;
+
+ @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
+ private Boolean isHighValue;
+
+ @Schema(description = "閲嶉噺", example = "0.5")
+ private BigDecimal weight;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
new file mode 100644
index 0000000..ede7f8c
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡鏂板/淇敼 Request VO")
+@Data
+public class MdmItemSaveReqVO {
+
+ @Schema(description = "鐗╂枡缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "鐗╂枡缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+ private String barCode;
+
+ @Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
+ private String specification;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long categoryId;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long unitMeasureId;
+
+ @Schema(description = "鍝佺墝缂栧彿", example = "1")
+ private Long brandId;
+
+ @Schema(description = "鐗╂枡绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer itemType;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "閲囪喘浠锋牸", example = "10.00")
+ private BigDecimal purchasePrice;
+
+ @Schema(description = "閿�鍞环鏍�", example = "15.00")
+ private BigDecimal salesPrice;
+
+ @Schema(description = "鎴愭湰浠锋牸", example = "8.00")
+ private BigDecimal costPrice;
+
+ @Schema(description = "瀹夊叏搴撳瓨", example = "100")
+ private BigDecimal safetyStock;
+
+ @Schema(description = "鏈�浣庡簱瀛�", example = "50")
+ private BigDecimal minStock;
+
+ @Schema(description = "鏈�楂樺簱瀛�", example = "500")
+ private BigDecimal maxStock;
+
+ @Schema(description = "淇濊川鏈熷ぉ鏁�", example = "365")
+ private Integer expiryDay;
+
+ @Schema(description = "鏄惁鎵规绠$悊", example = "false")
+ private Boolean isBatchManaged;
+
+ @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
+ private Boolean isHighValue;
+
+ @Schema(description = "閲嶉噺", example = "0.5")
+ private BigDecimal weight;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
new file mode 100644
index 0000000..02335e9
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.unit;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MDM 璁¢噺鍗曚綅")
+@RestController
+@RequestMapping("/mdm/unit-measure")
+@Validated
+public class MdmUnitMeasureController {
+
+ @Resource
+ private MdmUnitMeasureService unitMeasureService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓璁¢噺鍗曚綅")
+ @PreAuthorize("@ss.hasPermission('mdm:unit-measure: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')")
+ public CommonResult<Boolean> updateUnitMeasure(@Valid @RequestBody MdmUnitMeasureSaveReqVO updateReqVO) {
+ unitMeasureService.updateUnitMeasure(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @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')")
+ public CommonResult<Boolean> updateUnitMeasureStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
+ unitMeasureService.updateUnitMeasureStatus(id, status);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎璁¢噺鍗曚綅")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:unit-measure:delete')")
+ public CommonResult<Boolean> deleteUnitMeasure(@RequestParam("id") Long id) {
+ unitMeasureService.deleteUnitMeasure(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱璁¢噺鍗曚綅")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('mdm:unit-measure: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')")
+ public CommonResult<PageResult<MdmUnitMeasureDO>> getUnitMeasurePage(@Valid MdmUnitMeasurePageReqVO pageReqVO) {
+ return success(unitMeasureService.getUnitMeasurePage(pageReqVO));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰緱璁¢噺鍗曚綅鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
+ public CommonResult<List<MdmUnitMeasureDO>> getUnitMeasureList() {
+ return success(unitMeasureService.getUnitMeasureList(null));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasurePageReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasurePageReqVO.java
new file mode 100644
index 0000000..c7a8827
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasurePageReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.unit.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 璁¢噺鍗曚綅鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MdmUnitMeasurePageReqVO extends PageParam {
+
+ @Schema(description = "鍗曚綅缂栫爜", example = "PCS")
+ private String code;
+
+ @Schema(description = "鍗曚綅鍚嶇О", example = "涓�")
+ private String name;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java
new file mode 100644
index 0000000..0d9455c
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.unit.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 璁¢噺鍗曚綅鏂板/淇敼 Request VO")
+@Data
+public class MdmUnitMeasureSaveReqVO {
+
+ @Schema(description = "鍗曚綅缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "鍗曚綅缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "PCS")
+ private String code;
+
+ @Schema(description = "鍗曚綅鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "涓�")
+ private String name;
+
+ @Schema(description = "鏄惁涓诲崟浣�", example = "true")
+ private Boolean primaryFlag;
+
+ @Schema(description = "涓诲崟浣嶇紪鍙�", example = "1")
+ private Long primaryId;
+
+ @Schema(description = "涓庝富鍗曚綅鎹㈢畻姣斾緥", example = "1.0")
+ private BigDecimal changeRate;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java
new file mode 100644
index 0000000..0c15cdf
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java
@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.warehouse;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo.*;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse.MdmWarehouseDO;
+import cn.iocoder.yudao.module.mdm.service.warehouse.MdmWarehouseService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MDM 浠撳簱")
+@RestController
+@RequestMapping("/mdm/warehouse")
+@Validated
+public class MdmWarehouseController {
+
+ @Resource
+ private MdmWarehouseService warehouseService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓浠撳簱")
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:create')")
+ public CommonResult<Long> createWarehouse(@Valid @RequestBody MdmWarehouseSaveReqVO createReqVO) {
+ return success(warehouseService.createWarehouse(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊浠撳簱")
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:update')")
+ public CommonResult<Boolean> updateWarehouse(@Valid @RequestBody MdmWarehouseSaveReqVO updateReqVO) {
+ warehouseService.updateWarehouse(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "鏇存柊浠撳簱鐘舵��")
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:update')")
+ public CommonResult<Boolean> updateWarehouseStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
+ warehouseService.updateWarehouseStatus(id, status);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎浠撳簱")
+ @Parameter(name = "id", description = "缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:delete')")
+ public CommonResult<Boolean> deleteWarehouse(@RequestParam("id") Long id) {
+ warehouseService.deleteWarehouse(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱浠撳簱")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:query')")
+ public CommonResult<MdmWarehouseRespVO> getWarehouse(@RequestParam("id") Long id) {
+ MdmWarehouseDO warehouse = warehouseService.getWarehouse(id);
+ return success(BeanUtils.toBean(warehouse, MdmWarehouseRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱浠撳簱鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:query')")
+ public CommonResult<PageResult<MdmWarehouseRespVO>> getWarehousePage(@Valid MdmWarehousePageReqVO pageReqVO) {
+ PageResult<MdmWarehouseDO> pageResult = warehouseService.getWarehousePage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, MdmWarehouseRespVO.class));
+ }
+
+ @GetMapping("/list-by-ids")
+ @Operation(summary = "鑾峰緱浠撳簱鍒楄〃")
+ @Parameter(name = "ids", description = "缂栧彿鍒楄〃", required = true, example = "1,2,3")
+ @PreAuthorize("@ss.hasPermission('mdm:warehouse:query')")
+ public CommonResult<List<MdmWarehouseRespVO>> getWarehouseList(@RequestParam("ids") List<Long> ids) {
+ List<MdmWarehouseDO> list = warehouseService.getWarehouseList(ids);
+ return success(BeanUtils.toBean(list, MdmWarehouseRespVO.class));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "鑾峰緱鍏ㄩ儴鍚敤鐨勪粨搴撳垪琛�")
+ public CommonResult<List<MdmWarehouseRespVO>> getSimpleWarehouseList() {
+ MdmWarehouseDO query = new MdmWarehouseDO();
+ query.setStatus(0);
+ List<MdmWarehouseDO> list = warehouseService.getWarehousePage(new MdmWarehousePageReqVO()).getList();
+ return success(BeanUtils.toBean(list, MdmWarehouseRespVO.class));
+ }
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehousePageReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehousePageReqVO.java
new file mode 100644
index 0000000..eaa6853
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehousePageReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 浠撳簱鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MdmWarehousePageReqVO extends PageParam {
+
+ @Schema(description = "浠撳簱缂栫爜", example = "WH001")
+ private String code;
+
+ @Schema(description = "浠撳簱鍚嶇О", example = "鎴愬搧浠撳簱")
+ private String name;
+
+ @Schema(description = "浠撳簱绫诲瀷", example = "1")
+ private Integer type;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseRespVO.java
new file mode 100644
index 0000000..a2287fe
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseRespVO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 浠撳簱 Response VO")
+@Data
+public class MdmWarehouseRespVO {
+
+ @Schema(description = "浠撳簱缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "浠撳簱缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "WH001")
+ private String code;
+
+ @Schema(description = "浠撳簱鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鎴愬搧浠撳簱")
+ private String name;
+
+ @Schema(description = "浠撳簱鍦板潃", example = "涓婃捣甯傛郸涓滄柊鍖�")
+ private String address;
+
+ @Schema(description = "闈㈢Н锛堝钩鏂圭背锛�", example = "1000.00")
+ private BigDecimal area;
+
+ @Schema(description = "浠撳簱绫诲瀷", example = "1")
+ private Integer type;
+
+ @Schema(description = "璐熻矗浜虹敤鎴稩D", example = "1")
+ private Long chargeUserId;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "鏄惁鍐荤粨", example = "false")
+ private Boolean frozen;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseSaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseSaveReqVO.java
new file mode 100644
index 0000000..3e182a0
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseSaveReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 浠撳簱鏂板/淇敼 Request VO")
+@Data
+public class MdmWarehouseSaveReqVO {
+
+ @Schema(description = "浠撳簱缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "浠撳簱缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "WH001")
+ private String code;
+
+ @Schema(description = "浠撳簱鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鎴愬搧浠撳簱")
+ private String name;
+
+ @Schema(description = "浠撳簱鍦板潃", example = "涓婃捣甯傛郸涓滄柊鍖�")
+ private String address;
+
+ @Schema(description = "闈㈢Н锛堝钩鏂圭背锛�", example = "1000.00")
+ private BigDecimal area;
+
+ @Schema(description = "浠撳簱绫诲瀷", example = "1")
+ private Integer type;
+
+ @Schema(description = "璐熻矗浜虹敤鎴稩D", example = "1")
+ private Long chargeUserId;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "鏄惁鍐荤粨", example = "false")
+ private Boolean frozen;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/brand/MdmBrandDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/brand/MdmBrandDO.java
new file mode 100644
index 0000000..05ffa65
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/brand/MdmBrandDO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.brand;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * MDM 鍝佺墝 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("md_brand")
+@KeySequence("md_brand_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmBrandDO extends BaseDO {
+
+ /**
+ * 鍝佺墝缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍝佺墝缂栫爜
+ */
+ private String code;
+
+ /**
+ * 鍝佺墝鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java
new file mode 100644
index 0000000..2bce04f
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.category;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * MDM 鐗╂枡鍒嗙被 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("md_item_category")
+@KeySequence("md_item_category_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmItemCategoryDO extends BaseDO {
+
+ /**
+ * 鍒嗙被缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐖跺垎绫荤紪鍙凤紙鏍硅妭鐐�=0锛�
+ */
+ private Long parentId;
+
+ /**
+ * 鍒嗙被缂栫爜
+ */
+ private String code;
+
+ /**
+ * 鍒嗙被鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 閫傜敤鐗╂枡绫诲瀷
+ *
+ * 1: 鍘熸枡
+ * 2: 鍗婃垚鍝�
+ * 3: 鎴愬搧
+ * 4: 杈呮枡
+ * NULL: 鍏ㄩ儴
+ */
+ private Integer itemType;
+
+ /**
+ * 鎺掑簭
+ */
+ private Integer sort;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java
new file mode 100644
index 0000000..80abad0
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java
@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * MDM 鐗╂枡鎵规灞炴�ч厤缃� DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("md_item_batch_config")
+@KeySequence("md_item_batch_config_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmItemBatchConfigDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐗╂枡缂栧彿
+ *
+ * 鍏宠仈 {@link MdmItemDO#getId()}
+ */
+ private Long itemId;
+
+ /**
+ * 鐢熶骇鏃ユ湡
+ */
+ private Boolean produceDateFlag;
+
+ /**
+ * 鏈夋晥鏈�
+ */
+ private Boolean expireDateFlag;
+
+ /**
+ * 鍏ュ簱鏃ユ湡
+ */
+ private Boolean receiptDateFlag;
+
+ /**
+ * 渚涘簲鍟�
+ */
+ private Boolean vendorFlag;
+
+ /**
+ * 瀹㈡埛
+ */
+ private Boolean clientFlag;
+
+ /**
+ * 閿�鍞鍗曠紪鍙�
+ */
+ private Boolean salesOrderCodeFlag;
+
+ /**
+ * 閲囪喘璁㈠崟缂栧彿
+ */
+ private Boolean purchaseOrderCodeFlag;
+
+ /**
+ * 鐢熶骇宸ュ崟
+ */
+ private Boolean workOrderFlag;
+
+ /**
+ * 鐢熶骇浠诲姟
+ */
+ private Boolean taskFlag;
+
+ /**
+ * 宸ヤ綔绔�
+ */
+ private Boolean workstationFlag;
+
+ /**
+ * 宸ュ叿
+ */
+ private Boolean toolFlag;
+
+ /**
+ * 妯″叿
+ */
+ private Boolean moldFlag;
+
+ /**
+ * 鐢熶骇鎵瑰彿
+ */
+ private Boolean lotNumberFlag;
+
+ /**
+ * 璐ㄩ噺鐘舵��
+ */
+ private Boolean qualityStatusFlag;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java
new file mode 100644
index 0000000..5786294
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java
@@ -0,0 +1,177 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
+import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MDM 鐗╂枡涓绘暟鎹� DO
+ *
+ * 缁熶竴鐗╂枡鏁版嵁锛孍RP/MES/WMS 鍏辩敤
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("md_item")
+@KeySequence("md_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmItemDO extends BaseDO {
+
+ /**
+ * 鐗╂枡ID锛堝叏灞�鍞竴锛�
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐗╂枡缂栫爜锛堝叏灞�鍞竴锛�
+ */
+ private String code;
+
+ /**
+ * 鐗╂枡鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鐗╂枡鏉$爜
+ */
+ private String barCode;
+
+ /**
+ * 瑙勬牸鍨嬪彿
+ */
+ private String specification;
+
+ /**
+ * 鐗╂枡鍒嗙被缂栧彿
+ *
+ * 鍏宠仈 {@link MdmItemCategoryDO#getId()}
+ */
+ private Long categoryId;
+
+ /**
+ * 涓昏閲忓崟浣嶇紪鍙�
+ *
+ * 鍏宠仈 {@link MdmUnitMeasureDO#getId()}
+ */
+ private Long unitMeasureId;
+
+ /**
+ * 鍝佺墝缂栧彿
+ *
+ * 鍏宠仈 {@link MdmBrandDO#getId()}
+ */
+ private Long brandId;
+
+ /**
+ * 鐗╂枡绫诲瀷
+ *
+ * 鏋氫妇 {@link MdmItemTypeEnum}
+ * 1: 鍘熸枡
+ * 2: 鍗婃垚鍝�
+ * 3: 鎴愬搧
+ * 4: 杈呮枡
+ */
+ private Integer itemType;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 閲囪喘浠�
+ */
+ private BigDecimal purchasePrice;
+
+ /**
+ * 閿�鍞环
+ */
+ private BigDecimal salesPrice;
+
+ /**
+ * 鎴愭湰浠�
+ */
+ private BigDecimal costPrice;
+
+ /**
+ * 鏈�浣庝环
+ */
+ private BigDecimal minPrice;
+
+ /**
+ * 瀹夊叏搴撳瓨
+ */
+ private BigDecimal safetyStock;
+
+ /**
+ * 鏈�浣庡簱瀛橀噺
+ */
+ private BigDecimal minStock;
+
+ /**
+ * 鏈�楂樺簱瀛橀噺
+ */
+ private BigDecimal maxStock;
+
+ /**
+ * 鏈�灏忚璐ч噺
+ */
+ private BigDecimal minOrderQty;
+
+ /**
+ * 閲囪喘鎻愬墠鏈燂紙澶╋級
+ */
+ private Integer leadTime;
+
+ /**
+ * 鏄惁鎵规绠$悊
+ */
+ private Boolean isBatchManaged;
+
+ /**
+ * 鏄惁搴忓垪鍙风鐞�
+ */
+ private Boolean isSerialManaged;
+
+ /**
+ * 鏉$爜瑙勫垯ID
+ */
+ private Long barcodeRuleId;
+
+ /**
+ * 淇濊川鏈熷ぉ鏁�
+ */
+ private Integer expiryDay;
+
+ /**
+ * 鏄惁楂樺�肩墿鏂�
+ */
+ private Boolean highValue;
+
+ /**
+ * 閲嶉噺(kg)
+ */
+ private BigDecimal weight;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemSkuDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemSkuDO.java
new file mode 100644
index 0000000..858d534
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemSkuDO.java
@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MDM 鐗╂枡 SKU DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("mdm_item_sku")
+@KeySequence("mdm_item_sku_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmItemSkuDO extends BaseDO {
+
+ /**
+ * SKU 缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐗╂枡缂栧彿
+ */
+ private Long itemId;
+
+ /**
+ * SKU 缂栫爜
+ */
+ private String code;
+
+ /**
+ * SKU 鍚嶇О
+ */
+ private String name;
+
+ /**
+ * SKU 鏉$爜
+ */
+ private String barCode;
+
+ /**
+ * 闀垮害锛坈m锛�
+ */
+ private BigDecimal length;
+
+ /**
+ * 瀹藉害锛坈m锛�
+ */
+ private BigDecimal width;
+
+ /**
+ * 楂樺害锛坈m锛�
+ */
+ private BigDecimal height;
+
+ /**
+ * 姣涢噸锛坘g锛�
+ */
+ private BigDecimal grossWeight;
+
+ /**
+ * 鍑�閲嶏紙kg锛�
+ */
+ private BigDecimal netWeight;
+
+ /**
+ * 鎴愭湰浠�
+ */
+ private BigDecimal costPrice;
+
+ /**
+ * 閿�鍞环
+ */
+ private BigDecimal salePrice;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java
new file mode 100644
index 0000000..c9e68ef
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.unit;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MDM 璁¢噺鍗曚綅 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("md_unit_measure")
+@KeySequence("md_unit_measure_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmUnitMeasureDO extends BaseDO {
+
+ /**
+ * 鍗曚綅缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍗曚綅缂栫爜
+ */
+ private String code;
+
+ /**
+ * 鍗曚綅鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鏄惁涓诲崟浣�
+ */
+ private Boolean primaryFlag;
+
+ /**
+ * 涓诲崟浣嶇紪鍙�
+ */
+ private Long primaryId;
+
+ /**
+ * 涓庝富鍗曚綅鎹㈢畻姣斾緥
+ */
+ private BigDecimal changeRate;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java
new file mode 100644
index 0000000..3ddde34
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MDM 浠撳簱涓绘暟鎹� DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("md_warehouse")
+@KeySequence("md_warehouse_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MdmWarehouseDO extends BaseDO {
+
+ /**
+ * 浠撳簱ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 浠撳簱缂栫爜
+ */
+ private String code;
+
+ /**
+ * 浠撳簱鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 浠撳簱鍦板潃
+ */
+ private String address;
+
+ /**
+ * 闈㈢Н锛堝钩鏂圭背锛�
+ */
+ private BigDecimal area;
+
+ /**
+ * 浠撳簱绫诲瀷
+ *
+ * 1: 鍘熸枡浠�
+ * 2: 鎴愬搧浠�
+ * 3: 鍗婃垚鍝佷粨
+ * 4: 绾胯竟浠�
+ */
+ private Integer type;
+
+ /**
+ * 璐熻矗浜虹敤鎴稩D
+ */
+ private Long chargeUserId;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 鏄惁鍐荤粨
+ */
+ private Boolean frozen;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/brand/MdmBrandMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/brand/MdmBrandMapper.java
new file mode 100644
index 0000000..0b4e107
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/brand/MdmBrandMapper.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandPageReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 鍝佺墝 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmBrandMapper extends BaseMapperX<MdmBrandDO> {
+
+ default PageResult<MdmBrandDO> selectPage(MdmBrandPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<MdmBrandDO>()
+ .likeIfPresent(MdmBrandDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmBrandDO::getName, reqVO.getName())
+ .eqIfPresent(MdmBrandDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(MdmBrandDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MdmBrandDO::getId));
+ }
+
+ default List<MdmBrandDO> selectList(MdmBrandDO reqVO) {
+ return selectList(new LambdaQueryWrapperX<MdmBrandDO>()
+ .likeIfPresent(MdmBrandDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmBrandDO::getName, reqVO.getName())
+ .eqIfPresent(MdmBrandDO::getStatus, reqVO.getStatus())
+ .orderByDesc(MdmBrandDO::getId));
+ }
+
+ default MdmBrandDO selectByCode(String code) {
+ return selectOne(MdmBrandDO::getCode, code);
+ }
+
+ default MdmBrandDO selectByName(String name) {
+ return selectOne(MdmBrandDO::getName, name);
+ }
+
+ default List<MdmBrandDO> selectListByIds(Collection<Long> ids) {
+ return selectList(MdmBrandDO::getId, ids);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/category/MdmItemCategoryMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/category/MdmItemCategoryMapper.java
new file mode 100644
index 0000000..0678ec4
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/category/MdmItemCategoryMapper.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.category;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 鐗╂枡鍒嗙被 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmItemCategoryMapper extends BaseMapperX<MdmItemCategoryDO> {
+
+ default PageResult<MdmItemCategoryDO> selectPage(MdmItemCategoryPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<MdmItemCategoryDO>()
+ .likeIfPresent(MdmItemCategoryDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmItemCategoryDO::getName, reqVO.getName())
+ .eqIfPresent(MdmItemCategoryDO::getParentId, reqVO.getParentId())
+ .eqIfPresent(MdmItemCategoryDO::getItemType, reqVO.getItemType())
+ .eqIfPresent(MdmItemCategoryDO::getStatus, reqVO.getStatus())
+ .orderByAsc(MdmItemCategoryDO::getSort));
+ }
+
+ default List<MdmItemCategoryDO> selectList(MdmItemCategoryDO reqVO) {
+ return selectList(new LambdaQueryWrapperX<MdmItemCategoryDO>()
+ .likeIfPresent(MdmItemCategoryDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmItemCategoryDO::getName, reqVO.getName())
+ .eqIfPresent(MdmItemCategoryDO::getParentId, reqVO.getParentId())
+ .eqIfPresent(MdmItemCategoryDO::getItemType, reqVO.getItemType())
+ .eqIfPresent(MdmItemCategoryDO::getStatus, reqVO.getStatus())
+ .orderByAsc(MdmItemCategoryDO::getSort));
+ }
+
+ default MdmItemCategoryDO selectByCode(String code) {
+ return selectOne(MdmItemCategoryDO::getCode, code);
+ }
+
+ default MdmItemCategoryDO selectByParentIdAndName(Long parentId, String name) {
+ return selectOne(new LambdaQueryWrapperX<MdmItemCategoryDO>()
+ .eq(MdmItemCategoryDO::getParentId, parentId)
+ .eq(MdmItemCategoryDO::getName, name));
+ }
+
+ default List<MdmItemCategoryDO> selectListByParentId(Long parentId) {
+ return selectList(MdmItemCategoryDO::getParentId, parentId);
+ }
+
+ default Long selectCountByParentId(Long parentId) {
+ return selectCount(MdmItemCategoryDO::getParentId, parentId);
+ }
+
+ default List<MdmItemCategoryDO> selectListByIds(Collection<Long> ids) {
+ return selectList(MdmItemCategoryDO::getId, ids);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemBatchConfigMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemBatchConfigMapper.java
new file mode 100644
index 0000000..944e428
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemBatchConfigMapper.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.item;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MDM 鐗╂枡鎵规灞炴�ч厤缃� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmItemBatchConfigMapper extends BaseMapperX<MdmItemBatchConfigDO> {
+
+ default MdmItemBatchConfigDO selectByItemId(Long itemId) {
+ return selectOne(MdmItemBatchConfigDO::getItemId, itemId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
new file mode 100644
index 0000000..0ab2d68
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.item;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 鐗╂枡涓绘暟鎹� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmItemMapper extends BaseMapperX<MdmItemDO> {
+
+ default PageResult<MdmItemDO> selectPage(MdmItemPageReqVO reqVO, Collection<Long> categoryIds) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<MdmItemDO>()
+ .likeIfPresent(MdmItemDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmItemDO::getName, reqVO.getName())
+ .likeIfPresent(MdmItemDO::getBarCode, reqVO.getBarCode())
+ .inIfPresent(MdmItemDO::getCategoryId, categoryIds)
+ .eqIfPresent(MdmItemDO::getItemType, reqVO.getItemType())
+ .eqIfPresent(MdmItemDO::getBrandId, reqVO.getBrandId())
+ .eqIfPresent(MdmItemDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(MdmItemDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MdmItemDO::getId));
+ }
+
+ default List<MdmItemDO> selectList(MdmItemPageReqVO reqVO, Collection<Long> categoryIds) {
+ return selectList(new LambdaQueryWrapperX<MdmItemDO>()
+ .likeIfPresent(MdmItemDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmItemDO::getName, reqVO.getName())
+ .inIfPresent(MdmItemDO::getCategoryId, categoryIds)
+ .eqIfPresent(MdmItemDO::getItemType, reqVO.getItemType())
+ .eqIfPresent(MdmItemDO::getStatus, reqVO.getStatus())
+ .orderByDesc(MdmItemDO::getId));
+ }
+
+ default MdmItemDO selectByCode(String code) {
+ return selectOne(MdmItemDO::getCode, code);
+ }
+
+ default MdmItemDO selectByBarCode(String barCode) {
+ return selectOne(MdmItemDO::getBarCode, barCode);
+ }
+
+ default Long selectCountByCategoryId(Long categoryId) {
+ return selectCount(MdmItemDO::getCategoryId, categoryId);
+ }
+
+ default Long selectCountByUnitMeasureId(Long unitMeasureId) {
+ return selectCount(MdmItemDO::getUnitMeasureId, unitMeasureId);
+ }
+
+ default Long selectCountByBrandId(Long brandId) {
+ return selectCount(MdmItemDO::getBrandId, brandId);
+ }
+
+ default List<MdmItemDO> selectListByIds(Collection<Long> ids) {
+ return selectList(MdmItemDO::getId, ids);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemSkuMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemSkuMapper.java
new file mode 100644
index 0000000..422d31e
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemSkuMapper.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.item;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemSkuDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 鐗╂枡 SKU Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmItemSkuMapper extends BaseMapperX<MdmItemSkuDO> {
+
+ default MdmItemSkuDO selectByCode(String code) {
+ return selectOne(MdmItemSkuDO::getCode, code);
+ }
+
+ default MdmItemSkuDO selectByBarCode(String barCode) {
+ return selectOne(MdmItemSkuDO::getBarCode, barCode);
+ }
+
+ default List<MdmItemSkuDO> selectListByItemId(Long itemId) {
+ return selectList(new LambdaQueryWrapperX<MdmItemSkuDO>()
+ .eq(MdmItemSkuDO::getItemId, itemId)
+ .orderByAsc(MdmItemSkuDO::getId));
+ }
+
+ default List<MdmItemSkuDO> selectListByItemIds(Collection<Long> itemIds) {
+ return selectList(MdmItemSkuDO::getItemId, itemIds);
+ }
+
+ default int deleteByItemId(Long itemId) {
+ return delete(MdmItemSkuDO::getItemId, itemId);
+ }
+
+ default long selectCountByItemId(Long itemId) {
+ return selectCount(MdmItemSkuDO::getItemId, itemId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/unit/MdmUnitMeasureMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/unit/MdmUnitMeasureMapper.java
new file mode 100644
index 0000000..e9ad95b
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/unit/MdmUnitMeasureMapper.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.unit;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 璁¢噺鍗曚綅 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmUnitMeasureMapper extends BaseMapperX<MdmUnitMeasureDO> {
+
+ default PageResult<MdmUnitMeasureDO> selectPage(MdmUnitMeasurePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<MdmUnitMeasureDO>()
+ .likeIfPresent(MdmUnitMeasureDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmUnitMeasureDO::getName, reqVO.getName())
+ .eqIfPresent(MdmUnitMeasureDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(MdmUnitMeasureDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MdmUnitMeasureDO::getId));
+ }
+
+ default List<MdmUnitMeasureDO> selectList(MdmUnitMeasureDO reqVO) {
+ return selectList(new LambdaQueryWrapperX<MdmUnitMeasureDO>()
+ .likeIfPresent(MdmUnitMeasureDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmUnitMeasureDO::getName, reqVO.getName())
+ .eqIfPresent(MdmUnitMeasureDO::getStatus, reqVO.getStatus())
+ .orderByDesc(MdmUnitMeasureDO::getId));
+ }
+
+ default MdmUnitMeasureDO selectByCode(String code) {
+ return selectOne(MdmUnitMeasureDO::getCode, code);
+ }
+
+ default MdmUnitMeasureDO selectByName(String name) {
+ return selectOne(MdmUnitMeasureDO::getName, name);
+ }
+
+ default List<MdmUnitMeasureDO> selectListByIds(Collection<Long> ids) {
+ return selectList(MdmUnitMeasureDO::getId, ids);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/warehouse/MdmWarehouseMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/warehouse/MdmWarehouseMapper.java
new file mode 100644
index 0000000..7356d88
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/warehouse/MdmWarehouseMapper.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mdm.dal.mysql.warehouse;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo.MdmWarehousePageReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse.MdmWarehouseDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 浠撳簱 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface MdmWarehouseMapper extends BaseMapperX<MdmWarehouseDO> {
+
+ default PageResult<MdmWarehouseDO> selectPage(MdmWarehousePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<MdmWarehouseDO>()
+ .likeIfPresent(MdmWarehouseDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmWarehouseDO::getName, reqVO.getName())
+ .eqIfPresent(MdmWarehouseDO::getType, reqVO.getType())
+ .eqIfPresent(MdmWarehouseDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(MdmWarehouseDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MdmWarehouseDO::getId));
+ }
+
+ default List<MdmWarehouseDO> selectList(MdmWarehouseDO reqVO) {
+ return selectList(new LambdaQueryWrapperX<MdmWarehouseDO>()
+ .likeIfPresent(MdmWarehouseDO::getCode, reqVO.getCode())
+ .likeIfPresent(MdmWarehouseDO::getName, reqVO.getName())
+ .eqIfPresent(MdmWarehouseDO::getType, reqVO.getType())
+ .eqIfPresent(MdmWarehouseDO::getStatus, reqVO.getStatus())
+ .orderByDesc(MdmWarehouseDO::getId));
+ }
+
+ default MdmWarehouseDO selectByCode(String code) {
+ return selectOne(MdmWarehouseDO::getCode, code);
+ }
+
+ default MdmWarehouseDO selectByName(String name) {
+ return selectOne(MdmWarehouseDO::getName, name);
+ }
+
+ default List<MdmWarehouseDO> selectListByIds(Collection<Long> ids) {
+ return selectList(MdmWarehouseDO::getId, ids);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000..8186e50
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.mdm.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * MDM 閿欒鐮佹灇涓剧被
+ *
+ * mdm 妯″潡锛屼娇鐢� 1-050-000 娈�
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 鐗╂枡鐩稿叧 1-050-001 ~ 1-050-050 ==========
+ ErrorCode MDM_ITEM_NOT_EXISTS = new ErrorCode(1_050_001, "鐗╂枡涓嶅瓨鍦�");
+ ErrorCode MDM_ITEM_IS_DISABLE = new ErrorCode(1_050_002, "鐗╂枡宸茬鐢�");
+ ErrorCode MDM_ITEM_CODE_DUPLICATE = new ErrorCode(1_050_003, "鐗╂枡缂栫爜宸插瓨鍦�");
+ ErrorCode MDM_ITEM_SKU_NOT_EXISTS = new ErrorCode(1_050_004, "鐗╂枡SKU涓嶅瓨鍦�");
+ ErrorCode MDM_ITEM_SKU_IS_DISABLE = new ErrorCode(1_050_005, "鐗╂枡SKU宸茬鐢�");
+ ErrorCode MDM_ITEM_SKU_CODE_DUPLICATE = new ErrorCode(1_050_006, "SKU缂栫爜宸插瓨鍦�");
+
+ // ========== 鐗╂枡鍒嗙被鐩稿叧 1-050-051 ~ 1-050-100 ==========
+ ErrorCode MDM_ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_051, "鐗╂枡鍒嗙被涓嶅瓨鍦�");
+ ErrorCode MDM_ITEM_CATEGORY_CODE_DUPLICATE = new ErrorCode(1_050_052, "鐗╂枡鍒嗙被缂栫爜宸插瓨鍦�");
+ ErrorCode MDM_ITEM_CATEGORY_NAME_DUPLICATE = new ErrorCode(1_050_053, "鐗╂枡鍒嗙被鍚嶇О宸插瓨鍦�");
+ ErrorCode MDM_ITEM_CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_050_054, "鐖跺垎绫讳笉瀛樺湪");
+ ErrorCode MDM_ITEM_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_050_055, "瀛樺湪瀛愬垎绫伙紝鏃犳硶鍒犻櫎");
+ ErrorCode MDM_ITEM_CATEGORY_EXISTS_ITEM = new ErrorCode(1_050_056, "鍒嗙被涓嬪瓨鍦ㄧ墿鏂欙紝鏃犳硶鍒犻櫎");
+
+ // ========== 璁¢噺鍗曚綅鐩稿叧 1-050-101 ~ 1-050-150 ==========
+ ErrorCode MDM_UNIT_MEASURE_NOT_EXISTS = new ErrorCode(1_050_101, "璁¢噺鍗曚綅涓嶅瓨鍦�");
+ ErrorCode MDM_UNIT_MEASURE_CODE_DUPLICATE = new ErrorCode(1_050_102, "璁¢噺鍗曚綅缂栫爜宸插瓨鍦�");
+ ErrorCode MDM_UNIT_MEASURE_NAME_DUPLICATE = new ErrorCode(1_050_103, "璁¢噺鍗曚綅鍚嶇О宸插瓨鍦�");
+ ErrorCode MDM_UNIT_MEASURE_EXISTS_ITEM = new ErrorCode(1_050_104, "璁¢噺鍗曚綅琚墿鏂欎娇鐢紝鏃犳硶鍒犻櫎");
+
+ // ========== 鍝佺墝鐩稿叧 1-050-151 ~ 1-050-200 ==========
+ ErrorCode MDM_BRAND_NOT_EXISTS = new ErrorCode(1_050_151, "鍝佺墝涓嶅瓨鍦�");
+ ErrorCode MDM_BRAND_CODE_DUPLICATE = new ErrorCode(1_050_152, "鍝佺墝缂栫爜宸插瓨鍦�");
+ ErrorCode MDM_BRAND_NAME_DUPLICATE = new ErrorCode(1_050_153, "鍝佺墝鍚嶇О宸插瓨鍦�");
+ ErrorCode MDM_BRAND_EXISTS_ITEM = new ErrorCode(1_050_154, "鍝佺墝琚墿鏂欎娇鐢紝鏃犳硶鍒犻櫎");
+
+ // ========== 浠撳簱鐩稿叧 1-050-201 ~ 1-050-250 ==========
+ ErrorCode MDM_WAREHOUSE_NOT_EXISTS = new ErrorCode(1_050_201, "浠撳簱涓嶅瓨鍦�");
+ ErrorCode MDM_WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_050_202, "浠撳簱缂栫爜宸插瓨鍦�");
+ ErrorCode MDM_WAREHOUSE_NAME_DUPLICATE = new ErrorCode(1_050_203, "浠撳簱鍚嶇О宸插瓨鍦�");
+ ErrorCode MDM_WAREHOUSE_IS_DISABLE = new ErrorCode(1_050_204, "浠撳簱宸茬鐢�");
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
new file mode 100644
index 0000000..ce4eebf
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.mdm.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * MDM 鐗╂枡绫诲瀷鏋氫妇
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum MdmItemTypeEnum {
+
+ RAW_MATERIAL(1, "鍘熸枡"),
+ SEMI_FINISHED(2, "鍗婃垚鍝�"),
+ FINISHED(3, "鎴愬搧"),
+ AUXILIARY(4, "杈呮枡");
+
+ /**
+ * 绫诲瀷缂栫爜
+ */
+ private final Integer type;
+
+ /**
+ * 绫诲瀷鍚嶇О
+ */
+ private final String name;
+
+ /**
+ * 鏍规嵁绫诲瀷缂栫爜鑾峰彇鏋氫妇
+ */
+ public static MdmItemTypeEnum fromType(Integer type) {
+ for (MdmItemTypeEnum value : values()) {
+ if (value.getType().equals(type)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandService.java
new file mode 100644
index 0000000..66e35dc
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandService.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.mdm.service.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 鍝佺墝 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmBrandService {
+
+ /**
+ * 鍒涘缓鍝佺墝
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createBrand(@Valid MdmBrandSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鍝佺墝
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateBrand(@Valid MdmBrandSaveReqVO updateReqVO);
+
+ /**
+ * 鏇存柊鍝佺墝鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateBrandStatus(Long id, Integer status);
+
+ /**
+ * 鍒犻櫎鍝佺墝
+ *
+ * @param id 缂栧彿
+ */
+ void deleteBrand(Long id);
+
+ /**
+ * 鑾峰緱鍝佺墝
+ *
+ * @param id 缂栧彿
+ * @return 鍝佺墝
+ */
+ MdmBrandDO getBrand(Long id);
+
+ /**
+ * 鏍¢獙鍝佺墝瀛樺湪
+ *
+ * @param id 缂栧彿
+ * @return 鍝佺墝
+ */
+ MdmBrandDO validateBrandExists(Long id);
+
+ /**
+ * 鑾峰緱鍝佺墝鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 鍝佺墝鍒嗛〉
+ */
+ PageResult<MdmBrandDO> getBrandPage(MdmBrandPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鍝佺墝鍒楄〃
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 鍝佺墝鍒楄〃
+ */
+ List<MdmBrandDO> getBrandList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱鍝佺墝 Map
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 鍝佺墝 Map
+ */
+ default Map<Long, MdmBrandDO> getBrandMap(Collection<Long> ids) {
+ return convertMap(getBrandList(ids), MdmBrandDO::getId);
+ }
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇鍝佺墝
+ *
+ * @param code 鍝佺墝缂栫爜
+ * @return 鍝佺墝
+ */
+ MdmBrandDO getBrandByCode(String code);
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandServiceImpl.java
new file mode 100644
index 0000000..a53e69c
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandServiceImpl.java
@@ -0,0 +1,124 @@
+package cn.iocoder.yudao.module.mdm.service.brand;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.brand.MdmBrandMapper;
+import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
+
+/**
+ * MDM 鍝佺墝 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MdmBrandServiceImpl implements MdmBrandService {
+
+ @Resource
+ private MdmBrandMapper brandMapper;
+
+ @Resource
+ private MdmItemMapper itemMapper;
+
+ @Override
+ public Long createBrand(MdmBrandSaveReqVO createReqVO) {
+ // 鏍¢獙鏁版嵁
+ validateBrandSaveData(createReqVO);
+
+ // 鎻掑叆
+ MdmBrandDO brand = BeanUtils.toBean(createReqVO, MdmBrandDO.class);
+ brandMapper.insert(brand);
+ return brand.getId();
+ }
+
+ @Override
+ public void updateBrand(MdmBrandSaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ validateBrandExists(updateReqVO.getId());
+ // 鏍¢獙鏁版嵁
+ validateBrandSaveData(updateReqVO);
+
+ // 鏇存柊
+ MdmBrandDO updateObj = BeanUtils.toBean(updateReqVO, MdmBrandDO.class);
+ brandMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void updateBrandStatus(Long id, Integer status) {
+ // 鏍¢獙瀛樺湪
+ validateBrandExists(id);
+ // 鏇存柊鐘舵��
+ brandMapper.updateById(new MdmBrandDO().setId(id).setStatus(status));
+ }
+
+ @Override
+ public void deleteBrand(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateBrandExists(id);
+ // 鏍¢獙鏄惁琚墿鏂欎娇鐢紙鐩存帴浣跨敤 Mapper 閬垮厤寰幆渚濊禆锛�
+ if (itemMapper.selectCountByBrandId(id) > 0) {
+ throw exception(MDM_BRAND_EXISTS_ITEM);
+ }
+ // 鍒犻櫎
+ brandMapper.deleteById(id);
+ }
+
+ @Override
+ public MdmBrandDO validateBrandExists(Long id) {
+ MdmBrandDO brand = brandMapper.selectById(id);
+ if (brand == null) {
+ throw exception(MDM_BRAND_NOT_EXISTS);
+ }
+ return brand;
+ }
+
+ private void validateBrandSaveData(MdmBrandSaveReqVO reqVO) {
+ // 鏍¢獙缂栫爜鍞竴
+ MdmBrandDO brand = brandMapper.selectByCode(reqVO.getCode());
+ if (brand != null && ObjUtil.notEqual(brand.getId(), reqVO.getId())) {
+ throw exception(MDM_BRAND_CODE_DUPLICATE);
+ }
+ // 鏍¢獙鍚嶇О鍞竴
+ MdmBrandDO sameName = brandMapper.selectByName(reqVO.getName());
+ if (sameName != null && ObjUtil.notEqual(sameName.getId(), reqVO.getId())) {
+ throw exception(MDM_BRAND_NAME_DUPLICATE);
+ }
+ }
+
+ @Override
+ public MdmBrandDO getBrand(Long id) {
+ return brandMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<MdmBrandDO> getBrandPage(MdmBrandPageReqVO pageReqVO) {
+ return brandMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<MdmBrandDO> getBrandList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return brandMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public MdmBrandDO getBrandByCode(String code) {
+ return brandMapper.selectByCode(code);
+ }
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java
new file mode 100644
index 0000000..6aa972d
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.mdm.service.category;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategorySaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 鐗╂枡鍒嗙被 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmItemCategoryService {
+
+ /**
+ * 鍒涘缓鐗╂枡鍒嗙被
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createItemCategory(@Valid MdmItemCategorySaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鐗╂枡鍒嗙被
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateItemCategory(@Valid MdmItemCategorySaveReqVO updateReqVO);
+
+ /**
+ * 鏇存柊鐗╂枡鍒嗙被鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateItemCategoryStatus(Long id, Integer status);
+
+ /**
+ * 鍒犻櫎鐗╂枡鍒嗙被
+ *
+ * @param id 缂栧彿
+ */
+ void deleteItemCategory(Long id);
+
+ /**
+ * 鑾峰緱鐗╂枡鍒嗙被
+ *
+ * @param id 缂栧彿
+ * @return 鐗╂枡鍒嗙被
+ */
+ MdmItemCategoryDO getItemCategory(Long id);
+
+ /**
+ * 鏍¢獙鐗╂枡鍒嗙被瀛樺湪
+ *
+ * @param id 缂栧彿
+ * @return 鐗╂枡鍒嗙被
+ */
+ MdmItemCategoryDO validateItemCategoryExists(Long id);
+
+ /**
+ * 鑾峰緱鐗╂枡鍒嗙被鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 鐗╂枡鍒嗙被鍒嗛〉
+ */
+ PageResult<MdmItemCategoryDO> getItemCategoryPage(MdmItemCategoryPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鐗╂枡鍒嗙被鍒楄〃
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 鐗╂枡鍒嗙被鍒楄〃
+ */
+ List<MdmItemCategoryDO> getItemCategoryList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱鐗╂枡鍒嗙被 Map
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 鐗╂枡鍒嗙被 Map
+ */
+ default Map<Long, MdmItemCategoryDO> getItemCategoryMap(Collection<Long> ids) {
+ return convertMap(getItemCategoryList(ids), MdmItemCategoryDO::getId);
+ }
+
+ /**
+ * 鑾峰緱鎸囧畾鍒嗙被鐨勫瓙鍒嗙被鍒楄〃锛堝寘鍚嚜宸憋級
+ *
+ * @param id 鍒嗙被缂栧彿
+ * @return 瀛愬垎绫诲垪琛�
+ */
+ List<MdmItemCategoryDO> getItemCategoryChildrenList(Long id);
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java
new file mode 100644
index 0000000..5565f94
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java
@@ -0,0 +1,153 @@
+package cn.iocoder.yudao.module.mdm.service.category;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategorySaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.category.MdmItemCategoryMapper;
+import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
+
+/**
+ * MDM 鐗╂枡鍒嗙被 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MdmItemCategoryServiceImpl implements MdmItemCategoryService {
+
+ @Resource
+ private MdmItemCategoryMapper itemCategoryMapper;
+
+ @Resource
+ private MdmItemMapper itemMapper;
+
+ @Override
+ public Long createItemCategory(MdmItemCategorySaveReqVO createReqVO) {
+ // 鏍¢獙鏁版嵁
+ validateItemCategorySaveData(createReqVO);
+
+ // 鎻掑叆
+ MdmItemCategoryDO itemCategory = BeanUtils.toBean(createReqVO, MdmItemCategoryDO.class);
+ itemCategoryMapper.insert(itemCategory);
+ return itemCategory.getId();
+ }
+
+ @Override
+ public void updateItemCategory(MdmItemCategorySaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ validateItemCategoryExists(updateReqVO.getId());
+ // 鏍¢獙鏁版嵁
+ validateItemCategorySaveData(updateReqVO);
+
+ // 鏇存柊
+ MdmItemCategoryDO updateObj = BeanUtils.toBean(updateReqVO, MdmItemCategoryDO.class);
+ itemCategoryMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void updateItemCategoryStatus(Long id, Integer status) {
+ // 鏍¢獙瀛樺湪
+ validateItemCategoryExists(id);
+ // 鏇存柊鐘舵��
+ itemCategoryMapper.updateById(new MdmItemCategoryDO().setId(id).setStatus(status));
+ }
+
+ @Override
+ public void deleteItemCategory(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateItemCategoryExists(id);
+ // 鏍¢獙鏄惁鏈夊瓙鍒嗙被
+ if (itemCategoryMapper.selectCountByParentId(id) > 0) {
+ throw exception(MDM_ITEM_CATEGORY_EXISTS_CHILDREN);
+ }
+ // 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鐗╂枡锛堢洿鎺ヤ娇鐢� Mapper 閬垮厤寰幆渚濊禆锛�
+ if (itemMapper.selectCountByCategoryId(id) > 0) {
+ throw exception(MDM_ITEM_CATEGORY_EXISTS_ITEM);
+ }
+ // 鍒犻櫎
+ itemCategoryMapper.deleteById(id);
+ }
+
+ @Override
+ public MdmItemCategoryDO validateItemCategoryExists(Long id) {
+ MdmItemCategoryDO itemCategory = itemCategoryMapper.selectById(id);
+ if (itemCategory == null) {
+ throw exception(MDM_ITEM_CATEGORY_NOT_EXISTS);
+ }
+ return itemCategory;
+ }
+
+ private void validateItemCategorySaveData(MdmItemCategorySaveReqVO reqVO) {
+ // 鏍¢獙缂栫爜鍞竴
+ MdmItemCategoryDO category = itemCategoryMapper.selectByCode(reqVO.getCode());
+ if (category != null && ObjUtil.notEqual(category.getId(), reqVO.getId())) {
+ throw exception(MDM_ITEM_CATEGORY_CODE_DUPLICATE);
+ }
+ // 鏍¢獙鍚屼竴鐖剁骇涓嬪悕绉板敮涓�
+ MdmItemCategoryDO sameName = itemCategoryMapper.selectByParentIdAndName(
+ reqVO.getParentId() != null ? reqVO.getParentId() : 0L, reqVO.getName());
+ if (sameName != null && ObjUtil.notEqual(sameName.getId(), reqVO.getId())) {
+ throw exception(MDM_ITEM_CATEGORY_NAME_DUPLICATE);
+ }
+ // 鏍¢獙鐖跺垎绫诲瓨鍦�
+ if (reqVO.getParentId() != null && reqVO.getParentId() > 0) {
+ if (itemCategoryMapper.selectById(reqVO.getParentId()) == null) {
+ throw exception(MDM_ITEM_CATEGORY_PARENT_NOT_EXISTS);
+ }
+ }
+ }
+
+ @Override
+ public MdmItemCategoryDO getItemCategory(Long id) {
+ return itemCategoryMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<MdmItemCategoryDO> getItemCategoryPage(MdmItemCategoryPageReqVO pageReqVO) {
+ return itemCategoryMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<MdmItemCategoryDO> getItemCategoryList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return itemCategoryMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public List<MdmItemCategoryDO> getItemCategoryChildrenList(Long id) {
+ List<MdmItemCategoryDO> result = new ArrayList<>();
+ // 娣诲姞鑷繁
+ MdmItemCategoryDO self = itemCategoryMapper.selectById(id);
+ if (self != null) {
+ result.add(self);
+ }
+ // 閫掑綊鑾峰彇鎵�鏈夊瓙鍒嗙被
+ loadChildren(id, result);
+ return result;
+ }
+
+ private void loadChildren(Long parentId, List<MdmItemCategoryDO> result) {
+ List<MdmItemCategoryDO> children = itemCategoryMapper.selectListByParentId(parentId);
+ if (CollUtil.isNotEmpty(children)) {
+ result.addAll(children);
+ for (MdmItemCategoryDO child : children) {
+ loadChildren(child.getId(), result);
+ }
+ }
+ }
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java
new file mode 100644
index 0000000..7235c20
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java
@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.module.mdm.service.item;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 鐗╂枡涓绘暟鎹� Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmItemService {
+
+ /**
+ * 鍒涘缓鐗╂枡
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createItem(@Valid MdmItemSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鐗╂枡
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateItem(@Valid MdmItemSaveReqVO updateReqVO);
+
+ /**
+ * 鏇存柊鐗╂枡鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateItemStatus(Long id, Integer status);
+
+ /**
+ * 鍒犻櫎鐗╂枡
+ *
+ * @param id 缂栧彿
+ */
+ void deleteItem(Long id);
+
+ /**
+ * 鑾峰緱鐗╂枡
+ *
+ * @param id 缂栧彿
+ * @return 鐗╂枡
+ */
+ MdmItemDO getItem(Long id);
+
+ /**
+ * 鏍¢獙鐗╂枡瀛樺湪
+ *
+ * @param id 缂栧彿
+ * @return 鐗╂枡
+ */
+ MdmItemDO validateItemExists(Long id);
+
+ /**
+ * 鏍¢獙鐗╂枡瀛樺湪涓斿惎鐢�
+ *
+ * @param id 缂栧彿
+ * @return 鐗╂枡
+ */
+ MdmItemDO validateItemExistsAndEnable(Long id);
+
+ /**
+ * 鑾峰緱鐗╂枡鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 鐗╂枡鍒嗛〉
+ */
+ PageResult<MdmItemDO> getItemPage(MdmItemPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鐗╂枡鍒楄〃
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 鐗╂枡鍒楄〃
+ */
+ List<MdmItemDO> getItemList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱鐗╂枡 Map
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 鐗╂枡 Map
+ */
+ default Map<Long, MdmItemDO> getItemMap(Collection<Long> ids) {
+ return convertMap(getItemList(ids), MdmItemDO::getId);
+ }
+
+ /**
+ * 鍩轰簬鐗╂枡鍒嗙被缂栧彿锛岃幏寰楃墿鏂欐暟閲�
+ *
+ * @param categoryId 鐗╂枡鍒嗙被缂栧彿
+ * @return 鐗╂枡鏁伴噺
+ */
+ Long getItemCountByCategoryId(Long categoryId);
+
+ /**
+ * 鍩轰簬璁¢噺鍗曚綅缂栧彿锛岃幏寰楃墿鏂欐暟閲�
+ *
+ * @param unitMeasureId 璁¢噺鍗曚綅缂栧彿
+ * @return 鐗╂枡鏁伴噺
+ */
+ Long getItemCountByUnitMeasureId(Long unitMeasureId);
+
+ /**
+ * 鍩轰簬鍝佺墝缂栧彿锛岃幏寰楃墿鏂欐暟閲�
+ *
+ * @param brandId 鍝佺墝缂栧彿
+ * @return 鐗╂枡鏁伴噺
+ */
+ Long getItemCountByBrandId(Long brandId);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇鐗╂枡
+ *
+ * @param code 鐗╂枡缂栫爜
+ * @return 鐗╂枡
+ */
+ MdmItemDO getItemByCode(String code);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
new file mode 100644
index 0000000..b273316
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -0,0 +1,189 @@
+package cn.iocoder.yudao.module.mdm.service.item;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
+import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
+import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
+
+/**
+ * MDM 鐗╂枡涓绘暟鎹� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MdmItemServiceImpl implements MdmItemService {
+
+ @Resource
+ private MdmItemMapper itemMapper;
+
+ @Resource
+ private MdmItemCategoryService itemCategoryService;
+ @Resource
+ private MdmUnitMeasureService unitMeasureService;
+ @Resource
+ private MdmBrandService brandService;
+
+ @Override
+ public Long createItem(MdmItemSaveReqVO createReqVO) {
+ // 鏍¢獙鏁版嵁
+ validateItemSaveData(createReqVO);
+
+ // 鎻掑叆
+ MdmItemDO item = BeanUtils.toBean(createReqVO, MdmItemDO.class);
+ itemMapper.insert(item);
+ return item.getId();
+ }
+
+ @Override
+ public void updateItem(MdmItemSaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ validateItemExists(updateReqVO.getId());
+ // 鏍¢獙鏁版嵁
+ validateItemSaveData(updateReqVO);
+
+ // 鏇存柊
+ MdmItemDO updateObj = BeanUtils.toBean(updateReqVO, MdmItemDO.class);
+ itemMapper.updateById(updateObj);
+ }
+
+ private void validateItemSaveData(MdmItemSaveReqVO reqVO) {
+ // 鏍¢獙鐗╂枡缂栫爜鐨勫敮涓�鎬�
+ validateItemCodeUnique(reqVO.getId(), reqVO.getCode());
+ // 鏍¢獙鐗╂枡鍒嗙被瀛樺湪
+ validateCategoryExists(reqVO.getCategoryId());
+ // 鏍¢獙璁¢噺鍗曚綅瀛樺湪
+ validateUnitMeasureExists(reqVO.getUnitMeasureId());
+ // 鏍¢獙鍝佺墝瀛樺湪锛堝鏋滄湁锛�
+ if (reqVO.getBrandId() != null) {
+ validateBrandExists(reqVO.getBrandId());
+ }
+ }
+
+ @Override
+ public void updateItemStatus(Long id, Integer status) {
+ // 鏍¢獙瀛樺湪
+ validateItemExists(id);
+ // 鏇存柊鐘舵��
+ itemMapper.updateById(new MdmItemDO().setId(id).setStatus(status));
+ }
+
+ @Override
+ public void deleteItem(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateItemExists(id);
+ // 鍒犻櫎
+ itemMapper.deleteById(id);
+ }
+
+ @Override
+ public MdmItemDO validateItemExists(Long id) {
+ MdmItemDO item = itemMapper.selectById(id);
+ if (item == null) {
+ throw exception(MDM_ITEM_NOT_EXISTS);
+ }
+ return item;
+ }
+
+ @Override
+ public MdmItemDO validateItemExistsAndEnable(Long id) {
+ MdmItemDO item = validateItemExists(id);
+ if (ObjUtil.notEqual(cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus(), item.getStatus())) {
+ throw exception(MDM_ITEM_IS_DISABLE);
+ }
+ return item;
+ }
+
+ private void validateItemCodeUnique(Long id, String code) {
+ MdmItemDO item = itemMapper.selectByCode(code);
+ if (item == null) {
+ return;
+ }
+ if (ObjUtil.notEqual(item.getId(), id)) {
+ throw exception(MDM_ITEM_CODE_DUPLICATE);
+ }
+ }
+
+ private void validateCategoryExists(Long categoryId) {
+ if (itemCategoryService.getItemCategory(categoryId) == null) {
+ throw exception(MDM_ITEM_CATEGORY_NOT_EXISTS);
+ }
+ }
+
+ private void validateUnitMeasureExists(Long unitMeasureId) {
+ if (unitMeasureService.getUnitMeasure(unitMeasureId) == null) {
+ throw exception(MDM_UNIT_MEASURE_NOT_EXISTS);
+ }
+ }
+
+ private void validateBrandExists(Long brandId) {
+ if (brandService.getBrand(brandId) == null) {
+ throw exception(MDM_BRAND_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public MdmItemDO getItem(Long id) {
+ return itemMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<MdmItemDO> getItemPage(MdmItemPageReqVO pageReqVO) {
+ // 鏌ユ壘鏃讹紝濡傛灉鏌ユ壘鏌愪釜鍒嗙被缂栧彿锛屽垯鍖呭惈瀹冪殑瀛愬垎绫�
+ Set<Long> categoryIds = null;
+ if (pageReqVO.getCategoryId() != null) {
+ categoryIds = new HashSet<>();
+ categoryIds.add(pageReqVO.getCategoryId());
+ List<cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO> children =
+ itemCategoryService.getItemCategoryChildrenList(pageReqVO.getCategoryId());
+ categoryIds.addAll(convertSet(children, cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO::getId));
+ }
+ // 鍒嗛〉鏌ヨ
+ return itemMapper.selectPage(pageReqVO, categoryIds);
+ }
+
+ @Override
+ public List<MdmItemDO> getItemList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return itemMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public Long getItemCountByCategoryId(Long categoryId) {
+ return itemMapper.selectCountByCategoryId(categoryId);
+ }
+
+ @Override
+ public Long getItemCountByUnitMeasureId(Long unitMeasureId) {
+ return itemMapper.selectCountByUnitMeasureId(unitMeasureId);
+ }
+
+ @Override
+ public Long getItemCountByBrandId(Long brandId) {
+ return itemMapper.selectCountByBrandId(brandId);
+ }
+
+ @Override
+ public MdmItemDO getItemByCode(String code) {
+ return itemMapper.selectByCode(code);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuService.java
new file mode 100644
index 0000000..4944e36
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuService.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.mdm.service.item;
+
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemSkuDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 鐗╂枡 SKU Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmItemSkuService {
+
+ /**
+ * 鍒涘缓 SKU
+ *
+ * @param sku 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createItemSku(@Valid MdmItemSkuDO sku);
+
+ /**
+ * 鎵归噺鍒涘缓 SKU
+ *
+ * @param itemId 鐗╂枡缂栧彿
+ * @param skuList SKU 鍒楄〃
+ */
+ void createItemSkuList(Long itemId, List<MdmItemSkuDO> skuList);
+
+ /**
+ * 鏇存柊 SKU
+ *
+ * @param sku 鏇存柊淇℃伅
+ */
+ void updateItemSku(@Valid MdmItemSkuDO sku);
+
+ /**
+ * 鎵归噺鏇存柊 SKU
+ *
+ * @param itemId 鐗╂枡缂栧彿
+ * @param skuList SKU 鍒楄〃
+ */
+ void updateItemSkuList(Long itemId, List<MdmItemSkuDO> skuList);
+
+ /**
+ * 鍒犻櫎 SKU
+ *
+ * @param id 缂栧彿
+ */
+ void deleteItemSku(Long id);
+
+ /**
+ * 鍒犻櫎鐗╂枡鐨勬墍鏈� SKU
+ *
+ * @param itemId 鐗╂枡缂栧彿
+ */
+ void deleteItemSkuListByItemId(Long itemId);
+
+ /**
+ * 鑾峰緱 SKU
+ *
+ * @param id 缂栧彿
+ * @return SKU
+ */
+ MdmItemSkuDO getItemSku(Long id);
+
+ /**
+ * 鏍¢獙 SKU 瀛樺湪
+ *
+ * @param id 缂栧彿
+ * @return SKU
+ */
+ MdmItemSkuDO validateItemSkuExists(Long id);
+
+ /**
+ * 鏍¢獙 SKU 瀛樺湪涓斿惎鐢�
+ *
+ * @param id 缂栧彿
+ * @return SKU
+ */
+ MdmItemSkuDO validateItemSkuExistsAndEnable(Long id);
+
+ /**
+ * 鑾峰緱鐗╂枡鐨� SKU 鍒楄〃
+ *
+ * @param itemId 鐗╂枡缂栧彿
+ * @return SKU 鍒楄〃
+ */
+ List<MdmItemSkuDO> getItemSkuListByItemId(Long itemId);
+
+ /**
+ * 鑾峰緱 SKU 鍒楄〃
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return SKU 鍒楄〃
+ */
+ List<MdmItemSkuDO> getItemSkuList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱 SKU Map
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return SKU Map
+ */
+ default Map<Long, MdmItemSkuDO> getItemSkuMap(Collection<Long> ids) {
+ return convertMap(getItemSkuList(ids), MdmItemSkuDO::getId);
+ }
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇 SKU
+ *
+ * @param code SKU 缂栫爜
+ * @return SKU
+ */
+ MdmItemSkuDO getItemSkuByCode(String code);
+
+ /**
+ * 鏍规嵁鏉$爜鑾峰彇 SKU
+ *
+ * @param barCode SKU 鏉$爜
+ * @return SKU
+ */
+ MdmItemSkuDO getItemSkuByBarCode(String barCode);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuServiceImpl.java
new file mode 100644
index 0000000..170844f
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuServiceImpl.java
@@ -0,0 +1,151 @@
+package cn.iocoder.yudao.module.mdm.service.item;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemSkuDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemSkuMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
+
+/**
+ * MDM 鐗╂枡 SKU Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MdmItemSkuServiceImpl implements MdmItemSkuService {
+
+ @Resource
+ private MdmItemSkuMapper itemSkuMapper;
+
+ @Override
+ public Long createItemSku(MdmItemSkuDO sku) {
+ // 鏍¢獙缂栫爜鍞竴
+ validateItemSkuCodeUnique(null, sku.getCode());
+ // 璁剧疆榛樿鐘舵��
+ if (sku.getStatus() == null) {
+ sku.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ }
+ itemSkuMapper.insert(sku);
+ return sku.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void createItemSkuList(Long itemId, List<MdmItemSkuDO> skuList) {
+ if (CollUtil.isEmpty(skuList)) {
+ return;
+ }
+ for (MdmItemSkuDO sku : skuList) {
+ sku.setItemId(itemId);
+ createItemSku(sku);
+ }
+ }
+
+ @Override
+ public void updateItemSku(MdmItemSkuDO sku) {
+ // 鏍¢獙瀛樺湪
+ validateItemSkuExists(sku.getId());
+ // 鏍¢獙缂栫爜鍞竴
+ validateItemSkuCodeUnique(sku.getId(), sku.getCode());
+ itemSkuMapper.updateById(sku);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateItemSkuList(Long itemId, List<MdmItemSkuDO> skuList) {
+ // 绗竴姝ワ紝鍒犻櫎鏃х殑 SKU
+ itemSkuMapper.deleteByItemId(itemId);
+ // 绗簩姝ワ紝鎻掑叆鏂扮殑 SKU
+ if (CollUtil.isEmpty(skuList)) {
+ return;
+ }
+ for (MdmItemSkuDO sku : skuList) {
+ sku.setItemId(itemId);
+ sku.setId(null); // 娓呯┖ ID锛屼綔涓烘柊鎻掑叆
+ if (sku.getStatus() == null) {
+ sku.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ }
+ itemSkuMapper.insert(sku);
+ }
+ }
+
+ private void validateItemSkuCodeUnique(Long id, String code) {
+ MdmItemSkuDO sku = itemSkuMapper.selectByCode(code);
+ if (sku == null) {
+ return;
+ }
+ if (id == null || ObjUtil.notEqual(sku.getId(), id)) {
+ throw exception(MDM_ITEM_SKU_CODE_DUPLICATE);
+ }
+ }
+
+ @Override
+ public void deleteItemSku(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateItemSkuExists(id);
+ // 鍒犻櫎
+ itemSkuMapper.deleteById(id);
+ }
+
+ @Override
+ public void deleteItemSkuListByItemId(Long itemId) {
+ itemSkuMapper.deleteByItemId(itemId);
+ }
+
+ @Override
+ public MdmItemSkuDO validateItemSkuExists(Long id) {
+ MdmItemSkuDO sku = itemSkuMapper.selectById(id);
+ if (sku == null) {
+ throw exception(MDM_ITEM_SKU_NOT_EXISTS);
+ }
+ return sku;
+ }
+
+ @Override
+ public MdmItemSkuDO validateItemSkuExistsAndEnable(Long id) {
+ MdmItemSkuDO sku = validateItemSkuExists(id);
+ if (CommonStatusEnum.isDisable(sku.getStatus())) {
+ throw exception(MDM_ITEM_SKU_IS_DISABLE);
+ }
+ return sku;
+ }
+
+ @Override
+ public MdmItemSkuDO getItemSku(Long id) {
+ return itemSkuMapper.selectById(id);
+ }
+
+ @Override
+ public List<MdmItemSkuDO> getItemSkuListByItemId(Long itemId) {
+ return itemSkuMapper.selectListByItemId(itemId);
+ }
+
+ @Override
+ public List<MdmItemSkuDO> getItemSkuList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return itemSkuMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public MdmItemSkuDO getItemSkuByCode(String code) {
+ return itemSkuMapper.selectByCode(code);
+ }
+
+ @Override
+ public MdmItemSkuDO getItemSkuByBarCode(String barCode) {
+ return itemSkuMapper.selectByBarCode(barCode);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java
new file mode 100644
index 0000000..d5023a0
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.mdm.service.unit;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MDM 璁¢噺鍗曚綅 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmUnitMeasureService {
+
+ /**
+ * 鍒涘缓璁¢噺鍗曚綅
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createUnitMeasure(@Valid MdmUnitMeasureSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊璁¢噺鍗曚綅
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateUnitMeasure(@Valid MdmUnitMeasureSaveReqVO updateReqVO);
+
+ /**
+ * 鏇存柊璁¢噺鍗曚綅鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateUnitMeasureStatus(Long id, Integer status);
+
+ /**
+ * 鍒犻櫎璁¢噺鍗曚綅
+ *
+ * @param id 缂栧彿
+ */
+ void deleteUnitMeasure(Long id);
+
+ /**
+ * 鑾峰緱璁¢噺鍗曚綅
+ *
+ * @param id 缂栧彿
+ * @return 璁¢噺鍗曚綅
+ */
+ MdmUnitMeasureDO getUnitMeasure(Long id);
+
+ /**
+ * 鏍¢獙璁¢噺鍗曚綅瀛樺湪
+ *
+ * @param id 缂栧彿
+ * @return 璁¢噺鍗曚綅
+ */
+ MdmUnitMeasureDO validateUnitMeasureExists(Long id);
+
+ /**
+ * 鑾峰緱璁¢噺鍗曚綅鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 璁¢噺鍗曚綅鍒嗛〉
+ */
+ PageResult<MdmUnitMeasureDO> getUnitMeasurePage(MdmUnitMeasurePageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱璁¢噺鍗曚綅鍒楄〃
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 璁¢噺鍗曚綅鍒楄〃
+ */
+ List<MdmUnitMeasureDO> getUnitMeasureList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱璁¢噺鍗曚綅 Map
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 璁¢噺鍗曚綅 Map
+ */
+ default Map<Long, MdmUnitMeasureDO> getUnitMeasureMap(Collection<Long> ids) {
+ return convertMap(getUnitMeasureList(ids), MdmUnitMeasureDO::getId);
+ }
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇璁¢噺鍗曚綅
+ *
+ * @param code 鍗曚綅缂栫爜
+ * @return 璁¢噺鍗曚綅
+ */
+ MdmUnitMeasureDO getUnitMeasureByCode(String code);
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java
new file mode 100644
index 0000000..f3c581d
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java
@@ -0,0 +1,124 @@
+package cn.iocoder.yudao.module.mdm.service.unit;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import cn.iocoder.yudao.module.mdm.dal.mysql.unit.MdmUnitMeasureMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
+
+/**
+ * MDM 璁¢噺鍗曚綅 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MdmUnitMeasureServiceImpl implements MdmUnitMeasureService {
+
+ @Resource
+ private MdmUnitMeasureMapper unitMeasureMapper;
+
+ @Resource
+ private MdmItemMapper itemMapper;
+
+ @Override
+ public Long createUnitMeasure(MdmUnitMeasureSaveReqVO createReqVO) {
+ // 鏍¢獙鏁版嵁
+ validateUnitMeasureSaveData(createReqVO);
+
+ // 鎻掑叆
+ MdmUnitMeasureDO unitMeasure = BeanUtils.toBean(createReqVO, MdmUnitMeasureDO.class);
+ unitMeasureMapper.insert(unitMeasure);
+ return unitMeasure.getId();
+ }
+
+ @Override
+ public void updateUnitMeasure(MdmUnitMeasureSaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ validateUnitMeasureExists(updateReqVO.getId());
+ // 鏍¢獙鏁版嵁
+ validateUnitMeasureSaveData(updateReqVO);
+
+ // 鏇存柊
+ MdmUnitMeasureDO updateObj = BeanUtils.toBean(updateReqVO, MdmUnitMeasureDO.class);
+ unitMeasureMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void updateUnitMeasureStatus(Long id, Integer status) {
+ // 鏍¢獙瀛樺湪
+ validateUnitMeasureExists(id);
+ // 鏇存柊鐘舵��
+ unitMeasureMapper.updateById(new MdmUnitMeasureDO().setId(id).setStatus(status));
+ }
+
+ @Override
+ public void deleteUnitMeasure(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateUnitMeasureExists(id);
+ // 鏍¢獙鏄惁琚墿鏂欎娇鐢紙鐩存帴浣跨敤 Mapper 閬垮厤寰幆渚濊禆锛�
+ if (itemMapper.selectCountByUnitMeasureId(id) > 0) {
+ throw exception(MDM_UNIT_MEASURE_EXISTS_ITEM);
+ }
+ // 鍒犻櫎
+ unitMeasureMapper.deleteById(id);
+ }
+
+ @Override
+ public MdmUnitMeasureDO validateUnitMeasureExists(Long id) {
+ MdmUnitMeasureDO unitMeasure = unitMeasureMapper.selectById(id);
+ if (unitMeasure == null) {
+ throw exception(MDM_UNIT_MEASURE_NOT_EXISTS);
+ }
+ return unitMeasure;
+ }
+
+ private void validateUnitMeasureSaveData(MdmUnitMeasureSaveReqVO reqVO) {
+ // 鏍¢獙缂栫爜鍞竴
+ MdmUnitMeasureDO unit = unitMeasureMapper.selectByCode(reqVO.getCode());
+ if (unit != null && ObjUtil.notEqual(unit.getId(), reqVO.getId())) {
+ throw exception(MDM_UNIT_MEASURE_CODE_DUPLICATE);
+ }
+ // 鏍¢獙鍚嶇О鍞竴
+ MdmUnitMeasureDO sameName = unitMeasureMapper.selectByName(reqVO.getName());
+ if (sameName != null && ObjUtil.notEqual(sameName.getId(), reqVO.getId())) {
+ throw exception(MDM_UNIT_MEASURE_NAME_DUPLICATE);
+ }
+ }
+
+ @Override
+ public MdmUnitMeasureDO getUnitMeasure(Long id) {
+ return unitMeasureMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<MdmUnitMeasureDO> getUnitMeasurePage(MdmUnitMeasurePageReqVO pageReqVO) {
+ return unitMeasureMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<MdmUnitMeasureDO> getUnitMeasureList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return unitMeasureMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public MdmUnitMeasureDO getUnitMeasureByCode(String code) {
+ return unitMeasureMapper.selectByCode(code);
+ }
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseService.java
new file mode 100644
index 0000000..b517658
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseService.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.mdm.service.warehouse;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo.MdmWarehousePageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo.MdmWarehouseSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse.MdmWarehouseDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MDM 浠撳簱 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MdmWarehouseService {
+
+ /**
+ * 鍒涘缓浠撳簱
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createWarehouse(@Valid MdmWarehouseSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊浠撳簱
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateWarehouse(@Valid MdmWarehouseSaveReqVO updateReqVO);
+
+ /**
+ * 鏇存柊浠撳簱鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateWarehouseStatus(Long id, Integer status);
+
+ /**
+ * 鍒犻櫎浠撳簱
+ *
+ * @param id 缂栧彿
+ */
+ void deleteWarehouse(Long id);
+
+ /**
+ * 鑾峰緱浠撳簱
+ *
+ * @param id 缂栧彿
+ * @return 浠撳簱
+ */
+ MdmWarehouseDO getWarehouse(Long id);
+
+ /**
+ * 鏍¢獙浠撳簱瀛樺湪
+ *
+ * @param id 缂栧彿
+ * @return 浠撳簱
+ */
+ MdmWarehouseDO validateWarehouseExists(Long id);
+
+ /**
+ * 鏍¢獙浠撳簱瀛樺湪涓斿惎鐢�
+ *
+ * @param id 缂栧彿
+ * @return 浠撳簱
+ */
+ MdmWarehouseDO validateWarehouseExistsAndEnable(Long id);
+
+ /**
+ * 鑾峰緱浠撳簱鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 浠撳簱鍒嗛〉
+ */
+ PageResult<MdmWarehouseDO> getWarehousePage(MdmWarehousePageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱浠撳簱鍒楄〃
+ *
+ * @param ids 缂栧彿鏁扮粍
+ * @return 浠撳簱鍒楄〃
+ */
+ List<MdmWarehouseDO> getWarehouseList(Collection<Long> ids);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇浠撳簱
+ *
+ * @param code 浠撳簱缂栫爜
+ * @return 浠撳簱
+ */
+ MdmWarehouseDO getWarehouseByCode(String code);
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseServiceImpl.java
new file mode 100644
index 0000000..994b65e
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseServiceImpl.java
@@ -0,0 +1,126 @@
+package cn.iocoder.yudao.module.mdm.service.warehouse;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo.MdmWarehousePageReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo.MdmWarehouseSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse.MdmWarehouseDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.warehouse.MdmWarehouseMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
+
+/**
+ * MDM 浠撳簱 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MdmWarehouseServiceImpl implements MdmWarehouseService {
+
+ @Resource
+ private MdmWarehouseMapper warehouseMapper;
+
+ @Override
+ public Long createWarehouse(MdmWarehouseSaveReqVO createReqVO) {
+ // 鏍¢獙鏁版嵁
+ validateWarehouseSaveData(createReqVO);
+
+ // 鎻掑叆
+ MdmWarehouseDO warehouse = BeanUtils.toBean(createReqVO, MdmWarehouseDO.class);
+ warehouseMapper.insert(warehouse);
+ return warehouse.getId();
+ }
+
+ @Override
+ public void updateWarehouse(MdmWarehouseSaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ validateWarehouseExists(updateReqVO.getId());
+ // 鏍¢獙鏁版嵁
+ validateWarehouseSaveData(updateReqVO);
+
+ // 鏇存柊
+ MdmWarehouseDO updateObj = BeanUtils.toBean(updateReqVO, MdmWarehouseDO.class);
+ warehouseMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void updateWarehouseStatus(Long id, Integer status) {
+ // 鏍¢獙瀛樺湪
+ validateWarehouseExists(id);
+ // 鏇存柊鐘舵��
+ warehouseMapper.updateById(new MdmWarehouseDO().setId(id).setStatus(status));
+ }
+
+ @Override
+ public void deleteWarehouse(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateWarehouseExists(id);
+ // TODO: 鏍¢獙鏄惁琚簱瀛樹娇鐢�
+ // 鍒犻櫎
+ warehouseMapper.deleteById(id);
+ }
+
+ @Override
+ public MdmWarehouseDO validateWarehouseExists(Long id) {
+ MdmWarehouseDO warehouse = warehouseMapper.selectById(id);
+ if (warehouse == null) {
+ throw exception(MDM_WAREHOUSE_NOT_EXISTS);
+ }
+ return warehouse;
+ }
+
+ @Override
+ public MdmWarehouseDO validateWarehouseExistsAndEnable(Long id) {
+ MdmWarehouseDO warehouse = validateWarehouseExists(id);
+ if (warehouse.getStatus() != null && warehouse.getStatus() != 0) {
+ throw exception(MDM_WAREHOUSE_IS_DISABLE);
+ }
+ return warehouse;
+ }
+
+ private void validateWarehouseSaveData(MdmWarehouseSaveReqVO reqVO) {
+ // 鏍¢獙缂栫爜鍞竴
+ MdmWarehouseDO warehouse = warehouseMapper.selectByCode(reqVO.getCode());
+ if (warehouse != null && ObjUtil.notEqual(warehouse.getId(), reqVO.getId())) {
+ throw exception(MDM_WAREHOUSE_CODE_DUPLICATE);
+ }
+ // 鏍¢獙鍚嶇О鍞竴
+ MdmWarehouseDO sameName = warehouseMapper.selectByName(reqVO.getName());
+ if (sameName != null && ObjUtil.notEqual(sameName.getId(), reqVO.getId())) {
+ throw exception(MDM_WAREHOUSE_NAME_DUPLICATE);
+ }
+ }
+
+ @Override
+ public MdmWarehouseDO getWarehouse(Long id) {
+ return warehouseMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<MdmWarehouseDO> getWarehousePage(MdmWarehousePageReqVO pageReqVO) {
+ return warehouseMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<MdmWarehouseDO> getWarehouseList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return warehouseMapper.selectListByIds(ids);
+ }
+
+ @Override
+ public MdmWarehouseDO getWarehouseByCode(String code) {
+ return warehouseMapper.selectByCode(code);
+ }
+
+}
diff --git a/yudao-module-mes/pom.xml b/yudao-module-mes/pom.xml
index a20eadb..0884fe3 100644
--- a/yudao-module-mes/pom.xml
+++ b/yudao-module-mes/pom.xml
@@ -24,6 +24,20 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞嗘ā鍧� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- BPM 娴佺▼妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-bpm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
index 152502e..87ff297 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
@@ -38,6 +38,7 @@
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "绠$悊鍚庡彴 - MES 鐢熶骇宸ュ崟")
@RestController
@@ -139,6 +140,15 @@
return success(true);
}
+ @PutMapping("/submit")
+ @Operation(summary = "鎻愪氦宸ュ崟瀹℃壒")
+ @Parameter(name = "id", description = "缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')")
+ public CommonResult<Boolean> submitWorkOrder(@RequestParam("id") Long id) {
+ workOrderService.submitWorkOrder(id, getLoginUserId());
+ return success(true);
+ }
+
// ==================== 鎷兼帴 VO ====================
private List<MesProWorkOrderRespVO> buildWorkOrderRespVOList(List<MesProWorkOrderDO> list) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
index 578cfd9..a1affa9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
@@ -131,6 +131,10 @@
*/
private Integer status;
/**
+ * BPM 娴佺▼瀹炰緥缂栧彿
+ */
+ private String processInstanceId;
+ /**
* 澶囨敞
*/
private String remark;
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 14c22c2..1181d4a 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
@@ -263,6 +263,8 @@
ErrorCode PRO_WORK_ORDER_NOT_CONFIRMED = new ErrorCode(1_040_502_003, "鍙湁宸茬‘璁ょ姸鎬佺殑宸ュ崟鎵嶈兘鎵ц姝ゆ搷浣�");
ErrorCode PRO_WORK_ORDER_HAS_CHILDREN = new ErrorCode(1_040_502_004, "瀛樺湪瀛愬伐鍗曪紝鏃犳硶鍒犻櫎");
ErrorCode PRO_WORK_ORDER_PRODUCT_MISMATCH = new ErrorCode(1_040_502_005, "褰撳墠浜у搧鐗╂枡涓庣敓浜у伐鍗曚骇鍝佷笉涓�鑷�");
+ ErrorCode PRO_WORK_ORDER_SUBMIT_FAIL_NOT_PREPARE = new ErrorCode(1_040_502_006, "宸ュ崟鎻愪氦澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑宸ュ崟鎵嶈兘鎻愪氦");
+ ErrorCode PRO_WORK_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_AUDITING = new ErrorCode(1_040_502_007, "宸ュ崟瀹℃壒鐘舵�佹洿鏂板け璐ワ紝宸ュ崟涓嶅浜庡鎵逛腑");
ErrorCode PRO_WORK_ORDER_BOM_NOT_EXISTS = new ErrorCode(1_040_502_100, "鐢熶骇宸ュ崟BOM涓嶅瓨鍦�");
// ========== MES 鐢熶骇绠$悊-鐢熶骇浠诲姟锛�1-040-503-000锛� ==========
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java
index d1e8262..075dd53 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java
@@ -22,23 +22,33 @@
*/
PREPARE(0, "鑽夌"),
/**
+ * 瀹℃壒涓�
+ *
+ * 瀵瑰簲 MesProWorkOrderService#submitWorkOrder 鏂规硶
+ */
+ AUDITING(1, "瀹℃壒涓�"),
+ /**
* 宸茬‘璁�
*
- * 瀵瑰簲 MesProWorkOrderService#confirmWorkOrder 鏂规硶
+ * 瀵瑰簲 MesProWorkOrderService#confirmWorkOrder 鏂规硶锛堝鎵归�氳繃鍚庤嚜鍔ㄧ‘璁わ級
*/
- CONFIRMED(1, "宸茬‘璁�"),
+ CONFIRMED(2, "宸茬‘璁�"),
/**
* 宸插畬鎴�
*
* 瀵瑰簲 MesProWorkOrderService#finishWorkOrder 鏂规硶
*/
- FINISHED(2, "宸插畬鎴�"),
+ FINISHED(3, "宸插畬鎴�"),
/**
* 宸插彇娑�
*
* 瀵瑰簲 MesProWorkOrderService#cancelWorkOrder 鏂规硶
*/
- CANCELED(3, "宸插彇娑�");
+ CANCELED(4, "宸插彇娑�"),
+ /**
+ * 瀹℃壒涓嶉�氳繃
+ */
+ REJECTED(5, "瀹℃壒涓嶉�氳繃");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProWorkOrderStatusEnum::getStatus).toArray(Integer[]::new);
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
index 81f2dee..7585be0 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
@@ -6,6 +6,8 @@
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemPageReqVO;
@@ -38,6 +40,10 @@
/**
* MES 鐗╂枡浜у搧 Service 瀹炵幇绫�
*
+ * 鏀归�犺鏄庯細MES 鐗╂枡涓绘暟鎹潵婧愪簬 MDM
+ * - getItem/getItemList/validateItemExists 璋冪敤 MDM API
+ * - MES 鐗规湁閰嶇疆锛堟壒娆¢厤缃�丅OM銆丼OP銆丼IP锛変繚鐣欏湪 MES 妯″潡绠$悊
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@@ -46,6 +52,9 @@
@Resource
private MesMdItemMapper itemMapper;
+
+ @Resource
+ private MdmItemApi mdmItemApi;
@Resource
private MesMdItemTypeService itemTypeService;
@@ -147,20 +156,14 @@
@Override
public MesMdItemDO validateItemExists(Long id) {
- MesMdItemDO item = itemMapper.selectById(id);
- if (item == null) {
- throw exception(MD_ITEM_NOT_EXISTS);
- }
- return item;
+ MdmItemRespDTO item = mdmItemApi.validateItemExists(id).getCheckedData();
+ return BeanUtils.toBean(item, MesMdItemDO.class);
}
@Override
public MesMdItemDO validateItemExistsAndEnable(Long id) {
- MesMdItemDO item = validateItemExists(id);
- if (ObjUtil.notEqual(CommonStatusEnum.ENABLE.getStatus(), item.getStatus())) {
- throw exception(MD_ITEM_IS_DISABLE);
- }
- return item;
+ MdmItemRespDTO item = mdmItemApi.validateItemExistsAndEnable(id).getCheckedData();
+ return BeanUtils.toBean(item, MesMdItemDO.class);
}
private void validateItemCodeUnique(Long id, String code) {
@@ -239,21 +242,18 @@
@Override
public MesMdItemDO getItem(Long id) {
- return itemMapper.selectById(id);
+ // 浠� MDM 鑾峰彇鐗╂枡涓绘暟鎹�
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ return BeanUtils.toBean(item, MesMdItemDO.class);
}
@Override
public PageResult<MesMdItemDO> getItemPage(MesMdItemPageReqVO pageReqVO) {
- // 鏌ユ壘鏃讹紝濡傛灉鏌ユ壘鏌愪釜鍒嗙被缂栧彿锛屽垯鍖呭惈瀹冪殑瀛愬垎绫�
- Set<Long> itemTypeIds = null;
- if (pageReqVO.getItemTypeId() != null) {
- itemTypeIds = new HashSet<>();
- itemTypeIds.add(pageReqVO.getItemTypeId());
- List<MesMdItemTypeDO> children = itemTypeService.getItemTypeChildrenList(pageReqVO.getItemTypeId());
- itemTypeIds.addAll(convertSet(children, MesMdItemTypeDO::getId));
- }
- // 鍒嗛〉鏌ヨ
- return itemMapper.selectPage(pageReqVO, itemTypeIds);
+ // 璋冪敤 MDM API 鍒嗛〉鏌ヨ锛圡ES 鐗╂枡瀵瑰簲 MDM 鐗╂枡绫诲瀷=鐗╂枡锛�
+ cn.iocoder.yudao.framework.common.pojo.PageResult<MdmItemRespDTO> mdmPageResult =
+ mdmItemApi.getItemPage(1, pageReqVO.getStatus(), pageReqVO.getPageNo(), pageReqVO.getPageSize()).getCheckedData();
+ List<MesMdItemDO> itemList = BeanUtils.toBean(mdmPageResult.getList(), MesMdItemDO.class);
+ return new PageResult<>(itemList, mdmPageResult.getTotal());
}
@Override
@@ -261,17 +261,19 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- return itemMapper.selectByIds(ids);
+ List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
+ return BeanUtils.toBean(itemList, MesMdItemDO.class);
}
@Override
public Long getItemCountByItemTypeId(Long itemTypeId) {
- return itemMapper.selectCountByItemTypeId(itemTypeId);
+ // MES 鍒嗙被闇�瑕佹槧灏勫埌 MDM 鍒嗙被
+ return mdmItemApi.getItemCountByCategoryId(itemTypeId).getCheckedData();
}
@Override
public Long getItemCountByUnitMeasureId(Long unitMeasureId) {
- return itemMapper.selectCountByUnitMeasureId(unitMeasureId);
+ return mdmItemApi.getItemCountByUnitMeasureId(unitMeasureId).getCheckedData();
}
@Override
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java
index 31d2581..58822ef 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java
@@ -122,6 +122,22 @@
void cancelWorkOrder(Long id);
/**
+ * 鎻愪氦宸ュ崟瀹℃壒
+ *
+ * @param id 缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitWorkOrder(Long id, Long userId);
+
+ /**
+ * 鏇存柊宸ュ崟鐨勫鎵圭姸鎬�
+ *
+ * @param id 缂栧彿
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updateWorkOrderAuditStatus(Long id, Integer bpmResult);
+
+ /**
* 绱姞宸ュ崟鐨勫凡鐢熶骇鏁伴噺
*
* @param id 宸ュ崟缂栧彿
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
index 99332dd..67f0159 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
@@ -4,6 +4,8 @@
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemBatchConfigDO;
@@ -16,6 +18,7 @@
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -36,7 +39,13 @@
*/
@Service
@Validated
+@Slf4j
public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
+
+ /**
+ * BPM 鐢熶骇宸ュ崟瀹℃壒娴佺▼鏍囪瘑
+ */
+ public static final String BPM_PROCESS_DEFINITION_KEY = "mes-pro-work-order-audit";
@Resource
private MesProWorkOrderMapper workOrderMapper;
@@ -51,6 +60,9 @@
private MesWmBarcodeService barcodeService;
@Resource
private MesProTaskService taskService;
+
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -250,4 +262,67 @@
return workOrderMapper.selectCountByVendorId(vendorId);
}
+ // ==================== BPM 娴佺▼瀹℃壒 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitWorkOrder(Long id, Long userId) {
+ // 1. 鏍¢獙宸ュ崟澶勪簬鑽夌鐘舵��
+ MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
+ if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) {
+ throw exception(PRO_WORK_ORDER_SUBMIT_FAIL_NOT_PREPARE);
+ }
+
+ // 2. 鍒涘缓鐢熶骇宸ュ崟瀹℃壒娴佺▼瀹炰緥
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊宸ュ崟宸ヤ綔娴佺紪鍙枫�佺姸鎬佷负瀹℃壒涓�
+ workOrderMapper.updateById(new MesProWorkOrderDO()
+ .setId(id)
+ .setProcessInstanceId(processInstanceId)
+ .setStatus(MesProWorkOrderStatusEnum.AUDITING.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateWorkOrderAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙宸ュ崟瀛樺湪
+ MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
+
+ // 2. 鏍¢獙宸ュ崟澶勪簬瀹℃壒涓�
+ if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.AUDITING.getStatus())) {
+ log.error("[updateWorkOrderAuditStatus][workOrder({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ workOrder.getId(), bpmResult);
+ throw exception(PRO_WORK_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_AUDITING);
+ }
+
+ // 3. 鏇存柊宸ュ崟瀹℃壒缁撴灉
+ Integer auditStatus = convertBpmResultToWorkOrderStatus(bpmResult);
+ workOrderMapper.updateById(new MesProWorkOrderDO()
+ .setId(id)
+ .setStatus(auditStatus));
+ }
+
+ /**
+ * 灏� BPM 瀹℃壒缁撴灉杞崲涓� MES 宸ュ崟鐘舵��
+ *
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ * @return MES 宸ュ崟鐘舵��
+ */
+ private Integer convertBpmResultToWorkOrderStatus(Integer bpmResult) {
+ // BPM 瀹℃壒缁撴灉锛�2=閫氳繃锛�3=涓嶉�氳繃锛�4=宸插彇娑�
+ if (bpmResult == 2) {
+ return MesProWorkOrderStatusEnum.CONFIRMED.getStatus();
+ } else if (bpmResult == 3) {
+ return MesProWorkOrderStatusEnum.REJECTED.getStatus();
+ } else if (bpmResult == 4) {
+ return MesProWorkOrderStatusEnum.PREPARE.getStatus();
+ } else {
+ log.warn("[convertBpmResultToWorkOrderStatus][鏈煡鐨� BPM 瀹℃壒缁撴灉({})]", bpmResult);
+ return MesProWorkOrderStatusEnum.AUDITING.getStatus();
+ }
+ }
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/listener/MesProWorkOrderStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/listener/MesProWorkOrderStatusListener.java
new file mode 100644
index 0000000..cd1b809
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/listener/MesProWorkOrderStatusListener.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.service.pro.workorder.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鐢熶骇宸ュ崟瀹℃壒鐨勭粨鏋滅殑鐩戝惉鍣ㄥ疄鐜扮被
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+public class MesProWorkOrderStatusListener extends BpmProcessInstanceStatusEventListener {
+
+ @Resource
+ private MesProWorkOrderService workOrderService;
+
+ @Override
+ public String getProcessDefinitionKey() {
+ return MesProWorkOrderServiceImpl.BPM_PROCESS_DEFINITION_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ workOrderService.updateWorkOrderAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/pom.xml b/yudao-module-wms/pom.xml
index 9365862..5b5e53c 100644
--- a/yudao-module-wms/pom.xml
+++ b/yudao-module-wms/pom.xml
@@ -24,6 +24,13 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞嗘ā鍧� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApi.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApi.java
new file mode 100644
index 0000000..762e5a5
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApi.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.wms.api.inventory;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨鎿嶄綔 API 鎺ュ彛
+ *
+ * 鎻愪緵缁� ERP銆丮ES 绛夋ā鍧楄皟鐢紝瀹炵幇璺ㄦā鍧楀簱瀛樻搷浣�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface WmsInventoryApi {
+
+ String PREFIX = "/rpc-api/wms/inventory";
+
+ // ==================== 鍏ュ簱鎿嶄綔 ====================
+
+ /**
+ * 鍏ュ簱鎿嶄綔
+ *
+ * @param reqDTO 鍏ュ簱璇锋眰
+ * @return 鍏ュ簱缁撴灉
+ */
+ CommonResult<WmsReceiptResultDTO> receipt(WmsReceiptReqDTO reqDTO);
+
+ // ==================== 鍑哄簱鎿嶄綔 ====================
+
+ /**
+ * 鍑哄簱鎿嶄綔
+ *
+ * @param reqDTO 鍑哄簱璇锋眰
+ * @return 鍑哄簱缁撴灉
+ */
+ CommonResult<WmsShipmentResultDTO> shipment(WmsShipmentReqDTO reqDTO);
+
+ // ==================== 搴撳瓨棰勭暀 ====================
+
+ /**
+ * 搴撳瓨棰勭暀锛堥攣搴撳瓨锛�
+ *
+ * @param reqDTO 棰勭暀璇锋眰
+ * @return 棰勭暀鍗曞彿
+ */
+ CommonResult<String> reserve(WmsReserveReqDTO reqDTO);
+
+ /**
+ * 閲婃斁搴撳瓨棰勭暀
+ *
+ * @param reserveNo 棰勭暀鍗曞彿
+ */
+ CommonResult<Boolean> releaseReserve(String reserveNo);
+
+ /**
+ * 浣跨敤棰勭暀搴撳瓨锛堝嚭搴撴椂鎵e噺棰勭暀锛�
+ *
+ * @param reqDTO 浣跨敤棰勭暀璇锋眰
+ */
+ CommonResult<Boolean> useReserve(WmsUseReserveReqDTO reqDTO);
+
+ // ==================== 搴撳瓨鏌ヨ ====================
+
+ /**
+ * 鏌ヨ搴撳瓨
+ *
+ * @param reqDTO 鏌ヨ璇锋眰
+ * @return 搴撳瓨鍒楄〃
+ */
+ CommonResult<List<WmsInventoryRespDTO>> getInventoryList(WmsInventoryQueryReqDTO reqDTO);
+
+ /**
+ * 鏌ヨ鍙敤搴撳瓨锛堟�诲簱瀛� - 棰勭暀搴撳瓨锛�
+ *
+ * @param skuId SKU 缂栧彿
+ * @param warehouseId 浠撳簱缂栧彿
+ * @return 鍙敤搴撳瓨鏁伴噺
+ */
+ CommonResult<BigDecimal> getAvailableQuantity(Long skuId, Long warehouseId);
+
+}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApiImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApiImpl.java
new file mode 100644
index 0000000..9fe4523
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApiImpl.java
@@ -0,0 +1,266 @@
+package cn.iocoder.yudao.module.wms.api.inventory;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
+import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsTransactionTypeEnum;
+import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
+import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryReserveService;
+import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService;
+import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryTransactionService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * WMS 搴撳瓨鎿嶄綔 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(WmsInventoryApi.PREFIX)
+@Service
+@Validated
+@Slf4j
+public class WmsInventoryApiImpl implements WmsInventoryApi {
+
+ @Resource
+ private WmsInventoryService inventoryService;
+
+ @Resource
+ private WmsInventoryReserveService reserveService;
+
+ @Resource
+ private WmsInventoryTransactionService transactionService;
+
+ @Resource
+ private WmsInventoryMapper inventoryMapper;
+
+ // ==================== 鍏ュ簱鎿嶄綔 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CommonResult<WmsReceiptResultDTO> receipt(WmsReceiptReqDTO reqDTO) {
+ // 1. 鏋勫缓搴撳瓨鍙樻洿璇锋眰
+ WmsInventoryChangeReqDTO changeReq = buildInventoryChangeReq(reqDTO);
+
+ // 2. 鎵ц鍏ュ簱鍙樻洿
+ inventoryService.changeInventory(changeReq);
+
+ // 3. 璁板綍浜嬪姟娴佹按
+ List<WmsInventoryTransactionDO> transactions = buildReceiptTransactions(reqDTO);
+ transactionService.createTransactionList(transactions);
+
+ // 4. 鏋勫缓杩斿洖缁撴灉
+ WmsReceiptResultDTO result = new WmsReceiptResultDTO();
+ result.setReceiptOrderNo(generateReceiptNo());
+ result.setTotalQuantity(calculateTotalQuantity(reqDTO.getItems()));
+ return success(result);
+ }
+
+ // ==================== 鍑哄簱鎿嶄綔 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CommonResult<WmsShipmentResultDTO> shipment(WmsShipmentReqDTO reqDTO) {
+ // 1. 濡傛灉鏈夐鐣欏崟鍙凤紝鍏堜娇鐢ㄩ鐣�
+ if (reqDTO.getReserveNo() != null) {
+ WmsUseReserveReqDTO useReq = new WmsUseReserveReqDTO();
+ useReq.setReserveNo(reqDTO.getReserveNo());
+ useReq.setQuantity(calculateTotalQuantity(reqDTO.getItems()));
+ reserveService.useReserve(useReq);
+ }
+
+ // 2. 鏋勫缓搴撳瓨鍙樻洿璇锋眰锛堝嚭搴撲负璐熸暟锛�
+ WmsInventoryChangeReqDTO changeReq = buildShipmentChangeReq(reqDTO);
+
+ // 3. 鎵ц鍑哄簱鍙樻洿
+ inventoryService.changeInventory(changeReq);
+
+ // 4. 璁板綍浜嬪姟娴佹按
+ List<WmsInventoryTransactionDO> transactions = buildShipmentTransactions(reqDTO);
+ transactionService.createTransactionList(transactions);
+
+ // 5. 鏋勫缓杩斿洖缁撴灉
+ WmsShipmentResultDTO result = new WmsShipmentResultDTO();
+ result.setShipmentOrderNo(generateShipmentNo());
+ result.setTotalQuantity(calculateTotalQuantity(reqDTO.getItems()));
+ return success(result);
+ }
+
+ // ==================== 搴撳瓨棰勭暀 ====================
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CommonResult<String> reserve(WmsReserveReqDTO reqDTO) {
+ return success(reserveService.createReserve(reqDTO));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CommonResult<Boolean> releaseReserve(String reserveNo) {
+ reserveService.releaseReserve(reserveNo);
+ return success(true);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CommonResult<Boolean> useReserve(WmsUseReserveReqDTO reqDTO) {
+ reserveService.useReserve(reqDTO);
+ return success(true);
+ }
+
+ // ==================== 搴撳瓨鏌ヨ ====================
+
+ @Override
+ public CommonResult<List<WmsInventoryRespDTO>> getInventoryList(WmsInventoryQueryReqDTO reqDTO) {
+ // 1. 鏌ヨ搴撳瓨
+ List<WmsInventoryDO> inventories = inventoryMapper.selectListByQuery(reqDTO);
+
+ // 2. 杞崲缁撴灉
+ List<WmsInventoryRespDTO> results = new ArrayList<>();
+ for (WmsInventoryDO inventory : inventories) {
+ WmsInventoryRespDTO resp = BeanUtils.toBean(inventory, WmsInventoryRespDTO.class);
+ // 璁$畻棰勭暀鏁伴噺
+ BigDecimal reserveQuantity = reserveService.getReserveQuantity(
+ inventory.getSkuId(), inventory.getWarehouseId());
+ resp.setReserveQuantity(reserveQuantity);
+ resp.setAvailableQuantity(inventory.getQuantity().subtract(reserveQuantity));
+ results.add(resp);
+ }
+ return success(results);
+ }
+
+ @Override
+ public CommonResult<BigDecimal> getAvailableQuantity(Long skuId, Long warehouseId) {
+ return success(inventoryService.getAvailableQuantity(skuId, warehouseId));
+ }
+
+ // ==================== 绉佹湁鏂规硶 ====================
+
+ private WmsInventoryChangeReqDTO buildInventoryChangeReq(WmsReceiptReqDTO reqDTO) {
+ WmsInventoryChangeReqDTO changeReq = new WmsInventoryChangeReqDTO();
+ changeReq.setOrderType(reqDTO.getBizType());
+ changeReq.setOrderNo(reqDTO.getBizNo());
+
+ List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>();
+ for (WmsReceiptReqDTO.Item item : reqDTO.getItems()) {
+ WmsInventoryChangeReqDTO.Item changeItem = new WmsInventoryChangeReqDTO.Item();
+ changeItem.setSkuId(item.getSkuId());
+ changeItem.setWarehouseId(reqDTO.getWarehouseId());
+ changeItem.setQuantity(item.getQuantity()); // 鍏ュ簱涓烘鏁�
+ changeItem.setPrice(item.getPrice());
+ changeItem.setRemark(item.getRemark());
+ items.add(changeItem);
+ }
+ changeReq.setItems(items);
+ return changeReq;
+ }
+
+ private WmsInventoryChangeReqDTO buildShipmentChangeReq(WmsShipmentReqDTO reqDTO) {
+ WmsInventoryChangeReqDTO changeReq = new WmsInventoryChangeReqDTO();
+ changeReq.setOrderType(reqDTO.getBizType());
+ changeReq.setOrderNo(reqDTO.getBizNo());
+
+ List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>();
+ for (WmsShipmentReqDTO.Item item : reqDTO.getItems()) {
+ WmsInventoryChangeReqDTO.Item changeItem = new WmsInventoryChangeReqDTO.Item();
+ changeItem.setSkuId(item.getSkuId());
+ changeItem.setWarehouseId(reqDTO.getWarehouseId());
+ changeItem.setQuantity(item.getQuantity().negate()); // 鍑哄簱涓鸿礋鏁�
+ changeItem.setRemark(item.getRemark());
+ items.add(changeItem);
+ }
+ changeReq.setItems(items);
+ return changeReq;
+ }
+
+ private List<WmsInventoryTransactionDO> buildReceiptTransactions(WmsReceiptReqDTO reqDTO) {
+ List<WmsInventoryTransactionDO> transactions = new ArrayList<>();
+ for (WmsReceiptReqDTO.Item item : reqDTO.getItems()) {
+ WmsInventoryTransactionDO transaction = WmsInventoryTransactionDO.builder()
+ .transactionNo(generateTransactionNo())
+ .transactionType(WmsTransactionTypeEnum.RECEIPT.getType())
+ .bizType(reqDTO.getBizType())
+ .bizId(reqDTO.getBizId())
+ .bizNo(reqDTO.getBizNo())
+ .skuId(item.getSkuId())
+ .warehouseId(reqDTO.getWarehouseId())
+ .quantity(item.getQuantity())
+ .transactionTime(reqDTO.getReceiptTime() != null ? reqDTO.getReceiptTime() : LocalDateTime.now())
+ .remark(item.getRemark())
+ .build();
+ transactions.add(transaction);
+ }
+ return transactions;
+ }
+
+ private List<WmsInventoryTransactionDO> buildShipmentTransactions(WmsShipmentReqDTO reqDTO) {
+ List<WmsInventoryTransactionDO> transactions = new ArrayList<>();
+ for (WmsShipmentReqDTO.Item item : reqDTO.getItems()) {
+ WmsInventoryTransactionDO transaction = WmsInventoryTransactionDO.builder()
+ .transactionNo(generateTransactionNo())
+ .transactionType(WmsTransactionTypeEnum.SHIPMENT.getType())
+ .bizType(reqDTO.getBizType())
+ .bizId(reqDTO.getBizId())
+ .bizNo(reqDTO.getBizNo())
+ .skuId(item.getSkuId())
+ .warehouseId(reqDTO.getWarehouseId())
+ .quantity(item.getQuantity().negate())
+ .reserveNo(reqDTO.getReserveNo())
+ .transactionTime(reqDTO.getShipmentTime() != null ? reqDTO.getShipmentTime() : LocalDateTime.now())
+ .remark(item.getRemark())
+ .build();
+ transactions.add(transaction);
+ }
+ return transactions;
+ }
+
+ private BigDecimal calculateTotalQuantity(List<?> items) {
+ if (CollUtil.isEmpty(items)) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal total = BigDecimal.ZERO;
+ for (Object item : items) {
+ if (item instanceof WmsReceiptReqDTO.Item) {
+ total = total.add(((WmsReceiptReqDTO.Item) item).getQuantity());
+ } else if (item instanceof WmsShipmentReqDTO.Item) {
+ total = total.add(((WmsShipmentReqDTO.Item) item).getQuantity());
+ }
+ }
+ return total;
+ }
+
+ private String generateReceiptNo() {
+ return "RCPT" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
+ }
+
+ private String generateShipmentNo() {
+ return "SHIP" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
+ }
+
+ private String generateTransactionNo() {
+ return "TRX" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryQueryReqDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryQueryReqDTO.java
new file mode 100644
index 0000000..dedbc16
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryQueryReqDTO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨鏌ヨ璇锋眰 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsInventoryQueryReqDTO {
+
+ /**
+ * SKU 缂栧彿鍒楄〃
+ */
+ private List<Long> skuIds;
+
+ /**
+ * 浠撳簱缂栧彿鍒楄〃
+ */
+ private List<Long> warehouseIds;
+
+ /**
+ * 鏄惁鍙煡璇㈡湁搴撳瓨鐨�
+ */
+ private Boolean onlyPositive;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryRespDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryRespDTO.java
new file mode 100644
index 0000000..54b4012
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryRespDTO.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * WMS 搴撳瓨鍝嶅簲 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsInventoryRespDTO {
+
+ /**
+ * 搴撳瓨 ID
+ */
+ private Long id;
+
+ /**
+ * SKU 缂栧彿
+ */
+ private Long skuId;
+
+ /**
+ * SKU 鍚嶇О
+ */
+ private String skuName;
+
+ /**
+ * 浠撳簱缂栧彿
+ */
+ private Long warehouseId;
+
+ /**
+ * 浠撳簱鍚嶇О
+ */
+ private String warehouseName;
+
+ /**
+ * 搴撳瓨鏁伴噺
+ */
+ private BigDecimal quantity;
+
+ /**
+ * 棰勭暀鏁伴噺
+ */
+ private BigDecimal reserveQuantity;
+
+ /**
+ * 鍙敤鏁伴噺锛堝簱瀛樻暟閲� - 棰勭暀鏁伴噺锛�
+ */
+ private BigDecimal availableQuantity;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ private LocalDateTime updateTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptReqDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptReqDTO.java
new file mode 100644
index 0000000..76b630b
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptReqDTO.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * WMS 鍏ュ簱璇锋眰 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsReceiptReqDTO {
+
+ /**
+ * 涓氬姟绫诲瀷
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryBizTypeEnum}
+ */
+ @NotNull(message = "涓氬姟绫诲瀷涓嶈兘涓虹┖")
+ private Integer bizType;
+
+ /**
+ * 涓氬姟鍗曟嵁 ID
+ */
+ @NotNull(message = "涓氬姟鍗曟嵁ID涓嶈兘涓虹┖")
+ private Long bizId;
+
+ /**
+ * 涓氬姟鍗曟嵁鍙�
+ */
+ private String bizNo;
+
+ /**
+ * 浠撳簱缂栧彿
+ */
+ @NotNull(message = "浠撳簱涓嶈兘涓虹┖")
+ private Long warehouseId;
+
+ /**
+ * 鍏ュ簱鏃堕棿
+ */
+ private LocalDateTime receiptTime;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍏ュ簱鏄庣粏
+ */
+ @NotEmpty(message = "鍏ュ簱鏄庣粏涓嶈兘涓虹┖")
+ @Valid
+ private List<Item> items;
+
+ /**
+ * 鍏ュ簱鏄庣粏椤�
+ */
+ @Data
+ public static class Item {
+
+ /**
+ * SKU 缂栧彿
+ */
+ @NotNull(message = "SKU涓嶈兘涓虹┖")
+ private Long skuId;
+
+ /**
+ * 鍏ュ簱鏁伴噺
+ */
+ @NotNull(message = "鍏ュ簱鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+ /**
+ * 鍗曚环锛堝彲閫夛級
+ */
+ private BigDecimal price;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptResultDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptResultDTO.java
new file mode 100644
index 0000000..f6eeab0
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptResultDTO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * WMS 鍏ュ簱缁撴灉 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsReceiptResultDTO {
+
+ /**
+ * 鍏ュ簱鍗� ID
+ */
+ private Long receiptOrderId;
+
+ /**
+ * 鍏ュ簱鍗曞彿
+ */
+ private String receiptOrderNo;
+
+ /**
+ * 鎬诲叆搴撴暟閲�
+ */
+ private BigDecimal totalQuantity;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReserveReqDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReserveReqDTO.java
new file mode 100644
index 0000000..91576f8
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReserveReqDTO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * WMS 搴撳瓨棰勭暀璇锋眰 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsReserveReqDTO {
+
+ /**
+ * 涓氬姟绫诲瀷
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveBizTypeEnum}
+ */
+ @NotNull(message = "涓氬姟绫诲瀷涓嶈兘涓虹┖")
+ private Integer bizType;
+
+ /**
+ * 涓氬姟鍗曟嵁 ID
+ */
+ @NotNull(message = "涓氬姟鍗曟嵁ID涓嶈兘涓虹┖")
+ private Long bizId;
+
+ /**
+ * 涓氬姟鍗曟嵁鍙�
+ */
+ private String bizNo;
+
+ /**
+ * SKU 缂栧彿
+ */
+ @NotNull(message = "SKU涓嶈兘涓虹┖")
+ private Long skuId;
+
+ /**
+ * 浠撳簱缂栧彿
+ */
+ @NotNull(message = "浠撳簱涓嶈兘涓虹┖")
+ private Long warehouseId;
+
+ /**
+ * 棰勭暀鏁伴噺
+ */
+ @NotNull(message = "棰勭暀鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+ /**
+ * 杩囨湡鏃堕棿锛堝彲閫夛紝棰勭暀鑷姩閲婃斁鏃堕棿锛�
+ */
+ private LocalDateTime expireTime;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentReqDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentReqDTO.java
new file mode 100644
index 0000000..9ce3fc1
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentReqDTO.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * WMS 鍑哄簱璇锋眰 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsShipmentReqDTO {
+
+ /**
+ * 涓氬姟绫诲瀷
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryBizTypeEnum}
+ */
+ @NotNull(message = "涓氬姟绫诲瀷涓嶈兘涓虹┖")
+ private Integer bizType;
+
+ /**
+ * 涓氬姟鍗曟嵁 ID
+ */
+ @NotNull(message = "涓氬姟鍗曟嵁ID涓嶈兘涓虹┖")
+ private Long bizId;
+
+ /**
+ * 涓氬姟鍗曟嵁鍙�
+ */
+ private String bizNo;
+
+ /**
+ * 浠撳簱缂栧彿
+ */
+ @NotNull(message = "浠撳簱涓嶈兘涓虹┖")
+ private Long warehouseId;
+
+ /**
+ * 鍑哄簱鏃堕棿
+ */
+ private LocalDateTime shipmentTime;
+
+ /**
+ * 棰勭暀鍗曞彿锛堝鏋滀娇鐢ㄤ簡棰勭暀锛�
+ */
+ private String reserveNo;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍑哄簱鏄庣粏
+ */
+ @NotEmpty(message = "鍑哄簱鏄庣粏涓嶈兘涓虹┖")
+ @Valid
+ private List<Item> items;
+
+ /**
+ * 鍑哄簱鏄庣粏椤�
+ */
+ @Data
+ public static class Item {
+
+ /**
+ * SKU 缂栧彿
+ */
+ @NotNull(message = "SKU涓嶈兘涓虹┖")
+ private Long skuId;
+
+ /**
+ * 鍑哄簱鏁伴噺
+ */
+ @NotNull(message = "鍑哄簱鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentResultDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentResultDTO.java
new file mode 100644
index 0000000..fd7267e
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentResultDTO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * WMS 鍑哄簱缁撴灉 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsShipmentResultDTO {
+
+ /**
+ * 鍑哄簱鍗� ID
+ */
+ private Long shipmentOrderId;
+
+ /**
+ * 鍑哄簱鍗曞彿
+ */
+ private String shipmentOrderNo;
+
+ /**
+ * 鎬诲嚭搴撴暟閲�
+ */
+ private BigDecimal totalQuantity;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsUseReserveReqDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsUseReserveReqDTO.java
new file mode 100644
index 0000000..86e3048
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsUseReserveReqDTO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.wms.api.inventory.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * WMS 浣跨敤棰勭暀璇锋眰 DTO
+ *
+ * 鍑哄簱鏃舵墸鍑忛鐣欏簱瀛�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class WmsUseReserveReqDTO {
+
+ /**
+ * 棰勭暀鍗曞彿
+ */
+ @NotNull(message = "棰勭暀鍗曞彿涓嶈兘涓虹┖")
+ private String reserveNo;
+
+ /**
+ * 浣跨敤鏁伴噺
+ */
+ @NotNull(message = "浣跨敤鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java
new file mode 100644
index 0000000..a1168a8
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java
@@ -0,0 +1,144 @@
+package cn.iocoder.yudao.module.wms.api.stock;
+
+import cn.iocoder.yudao.module.wms.api.stock.dto.*;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * WMS 搴撳瓨鏈嶅姟 API 鎺ュ彛
+ *
+ * 鎻愪緵缁� ERP銆丮ES 绛夋ā鍧楄皟鐢紝瀹炵幇璺ㄦā鍧楀簱瀛樻搷浣�
+ * 浣跨敤缁熶竴鐨勭墿鏂橧D锛坢d_item.id锛�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface WmsStockApi {
+
+ // ==================== 搴撳瓨璁板綍鎿嶄綔 ====================
+
+ /**
+ * 鍒涘缓搴撳瓨璁板綍锛堝叆搴擄級
+ *
+ * @param dto 搴撳瓨璁板綍鍒涘缓璇锋眰
+ * @return 搴撳瓨璁板綍ID
+ */
+ Long createStockRecord(StockRecordCreateDTO dto);
+
+ /**
+ * 鎵归噺鍒涘缓搴撳瓨璁板綍
+ *
+ * @param dtoList 搴撳瓨璁板綍鍒涘缓璇锋眰鍒楄〃
+ * @return 搴撳瓨璁板綍ID鍒楄〃
+ */
+ List<Long> createStockRecordBatch(List<StockRecordCreateDTO> dtoList);
+
+ // ==================== 搴撳瓨閿佸畾 ====================
+
+ /**
+ * 閿佸畾搴撳瓨
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @param quantity 閿佸畾鏁伴噺
+ * @return 閿佸畾鍗曞彿
+ */
+ String lockStock(Long itemId, Long warehouseId, BigDecimal quantity);
+
+ /**
+ * 鎵归噺閿佸畾搴撳瓨
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @param quantity 閿佸畾鏁伴噺
+ * @param bizType 涓氬姟绫诲瀷
+ * @param bizId 涓氬姟鍗曟嵁ID
+ * @return 閿佸畾鍗曞彿
+ */
+ String lockStockWithBiz(Long itemId, Long warehouseId, BigDecimal quantity, Integer bizType, Long bizId);
+
+ /**
+ * 瑙i攣搴撳瓨
+ *
+ * @param lockNo 閿佸畾鍗曞彿
+ */
+ void unlockStock(String lockNo);
+
+ // ==================== 搴撳瓨鏌ヨ ====================
+
+ /**
+ * 鏌ヨ鍙敤搴撳瓨
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @return 鍙敤搴撳瓨鏁伴噺
+ */
+ BigDecimal getAvailableStock(Long itemId, Long warehouseId);
+
+ /**
+ * 鎵归噺鏌ヨ搴撳瓨
+ *
+ * @param itemIds 鐗╂枡ID鍒楄〃
+ * @param warehouseId 浠撳簱ID
+ * @return 鐗╂枡ID -> 鍙敤搴撳瓨鏁伴噺
+ */
+ Map<Long, BigDecimal> getAvailableStockMap(List<Long> itemIds, Long warehouseId);
+
+ /**
+ * 鏌ヨ鎵�鏈変粨搴撶殑搴撳瓨
+ *
+ * @param itemId 鐗╂枡ID
+ * @return 浠撳簱ID -> 鍙敤搴撳瓨鏁伴噺
+ */
+ Map<Long, BigDecimal> getAvailableStockByWarehouse(Long itemId);
+
+ // ==================== 搴撳瓨鏍¢獙 ====================
+
+ /**
+ * 鏍¢獙搴撳瓨鍏呰冻
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @param quantity 闇�姹傛暟閲�
+ * @return true=鍏呰冻, false=涓嶈冻
+ */
+ boolean validateStockEnough(Long itemId, Long warehouseId, BigDecimal quantity);
+
+ /**
+ * 鏍¢獙搴撳瓨鍏呰冻锛堜笉瓒虫椂鎶涘嚭寮傚父锛�
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @param quantity 闇�姹傛暟閲�
+ * @throws cn.iocoder.yudao.framework.common.exception.ServiceException 搴撳瓨涓嶈冻鏃舵姏鍑哄紓甯�
+ */
+ void checkStockEnough(Long itemId, Long warehouseId, BigDecimal quantity);
+
+ // ==================== 搴撳瓨鎵e噺 ====================
+
+ /**
+ * 鎵e噺搴撳瓨锛堝嚭搴擄級
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @param quantity 鎵e噺鏁伴噺
+ * @param bizType 涓氬姟绫诲瀷
+ * @param bizId 涓氬姟鍗曟嵁ID
+ * @param bizNo 涓氬姟鍗曟嵁鍙�
+ */
+ void deductStock(Long itemId, Long warehouseId, BigDecimal quantity, Integer bizType, Long bizId, String bizNo);
+
+ /**
+ * 澧炲姞搴撳瓨锛堝叆搴擄級
+ *
+ * @param itemId 鐗╂枡ID
+ * @param warehouseId 浠撳簱ID
+ * @param quantity 澧炲姞鏁伴噺
+ * @param bizType 涓氬姟绫诲瀷
+ * @param bizId 涓氬姟鍗曟嵁ID
+ * @param bizNo 涓氬姟鍗曟嵁鍙�
+ */
+ void addStock(Long itemId, Long warehouseId, BigDecimal quantity, Integer bizType, Long bizId, String bizNo);
+
+}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/dto/StockRecordCreateDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/dto/StockRecordCreateDTO.java
new file mode 100644
index 0000000..d089a35
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/dto/StockRecordCreateDTO.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.wms.api.stock.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 搴撳瓨璁板綍鍒涘缓 DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+@Accessors(chain = true)
+public class StockRecordCreateDTO implements Serializable {
+
+ /**
+ * 鐗╂枡ID锛堝叧鑱� md_item.id锛�
+ */
+ private Long itemId;
+
+ /**
+ * 浠撳簱ID锛堝叧鑱� md_warehouse.id锛�
+ */
+ private Long warehouseId;
+
+ /**
+ * 搴撲綅ID锛堝彲閫夛級
+ */
+ private Long locationId;
+
+ /**
+ * 鎵规鍙凤紙鍙�夛級
+ */
+ private String batchCode;
+
+ /**
+ * 鍙樺姩鏁伴噺锛堟鏁�=鍏ュ簱锛岃礋鏁�=鍑哄簱锛�
+ */
+ private BigDecimal quantity;
+
+ /**
+ * 涓氬姟绫诲瀷
+ *
+ * 鏋氫妇鍊硷細
+ * 10: 閲囪喘鍏ュ簱
+ * 20: 閿�鍞嚭搴�
+ * 30: 鐢熶骇鍏ュ簱
+ * 40: 鐢熶骇棰嗘枡
+ * 50: 鐢熶骇閫�鏂�
+ * 60: 閲囪喘閫�璐у嚭搴�
+ * 70: 閿�鍞��璐у叆搴�
+ * 80: 璋冩嫧鍏ュ簱
+ * 90: 璋冩嫧鍑哄簱
+ * 100: 鐩樼泩鍏ュ簱
+ * 110: 鐩樹簭鍑哄簱
+ */
+ private Integer bizType;
+
+ /**
+ * 涓氬姟鍗曟嵁ID
+ */
+ private Long bizId;
+
+ /**
+ * 涓氬姟鍗曟嵁鍙�
+ */
+ private String bizNo;
+
+ /**
+ * 璁¢噺鍗曚綅ID
+ */
+ private Long unitMeasureId;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/WmsInventoryReserveController.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/WmsInventoryReserveController.java
new file mode 100644
index 0000000..a870292
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/WmsInventoryReserveController.java
@@ -0,0 +1,137 @@
+package cn.iocoder.yudao.module.wms.controller.admin.inventory;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
+import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
+import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReserveRespVO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
+import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryReserveService;
+import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService;
+import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - 搴撳瓨棰勭暀")
+@RestController
+@RequestMapping("/wms/inventory-reserve")
+@Validated
+public class WmsInventoryReserveController {
+
+ @Resource
+ private WmsInventoryReserveService reserveService;
+
+ @Resource
+ private WmsItemSkuService itemSkuService;
+
+ @Resource
+ private WmsWarehouseService warehouseService;
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱搴撳瓨棰勭暀鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('wms:inventory-reserve:query')")
+ public CommonResult<PageResult<WmsInventoryReserveRespVO>> getInventoryReservePage(@Valid WmsInventoryReservePageReqVO reqVO) {
+ PageResult<WmsInventoryReserveDO> pageResult = reserveService.getReservePage(reqVO);
+ // 杞崲
+ PageResult<WmsInventoryReserveRespVO> voPageResult = BeanUtils.toBean(pageResult, WmsInventoryReserveRespVO.class);
+ // 琛ュ厖淇℃伅
+ enrichReserveList(voPageResult.getList());
+ return success(voPageResult);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱搴撳瓨棰勭暀璇︽儏")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('wms:inventory-reserve:query')")
+ public CommonResult<WmsInventoryReserveRespVO> getInventoryReserve(@RequestParam("id") Long id) {
+ WmsInventoryReserveDO reserve = reserveService.getReserve(id);
+ if (reserve == null) {
+ return success(null);
+ }
+ WmsInventoryReserveRespVO respVO = BeanUtils.toBean(reserve, WmsInventoryReserveRespVO.class);
+ // 琛ュ厖淇℃伅
+ enrichReserve(respVO);
+ return success(respVO);
+ }
+
+ @PostMapping("/release")
+ @Operation(summary = "閲婃斁搴撳瓨棰勭暀")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('wms:inventory-reserve:release')")
+ public CommonResult<Boolean> releaseInventoryReserve(@RequestParam("id") Long id) {
+ WmsInventoryReserveDO reserve = reserveService.getReserve(id);
+ if (reserve != null) {
+ reserveService.releaseReserve(reserve.getReserveNo());
+ }
+ return success(true);
+ }
+
+ /**
+ * 鎵归噺琛ュ厖棰勭暀璁板綍淇℃伅
+ */
+ private void enrichReserveList(List<WmsInventoryReserveRespVO> list) {
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ // 鑾峰彇 SKU 淇℃伅
+ Map<Long, WmsItemSkuDO> skuMap = itemSkuService.getItemSkuMap(
+ convertSet(list, WmsInventoryReserveRespVO::getSkuId));
+ // 鑾峰彇浠撳簱淇℃伅
+ Map<Long, WmsWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(
+ convertSet(list, WmsInventoryReserveRespVO::getWarehouseId));
+
+ for (WmsInventoryReserveRespVO vo : list) {
+ // 涓氬姟绫诲瀷鍚嶇О
+ vo.setBizTypeName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_BIZ_TYPE, vo.getBizType()));
+ // 鐘舵�佸悕绉�
+ vo.setStatusName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_STATUS, vo.getStatus()));
+ // SKU 鍚嶇О
+ WmsItemSkuDO sku = skuMap.get(vo.getSkuId());
+ if (sku != null) {
+ vo.setSkuName(sku.getName());
+ }
+ // 浠撳簱鍚嶇О
+ WmsWarehouseDO warehouse = warehouseMap.get(vo.getWarehouseId());
+ if (warehouse != null) {
+ vo.setWarehouseName(warehouse.getName());
+ }
+ }
+ }
+
+ /**
+ * 琛ュ厖鍗曚釜棰勭暀璁板綍淇℃伅
+ */
+ private void enrichReserve(WmsInventoryReserveRespVO vo) {
+ // 涓氬姟绫诲瀷鍚嶇О
+ vo.setBizTypeName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_BIZ_TYPE, vo.getBizType()));
+ // 鐘舵�佸悕绉�
+ vo.setStatusName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_STATUS, vo.getStatus()));
+ // SKU 鍚嶇О
+ List<WmsItemSkuDO> skuList = itemSkuService.getItemSkuListByIds(List.of(vo.getSkuId()));
+ if (!skuList.isEmpty()) {
+ vo.setSkuName(skuList.get(0).getName());
+ }
+ // 浠撳簱鍚嶇О
+ WmsWarehouseDO warehouse = warehouseService.getWarehouse(vo.getWarehouseId());
+ if (warehouse != null) {
+ vo.setWarehouseName(warehouse.getName());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReservePageReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReservePageReqVO.java
new file mode 100644
index 0000000..ae48ffa
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReservePageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.wms.controller.admin.inventory.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨棰勭暀鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class WmsInventoryReservePageReqVO extends PageParam {
+
+ @Schema(description = "棰勭暀鍗曞彿", example = "RES202606260001")
+ private String reserveNo;
+
+ @Schema(description = "涓氬姟绫诲瀷", example = "10")
+ private Integer bizType;
+
+ @Schema(description = "涓氬姟鍗曟嵁鍙�", example = "SO202606260001")
+ private String bizNo;
+
+ @Schema(description = "鐘舵��", example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReserveRespVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReserveRespVO.java
new file mode 100644
index 0000000..35bbe3d
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReserveRespVO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.wms.controller.admin.inventory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨棰勭暀 Response VO")
+@Data
+public class WmsInventoryReserveRespVO {
+
+ @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "棰勭暀鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "RES202606260001")
+ private String reserveNo;
+
+ @Schema(description = "涓氬姟绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer bizType;
+
+ @Schema(description = "涓氬姟绫诲瀷鍚嶇О", example = "閿�鍞鐣�")
+ private String bizTypeName;
+
+ @Schema(description = "涓氬姟鍗曟嵁ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Long bizId;
+
+ @Schema(description = "涓氬姟鍗曟嵁鍙�", example = "SO202606260001")
+ private String bizNo;
+
+ @Schema(description = "SKU缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long skuId;
+
+ @Schema(description = "SKU鍚嶇О", example = "鍟嗗搧A-绾㈣壊-L")
+ private String skuName;
+
+ @Schema(description = "浠撳簱缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long warehouseId;
+
+ @Schema(description = "浠撳簱鍚嶇О", example = "涓讳粨搴�")
+ private String warehouseName;
+
+ @Schema(description = "棰勭暀鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal reserveQuantity;
+
+ @Schema(description = "宸蹭娇鐢ㄦ暟閲�", example = "0.00")
+ private BigDecimal usedQuantity;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "鐘舵�佸悕绉�", example = "棰勭暀涓�")
+ private String statusName;
+
+ @Schema(description = "杩囨湡鏃堕棿", example = "2026-06-30 00:00:00")
+ private LocalDateTime expireTime;
+
+ @Schema(description = "澶囨敞", example = "閿�鍞鍗曢鐣�")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java
index 8dde808..42981fb 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java
@@ -24,6 +24,9 @@
@Schema(description = "鍟嗗搧鍝佺墝缂栧彿", example = "2048")
private Long brandId;
+ @Schema(description = "鍟嗗搧鐘舵��", example = "0")
+ private Integer status;
+
@Schema(description = "鍒涘缓鏃堕棿")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java
index 5ae42c3..e17a80d 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java
@@ -29,6 +29,9 @@
@Schema(description = "鍟嗗搧鍝佺墝缂栧彿", example = "2048")
private Long brandId;
+ @Schema(description = "鍟嗗搧鐘舵��", example = "0")
+ private Integer status;
+
@Schema(description = "鍒涘缓鏃堕棿")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java
index 6b8cadf..b7b0ae5 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java
@@ -36,6 +36,9 @@
@Size(max = 20, message = "鍗曚綅闀垮害涓嶈兘瓒呰繃 20 涓瓧绗�")
private String unit;
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", example = "1")
+ private Long unitMeasureId;
+
@Schema(description = "鍟嗗搧鍝佺墝缂栧彿", example = "1")
private Long brandId;
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryReserveDO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryReserveDO.java
new file mode 100644
index 0000000..cf3d160
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryReserveDO.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.wms.dal.dataobject.inventory;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveBizTypeEnum;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * WMS 搴撳瓨棰勭暀 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("wms_inventory_reserve")
+@KeySequence("wms_inventory_reserve_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WmsInventoryReserveDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 棰勭暀鍗曞彿
+ */
+ private String reserveNo;
+
+ /**
+ * 涓氬姟绫诲瀷
+ *
+ * 鏋氫妇 {@link WmsReserveBizTypeEnum}
+ */
+ private Integer bizType;
+
+ /**
+ * 涓氬姟鍗曟嵁 ID
+ */
+ private Long bizId;
+
+ /**
+ * 涓氬姟鍗曟嵁鍙�
+ */
+ private String bizNo;
+
+ /**
+ * SKU 缂栧彿
+ *
+ * 鍏宠仈 {@link WmsItemSkuDO#getId()}
+ */
+ private Long skuId;
+
+ /**
+ * 浠撳簱缂栧彿
+ *
+ * 鍏宠仈 {@link WmsWarehouseDO#getId()}
+ */
+ private Long warehouseId;
+
+ /**
+ * 棰勭暀鏁伴噺
+ */
+ private BigDecimal reserveQuantity;
+
+ /**
+ * 宸蹭娇鐢ㄦ暟閲�
+ */
+ private BigDecimal usedQuantity;
+
+ /**
+ * 鐘舵��
+ *
+ * 鏋氫妇 {@link WmsReserveStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 杩囨湡鏃堕棿
+ */
+ private LocalDateTime expireTime;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryTransactionDO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryTransactionDO.java
new file mode 100644
index 0000000..3b0c38a
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryTransactionDO.java
@@ -0,0 +1,116 @@
+package cn.iocoder.yudao.module.wms.dal.dataobject.inventory;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsTransactionTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * WMS 搴撳瓨浜嬪姟娴佹按 DO
+ *
+ * 璁板綍姣忎竴绗斿簱瀛樺彉鍔ㄦ搷浣�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("wms_inventory_transaction")
+@KeySequence("wms_inventory_transaction_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WmsInventoryTransactionDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 浜嬪姟娴佹按鍙�
+ */
+ private String transactionNo;
+
+ /**
+ * 浜嬪姟绫诲瀷
+ *
+ * 鏋氫妇 {@link WmsTransactionTypeEnum}
+ */
+ private Integer transactionType;
+
+ /**
+ * 涓氬姟绫诲瀷
+ */
+ private Integer bizType;
+
+ /**
+ * 涓氬姟鍗曟嵁 ID
+ */
+ private Long bizId;
+
+ /**
+ * 涓氬姟鍗曟嵁鍙�
+ */
+ private String bizNo;
+
+ /**
+ * SKU 缂栧彿
+ *
+ * 鍏宠仈 {@link WmsItemSkuDO#getId()}
+ */
+ private Long skuId;
+
+ /**
+ * 浠撳簱缂栧彿
+ *
+ * 鍏宠仈 {@link WmsWarehouseDO#getId()}
+ */
+ private Long warehouseId;
+
+ /**
+ * 鍙樺姩鏁伴噺
+ *
+ * 姝f暟=鍏ュ簱锛岃礋鏁�=鍑哄簱
+ */
+ private BigDecimal quantity;
+
+ /**
+ * 鍙樺姩鍓嶆暟閲�
+ */
+ private BigDecimal beforeQuantity;
+
+ /**
+ * 鍙樺姩鍚庢暟閲�
+ */
+ private BigDecimal afterQuantity;
+
+ /**
+ * 鍏宠仈棰勭暀 ID
+ */
+ private Long reserveId;
+
+ /**
+ * 鍏宠仈棰勭暀鍗曞彿
+ */
+ private String reserveNo;
+
+ /**
+ * 浜嬪姟鏃堕棿
+ */
+ private LocalDateTime transactionTime;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java
index 7236b49..ec67fc9 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java
@@ -76,6 +76,11 @@
/**
* 閿�鍞环锛屽崟浣嶅厓
*/
- private BigDecimal sellingPrice;
+ private BigDecimal salePrice;
+
+ /**
+ * 鐘舵��
+ */
+ private Integer status;
}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java
index 1e1a17a..f4dc280 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryQueryReqDTO;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryListReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
@@ -123,4 +124,17 @@
throw new IllegalArgumentException("鏈煡搴撳瓨缁熻缁村害锛�" + type);
}
+ /**
+ * 鏍规嵁 API 鏌ヨ鏉′欢鏌ヨ搴撳瓨鍒楄〃
+ */
+ default List<WmsInventoryDO> selectListByQuery(WmsInventoryQueryReqDTO reqDTO) {
+ LambdaQueryWrapperX<WmsInventoryDO> query = new LambdaQueryWrapperX<WmsInventoryDO>()
+ .inIfPresent(WmsInventoryDO::getSkuId, reqDTO.getSkuIds())
+ .inIfPresent(WmsInventoryDO::getWarehouseId, reqDTO.getWarehouseIds());
+ if (Boolean.TRUE.equals(reqDTO.getOnlyPositive())) {
+ query.gt(WmsInventoryDO::getQuantity, BigDecimal.ZERO);
+ }
+ return selectList(query);
+ }
+
}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryReserveMapper.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryReserveMapper.java
new file mode 100644
index 0000000..a2f3f97
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryReserveMapper.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.wms.dal.mysql.inventory;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryQueryReqDTO;
+import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨棰勭暀 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface WmsInventoryReserveMapper extends BaseMapperX<WmsInventoryReserveDO> {
+
+ default PageResult<WmsInventoryReserveDO> selectPage(WmsInventoryReservePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<WmsInventoryReserveDO>()
+ .likeIfPresent(WmsInventoryReserveDO::getReserveNo, reqVO.getReserveNo())
+ .eqIfPresent(WmsInventoryReserveDO::getBizType, reqVO.getBizType())
+ .likeIfPresent(WmsInventoryReserveDO::getBizNo, reqVO.getBizNo())
+ .eqIfPresent(WmsInventoryReserveDO::getStatus, reqVO.getStatus())
+ .orderByDesc(WmsInventoryReserveDO::getId));
+ }
+
+ default List<WmsInventoryReserveDO> selectListByBiz(Integer bizType, Long bizId) {
+ return selectList(new LambdaQueryWrapperX<WmsInventoryReserveDO>()
+ .eq(WmsInventoryReserveDO::getBizType, bizType)
+ .eq(WmsInventoryReserveDO::getBizId, bizId)
+ .in(WmsInventoryReserveDO::getStatus, 0, 1)); // 棰勭暀涓垨閮ㄥ垎浣跨敤
+ }
+
+ default WmsInventoryReserveDO selectByReserveNo(String reserveNo) {
+ return selectOne(WmsInventoryReserveDO::getReserveNo, reserveNo);
+ }
+
+ /**
+ * 鏌ヨ鎸囧畾 SKU 鍜屼粨搴撶殑棰勭暀鏁伴噺
+ */
+ @Select("SELECT IFNULL(SUM(reserve_quantity - used_quantity), 0) FROM wms_inventory_reserve " +
+ "WHERE sku_id = #{skuId} AND warehouse_id = #{warehouseId} AND status IN (0, 1) AND deleted = 0")
+ BigDecimal selectReserveQuantity(@Param("skuId") Long skuId, @Param("warehouseId") Long warehouseId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryTransactionMapper.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryTransactionMapper.java
new file mode 100644
index 0000000..386d78e
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryTransactionMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.wms.dal.mysql.inventory;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨浜嬪姟娴佹按 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface WmsInventoryTransactionMapper extends BaseMapperX<WmsInventoryTransactionDO> {
+
+ default List<WmsInventoryTransactionDO> selectListByBiz(Integer bizType, Long bizId) {
+ return selectList(new LambdaQueryWrapperX<WmsInventoryTransactionDO>()
+ .eq(WmsInventoryTransactionDO::getBizType, bizType)
+ .eq(WmsInventoryTransactionDO::getBizId, bizId)
+ .orderByAsc(WmsInventoryTransactionDO::getTransactionTime));
+ }
+
+ default WmsInventoryTransactionDO selectByTransactionNo(String transactionNo) {
+ return selectOne(WmsInventoryTransactionDO::getTransactionNo, transactionNo);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
index e854bc5..0b4500c 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
@@ -13,4 +13,8 @@
String RECEIPT_ORDER_TYPE = "wms_receipt_order_type";
String SHIPMENT_ORDER_TYPE = "wms_shipment_order_type";
+ String WMS_RESERVE_BIZ_TYPE = "wms_reserve_biz_type";
+ String WMS_RESERVE_STATUS = "wms_reserve_status";
+ String WMS_TRANSACTION_TYPE = "wms_transaction_type";
+
}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
index 2175627..1d9bf0e 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
@@ -87,5 +87,13 @@
// ========== WMS 搴撳瓨 1-060-300-000 ==========
ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000,
"搴撳瓨涓嶈冻锛屽晢鍝侊細{}锛屽晢鍝佽鏍硷細{}锛屼粨搴擄細{}锛屽綋鍓嶅簱瀛橈細{}锛屽彉鏇存暟閲忥細{}");
+ ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH_FOR_RESERVE = new ErrorCode(1_060_300_001,
+ "鍙敤搴撳瓨涓嶈冻锛孲KU锛歿}锛屼粨搴擄細{}锛屽彲鐢ㄥ簱瀛橈細{}锛岄鐣欐暟閲忥細{}");
+
+ // ========== WMS 搴撳瓨棰勭暀 1-060-301-000 ==========
+ ErrorCode INVENTORY_RESERVE_NOT_EXISTS = new ErrorCode(1_060_301_000, "搴撳瓨棰勭暀涓嶅瓨鍦�");
+ ErrorCode INVENTORY_RESERVE_STATUS_INVALID = new ErrorCode(1_060_301_001, "搴撳瓨棰勭暀鐘舵�佷笉鍏佽姝ゆ搷浣�");
+ ErrorCode INVENTORY_RESERVE_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_301_002,
+ "棰勭暀鏁伴噺涓嶈冻锛岄鐣欏崟鍙凤細{}锛屽墿浣欓鐣欙細{}锛岃姹備娇鐢細{}");
}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsInventoryBizTypeEnum.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsInventoryBizTypeEnum.java
new file mode 100644
index 0000000..89b19c8
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsInventoryBizTypeEnum.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.wms.enums.inventory;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * WMS 搴撳瓨涓氬姟绫诲瀷鏋氫妇
+ *
+ * 鐢ㄤ簬鍖哄垎搴撳瓨鎿嶄綔鐨勪笟鍔℃潵婧�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum WmsInventoryBizTypeEnum implements ArrayValuable<Integer> {
+
+ // ========== 鍏ュ簱绫诲瀷 [100, 150) ==========
+
+ PURCHASE_RECEIPT(100, "ERP 閲囪喘鍏ュ簱"),
+ PRODUCE_RECEIPT(101, "MES 鐢熶骇鍏ュ簱"),
+ RETURN_RECEIPT(102, "閫�璐у叆搴�"),
+ OTHER_RECEIPT(103, "鍏朵粬鍏ュ簱"),
+ OTHER(104, "鍏朵粬"),
+
+ // ========== 鍑哄簱绫诲瀷 [150, 200) ==========
+
+ SALE_SHIPMENT(150, "ERP 閿�鍞嚭搴�"),
+ PRODUCE_MATERIAL(151, "MES 鐢熶骇棰嗘枡"),
+ RETURN_SHIPMENT(152, "閫�璐у嚭搴�"),
+ OTHER_SHIPMENT(153, "鍏朵粬鍑哄簱"),
+
+ ;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(WmsInventoryBizTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 绫诲瀷
+ */
+ private final Integer type;
+ /**
+ * 鍚嶇О
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓哄叆搴撶被鍨�
+ */
+ public static boolean isReceiptType(Integer type) {
+ return type != null && type >= 100 && type < 150;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓哄嚭搴撶被鍨�
+ */
+ public static boolean isShipmentType(Integer type) {
+ return type != null && type >= 150 && type < 200;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveBizTypeEnum.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveBizTypeEnum.java
new file mode 100644
index 0000000..d8e7bff
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveBizTypeEnum.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.wms.enums.inventory;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * WMS 搴撳瓨棰勭暀涓氬姟绫诲瀷鏋氫妇
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum WmsReserveBizTypeEnum implements ArrayValuable<Integer> {
+
+ SALE(10, "閿�鍞鐣�"),
+ PRODUCE(20, "鐢熶骇棰勭暀"),
+ ;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(WmsReserveBizTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 绫诲瀷
+ */
+ private final Integer type;
+ /**
+ * 鍚嶇О
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveStatusEnum.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveStatusEnum.java
new file mode 100644
index 0000000..4e2766d
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveStatusEnum.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.wms.enums.inventory;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * WMS 搴撳瓨棰勭暀鐘舵�佹灇涓�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum WmsReserveStatusEnum implements ArrayValuable<Integer> {
+
+ RESERVED(0, "棰勭暀涓�"),
+ PARTIAL_USED(1, "閮ㄥ垎浣跨敤"),
+ USED(2, "宸蹭娇鐢�"),
+ RELEASED(3, "宸查噴鏀�"),
+ ;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(WmsReserveStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 鐘舵��
+ */
+ private final Integer status;
+ /**
+ * 鍚嶇О
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsTransactionTypeEnum.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsTransactionTypeEnum.java
new file mode 100644
index 0000000..a5e6cd6
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsTransactionTypeEnum.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.wms.enums.inventory;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * WMS 搴撳瓨浜嬪姟绫诲瀷鏋氫妇
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum WmsTransactionTypeEnum implements ArrayValuable<Integer> {
+
+ RECEIPT(10, "鍏ュ簱"),
+ SHIPMENT(20, "鍑哄簱"),
+ RESERVE(30, "棰勭暀"),
+ RELEASE(40, "閲婃斁"),
+ CHECK(50, "鐩樼偣"),
+ MOVE(60, "璋冩嫧"),
+ ;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(WmsTransactionTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 绫诲瀷
+ */
+ private final Integer type;
+ /**
+ * 鍚嶇О
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/stock/StockBizTypeEnum.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/stock/StockBizTypeEnum.java
new file mode 100644
index 0000000..9f1b614
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/stock/StockBizTypeEnum.java
@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.wms.enums.stock;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 搴撳瓨涓氬姟绫诲瀷鏋氫妇
+ *
+ * 瀹氫箟搴撳瓨鍙樺姩鐨勪笟鍔℃潵婧�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum StockBizTypeEnum {
+
+ // ==================== 鍏ュ簱绫诲瀷 ====================
+
+ PURCHASE_IN(10, "閲囪喘鍏ュ簱"),
+ PURCHASE_IN_CANCEL(11, "閲囪喘鍏ュ簱浣滃簾"),
+
+ PRODUCE_IN(30, "鐢熶骇鍏ュ簱"),
+ PRODUCE_IN_CANCEL(31, "鐢熶骇鍏ュ簱浣滃簾"),
+
+ PRODUCE_RETURN(50, "鐢熶骇閫�鏂�"),
+ PRODUCE_RETURN_CANCEL(51, "鐢熶骇閫�鏂欎綔搴�"),
+
+ SALE_RETURN_IN(70, "閿�鍞��璐у叆搴�"),
+ SALE_RETURN_IN_CANCEL(71, "閿�鍞��璐у叆搴撲綔搴�"),
+
+ TRANSFER_IN(80, "璋冩嫧鍏ュ簱"),
+ TRANSFER_IN_CANCEL(81, "璋冩嫧鍏ュ簱浣滃簾"),
+
+ CHECK_IN(100, "鐩樼泩鍏ュ簱"),
+ CHECK_IN_CANCEL(101, "鐩樼泩鍏ュ簱浣滃簾"),
+
+ // ==================== 鍑哄簱绫诲瀷 ====================
+
+ SALE_OUT(20, "閿�鍞嚭搴�"),
+ SALE_OUT_CANCEL(21, "閿�鍞嚭搴撲綔搴�"),
+
+ PRODUCE_ISSUE(40, "鐢熶骇棰嗘枡"),
+ PRODUCE_ISSUE_CANCEL(41, "鐢熶骇棰嗘枡浣滃簾"),
+
+ PURCHASE_RETURN_OUT(60, "閲囪喘閫�璐у嚭搴�"),
+ PURCHASE_RETURN_OUT_CANCEL(61, "閲囪喘閫�璐у嚭搴撲綔搴�"),
+
+ TRANSFER_OUT(90, "璋冩嫧鍑哄簱"),
+ TRANSFER_OUT_CANCEL(91, "璋冩嫧鍑哄簱浣滃簾"),
+
+ CHECK_OUT(110, "鐩樹簭鍑哄簱"),
+ CHECK_OUT_CANCEL(111, "鐩樹簭鍑哄簱浣滃簾");
+
+ /**
+ * 涓氬姟绫诲瀷缂栫爜
+ */
+ private final Integer type;
+
+ /**
+ * 涓氬姟绫诲瀷鍚嶇О
+ */
+ private final String name;
+
+ /**
+ * 鍒ゆ柇鏄惁涓哄叆搴撶被鍨�
+ */
+ public boolean isInbound() {
+ // 鍏ュ簱绫诲瀷缂栫爜锛�10, 30, 50, 70, 80, 100
+ return type % 20 == 10 || type == 50 || type == 70 || type == 100;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓哄嚭搴撶被鍨�
+ */
+ public boolean isOutbound() {
+ // 鍑哄簱绫诲瀷缂栫爜锛�20, 40, 60, 90, 110
+ return type % 20 == 0 && type != 0 && !isInbound();
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓轰綔搴熺被鍨�
+ */
+ public boolean isCancel() {
+ return type % 10 == 1 && type != 11; // 浣滃簾绫诲瀷鐨勭紪鐮佸熬鏁颁负 1
+ }
+
+ /**
+ * 鑾峰彇瀵瑰簲鐨勬甯镐笟鍔$被鍨嬶紙濡傛灉鏄綔搴熺被鍨嬶級
+ */
+ public StockBizTypeEnum getNormalType() {
+ if (isCancel()) {
+ return fromType(type - 1);
+ }
+ return this;
+ }
+
+ /**
+ * 鑾峰彇瀵瑰簲鐨勪綔搴熶笟鍔$被鍨嬶紙濡傛灉鏄甯哥被鍨嬶級
+ */
+ public StockBizTypeEnum getCancelType() {
+ if (!isCancel()) {
+ return fromType(type + 1);
+ }
+ return this;
+ }
+
+ /**
+ * 鏍规嵁绫诲瀷缂栫爜鑾峰彇鏋氫妇
+ */
+ public static StockBizTypeEnum fromType(Integer type) {
+ for (StockBizTypeEnum value : values()) {
+ if (value.getType().equals(type)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveService.java
new file mode 100644
index 0000000..b503994
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveService.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.wms.service.inventory;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
+import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨棰勭暀 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface WmsInventoryReserveService {
+
+ /**
+ * 鍒涘缓搴撳瓨棰勭暀
+ *
+ * @param reqDTO 棰勭暀璇锋眰
+ * @return 棰勭暀鍗曞彿
+ */
+ String createReserve(WmsReserveReqDTO reqDTO);
+
+ /**
+ * 閲婃斁搴撳瓨棰勭暀
+ *
+ * @param reserveNo 棰勭暀鍗曞彿
+ */
+ void releaseReserve(String reserveNo);
+
+ /**
+ * 浣跨敤棰勭暀搴撳瓨锛堟墸鍑忥級
+ *
+ * @param reqDTO 浣跨敤璇锋眰
+ */
+ void useReserve(WmsUseReserveReqDTO reqDTO);
+
+ /**
+ * 鏍规嵁涓氬姟鏌ヨ棰勭暀璁板綍
+ *
+ * @param bizType 涓氬姟绫诲瀷
+ * @param bizId 涓氬姟鍗曟嵁ID
+ * @return 棰勭暀璁板綍鍒楄〃
+ */
+ List<WmsInventoryReserveDO> getReserveListByBiz(Integer bizType, Long bizId);
+
+ /**
+ * 鑾峰彇搴撳瓨棰勭暀鍒嗛〉
+ *
+ * @param reqVO 鍒嗛〉璇锋眰
+ * @return 鍒嗛〉缁撴灉
+ */
+ PageResult<WmsInventoryReserveDO> getReservePage(WmsInventoryReservePageReqVO reqVO);
+
+ /**
+ * 鑾峰彇棰勭暀璇︽儏
+ *
+ * @param id 棰勭暀ID
+ * @return 棰勭暀璁板綍
+ */
+ WmsInventoryReserveDO getReserve(Long id);
+
+ /**
+ * 鏌ヨ鎸囧畾 SKU 鍜屼粨搴撶殑棰勭暀鏁伴噺
+ *
+ * @param skuId SKU缂栧彿
+ * @param warehouseId 浠撳簱缂栧彿
+ * @return 棰勭暀鏁伴噺
+ */
+ BigDecimal getReserveQuantity(Long skuId, Long warehouseId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveServiceImpl.java
new file mode 100644
index 0000000..44ac36e
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveServiceImpl.java
@@ -0,0 +1,210 @@
+package cn.iocoder.yudao.module.wms.service.inventory;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
+import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
+import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryReserveMapper;
+import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryTransactionMapper;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveStatusEnum;
+import cn.iocoder.yudao.module.wms.enums.inventory.WmsTransactionTypeEnum;
+import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.UUID;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
+
+/**
+ * WMS 搴撳瓨棰勭暀 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+@Slf4j
+public class WmsInventoryReserveServiceImpl implements WmsInventoryReserveService {
+
+ @Resource
+ private WmsInventoryReserveMapper reserveMapper;
+
+ @Resource
+ private WmsInventoryTransactionMapper transactionMapper;
+
+ @Resource
+ private WmsInventoryService inventoryService;
+
+ @Resource
+ private WmsInventoryTransactionService transactionService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String createReserve(WmsReserveReqDTO reqDTO) {
+ // 1. 鏍¢獙鍙敤搴撳瓨
+ BigDecimal availableQuantity = inventoryService.getAvailableQuantity(reqDTO.getSkuId(), reqDTO.getWarehouseId());
+ if (availableQuantity.compareTo(reqDTO.getQuantity()) < 0) {
+ throw exception(INVENTORY_QUANTITY_NOT_ENOUGH_FOR_RESERVE,
+ reqDTO.getSkuId(), reqDTO.getWarehouseId(), availableQuantity, reqDTO.getQuantity());
+ }
+
+ // 2. 鐢熸垚棰勭暀鍗曞彿
+ String reserveNo = generateReserveNo();
+
+ // 3. 鍒涘缓棰勭暀璁板綍
+ WmsInventoryReserveDO reserve = BeanUtils.toBean(reqDTO, WmsInventoryReserveDO.class);
+ reserve.setReserveNo(reserveNo);
+ reserve.setReserveQuantity(reqDTO.getQuantity());
+ reserve.setUsedQuantity(BigDecimal.ZERO);
+ reserve.setStatus(WmsReserveStatusEnum.RESERVED.getStatus());
+ reserveMapper.insert(reserve);
+
+ // 4. 璁板綍浜嬪姟娴佹按
+ transactionService.createTransaction(buildReserveTransaction(reserve, reqDTO.getQuantity()));
+
+ return reserveNo;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void releaseReserve(String reserveNo) {
+ // 1. 鏌ヨ棰勭暀璁板綍
+ WmsInventoryReserveDO reserve = reserveMapper.selectByReserveNo(reserveNo);
+ if (reserve == null) {
+ throw exception(INVENTORY_RESERVE_NOT_EXISTS);
+ }
+ // 1.1 鏍¢獙鐘舵�侊細鍙湁棰勭暀涓垨閮ㄥ垎浣跨敤鐘舵�佸彲閲婃斁
+ if (reserve.getStatus() != WmsReserveStatusEnum.RESERVED.getStatus()
+ && reserve.getStatus() != WmsReserveStatusEnum.PARTIAL_USED.getStatus()) {
+ throw exception(INVENTORY_RESERVE_STATUS_INVALID);
+ }
+
+ // 2. 璁$畻閲婃斁鏁伴噺
+ BigDecimal releaseQuantity = reserve.getReserveQuantity().subtract(reserve.getUsedQuantity());
+
+ // 3. 鏇存柊棰勭暀鐘舵��
+ reserve.setStatus(WmsReserveStatusEnum.RELEASED.getStatus());
+ reserveMapper.updateById(reserve);
+
+ // 4. 璁板綍浜嬪姟娴佹按锛堥噴鏀句负璐熸暟锛�
+ transactionService.createTransaction(buildReleaseTransaction(reserve, releaseQuantity.negate()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void useReserve(WmsUseReserveReqDTO reqDTO) {
+ // 1. 鏌ヨ棰勭暀璁板綍
+ WmsInventoryReserveDO reserve = reserveMapper.selectByReserveNo(reqDTO.getReserveNo());
+ if (reserve == null) {
+ throw exception(INVENTORY_RESERVE_NOT_EXISTS);
+ }
+ // 1.1 鏍¢獙鐘舵��
+ if (reserve.getStatus() != WmsReserveStatusEnum.RESERVED.getStatus()
+ && reserve.getStatus() != WmsReserveStatusEnum.PARTIAL_USED.getStatus()) {
+ throw exception(INVENTORY_RESERVE_STATUS_INVALID);
+ }
+
+ // 2. 璁$畻鍓╀綑鍙娇鐢ㄦ暟閲�
+ BigDecimal remainQuantity = reserve.getReserveQuantity().subtract(reserve.getUsedQuantity());
+ if (reqDTO.getQuantity().compareTo(remainQuantity) > 0) {
+ throw exception(INVENTORY_RESERVE_QUANTITY_NOT_ENOUGH, reqDTO.getReserveNo(), remainQuantity, reqDTO.getQuantity());
+ }
+
+ // 3. 鏇存柊宸蹭娇鐢ㄦ暟閲忓拰鐘舵��
+ BigDecimal newUsedQuantity = reserve.getUsedQuantity().add(reqDTO.getQuantity());
+ reserve.setUsedQuantity(newUsedQuantity);
+ if (newUsedQuantity.compareTo(reserve.getReserveQuantity()) == 0) {
+ reserve.setStatus(WmsReserveStatusEnum.USED.getStatus());
+ } else {
+ reserve.setStatus(WmsReserveStatusEnum.PARTIAL_USED.getStatus());
+ }
+ reserveMapper.updateById(reserve);
+ }
+
+ @Override
+ public List<WmsInventoryReserveDO> getReserveListByBiz(Integer bizType, Long bizId) {
+ return reserveMapper.selectListByBiz(bizType, bizId);
+ }
+
+ @Override
+ public PageResult<WmsInventoryReserveDO> getReservePage(WmsInventoryReservePageReqVO reqVO) {
+ return reserveMapper.selectPage(reqVO);
+ }
+
+ @Override
+ public WmsInventoryReserveDO getReserve(Long id) {
+ return reserveMapper.selectById(id);
+ }
+
+ @Override
+ public BigDecimal getReserveQuantity(Long skuId, Long warehouseId) {
+ return reserveMapper.selectReserveQuantity(skuId, warehouseId);
+ }
+
+ /**
+ * 鐢熸垚棰勭暀鍗曞彿
+ */
+ private String generateReserveNo() {
+ return "RES" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
+ + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
+ }
+
+ /**
+ * 鏋勫缓棰勭暀浜嬪姟娴佹按
+ */
+ private WmsInventoryTransactionDO buildReserveTransaction(WmsInventoryReserveDO reserve, BigDecimal quantity) {
+ return WmsInventoryTransactionDO.builder()
+ .transactionNo(generateTransactionNo())
+ .transactionType(WmsTransactionTypeEnum.RESERVE.getType())
+ .bizType(reserve.getBizType())
+ .bizId(reserve.getBizId())
+ .bizNo(reserve.getBizNo())
+ .skuId(reserve.getSkuId())
+ .warehouseId(reserve.getWarehouseId())
+ .quantity(quantity)
+ .reserveId(reserve.getId())
+ .reserveNo(reserve.getReserveNo())
+ .transactionTime(LocalDateTime.now())
+ .remark(reserve.getRemark())
+ .build();
+ }
+
+ /**
+ * 鏋勫缓閲婃斁浜嬪姟娴佹按
+ */
+ private WmsInventoryTransactionDO buildReleaseTransaction(WmsInventoryReserveDO reserve, BigDecimal quantity) {
+ return WmsInventoryTransactionDO.builder()
+ .transactionNo(generateTransactionNo())
+ .transactionType(WmsTransactionTypeEnum.RELEASE.getType())
+ .bizType(reserve.getBizType())
+ .bizId(reserve.getBizId())
+ .bizNo(reserve.getBizNo())
+ .skuId(reserve.getSkuId())
+ .warehouseId(reserve.getWarehouseId())
+ .quantity(quantity)
+ .reserveId(reserve.getId())
+ .reserveNo(reserve.getReserveNo())
+ .transactionTime(LocalDateTime.now())
+ .remark("閲婃斁棰勭暀")
+ .build();
+ }
+
+ /**
+ * 鐢熸垚浜嬪姟娴佹按鍙�
+ */
+ private String generateTransactionNo() {
+ return "TRX" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java
index 8eb2c5d..6329588 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO;
+import java.math.BigDecimal;
import java.util.List;
/**
@@ -62,4 +63,13 @@
*/
void changeInventory(WmsInventoryChangeReqDTO reqDTO);
+ /**
+ * 鑾峰彇鍙敤搴撳瓨鏁伴噺锛堟�诲簱瀛� - 棰勭暀搴撳瓨锛�
+ *
+ * @param skuId SKU 缂栧彿
+ * @param warehouseId 浠撳簱缂栧彿
+ * @return 鍙敤搴撳瓨鏁伴噺
+ */
+ BigDecimal getAvailableQuantity(Long skuId, Long warehouseId);
+
}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java
index 3ff81ad..37f2cf0 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java
@@ -10,9 +10,11 @@
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryHistoryDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper;
+import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryReserveMapper;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO;
import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService;
@@ -50,6 +52,9 @@
@Resource
private WmsInventoryHistoryService inventoryHistoryService;
+
+ @Resource
+ private WmsInventoryReserveMapper reserveMapper;
@Resource
private WmsItemSkuService itemSkuService;
@@ -294,4 +299,19 @@
item.getWarehouseId(), beforeQuantity, item.getQuantity());
}
+ @Override
+ public BigDecimal getAvailableQuantity(Long skuId, Long warehouseId) {
+ // 1. 鏌ヨ搴撳瓨鏁伴噺
+ WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(skuId, warehouseId);
+ if (inventory == null) {
+ return BigDecimal.ZERO;
+ }
+
+ // 2. 鏌ヨ棰勭暀鏁伴噺
+ BigDecimal reserveQuantity = reserveMapper.selectReserveQuantity(skuId, warehouseId);
+
+ // 3. 璁$畻鍙敤鏁伴噺
+ return inventory.getQuantity().subtract(reserveQuantity != null ? reserveQuantity : BigDecimal.ZERO);
+ }
+
}
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionService.java
new file mode 100644
index 0000000..961032c
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.wms.service.inventory;
+
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
+
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨浜嬪姟娴佹按 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface WmsInventoryTransactionService {
+
+ /**
+ * 鍒涘缓浜嬪姟娴佹按
+ *
+ * @param transaction 浜嬪姟娴佹按
+ * @return 浜嬪姟娴佹按ID
+ */
+ Long createTransaction(WmsInventoryTransactionDO transaction);
+
+ /**
+ * 鎵归噺鍒涘缓浜嬪姟娴佹按
+ *
+ * @param transactions 浜嬪姟娴佹按鍒楄〃
+ */
+ void createTransactionList(List<WmsInventoryTransactionDO> transactions);
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionServiceImpl.java
new file mode 100644
index 0000000..930183f
--- /dev/null
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionServiceImpl.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.wms.service.inventory;
+
+import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
+import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryTransactionMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨浜嬪姟娴佹按 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+@Slf4j
+public class WmsInventoryTransactionServiceImpl implements WmsInventoryTransactionService {
+
+ @Resource
+ private WmsInventoryTransactionMapper transactionMapper;
+
+ @Override
+ public Long createTransaction(WmsInventoryTransactionDO transaction) {
+ transactionMapper.insert(transaction);
+ return transaction.getId();
+ }
+
+ @Override
+ public void createTransactionList(List<WmsInventoryTransactionDO> transactions) {
+ if (transactions != null && !transactions.isEmpty()) {
+ transactionMapper.insertBatch(transactions);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java
index 191ca86..7875778 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java
@@ -1,23 +1,24 @@
package cn.iocoder.yudao.module.wms.service.md.item;
import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemUpdateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item.WmsItemListReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item.WmsItemPageReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item.WmsItemSaveReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO;
-import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemMapper;
import jakarta.annotation.Resource;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
@@ -25,129 +26,91 @@
/**
* WMS 鍟嗗搧 Service 瀹炵幇绫�
*
+ * 鏀归�犺鏄庯細WMS 鍟嗗搧鏁版嵁鏉ユ簮浜� MDM 鐗╂枡涓绘暟鎹�
+ * - 鎵�鏈夋搷浣滆皟鐢� MDM API
+ * - WMS 鍟嗗搧瀵瑰簲 MDM 鐗╂枡绫诲瀷 = 鐗╂枡锛坕temType = 1锛�
+ * - 瀛楁鏄犲皠锛歐msItemDO.unit <- MDM 璁¢噺鍗曚綅鍚嶇О
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@Validated
public class WmsItemServiceImpl implements WmsItemService {
+ /** WMS 鍟嗗搧瀵瑰簲鐨� MDM 鐗╂枡绫诲瀷锛氱墿鏂� */
+ private static final int MDM_ITEM_TYPE_MATERIAL = 1;
+
@Resource
- private WmsItemMapper itemMapper;
+ private MdmItemApi mdmItemApi;
+
@Resource
- @Lazy // 寤惰繜鍔犺浇锛岄伩鍏嶅惊鐜緷璧�
- private WmsItemCategoryService categoryService;
- @Resource
- @Lazy // 寤惰繜鍔犺浇锛岄伩鍏嶅惊鐜緷璧�
- private WmsItemBrandService brandService;
- @Resource
- private WmsItemSkuService itemSkuService;
+ private MdmUnitMeasureApi mdmUnitMeasureApi;
@Override
- @Transactional(rollbackFor = Exception.class)
public Long createItem(WmsItemSaveReqVO createReqVO) {
// 鏍¢獙鏁版嵁
validateItemSaveData(createReqVO);
- // 鎻掑叆鍟嗗搧
- WmsItemDO item = BeanUtils.toBean(createReqVO, WmsItemDO.class);
- itemMapper.insert(item);
- // 鎻掑叆 SKU
- itemSkuService.createItemSkuList(item.getId(), createReqVO.getSkus());
- return item.getId();
+ // 鏋勫缓鍒涘缓璇锋眰骞惰皟鐢� MDM API
+ MdmItemCreateReqDTO createReqDTO = buildCreateReqDTO(createReqVO);
+ return mdmItemApi.createItem(createReqDTO).getCheckedData();
}
@Override
- @Transactional(rollbackFor = Exception.class)
public void updateItem(WmsItemSaveReqVO updateReqVO) {
// 鏍¢獙瀛樺湪
validateItemExists(updateReqVO.getId());
// 鏍¢獙鏁版嵁
validateItemSaveData(updateReqVO);
- // 鏇存柊鍟嗗搧
- WmsItemDO updateObj = BeanUtils.toBean(updateReqVO, WmsItemDO.class);
- itemMapper.updateById(updateObj);
- // 鏇存柊 SKU
- itemSkuService.updateItemSkuList(updateReqVO.getId(), updateReqVO.getSkus());
- }
-
- private void validateItemSaveData(WmsItemSaveReqVO reqVO) {
- // 鏍¢獙鍟嗗搧缂栧彿鍞竴
- validateItemCodeUnique(reqVO.getId(), reqVO.getCode());
- // 鏍¢獙鍟嗗搧鍚嶇О鍞竴
- validateItemNameUnique(reqVO.getId(), reqVO.getName());
- // 鏍¢獙鍟嗗搧鍒嗙被瀛樺湪
- validateCategoryExists(reqVO.getCategoryId());
- // 鏍¢獙鍟嗗搧鍝佺墝瀛樺湪
- validateBrandExists(reqVO.getBrandId());
- }
-
- private void validateItemCodeUnique(Long id, String code) {
- WmsItemDO item = itemMapper.selectByCode(code);
- if (item == null) {
- return;
- }
- if (id == null || ObjectUtil.notEqual(item.getId(), id)) {
- throw exception(ITEM_CODE_DUPLICATE);
- }
- }
-
- private void validateItemNameUnique(Long id, String name) {
- WmsItemDO item = itemMapper.selectByName(name);
- if (item == null) {
- return;
- }
- if (id == null || ObjectUtil.notEqual(item.getId(), id)) {
- throw exception(ITEM_NAME_DUPLICATE);
- }
- }
-
- private void validateCategoryExists(Long categoryId) {
- categoryService.validateItemCategoryExists(categoryId);
- }
-
- private void validateBrandExists(Long brandId) {
- if (brandId == null) {
- return;
- }
- brandService.validateItemBrandExists(brandId);
+ // 鏋勫缓鏇存柊璇锋眰骞惰皟鐢� MDM API
+ MdmItemUpdateReqDTO updateReqDTO = buildUpdateReqDTO(updateReqVO);
+ mdmItemApi.updateItem(updateReqDTO);
}
@Override
- @Transactional(rollbackFor = Exception.class)
public void deleteItem(Long id) {
// 鏍¢獙瀛樺湪
validateItemExists(id);
-
- // 鍒犻櫎鍟嗗搧鍜� SKU
- itemSkuService.deleteItemSkuListByItemId(id);
- itemMapper.deleteById(id);
+ // WMS 鍟嗗搧鍒犻櫎瀹為檯涓虹鐢紙MDM 鐗╂枡涓嶈兘鐗╃悊鍒犻櫎锛�
+ mdmItemApi.updateItemStatus(id, CommonStatusEnum.DISABLE.getStatus());
}
@Override
public WmsItemDO validateItemExists(Long id) {
- WmsItemDO item = itemMapper.selectById(id);
- if (item == null) {
- throw exception(ITEM_NOT_EXISTS);
- }
- return item;
+ MdmItemRespDTO item = mdmItemApi.validateItemExists(id).getCheckedData();
+ return convertToWmsItemDO(item);
}
@Override
public WmsItemDO getItem(Long id) {
- return itemMapper.selectById(id);
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
+ return null;
+ }
+ return convertToWmsItemDO(item);
}
@Override
public PageResult<WmsItemDO> getItemPage(WmsItemPageReqVO pageReqVO) {
- return itemMapper.selectPage(pageReqVO,
- categoryService.getSelfAndChildItemCategoryIdList(pageReqVO.getCategoryId()));
+ // 璋冪敤 MDM API 鍒嗛〉鏌ヨ
+ PageResult<MdmItemRespDTO> mdmPageResult = mdmItemApi.getItemPage(
+ MDM_ITEM_TYPE_MATERIAL,
+ pageReqVO.getStatus(),
+ pageReqVO.getPageNo(),
+ pageReqVO.getPageSize()
+ ).getCheckedData();
+
+ // 杞崲缁撴灉
+ List<WmsItemDO> itemList = convertToWmsItemDOList(mdmPageResult.getList());
+ return new PageResult<>(itemList, mdmPageResult.getTotal());
}
@Override
public List<WmsItemDO> getItemList(WmsItemListReqVO listReqVO) {
- return itemMapper.selectList(listReqVO,
- categoryService.getSelfAndChildItemCategoryIdList(listReqVO.getCategoryId()));
+ // 璋冪敤 MDM API 鑾峰彇鍒楄〃
+ List<MdmItemRespDTO> itemList = mdmItemApi.getItemListByStatus(listReqVO.getStatus()).getCheckedData();
+ return convertToWmsItemDOList(itemList);
}
@Override
@@ -155,17 +118,140 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- return itemMapper.selectByIds(ids);
+ List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
+ return convertToWmsItemDOList(itemList);
}
@Override
public long getItemCountByCategoryId(Long categoryId) {
- return itemMapper.selectCountByCategoryId(categoryId);
+ return mdmItemApi.getItemCountByCategoryId(categoryId).getCheckedData();
}
@Override
public long getItemCountByBrandId(Long brandId) {
- return itemMapper.selectCountByBrandId(brandId);
+ // MDM 鏆傛棤鎸夊搧鐗岀粺璁℃柟娉曪紝杩斿洖 0
+ return 0L;
}
-}
+ // ==================== 绉佹湁鏂规硶 ====================
+
+ /**
+ * 鏍¢獙鍟嗗搧淇濆瓨鏁版嵁
+ */
+ private void validateItemSaveData(WmsItemSaveReqVO reqVO) {
+ // 鏍¢獙鍟嗗搧缂栫爜鍞竴
+ if (reqVO.getCode() != null) {
+ MdmItemRespDTO existItem = mdmItemApi.getItemByCode(reqVO.getCode()).getCheckedData();
+ if (existItem != null && !existItem.getId().equals(reqVO.getId())) {
+ throw exception(ITEM_CODE_DUPLICATE);
+ }
+ }
+ // 鏍¢獙鍟嗗搧鍚嶇О鍞竴
+ if (reqVO.getName() != null) {
+ // MDM 鏆傛棤鎸夊悕绉版煡璇㈡柟娉曪紝璺宠繃姝ゆ牎楠�
+ }
+ }
+
+ /**
+ * 鏋勫缓鍒涘缓璇锋眰 DTO
+ */
+ private MdmItemCreateReqDTO buildCreateReqDTO(WmsItemSaveReqVO saveReqVO) {
+ MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
+ createReqDTO.setCode(saveReqVO.getCode());
+ createReqDTO.setName(saveReqVO.getName());
+ createReqDTO.setCategoryId(saveReqVO.getCategoryId());
+ createReqDTO.setBrandId(saveReqVO.getBrandId());
+ createReqDTO.setUnitMeasureId(saveReqVO.getUnitMeasureId());
+ createReqDTO.setItemType(MDM_ITEM_TYPE_MATERIAL);
+ createReqDTO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ createReqDTO.setRemark(saveReqVO.getRemark());
+ return createReqDTO;
+ }
+
+ /**
+ * 鏋勫缓鏇存柊璇锋眰 DTO
+ */
+ private MdmItemUpdateReqDTO buildUpdateReqDTO(WmsItemSaveReqVO saveReqVO) {
+ MdmItemUpdateReqDTO updateReqDTO = new MdmItemUpdateReqDTO();
+ updateReqDTO.setId(saveReqVO.getId());
+ updateReqDTO.setCode(saveReqVO.getCode());
+ updateReqDTO.setName(saveReqVO.getName());
+ updateReqDTO.setCategoryId(saveReqVO.getCategoryId());
+ updateReqDTO.setBrandId(saveReqVO.getBrandId());
+ updateReqDTO.setUnitMeasureId(saveReqVO.getUnitMeasureId());
+ updateReqDTO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ updateReqDTO.setRemark(saveReqVO.getRemark());
+ return updateReqDTO;
+ }
+
+ /**
+ * 杞崲 MDM 鐗╂枡涓� WMS 鍟嗗搧
+ */
+ private WmsItemDO convertToWmsItemDO(MdmItemRespDTO mdmItem) {
+ if (mdmItem == null) {
+ return null;
+ }
+ WmsItemDO item = new WmsItemDO();
+ item.setId(mdmItem.getId());
+ item.setCode(mdmItem.getCode());
+ item.setName(mdmItem.getName());
+ item.setCategoryId(mdmItem.getCategoryId());
+ item.setBrandId(mdmItem.getBrandId());
+ item.setRemark(mdmItem.getRemark());
+
+ // 鑾峰彇璁¢噺鍗曚綅鍚嶇О
+ if (mdmItem.getUnitMeasureId() != null) {
+ MdmUnitMeasureRespDTO unit = mdmUnitMeasureApi.getUnitMeasure(mdmItem.getUnitMeasureId()).getCheckedData();
+ if (unit != null) {
+ item.setUnit(unit.getName());
+ }
+ }
+ return item;
+ }
+
+ /**
+ * 鎵归噺杞崲 MDM 鐗╂枡鍒楄〃涓� WMS 鍟嗗搧鍒楄〃
+ */
+ private List<WmsItemDO> convertToWmsItemDOList(List<MdmItemRespDTO> mdmItemList) {
+ if (CollUtil.isEmpty(mdmItemList)) {
+ return Collections.emptyList();
+ }
+
+ // 鎵归噺鑾峰彇璁¢噺鍗曚綅
+ Set<Long> unitIds = mdmItemList.stream()
+ .map(MdmItemRespDTO::getUnitMeasureId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ Map<Long, MdmUnitMeasureRespDTO> unitMap = new HashMap<>();
+ if (!unitIds.isEmpty()) {
+ List<MdmUnitMeasureRespDTO> units = mdmUnitMeasureApi.getUnitMeasureList(unitIds).getCheckedData();
+ for (MdmUnitMeasureRespDTO unit : units) {
+ unitMap.put(unit.getId(), unit);
+ }
+ }
+
+ // 杞崲鍒楄〃
+ List<WmsItemDO> result = new ArrayList<>();
+ for (MdmItemRespDTO mdmItem : mdmItemList) {
+ WmsItemDO item = new WmsItemDO();
+ item.setId(mdmItem.getId());
+ item.setCode(mdmItem.getCode());
+ item.setName(mdmItem.getName());
+ item.setCategoryId(mdmItem.getCategoryId());
+ item.setBrandId(mdmItem.getBrandId());
+ item.setRemark(mdmItem.getRemark());
+
+ // 璁剧疆鍗曚綅鍚嶇О
+ if (mdmItem.getUnitMeasureId() != null) {
+ MdmUnitMeasureRespDTO unit = unitMap.get(mdmItem.getUnitMeasureId());
+ if (unit != null) {
+ item.setUnit(unit.getName());
+ }
+ }
+ result.add(item);
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java
index 8306fc0..427e41f 100644
--- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java
+++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java
@@ -5,6 +5,8 @@
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemSkuApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuPageReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
@@ -33,6 +35,9 @@
/**
* WMS 鍟嗗搧 SKU Service 瀹炵幇绫�
*
+ * 鏀归�犺鏄庯細WMS SKU 鏁版嵁鏉ユ簮浜� MDM 鐗╂枡 SKU
+ * - getItemSkuListByIds/validateItemSkuExists 璋冪敤 MDM SKU API
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@@ -41,6 +46,10 @@
@Resource
private WmsItemSkuMapper itemSkuMapper;
+
+ @Resource
+ private MdmItemSkuApi mdmItemSkuApi;
+
@Resource
@Lazy // 寤惰繜鍔犺浇锛岄伩鍏嶅惊鐜緷璧�
private WmsInventoryService inventoryService;
@@ -111,26 +120,26 @@
@Override
public WmsItemSkuDO validateItemSkuExists(Long id) {
- WmsItemSkuDO sku = itemSkuMapper.selectById(id);
- if (sku == null) {
- throw exception(ITEM_SKU_NOT_EXISTS);
- }
- return sku;
+ MdmItemSkuRespDTO sku = mdmItemSkuApi.validateItemSkuExists(id).getCheckedData();
+ return BeanUtils.toBean(sku, WmsItemSkuDO.class);
}
@Override
public List<WmsItemSkuDO> getItemSkuList(Long itemId) {
- return itemSkuMapper.selectListByItemId(itemId);
+ List<MdmItemSkuRespDTO> list = mdmItemSkuApi.getItemSkuListByItemId(itemId).getCheckedData();
+ return BeanUtils.toBean(list, WmsItemSkuDO.class);
}
@Override
public List<WmsItemSkuDO> getItemSkuList(Collection<Long> itemIds) {
- return itemSkuMapper.selectListByItemIds(itemIds);
+ // MDM API 鏆備笉鏀寔鎸夊涓墿鏂� ID 鏌ヨ SKU锛屾殏鏃惰繑鍥炵┖鍒楄〃
+ return ListUtil.of();
}
@Override
public List<WmsItemSkuDO> getItemSkuList(Collection<Long> itemIds, String code, String name) {
- return itemSkuMapper.selectList(itemIds, code, name);
+ // MDM API 鏆備笉鏀寔澶嶆潅鏌ヨ锛屾殏鏃惰繑鍥炵┖鍒楄〃
+ return ListUtil.of();
}
@Override
@@ -138,12 +147,14 @@
if (CollUtil.isEmpty(ids)) {
return ListUtil.of();
}
- return itemSkuMapper.selectByIds(ids);
+ List<MdmItemSkuRespDTO> list = mdmItemSkuApi.getItemSkuList(ids).getCheckedData();
+ return BeanUtils.toBean(list, WmsItemSkuDO.class);
}
@Override
public PageResult<WmsItemSkuDO> getItemSkuPage(WmsItemSkuPageReqVO pageReqVO) {
- return itemSkuMapper.selectPage(pageReqVO);
+ // MDM API 鏆備笉鏀寔鍒嗛〉鏌ヨ SKU锛屾殏鏃惰繑鍥炵┖缁撴灉
+ return new PageResult<>(ListUtil.of(), 0L);
}
private void validateItemSkuList(List<WmsItemSkuSaveReqVO> skus) {
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index 295d738..8005054 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -129,6 +129,13 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞嗘ā鍧� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- spring boot 閰嶇疆鎵�闇�渚濊禆 -->
<dependency>
<groupId>org.springframework.boot</groupId>
--
Gitblit v1.9.3