From 6b5f7c66fc40d7f6099d561e31a34fbd50dd20d3 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 01 七月 2026 15:54:57 +0800
Subject: [PATCH] 初始化项目
---
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java | 35
docs/sql/mdm_migration_02_item_data.sql | 291 +
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java | 96
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java | 17
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApi.java | 64
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java | 23
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandSaveReqVO.java | 25
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java | 31
yudao-server/pom.xml | 7
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApi.java | 40
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApiImpl.java | 130
docs/mdm_frontend_change_phase2.md | 556 ++
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java | 107
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java | 36
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-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java | 93
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java | 8
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java | 124
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java | 167
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemStatusUpdateReqVO.java | 20
docs/file_upload_guide.md | 244 +
docs/mdm_migration_plan.md | 260 +
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java | 4
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApiImpl.java | 73
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmBusinessAnalyzeRespDTO.java | 39
yudao-module-erp-api/pom.xml | 33
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java | 33
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-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java | 3
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java | 77
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java | 8
docs/sql/erp_purchase_enhance.sql | 96
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java | 158
docs/sql/mdm_migration_04_alter_columns.sql | 11
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java | 8
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java | 176
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-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
yudao-module-wms/pom.xml | 7
yudao-module-mdm-api/pom.xml | 33
docs/sql/bpm_purchase_approval_init.sql | 86
docs/marketing_report_frontend.md | 361 +
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderCreateReqDTO.java | 65
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java | 110
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java | 84
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java | 143
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java | 9
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/category/MdmItemCategoryMapper.java | 63
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java | 41
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java | 93
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java | 22
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesPerformanceRespDTO.java | 48
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java | 86
docs/sales_traceability_frontend.md | 349 +
pom.xml | 5
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesFunnelRespDTO.java | 36
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApi.java | 39
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryRespVO.java | 39
docs/erp_purchase_frontend_change.md | 288 +
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java | 156
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-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderRespDTO.java | 31
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java | 10
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java | 72
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemBatchConfigMapper.java | 19
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApiImpl.java | 55
docs/crm_erp_integration_frontend.md | 140
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApiImpl.java | 49
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java | 67
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java | 75
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java | 88
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-crm-api/pom.xml | 33
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-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java | 110
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java | 403 +
.claude/settings.local.json | 13
docs/crm_erp_integration_sql.sql | 87
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java | 8
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java | 98
docs/im_module_tables.sql | 401 +
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java | 52
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java | 6
docs/sql/mdm_migration_03_update_refs.sql | 50
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemSkuDO.java | 99
docs/mdm_frontend_change_phase1.md | 173
yudao-module-mdm/pom.xml | 69
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 | 2
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java | 8
docs/mdm_frontend_change.md | 241
yudao-module-mes/pom.xml | 7
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/dal/mysql/sale/ErpSaleOrderMapper.java | 4
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java | 100
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java | 15
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasurePageReqVO.java | 29
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java | 50
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemSkuMapper.java | 45
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java | 6
yudao-module-crm/pom.xml | 21
yudao-module-erp/pom.xml | 28
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java | 4
docs/erp_purchase_request_frontend.md | 386 +
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/brand/MdmBrandMapper.java | 50
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java | 2
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java | 46
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandServiceImpl.java | 124
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmCustomerAnalyzeRespDTO.java | 33
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java | 134
docs/contract_generate_order_frontend.md | 276 +
docs/crm_erp_integration_plan.md | 193
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryPageReqVO.java | 30
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java | 46
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
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 | 41
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java | 70
docs/sql/mdm_migration_01_basic_data.sql | 191
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java | 42
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmContractAnalyzeRespDTO.java | 39
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java | 72
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandPageReqVO.java | 29
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java | 12
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java | 75
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java | 66
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 | 99
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java | 6
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/dto/CrmSaleTraceRespDTO.java | 125
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApiImpl.java | 190
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java | 122
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java | 66
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/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java | 6
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java | 181
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApiImpl.java | 244 +
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-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java | 5
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java | 240
docs/mdm_product_integration_frontend.md | 276 +
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java | 109
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java | 68
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApiImpl.java | 59
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuService.java | 130
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java | 68
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java | 95
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java | 11
yudao-module-mdm-api/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/warehouse/MdmWarehouseApi.java | 72
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/dto/MdmBrandRespDTO.java | 22
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java | 210
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java | 2
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java | 30
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java | 17
yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java | 50
docs/sql/mdm_schema.sql | 194
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java | 56
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/dto/MdmWarehouseRespDTO.java | 39
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java | 4
172 files changed, 14,371 insertions(+), 139 deletions(-)
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 32b640f..d156d7e 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -14,7 +14,18 @@
"mcp__ide__getDiagnostics",
"WebFetch(domain:help.jimureport.com)",
"WebSearch",
- "Bash(python3 *)"
+ "Bash(python3 *)",
+ "Bash(mvn compile *)",
+ "Bash(mysql -u root -p123456 -D ruoyi-vue-pro)",
+ "Bash(mysql -h127.0.0.1 -P3306 -uroot -p123456 ruoyi-vue-pro)",
+ "Bash(where mysql *)",
+ "Bash(dir /s /b \"C:\\\\Program Files\\\\MySQL\\\\*\\\\bin\\\\mysql.exe\")",
+ "Bash(dir /s /b \"C:\\\\Program Files \\(x86\\)\\\\MySQL\\\\*\\\\bin\\\\mysql.exe\")",
+ "Bash(cmd.exe /c \"where mysql\")",
+ "Bash(cmd.exe /c \"dir /s /b C:\\\\ProgramData\\\\MySQL\\\\*\\\\mysql.exe\")",
+ "Bash(cmd.exe /c \"dir /s /b C:\\\\Program Files\\\\MySQL\\\\*\\\\mysql.exe\")",
+ "Bash(mysql -u root -p123456 ruoyi-vue-pro -e \"SELECT id, name, code, deleted FROM bpm_category WHERE code = 'purchase_approve' OR name LIKE '%閲囪喘瀹℃牳%';\")",
+ "Bash(dir /s /b \"D:\\\\鐗涢┈\\\\鍗楅�歕\\\鍚庣\\\\mom-pro2-after\\\\yudao-module-mdm-api\\\\*.java\")"
],
"deny": [
"Bash(rm -rf *)",
diff --git a/docs/contract_generate_order_frontend.md b/docs/contract_generate_order_frontend.md
new file mode 100644
index 0000000..cab4848
--- /dev/null
+++ b/docs/contract_generate_order_frontend.md
@@ -0,0 +1,276 @@
+# CRM 鍚堝悓鐢熸垚 ERP 閿�鍞鍗� - 鍓嶇鏂囨。
+
+## 姒傝堪
+
+鏈鏀归�犲疄鐜� CRM 鍚堝悓瀹℃壒閫氳繃鍚庯紝鍙洿鎺ョ敓鎴� ERP 閿�鍞鍗曪紝鎵撻�氬悎鍚屽埌璁㈠崟鐨勪笟鍔℃祦绋嬨��
+
+---
+
+## 涓�銆佹敼閫犳竻鍗�
+
+### 鍚庣鏀归�狅紙宸插畬鎴愶級
+
+| 鏂囦欢 | 鏀瑰姩璇存槑 |
+|------|----------|
+| `yudao-module-erp-api/` | 鏂板 ERP 閿�鍞鍗� API 妯″潡 |
+| `ErpSaleOrderApi.java` | 閿�鍞鍗� API 鎺ュ彛 |
+| `ErpSaleOrderCreateReqDTO.java` | 閿�鍞鍗曞垱寤鸿姹� DTO |
+| `ErpSaleOrderRespDTO.java` | 閿�鍞鍗曞搷搴� DTO |
+| `ErpSaleOrderApiImpl.java` | ERP 閿�鍞鍗� API 瀹炵幇 |
+| `CrmContractDO.java` | 鏂板 orderId銆乷rderNo 瀛楁 |
+| `ErpSaleOrderDO.java` | 鏂板 contractId銆乧ontractNo 瀛楁 |
+| `CrmContractService.java` | 鏂板 generateSaleOrder 鏂规硶 |
+| `CrmContractServiceImpl.java` | 瀹炵幇鐢熸垚閿�鍞鍗曢�昏緫 |
+| `CrmContractController.java` | 鏂板鐢熸垚閿�鍞鍗� API |
+| `CrmContractRespVO.java` | 鏂板 orderId銆乷rderNo 瀛楁 |
+
+---
+
+## 浜屻�佹秹鍙婇〉闈�
+
+### CRM 鍚堝悓绠$悊
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 鍚堝悓鍒楄〃 | `/crm/contract/index` | 娣诲姞"鐢熸垚閿�鍞鍗�"鎸夐挳 |
+| 鍚堝悓璇︽儏 | `/crm/contract/detail` | 鏄剧ず鍏宠仈鐨勯攢鍞鍗曚俊鎭� |
+
+### ERP 閿�鍞鍗曠鐞�
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 閿�鍞鍗曞垪琛� | `/erp/sale/order/index` | 鏄剧ず鍏宠仈鍚堝悓淇℃伅 |
+| 閿�鍞鍗曡鎯� | `/erp/sale/order/detail` | 鏄剧ず鍏宠仈鍚堝悓淇℃伅 |
+
+---
+
+## 涓夈�丄PI 鎺ュ彛
+
+### CRM 鍚堝悓 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /crm/contract/generate-sale-order | 鏍规嵁鍚堝悓鐢熸垚閿�鍞鍗� |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 鍚堝悓缂栧彿 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": 12345,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+### ERP 閿�鍞鍗� API锛堟柊澧� RPC锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /rpc-api/erp/sale-order/create | 鍒涘缓閿�鍞鍗� |
+| GET | /rpc-api/erp/sale-order/get | 鑾峰彇閿�鍞鍗� |
+| GET | /rpc-api/erp/sale-order/get-by-contract-id | 鏍规嵁鍚堝悓缂栧彿鑾峰彇璁㈠崟 |
+
+---
+
+## 鍥涖�佸墠绔慨鏀圭偣
+
+### 4.1 鍚堝悓鍒楄〃椤甸潰
+
+**鏂囦欢璺緞锛�** `/src/views/crm/contract/index.vue`
+
+**鏂板鎸夐挳锛�**
+
+```html
+<!-- 鍦ㄥ悎鍚屾搷浣滃垪娣诲姞"鐢熸垚閿�鍞鍗�"鎸夐挳 -->
+<el-button
+ v-if="row.auditStatus === 20 && !row.orderId"
+ type="success"
+ size="small"
+ @click="handleGenerateOrder(row)"
+>
+ 鐢熸垚閿�鍞鍗�
+</el-button>
+<el-button
+ v-if="row.orderId"
+ type="primary"
+ size="small"
+ @click="handleViewOrder(row)"
+>
+ 鏌ョ湅璁㈠崟
+</el-button>
+```
+
+**鏂板鏂规硶锛�**
+
+```js
+methods: {
+ // 鐢熸垚閿�鍞鍗�
+ async handleGenerateOrder(row) {
+ try {
+ await this.$confirm('纭鏍规嵁璇ュ悎鍚岀敓鎴愰攢鍞鍗曪紵', '鎻愮ず')
+ const { data } = await generateSaleOrder(row.id)
+ this.$message.success('鐢熸垚閿�鍞鍗曟垚鍔燂紝璁㈠崟鍙凤細' + data)
+ this.getList() // 鍒锋柊鍒楄〃
+ } catch (e) {
+ if (e !== 'cancel') {
+ this.$message.error(e.message || '鐢熸垚澶辫触')
+ }
+ }
+ },
+ // 鏌ョ湅閿�鍞鍗�
+ handleViewOrder(row) {
+ this.$router.push({
+ path: '/erp/sale/order/detail',
+ query: { id: row.orderId }
+ })
+ }
+}
+```
+
+**鏂板 API锛�**
+
+```js
+// api/crm/contract.js
+export function generateSaleOrder(id) {
+ return request({
+ url: '/crm/contract/generate-sale-order',
+ method: 'post',
+ params: { id }
+ })
+}
+```
+
+### 4.2 鍚堝悓璇︽儏椤甸潰
+
+**鏂囦欢璺緞锛�** `/src/views/crm/contract/detail.vue`
+
+**鏂板鏄剧ず瀛楁锛�**
+
+```html
+<!-- 鍦ㄥ悎鍚屽熀鏈俊鎭尯鍩熸坊鍔� -->
+<el-descriptions-item label="閿�鍞鍗�">
+ <span v-if="contract.orderId">
+ <el-link type="primary" @click="handleViewOrder">
+ {{ contract.orderNo }}
+ </el-link>
+ </span>
+ <span v-else>
+ <el-button
+ v-if="contract.auditStatus === 20"
+ type="success"
+ size="small"
+ @click="handleGenerateOrder"
+ >
+ 鐢熸垚閿�鍞鍗�
+ </el-button>
+ <span v-else>鏈敓鎴�</span>
+ </span>
+</el-descriptions-item>
+```
+
+### 4.3 閿�鍞鍗曞垪琛ㄩ〉闈�
+
+**鏂囦欢璺緞锛�** `/src/views/erp/sale/order/index.vue`
+
+**鏂板鍒楁樉绀猴細**
+
+```html
+<el-table-column label="鍏宠仈鍚堝悓" prop="contractNo" width="150">
+ <template #default="{ row }">
+ <span v-if="row.contractId">
+ <el-link type="primary" @click="handleViewContract(row)">
+ {{ row.contractNo }}
+ </el-link>
+ </span>
+ <span v-else>-</span>
+ </template>
+</el-table-column>
+```
+
+### 4.4 閿�鍞鍗曡鎯呴〉闈�
+
+**鏂囦欢璺緞锛�** `/src/views/erp/sale/order/detail.vue`
+
+**鏂板鏄剧ず瀛楁锛�**
+
+```html
+<el-descriptions-item label="鍏宠仈鍚堝悓">
+ <span v-if="order.contractId">
+ <el-link type="primary" @click="handleViewContract">
+ {{ order.contractNo }}
+ </el-link>
+ </span>
+ <span v-else>-</span>
+</el-descriptions-item>
+```
+
+---
+
+## 浜斻�佹暟鎹ā鍨嬪彉鏇�
+
+### CRM 鍚堝悓琛ㄥ彉鏇�
+
+```sql
+ALTER TABLE `crm_contract`
+ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '鍏宠仈 ERP 閿�鍞鍗曠紪鍙�' AFTER `total_price`,
+ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 閿�鍞鍗曞崟鍙�' AFTER `order_id`;
+```
+
+### ERP 閿�鍞鍗曡〃鍙樻洿
+
+```sql
+ALTER TABLE `erp_sale_order`
+ADD COLUMN `contract_id` bigint DEFAULT NULL COMMENT '鍏宠仈 CRM 鍚堝悓缂栧彿' AFTER `customer_id`,
+ADD COLUMN `contract_no` varchar(32) DEFAULT NULL COMMENT 'CRM 鍚堝悓鍗曞彿' AFTER `contract_id`;
+```
+
+---
+
+## 鍏�佷笟鍔¤鍒�
+
+### 6.1 鐢熸垚閿�鍞鍗曟潯浠�
+
+- 鍚堝悓蹇呴』瀹℃壒閫氳繃锛坅uditStatus = 20锛�
+- 鍚堝悓鏈叧鑱旈攢鍞鍗曪紙orderId 涓虹┖锛�
+- 鍚堝悓蹇呴』鍖呭惈浜у搧淇℃伅
+
+### 6.2 閿�鍞鍗曟暟鎹潵婧�
+
+- 瀹㈡埛锛氬悎鍚屽鎴�
+- 浜у搧锛氬悎鍚屼骇鍝佸垪琛�
+- 閲戦锛氬悎鍚岄噾棰濓紙鎵i櫎鎶樻墸锛�
+- 澶囨敞锛氳嚜鍔ㄦ坊鍔�"鐢卞悎鍚屻�恱xx銆戣嚜鍔ㄧ敓鎴�"
+
+### 6.3 鏁版嵁鍏宠仈
+
+- 閿�鍞鍗曞垱寤哄悗锛岃嚜鍔ㄥ洖濉悎鍚� orderId銆乷rderNo
+- 閿�鍞鍗曡褰� contractId銆乧ontractNo 鐢ㄤ簬鍙嶅悜鏌ヨ
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **鐘舵�佹帶鍒�**锛氬彧鏈夊鎵归�氳繃鐨勫悎鍚屾墠鑳界敓鎴愯鍗�
+2. **鍞竴鎬�**锛氫竴涓悎鍚屽彧鑳界敓鎴愪竴涓攢鍞鍗�
+3. **鏁版嵁鍚屾**锛氱敓鎴愯鍗曞悗锛屽悎鍚屼笌璁㈠崟鍙屽悜鍏宠仈
+4. **鏉冮檺瑕佹眰**锛氶渶瑕佹湁鍚堝悓缂栬緫鏉冮檺鎵嶈兘鐢熸垚璁㈠崟
+5. **璁㈠崟鐘舵��**锛氱敓鎴愮殑閿�鍞鍗曢粯璁や负"鏈鏍�"鐘舵�侊紝闇�瑕佸湪 ERP 涓鎵�
+
+---
+
+## 鍏�佸悗缁鍒�
+
+| Phase | 鍐呭 | 鐘舵�� |
+|-------|------|------|
+| Phase 1 | 瀹㈡埛鏁版嵁缁熶竴 - ERP 浣跨敤 CRM 瀹㈡埛 | **宸插畬鎴�** |
+| Phase 2 | 浜у搧鏁版嵁缁熶竴 - CRM/ERP 浣跨敤 MDM 鐗╂枡 | **宸插畬鎴�** |
+| Phase 3 | 鍚堝悓鐢熸垚閿�鍞鍗� | **宸插畬鎴�** |
+| Phase 4 | 閿�鍞叏娴佺▼杩芥函 | 寰呭紑濮� |
+| Phase 5 | 钀ラ攢鏁版嵁鎶ヨ〃 | 寰呭紑濮� |
+| Phase 6 | 鏁版嵁杩佺Щ涓庢竻鐞� | 寰呭紑濮� |
diff --git a/docs/crm_erp_integration_frontend.md b/docs/crm_erp_integration_frontend.md
new file mode 100644
index 0000000..066a188
--- /dev/null
+++ b/docs/crm_erp_integration_frontend.md
@@ -0,0 +1,140 @@
+# CRM 涓� ERP 閿�鍞竴浣撳寲鏀归�� - 鍓嶇鏂囨。
+
+## 姒傝堪
+
+鏈鏀归�犲皢 CRM 瀹㈡埛浣滀负缁熶竴瀹㈡埛涓绘暟鎹紝ERP 閿�鍞ā鍧楋紙閿�鍞鍗曘�侀攢鍞嚭搴撱�侀攢鍞��璐с�佹敹娆惧崟锛夌粺涓�浣跨敤 CRM 瀹㈡埛銆�
+
+---
+
+## 涓�銆佹敼閫犳竻鍗�
+
+### 鍚庣鏀归�狅紙宸插畬鎴愶級
+
+| 鏂囦欢 | 鏀瑰姩璇存槑 |
+|------|----------|
+| `yudao-module-crm-api/` | 鏂板 CRM 瀹㈡埛 API 妯″潡 |
+| `CrmCustomerApi.java` | 鏂板瀹㈡埛 API 鎺ュ彛 |
+| `CrmCustomerRespDTO.java` | 鏂板瀹㈡埛 DTO |
+| `CrmCustomerApiImpl.java` | 瀹炵幇 CRM 瀹㈡埛 API |
+| `ErpSaleOrderServiceImpl.java` | 浣跨敤 CRM 瀹㈡埛 API |
+| `ErpFinanceReceiptServiceImpl.java` | 浣跨敤 CRM 瀹㈡埛 API |
+| `ErpSaleOrderController.java` | 浣跨敤 CRM 瀹㈡埛 API |
+| `ErpSaleOrderDO.java` | 鏇存柊娉ㄩ噴鍏宠仈 CRM 瀹㈡埛 |
+| `ErpSaleOutDO.java` | 鏇存柊娉ㄩ噴鍏宠仈 CRM 瀹㈡埛 |
+| `ErpSaleReturnDO.java` | 鏇存柊娉ㄩ噴鍏宠仈 CRM 瀹㈡埛 |
+| `ErpFinanceReceiptDO.java` | 鏇存柊娉ㄩ噴鍏宠仈 CRM 瀹㈡埛 |
+
+### 鍚庣鏀归�狅紙寰呭畬鎴愶級
+
+| 鏂囦欢 | 鏀瑰姩璇存槑 |
+|------|----------|
+| `ErpSaleOutController.java` | 浣跨敤 CRM 瀹㈡埛 API |
+| `ErpSaleReturnController.java` | 浣跨敤 CRM 瀹㈡埛 API |
+| `ErpFinanceReceiptController.java` | 浣跨敤 CRM 瀹㈡埛 API |
+
+---
+
+## 浜屻�佹秹鍙婇〉闈�
+
+### ERP 閿�鍞ā鍧�
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 閿�鍞鍗曞垪琛� | `/erp/sale/order/index` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 閿�鍞鍗曡〃鍗� | `/erp/sale/order/form` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 閿�鍞嚭搴撳垪琛� | `/erp/sale/out/index` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 閿�鍞嚭搴撹〃鍗� | `/erp/sale/out/form` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 閿�鍞��璐у垪琛� | `/erp/sale/return/index` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 閿�鍞��璐ц〃鍗� | `/erp/sale/return/form` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 鏀舵鍗曞垪琛� | `/erp/finance/receipt/index` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+| 鏀舵鍗曡〃鍗� | `/erp/finance/receipt/form` | 瀹㈡埛閫夋嫨鏀逛负 CRM 瀹㈡埛閫夋嫨缁勪欢 |
+
+---
+
+## 涓夈�佸鎴烽�夋嫨缁勪欢
+
+### 3.1 浣跨敤 CRM 瀹㈡埛閫夋嫨缁勪欢
+
+ERP 閿�鍞ā鍧楃殑瀹㈡埛閫夋嫨缁熶竴浣跨敤 CRM 瀹㈡埛閫夋嫨缁勪欢銆�
+
+```html
+<!-- 鍘熸潵锛欵RP 瀹㈡埛閫夋嫨 -->
+<el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛">
+ <el-option
+ v-for="item in customerList"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id"
+ />
+</el-select>
+
+<!-- 鏀逛负锛欳RM 瀹㈡埛閫夋嫨缁勪欢 -->
+<CrmCustomerSelect v-model="form.customerId" />
+```
+
+### 3.2 CRM 瀹㈡埛 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /crm/customer/simple-list | 鑾峰彇瀹㈡埛绮剧畝鍒楄〃锛堢敤浜庝笅鎷夐�夋嫨锛� |
+| GET | /crm/customer/list-by-ids | 鎵归噺鑾峰彇瀹㈡埛淇℃伅 |
+
+**瀹㈡埛绮剧畝鍒楄〃鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "id": 1,
+ "name": "瀹㈡埛A",
+ "mobile": "13800138000",
+ "telephone": "0571-12345678",
+ "ownerUserName": "寮犱笁"
+ }
+ ]
+}
+```
+
+---
+
+## 鍥涖�佸簾寮冪殑 ERP 瀹㈡埛绠$悊
+
+### 4.1 ERP 瀹㈡埛绠$悊椤甸潰搴熷純
+
+浠ヤ笅椤甸潰鏍囪涓哄簾寮冿紝寤鸿鍦ㄨ彍鍗曚腑闅愯棌鎴栧垹闄わ細
+
+- `/erp/customer/index` - ERP 瀹㈡埛鍒楄〃
+- `/erp/customer/form` - ERP 瀹㈡埛琛ㄥ崟
+
+---
+
+## 浜斻�丼QL 鎵ц
+
+瑙� `docs/crm_erp_integration_sql.sql`
+
+---
+
+## 鍏�佹敼閫犲畬鎴愮姸鎬�
+
+| Phase | 鍐呭 | 鐘舵�� |
+|-------|------|------|
+| Phase 1 | 瀹㈡埛鏁版嵁缁熶竴 - ERP 浣跨敤 CRM 瀹㈡埛 | **宸插畬鎴�** |
+| Phase 2 | 浜у搧鏁版嵁缁熶竴 - CRM/ERP 浣跨敤 MDM 鐗╂枡 | **宸插畬鎴�** |
+| Phase 3 | 鍚堝悓鐢熸垚閿�鍞鍗� | **宸插畬鎴�** |
+| Phase 4 | 閿�鍞叏娴佺▼杩芥函 | **宸插畬鎴�** |
+| Phase 5 | 钀ラ攢鏁版嵁鎶ヨ〃 | **宸插畬鎴�** |
+| Phase 6 | 鏁版嵁杩佺Щ涓庢竻鐞� | **宸插畬鎴�** |
+
+---
+
+## 涓冦�佷骇鍑烘枃妗f竻鍗�
+
+| 鏂囨。 | 璇存槑 |
+|------|------|
+| `docs/crm_erp_integration_frontend.md` | CRM 瀹㈡埛涓�浣撳寲鍓嶇鏂囨。 |
+| `docs/crm_erp_integration_sql.sql` | SQL 鍙樻洿鑴氭湰 |
+| `docs/mdm_product_integration_frontend.md` | MDM 浜у搧涓�浣撳寲鍓嶇鏂囨。 |
+| `docs/contract_generate_order_frontend.md` | 鍚堝悓鐢熸垚璁㈠崟鍓嶇鏂囨。 |
+| `docs/sales_traceability_frontend.md` | 閿�鍞拷婧墠绔枃妗� |
+| `docs/marketing_report_frontend.md` | 钀ラ攢鎶ヨ〃鍓嶇鏂囨。 |
diff --git a/docs/crm_erp_integration_plan.md b/docs/crm_erp_integration_plan.md
new file mode 100644
index 0000000..43c45a8
--- /dev/null
+++ b/docs/crm_erp_integration_plan.md
@@ -0,0 +1,193 @@
+# CRM 涓� ERP 閿�鍞竴浣撳寲鏀归�犺鍒�
+
+## 涓�銆佺幇鐘跺垎鏋�
+
+### 1.1 褰撳墠闂
+
+| 闂 | 璇存槑 |
+|------|------|
+| **瀹㈡埛鏁版嵁鐙珛** | CRM 瀹㈡埛 (`crm_customer`) 鍜� ERP 瀹㈡埛 (`erp_customer`) 鏄袱寮犲畬鍏ㄧ嫭绔嬬殑琛紝鏃犳硶鍏变韩 |
+| **浜у搧鏁版嵁鐙珛** | CRM 浜у搧 (`crm_product`) 鍜� ERP 浜у搧 (`erp_product`) 鐙珛缁存姢 |
+| **鍚堝悓涓庤鍗曡劚鑺�** | CRM 鍚堝悓瀹℃壒閫氳繃鍚庢棤娉曡嚜鍔ㄧ敓鎴� ERP 閿�鍞鍗� |
+| **鍥炴鏁版嵁鐙珛** | CRM 鍥炴 (`crm_receivable`) 鍜� ERP 鏀舵鍗� (`erp_finance_receipt`) 鏃犲叧鑱� |
+| **閿�鍞祦绋嬫柇瑁�** | 绾跨储 鈫� 瀹㈡埛 鈫� 鍟嗘満 鈫� 鍚堝悓 鈫� 璁㈠崟 鈫� 鍑哄簱 鈫� 鏀舵锛屾祦绋嬪湪鍚堝悓鍜岃鍗曚箣闂存柇瑁� |
+
+### 1.2 鏀归�犵洰鏍�
+
+鏍规嵁闇�姹傛弿杩帮紝鏀归�犵洰鏍囦负锛�
+
+1. **缁熶竴瀹㈡埛绠$悊** - CRM 瀹㈡埛浣滀负缁熶竴瀹㈡埛涓绘暟鎹紝ERP 浣跨敤 CRM 瀹㈡埛
+2. **缁熶竴浜у搧绠$悊** - ERP 浜у搧浣滀负浜у搧涓绘暟鎹紝CRM 浣跨敤 ERP 浜у搧
+3. **閿�鍞祦绋嬫墦閫�** - 鍚堝悓瀹℃壒閫氳繃鍚庡彲鐢熸垚閿�鍞鍗曪紝瀹炵幇鍏ㄦ祦绋嬭拷婧�
+4. **閿�鍞暟鎹仈鍔�** - 閿�鍞鍗曠姸鎬佸疄鏃跺悓姝ワ紝鏀寔閿�鍞喅绛栧垎鏋�
+5. **钀ラ攢鏁版嵁鎶ヨ〃** - 鏁村悎 CRM 鍜� ERP 鏁版嵁锛屾彁渚涘畬鏁寸殑閿�鍞垎鏋愭姤琛�
+
+---
+
+## 浜屻�佸垎鎵规敼閫犺鍒�
+
+### Phase 1: 瀹㈡埛鏁版嵁缁熶竴锛堥璁� 2 澶╋級
+
+**鐩爣**: 缁熶竴瀹㈡埛涓绘暟鎹紝ERP 閿�鍞ā鍧椾娇鐢� CRM 瀹㈡埛
+
+**鏀归�犲唴瀹�**:
+
+| 搴忓彿 | 鏀归�犻」 | 璇存槑 |
+|------|--------|------|
+| 1.1 | 鍒涘缓 CRM 瀹㈡埛 API 妯″潡 | 鏆撮湶瀹㈡埛鏌ヨ鎺ュ彛渚� ERP 璋冪敤 |
+| 1.2 | ERP 閿�鍞鍗曞叧鑱� CRM 瀹㈡埛 | `erp_sale_order.customer_id` 鏀逛负鍏宠仈 `crm_customer.id` |
+| 1.3 | ERP 閿�鍞嚭搴撳叧鑱� CRM 瀹㈡埛 | `erp_sale_out.customer_id` 鏀逛负鍏宠仈 `crm_customer.id` |
+| 1.4 | ERP 閿�鍞��璐у叧鑱� CRM 瀹㈡埛 | `erp_sale_return.customer_id` 鏀逛负鍏宠仈 `crm_customer.id` |
+| 1.5 | ERP 鏀舵鍗曞叧鑱� CRM 瀹㈡埛 | `erp_finance_receipt.customer_id` 鏀逛负鍏宠仈 `crm_customer.id` |
+| 1.6 | 搴熷純 ERP 瀹㈡埛琛� | 鏍囪搴熷純锛屼繚鐣欐暟鎹縼绉绘帴鍙� |
+
+**娑夊強鏂囦欢**:
+- 鏂板: `yudao-module-crm-api/` 妯″潡
+- 淇敼: `ErpSaleOrderDO.java`, `ErpSaleOutDO.java`, `ErpSaleReturnDO.java`
+- 淇敼: `ErpSaleOrderServiceImpl.java`, `ErpSaleOutServiceImpl.java`, `ErpSaleReturnServiceImpl.java`
+
+---
+
+### Phase 2: 浜у搧鏁版嵁缁熶竴锛堥璁� 1 澶╋級
+
+**鐩爣**: 缁熶竴浜у搧涓绘暟鎹紝CRM 浣跨敤 ERP 浜у搧
+
+**鏀归�犲唴瀹�**:
+
+| 搴忓彿 | 鏀归�犻」 | 璇存槑 |
+|------|--------|------|
+| 2.1 | 鍒涘缓 ERP 浜у搧 API 妯″潡 | 鏆撮湶浜у搧鏌ヨ鎺ュ彛渚� CRM 璋冪敤 |
+| 2.2 | CRM 鍟嗘満浜у搧鍏宠仈 ERP 浜у搧 | `crm_business_product.product_id` 鏀逛负鍏宠仈 `erp_product.id` |
+| 2.3 | CRM 鍚堝悓浜у搧鍏宠仈 ERP 浜у搧 | `crm_contract_product.product_id` 鏀逛负鍏宠仈 `erp_product.id` |
+| 2.4 | 搴熷純 CRM 浜у搧琛� | 鏍囪搴熷純锛屼繚鐣欐暟鎹縼绉绘帴鍙� |
+
+**娑夊強鏂囦欢**:
+- 鏂板: `yudao-module-erp-api/` 妯″潡锛堝鏋滀笉瀛樺湪锛�
+- 淇敼: `CrmBusinessProductDO.java`, `CrmContractProductDO.java`
+- 淇敼: CRM 鍟嗘満/鍚堝悓 Service 灞�
+
+---
+
+### Phase 3: 鍚堝悓鐢熸垚閿�鍞鍗曪紙棰勮 2 澶╋級
+
+**鐩爣**: CRM 鍚堝悓瀹℃壒閫氳繃鍚庡彲鐢熸垚 ERP 閿�鍞鍗�
+
+**鏀归�犲唴瀹�**:
+
+| 搴忓彿 | 鏀归�犻」 | 璇存槑 |
+|------|--------|------|
+| 3.1 | ERP 閿�鍞鍗曞鍔犲叧鑱斿悎鍚屽瓧娈� | 鏂板 `contract_id`, `contract_no` 瀛楁 |
+| 3.2 | CRM 鍚堝悓澧炲姞璁㈠崟鍏宠仈瀛楁 | 鏂板 `order_id`, `order_no` 瀛楁 |
+| 3.3 | 鍚堝悓鐢熸垚璁㈠崟鎺ュ彛 | 鍚堝悓瀹℃壒閫氳繃鍚庯紝璋冪敤 ERP 鏈嶅姟鐢熸垚閿�鍞鍗� |
+| 3.4 | 鍚堝悓鐘舵�佺洃鍚櫒 | 鐩戝惉鍚堝悓瀹℃壒閫氳繃浜嬩欢锛岃嚜鍔�/鎵嬪姩瑙﹀彂璁㈠崟鐢熸垚 |
+
+**娑夊強鏂囦欢**:
+- 淇敼: `ErpSaleOrderDO.java`, `CrmContractDO.java`
+- 鏂板: `CrmContractToOrderService.java`
+- 淇敼: `CrmContractStatusListener.java`
+
+---
+
+### Phase 4: 閿�鍞叏娴佺▼杩芥函锛堥璁� 1 澶╋級
+
+**鐩爣**: 瀹炵幇浠庣嚎绱㈠埌鏀舵鐨勫畬鏁撮攢鍞祦绋嬭拷婧�
+
+**鏀归�犲唴瀹�**:
+
+| 搴忓彿 | 鏀归�犻」 | 璇存槑 |
+|------|--------|------|
+| 4.1 | 鍟嗘満鍏宠仈閿�鍞鍗� | `crm_business` 澧炲姞璁㈠崟鍏宠仈锛屾敮鎸佸晢鏈虹洿鎺ヤ笅鍗� |
+| 4.2 | 閿�鍞鍗曠姸鎬佸洖鍐� | 鍑哄簱/閫�璐�/鏀舵鐘舵�佸疄鏃跺洖鍐欏埌璁㈠崟鍜屽叧鑱斿悎鍚� |
+| 4.3 | 鍏ㄦ祦绋嬫煡璇㈡帴鍙� | 鎻愪緵鎸夊鎴�/鍟嗘満/鍚堝悓鏌ヨ瀹屾暣閿�鍞摼璺殑鎺ュ彛 |
+
+**娑夊強鏂囦欢**:
+- 淇敼: `CrmBusinessDO.java`
+- 鏂板: `CrmSaleTraceService.java`
+
+---
+
+### Phase 5: 钀ラ攢鏁版嵁鎶ヨ〃锛堥璁� 2 澶╋級
+
+**鐩爣**: 鏁村悎 CRM 鍜� ERP 鏁版嵁锛屾彁渚涢攢鍞喅绛栨姤琛�
+
+**鏀归�犲唴瀹�**:
+
+| 搴忓彿 | 鏀归�犻」 | 璇存槑 |
+|------|--------|------|
+| 5.1 | 閿�鍞紡鏂楀垎鏋愬寮� | 鏁村悎 ERP 璁㈠崟鏁版嵁锛屽睍绀轰粠鍟嗘満鍒拌鍗曠殑杞寲鐜� |
+| 5.2 | 瀹㈡埛閿�鍞垎鏋� | 鎸夊鎴风粺璁★細鍟嗘満閲戦銆佸悎鍚岄噾棰濄�佽鍗曢噾棰濄�佸嚭搴撻噾棰濄�佹敹娆鹃噾棰� |
+| 5.3 | 閿�鍞笟缁╁垎鏋� | 鏁村悎 CRM 鍟嗘満/鍚堝悓鍜� ERP 璁㈠崟/鏀舵锛岃绠楅攢鍞笟缁� |
+| 5.4 | 閿�鍞秼鍔垮垎鏋� | 鎸夋椂闂寸淮搴﹀垎鏋愰攢鍞秼鍔� |
+| 5.5 | 閿�鍞帓琛屾 | 缁煎悎鎺掑悕锛氬晢鏈恒�佸悎鍚屻�佽鍗曘�佹敹娆� |
+
+**娑夊強鏂囦欢**:
+- 淇敼: CRM 缁熻鐩稿叧 Service
+- 鏂板: `CrmErpSalesStatisticsService.java`
+
+---
+
+### Phase 6: 鏁版嵁杩佺Щ涓庢竻鐞嗭紙棰勮 1 澶╋級
+
+**鐩爣**: 灏嗙幇鏈夋暟鎹縼绉诲埌缁熶竴缁撴瀯
+
+**鏀归�犲唴瀹�**:
+
+| 搴忓彿 | 鏀归�犻」 | 璇存槑 |
+|------|--------|------|
+| 6.1 | ERP 瀹㈡埛杩佺Щ鍒� CRM | 鏁版嵁杩佺Щ SQL 鑴氭湰 |
+| 6.2 | CRM 浜у搧杩佺Щ鍒� ERP | 鏁版嵁杩佺Щ SQL 鑴氭湰 |
+| 6.3 | 鏁版嵁鏍¢獙鑴氭湰 | 楠岃瘉杩佺Щ鍚庢暟鎹畬鏁存�� |
+
+---
+
+## 涓夈�佹敼閫犱紭鍏堢骇寤鸿
+
+| 浼樺厛绾� | Phase | 璇存槑 |
+|--------|-------|------|
+| P0 | Phase 1 | 瀹㈡埛缁熶竴鏄熀纭�锛屽繀椤绘渶鍏堝畬鎴� |
+| P0 | Phase 2 | 浜у搧缁熶竴鏄熀纭�锛屽繀椤绘渶鍏堝畬鎴� |
+| P1 | Phase 3 | 鍚堝悓鐢熸垚璁㈠崟鏄牳蹇冧笟鍔℃祦绋� |
+| P1 | Phase 4 | 鍏ㄦ祦绋嬭拷婧槸鍚堣瑕佹眰 |
+| P2 | Phase 5 | 鎶ヨ〃鍒嗘瀽鍙悗缁畬鍠� |
+| P2 | Phase 6 | 鏁版嵁杩佺Щ鍙湪绯荤粺绋冲畾鍚庤繘琛� |
+
+---
+
+## 鍥涖�佹妧鏈柟妗堣鐐�
+
+### 4.1 璺ㄦā鍧楄皟鐢ㄦ柟妗�
+
+浣跨敤妯″潡闂� API 璋冪敤锛�
+- CRM 鎻愪緵 `CrmCustomerApi` 渚� ERP 璋冪敤
+- ERP 鎻愪緵 `ErpProductApi`銆乣ErpSaleOrderApi` 渚� CRM 璋冪敤
+
+### 4.2 鏁版嵁涓�鑷存�ф柟妗�
+
+- 浣跨敤 Spring Event 杩涜妯″潡闂翠簨浠堕�氱煡
+- 鍏抽敭鎿嶄綔浣跨敤浜嬪姟淇濊瘉涓�鑷存��
+- 澧炲姞鏁版嵁鍚屾鐘舵�佸瓧娈碉紝鏀寔寮傚父鎭㈠
+
+### 4.3 鏉冮檺鎺у埗鏂规
+
+- CRM 瀹㈡埛娌跨敤鐜版湁鐨勫洟闃熸潈闄愭満鍒�
+- ERP 妯″潡閫氳繃 API 鏌ヨ瀹㈡埛鏃讹紝闇�鏍¢獙鏁版嵁鏉冮檺
+
+---
+
+## 浜斻�侀闄╀笌娉ㄦ剰浜嬮」
+
+| 椋庨櫓 | 搴斿鎺柦 |
+|------|----------|
+| 鐜版湁鏁版嵁杩佺Щ澶嶆潅 | 鎻愪緵杩佺Щ鑴氭湰鍜屽洖婊氳剼鏈紝鍒嗘壒杩佺Щ |
+| 璺ㄦā鍧楄皟鐢ㄦ�ц兘 | 浣跨敤缂撳瓨鍑忓皯 API 璋冪敤锛屾壒閲忔煡璇紭鍖� |
+| 鏉冮檺鎺у埗澶嶆潅 | 缁熶竴鏉冮檺鏍¢獙閫昏緫锛屽鐢� CRM 鏉冮檺鏈嶅姟 |
+| 涓氬姟娴佺▼鍙樻洿 | 鍏呭垎娴嬭瘯锛屾彁渚涚敤鎴峰煿璁枃妗� |
+
+---
+
+## 鍏�佷氦浠樼墿
+
+姣忎釜 Phase 瀹屾垚鍚庝氦浠橈細
+1. **SQL 鑴氭湰** - 琛ㄧ粨鏋勫彉鏇淬�佹暟鎹縼绉�
+2. **鍓嶇鏂囨。** - 鎺ュ彛鍙樻洿銆侀〉闈慨鏀硅鏄�
+3. **娴嬭瘯鐢ㄤ緥** - 鍔熻兘娴嬭瘯銆佸洖褰掓祴璇�
diff --git a/docs/crm_erp_integration_sql.sql b/docs/crm_erp_integration_sql.sql
new file mode 100644
index 0000000..4c632b4
--- /dev/null
+++ b/docs/crm_erp_integration_sql.sql
@@ -0,0 +1,87 @@
+-- =============================================
+-- CRM 涓� ERP 閿�鍞竴浣撳寲鏀归�� - SQL 鎵ц璁板綍
+-- =============================================
+--
+-- 鎵ц鐘舵�侊細
+-- Phase 1: 瀹㈡埛鏁版嵁缁熶竴 - 宸插畬鎴愶紙鏃犳柊澧炶〃锛�
+-- Phase 2: 浜у搧鏁版嵁缁熶竴 - 宸插畬鎴愶紙MDM 鐗╂枡琛ㄥ凡瀛樺湪锛�
+-- Phase 3: 鍚堝悓璁㈠崟鍏宠仈 - SQL 宸叉墽琛� 鉁�
+-- Phase 4: 鍟嗘満璁㈠崟鍏宠仈 - SQL 宸叉墽琛� 鉁�
+-- Phase 5: 钀ラ攢鎶ヨ〃 - 鏃犻渶鏂板琛�
+-- Phase 6: 鏁版嵁杩佺Щ娓呯悊 - 鐢ㄦ埛鎵嬪姩鎵ц瀹屾垚 鉁�
+--
+-- =============================================
+
+-- =============================================
+-- Phase 1 SQL
+-- 瀹㈡埛鏁版嵁缁熶竴锛欵RP 閿�鍞ā鍧椾娇鐢� CRM 瀹㈡埛
+-- =============================================
+
+-- 1. 鍒涘缓 CRM 瀹㈡埛 API 妯″潡鐩稿叧琛紙鏃犳柊澧炶〃锛屼娇鐢ㄧ幇鏈� crm_customer锛�
+
+-- 2. 鏁版嵁杩佺Щ锛氬皢 ERP 瀹㈡埛鏁版嵁杩佺Щ鍒� CRM 瀹㈡埛锛堝彲閫夛紝鏍规嵁瀹為檯鎯呭喌鎵ц锛�
+-- 娉ㄦ剰锛氭姝ラ闇�瑕佹牴鎹疄闄呮暟鎹儏鍐佃繘琛岃皟鏁�
+
+-- 2.1 鏌ョ湅 ERP 瀹㈡埛鏁版嵁
+-- SELECT * FROM erp_customer WHERE deleted = 0;
+
+-- 2.2 杩佺Щ ERP 瀹㈡埛鍒� CRM 瀹㈡埛锛堢ず渚嬭剼鏈紝闇�鏍规嵁瀹為檯鎯呭喌璋冩暣锛�
+-- INSERT INTO crm_customer (id, name, mobile, telephone, email, detail_address,
+-- follow_up_status, lock_status, deal_status, owner_user_id, owner_time,
+-- creator, create_time, updater, update_time, deleted, tenant_id)
+-- SELECT
+-- id,
+-- name,
+-- mobile,
+-- telephone,
+-- email,
+-- NULL as detail_address,
+-- 0 as follow_up_status,
+-- 0 as lock_status,
+-- 0 as deal_status,
+-- NULL as owner_user_id,
+-- NOW() as owner_time,
+-- creator,
+-- create_time,
+-- updater,
+-- update_time,
+-- deleted,
+-- tenant_id
+-- FROM erp_customer WHERE deleted = 0;
+
+-- 3. 搴熷純 ERP 瀹㈡埛琛紙淇濈暀琛ㄧ粨鏋勶紝浠呮爣璁拌鏄庯級
+-- 娉ㄦ剰锛歟rp_customer 琛ㄤ繚鐣欙紝浣嗕笉鍐嶄綔涓轰富鏁版嵁浣跨敤
+-- 鍚庣画鍙垹闄ゆ垨褰掓。
+
+-- =============================================
+-- Phase 3 SQL锛堥鍛婏級
+-- 鍚堝悓鐢熸垚閿�鍞鍗曠浉鍏�
+-- =============================================
+
+-- 4. ERP 閿�鍞鍗曞鍔犲叧鑱斿悎鍚屽瓧娈�
+ALTER TABLE `erp_sale_order`
+ADD COLUMN `contract_id` bigint DEFAULT NULL COMMENT '鍏宠仈 CRM 鍚堝悓缂栧彿' AFTER `customer_id`,
+ADD COLUMN `contract_no` varchar(32) DEFAULT NULL COMMENT 'CRM 鍚堝悓鍗曞彿' AFTER `contract_id`;
+
+-- 5. CRM 鍚堝悓澧炲姞鍏宠仈璁㈠崟瀛楁
+ALTER TABLE `crm_contract`
+ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '鍏宠仈 ERP 閿�鍞鍗曠紪鍙�' AFTER `total_price`,
+ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 閿�鍞鍗曞崟鍙�' AFTER `order_id`;
+
+-- =============================================
+-- Phase 4 SQL锛堥鍛婏級
+-- 鍟嗘満鍏宠仈閿�鍞鍗�
+-- =============================================
+
+-- 6. CRM 鍟嗘満澧炲姞鍏宠仈璁㈠崟瀛楁
+ALTER TABLE `crm_business`
+ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '鍏宠仈 ERP 閿�鍞鍗曠紪鍙�' AFTER `total_price`,
+ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 閿�鍞鍗曞崟鍙�' AFTER `order_id`;
+
+-- =============================================
+-- 娉ㄦ剰浜嬮」
+-- =============================================
+-- 1. 鎵ц SQL 鍓嶈澶囦唤鏁版嵁
+-- 2. 鏁版嵁杩佺Щ鑴氭湰闇�瑕佹牴鎹疄闄呮暟鎹儏鍐佃皟鏁�
+-- 3. 搴熷純鐨� ERP 瀹㈡埛琛ㄥ缓璁繚鐣欎竴娈垫椂闂村悗鍐嶅垹闄�
+-- 4. 鍚堝悓鍜屽晢鏈哄叧鑱旇鍗曞瓧娈靛彲鍏堟坊鍔狅紝鍚庣画鍔熻兘瀹炵幇鏃跺啀浣跨敤
\ No newline at end of file
diff --git a/docs/erp_purchase_frontend_change.md b/docs/erp_purchase_frontend_change.md
new file mode 100644
index 0000000..50f263d
--- /dev/null
+++ b/docs/erp_purchase_frontend_change.md
@@ -0,0 +1,288 @@
+# ERP 閲囪喘绠$悊妯″潡瀹屽杽 - 鍓嶇鍙樻洿鏂囨。
+
+## 姒傝堪
+
+瀹屽杽 ERP 閲囪喘绠$悊妯″潡锛屽疄鐜颁粠閲囪喘鐢宠鍒伴噰璐叆搴撶殑瀹屾暣娴佺▼绠$悊銆�
+
+**娴佺▼璇存槑**锛�
+- **閲囪喘鐢宠**锛氶渶瑕� BPM 瀹℃壒锛屽鎵归�氳繃鍚庡彲鐢熸垚閲囪喘璁㈠崟
+- **閲囪喘璁㈠崟**锛氱敱閲囪喘鐢宠鐢熸垚锛屾棤闇�瀹℃壒锛岀洿鎺ュ彲杩涜鍏ュ簱鎿嶄綔
+
+---
+
+## 涓�銆佸墠缃潯浠�
+
+### BPM 娴佺▼閰嶇疆锛堥噰璐敵璇凤級
+
+閲囪喘鐢宠瀹℃壒闇�瑕� BPM 娴佺▼鏀寔锛屽繀椤诲厛瀹屾垚浠ヤ笅閰嶇疆锛�
+
+1. **鍒涘缓閲囪喘鐢宠瀹℃壒鍒嗙被**锛氬湪 BPM 娴佺▼鍒嗙被涓垱寤� `purchase_request_approve` 鍒嗙被
+2. **鍒涘缓閲囪喘鐢宠瀹℃壒娴佺▼**锛氬湪 BPM 娴佺▼妯″瀷涓垱寤洪噰璐敵璇峰鎵规祦绋�
+
+**鎵ц SQL**锛�
+```sql
+-- 鍒涘缓 BPM 閲囪喘鐢宠瀹℃壒鍒嗙被
+INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
+VALUES (101, '閲囪喘鐢宠瀹℃壒', 'purchase_request_approve', '閲囪喘鐢宠瀹℃壒娴佺▼鍒嗙被', 0, 11, '1', NOW(), '1', NOW(), 0);
+```
+
+---
+
+## 浜屻�佸姛鑳芥竻鍗�
+
+| 搴忓彿 | 鍔熻兘妯″潡 | 椤甸潰璺緞 | 鍔熻兘璇存槑 |
+|------|----------|----------|----------|
+| 1 | 閲囪喘鐢宠 | `/erp/purchase/request` | 闇�姹傞儴闂ㄥ彂璧烽噰璐渶姹傘�丅PM 瀹℃壒銆佺敓鎴愰噰璐鍗� |
+| 2 | 渚涘簲鍟嗙鐞� | `/erp/purchase/supplier` | 渚涘簲鍟嗕富鏁版嵁妗f绠$悊 |
+| 3 | 閲囪喘璁㈠崟 | `/erp/purchase/order` | 閲囪喘璁㈠崟绠$悊锛堟棤闇�瀹℃壒锛岀敱閲囪喘鐢宠鐢熸垚锛� |
+| 4 | 閲囪喘鍏ュ簱 | `/erp/purchase/in` | 鍒拌揣鍏ュ簱銆侀獙鏀剁鐞� |
+| 5 | 閲囪喘閫�璐� | `/erp/purchase/return` | 閲囪喘閫�璐х鐞� |
+| 6 | 閲囪喘缁熻 | `/erp/purchase/statistics` | 缁熻鍒嗘瀽鎶ヨ〃 |
+
+### 閲囪喘娴佺▼璇存槑
+
+瀹屾暣鐨勯噰璐祦绋嬩负锛�
+
+**閲囪喘鐢宠 -> 瀹℃壒 -> 閲囪喘璁㈠崟 -> 鍏ュ簱 -> 浠樻**
+
+1. **閲囪喘鐢宠**锛氶渶姹傞儴闂ㄥ彂璧烽噰璐渶姹傦紝濉啓鐢宠鐞嗙敱鍜屼骇鍝佹槑缁�
+2. **瀹℃壒**锛氶�氳繃 BPM 宸ヤ綔娴佽繘琛屽鎵�
+3. **閲囪喘璁㈠崟**锛氬鎵归�氳繃鍚庯紝閫夋嫨渚涘簲鍟嗙敓鎴愰噰璐鍗曪紙鏃犻渶鍐嶅鎵癸級
+4. **鍏ュ簱**锛氫緵搴斿晢鍙戣揣鍚庯紝杩涜鍏ュ簱楠屾敹
+5. **浠樻**锛氭牴鎹叆搴撴儏鍐佃繘琛屼粯娆�
+
+**璇︾粏鏂囨。**锛氶噰璐敵璇峰姛鑳借瑙� `docs/erp_purchase_request_frontend.md`
+
+---
+
+## 涓夈�佷緵搴斿晢绠$悊椤甸潰
+
+### 娑夊強椤甸潰
+
+- `/erp/purchase/supplier/index` - 渚涘簲鍟嗗垪琛�
+- `/erp/purchase/supplier/form` - 渚涘簲鍟嗚〃鍗�
+
+### 鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| creditCode | String | 缁熶竴绀句細淇$敤浠g爜 |
+| bankName | String | 寮�鎴烽摱琛� |
+| bankAccount | String | 閾惰璐﹀彿 |
+| address | String | 鍦板潃 |
+| contact | String | 鑱旂郴浜� |
+| phone | String | 鑱旂郴鐢佃瘽 |
+| email | String | 閭 |
+| taxNo | String | 绋庡彿 |
+| qualification | String | 璧勮川淇℃伅 |
+| cooperationTime | Date | 鍚堜綔寮�濮嬫椂闂� |
+
+### 鍓嶇琛ㄥ崟淇敼
+
+```html
+<el-form :model="form" :rules="rules">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="渚涘簲鍟嗗悕绉�" prop="name">
+ <el-input v-model="form.name" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="缁熶竴绀句細淇$敤浠g爜" prop="creditCode">
+ <el-input v-model="form.creditCode" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庡彿" prop="taxNo">
+ <el-input v-model="form.taxNo" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鑱旂郴浜�" prop="contact">
+ <el-input v-model="form.contact" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鑱旂郴鐢佃瘽" prop="phone">
+ <el-input v-model="form.phone" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="閭" prop="email">
+ <el-input v-model="form.email" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="寮�鎴烽摱琛�" prop="bankName">
+ <el-input v-model="form.bankName" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="閾惰璐﹀彿" prop="bankAccount">
+ <el-input v-model="form.bankAccount" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍚堜綔寮�濮嬫椂闂�" prop="cooperationTime">
+ <el-date-picker v-model="form.cooperationTime" type="date" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="16">
+ <el-form-item label="鍦板潃" prop="address">
+ <el-input v-model="form.address" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio :label="0">鍚敤</el-radio>
+ <el-radio :label="1">绂佺敤</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="璧勮川淇℃伅" prop="qualification">
+ <el-input v-model="form.qualification" type="textarea" :rows="3" />
+ </el-form-item>
+</el-form>
+```
+
+---
+
+## 鍥涖�侀噰璐叆搴撻〉闈紙楠屾敹鍔熻兘锛�
+
+### 鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| inspectionStatus | Integer | 楠屾敹鐘舵�侊紙0:寰呴獙鏀� 1:宸查獙鏀� 2:楠屾敹涓嶅悎鏍硷級 |
+| inspectionTime | DateTime | 楠屾敹鏃堕棿 |
+| inspectorId | Long | 閬囨敹浜哄憳缂栧彿 |
+| inspectionResult | String | 楠屾敹缁撴灉 |
+
+### 鍓嶇淇敼
+
+```html
+<!-- 楠屾敹鎸夐挳 -->
+<el-button @click="handleInspection(row)" v-if="row.inspectionStatus === 0">
+ 楠屾敹
+</el-button>
+
+<!-- 楠屾敹寮圭獥 -->
+<el-dialog title="閲囪喘鍏ュ簱楠屾敹" v-model="inspectionDialogVisible">
+ <el-form :model="inspectionForm">
+ <el-form-item label="楠屾敹鐘舵��">
+ <el-radio-group v-model="inspectionForm.inspectionStatus">
+ <el-radio :label="1">楠屾敹鍚堟牸</el-radio>
+ <el-radio :label="2">楠屾敹涓嶅悎鏍�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="楠屾敹缁撴灉">
+ <el-input v-model="inspectionForm.inspectionResult" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="inspectionDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitInspection">纭畾</el-button>
+ </template>
+</el-dialog>
+```
+
+---
+
+## 浜斻�侀噰璐粺璁¢〉闈�
+
+### 娑夊強椤甸潰
+
+- `/erp/purchase/statistics/index` - 閲囪喘缁熻棣栭〉
+- `/erp/purchase/statistics/supplier` - 渚涘簲鍟嗕緵璐х粺璁�
+- `/erp/purchase/statistics/price` - 閲囪喘浠锋牸鍒嗘瀽
+- `/erp/purchase/statistics/uncompleted` - 鏈畬鎴愯鍗曡窡韪�
+
+### API 鍙樻洿
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /erp/purchase-statistics/summary | 閲囪喘姹囨�荤粺璁� |
+| GET | /erp/purchase-statistics/supplier | 渚涘簲鍟嗕緵璐х粺璁� |
+| GET | /erp/purchase-statistics/price | 閲囪喘浠锋牸鍒嗘瀽 |
+| GET | /erp/purchase-statistics/uncompleted | 鏈畬鎴愯鍗曡窡韪� |
+
+#### 渚涘簲鍟嗕緵璐х粺璁″搷搴�
+
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "supplierId": 1,
+ "supplierName": "渚涘簲鍟咥",
+ "orderCount": 10,
+ "orderAmount": 100000.00,
+ "inCount": 8,
+ "inAmount": 80000.00,
+ "returnCount": 1,
+ "returnAmount": 5000.00
+ }
+ ],
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 閲囪喘浠锋牸鍒嗘瀽鍝嶅簲
+
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "productId": 1,
+ "productName": "浜у搧A",
+ "productBarCode": "12345678",
+ "avgPrice": 50.00,
+ "minPrice": 45.00,
+ "maxPrice": 55.00,
+ "latestPrice": 52.00,
+ "priceChangeRate": 4.0
+ }
+ ],
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 鏈畬鎴愯鍗曡窡韪搷搴�
+
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "orderId": 1,
+ "orderNo": "PO202401001",
+ "supplierName": "渚涘簲鍟咥",
+ "orderTime": "2024-01-01",
+ "productNames": "浜у搧A,浜у搧B",
+ "orderAmount": 10000.00,
+ "inAmount": 5000.00,
+ "uncompletedAmount": 5000.00,
+ "daysOverdue": 5
+ }
+ ],
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. **鏁版嵁瀹屾暣鎬�**锛氫緵搴斿晢涓绘暟鎹渶濉啓瀹屾暣淇℃伅锛屽寘鎷俊鐢ㄤ唬鐮併�佽仈绯绘柟寮忋�侀摱琛屼俊鎭瓑
+2. **楠屾敹绠$悊**锛氶噰璐叆搴撻渶杩涜楠屾敹锛岄獙鏀跺悎鏍煎悗鎵嶆寮忓叆搴�
+3. **鏉冮檺鎺у埗**锛氭瘡涓姛鑳芥ā鍧楅兘鏈夌嫭绔嬬殑鏉冮檺鎺у埗锛岄渶閰嶇疆瑙掕壊鏉冮檺
+4. **鏁版嵁杩芥函**锛氭墍鏈夋搷浣滆褰曢兘鏈夊畬鏁寸殑鍒涘缓浜恒�佸垱寤烘椂闂淬�佸鎵逛汉銆佸鎵规椂闂寸瓑淇℃伅
\ No newline at end of file
diff --git a/docs/erp_purchase_request_frontend.md b/docs/erp_purchase_request_frontend.md
new file mode 100644
index 0000000..f043ba3
--- /dev/null
+++ b/docs/erp_purchase_request_frontend.md
@@ -0,0 +1,386 @@
+# ERP 閲囪喘鐢宠鍔熻兘 - 鍓嶇鍙樻洿鏂囨。
+
+## 姒傝堪
+
+鏂板 ERP 閲囪喘鐢宠鍔熻兘锛屽疄鐜颁粠闇�姹傞儴闂ㄥ彂璧烽噰璐渶姹傚埌鐢熸垚閲囪喘璁㈠崟鐨勫畬鏁存祦绋嬬鐞嗐��
+
+**閲嶈璇存槑**锛氶噰璐敵璇峰鎵归渶瑕侀泦鎴� BPM 宸ヤ綔娴侊紝蹇呴』鍏堝湪 BPM 妯″潡鍒涘缓 `purchase_request_approve` 鍒嗙被鍜屽搴旂殑瀹℃壒娴佺▼銆�
+
+---
+
+## 涓�銆佸墠缃潯浠�
+
+### BPM 娴佺▼閰嶇疆
+
+閲囪喘鐢宠瀹℃壒闇�瑕� BPM 娴佺▼鏀寔锛屽繀椤诲厛瀹屾垚浠ヤ笅閰嶇疆锛�
+
+1. **鍒涘缓閲囪喘鐢宠瀹℃壒鍒嗙被**锛氬湪 BPM 娴佺▼鍒嗙被涓垱寤� `purchase_request_approve` 鍒嗙被
+2. **鍒涘缓閲囪喘鐢宠瀹℃壒娴佺▼**锛氬湪 BPM 娴佺▼妯″瀷涓垱寤洪噰璐敵璇峰鎵规祦绋�
+ - **娴佺▼鏍囪瘑锛圞ey锛夊繀椤讳负**锛歚erp-purchase-request-audit`
+ - 鍏宠仈鍒� `purchase_request_approve` 鍒嗙被
+ - 鍙戝竷骞舵縺娲绘祦绋�
+
+**鎵ц SQL**锛�
+```sql
+-- 鍒涘缓 BPM 閲囪喘鐢宠瀹℃壒鍒嗙被
+INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
+VALUES (101, '閲囪喘鐢宠瀹℃壒', 'purchase_request_approve', '閲囪喘鐢宠瀹℃壒娴佺▼鍒嗙被', 0, 11, '1', NOW(), '1', NOW(), 0);
+```
+
+**鏍¢獙閫昏緫**锛�
+- 鎻愪氦閲囪喘鐢宠鏃讹紝绯荤粺浼氭牎楠� BPM 閲囪喘鐢宠瀹℃壒鍒嗙被鏄惁瀛樺湪
+- 濡傛灉鍒嗙被涓嶅瓨鍦紝浼氭姏鍑哄紓甯革細`閲囪喘鐢宠瀹℃壒鍒嗙被涓嶅瓨鍦紝璇峰厛鍦˙PM妯″潡鍒涘缓閲囪喘鐢宠瀹℃壒娴佺▼鍒嗙被`
+- 濡傛灉娴佺▼鏍囪瘑涓� `erp-purchase-request-audit` 鐨勬祦绋嬫湭鍙戝竷鎴栨湭婵�娲伙紝浼氭姏鍑哄紓甯革細`閲囪喘鐢宠瀹℃壒娴佺▼鏈彂甯冩垨鏈縺娲伙紝璇峰厛鍒涘缓娴佺▼鏍囪瘑涓篬erp-purchase-request-audit]鐨勬祦绋嬫ā鍨嬪苟鍙戝竷`
+
+---
+
+## 浜屻�佹秹鍙婇〉闈�
+
+- `/erp/purchase/request/index` - 閲囪喘鐢宠鍒楄〃
+- `/erp/purchase/request/form` - 閲囪喘鐢宠琛ㄥ崟锛堟柊澧�/缂栬緫锛�
+- `/erp/purchase/request/detail` - 閲囪喘鐢宠璇︽儏
+
+---
+
+## 涓夈�佸姛鑳芥竻鍗�
+
+| 搴忓彿 | 鍔熻兘 | 璇存槑 |
+|------|------|------|
+| 1 | 鏂板閲囪喘鐢宠 | 鍒涘缓鑽夌鐘舵�佺殑閲囪喘鐢宠 |
+| 2 | 缂栬緫閲囪喘鐢宠 | 淇敼鑽夌鐘舵�佺殑閲囪喘鐢宠 |
+| 3 | 鍒犻櫎閲囪喘鐢宠 | 鍒犻櫎鑽夌鐘舵�佺殑閲囪喘鐢宠 |
+| 4 | 鎻愪氦瀹℃壒 | 鎻愪氦 BPM 娴佺▼瀹℃壒 |
+| 5 | 鐢熸垚閲囪喘璁㈠崟 | 瀹℃壒閫氳繃鍚庣敓鎴愰噰璐鍗� |
+| 6 | 瀵煎嚭 Excel | 瀵煎嚭閲囪喘鐢宠鍒楄〃 |
+
+---
+
+## 鍥涖�丄PI 鎺ュ彛
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/purchase-request/create | 鍒涘缓閲囪喘鐢宠 |
+| PUT | /erp/purchase-request/update | 鏇存柊閲囪喘鐢宠 |
+| PUT | /erp/purchase-request/submit | 鎻愪氦閲囪喘鐢宠瀹℃壒 |
+| DELETE | /erp/purchase-request/delete | 鍒犻櫎閲囪喘鐢宠 |
+| GET | /erp/purchase-request/get | 鑾峰緱閲囪喘鐢宠璇︽儏 |
+| GET | /erp/purchase-request/page | 鑾峰緱閲囪喘鐢宠鍒嗛〉 |
+| GET | /erp/purchase-request/export-excel | 瀵煎嚭閲囪喘鐢宠 Excel |
+| POST | /erp/purchase-request/generate-order | 鐢熸垚閲囪喘璁㈠崟 |
+| GET | /erp/purchase-request/approve-process-list | 鑾峰彇瀹℃壒娴佺▼鍒楄〃 |
+
+> **鏂囦欢涓婁紶**锛氫娇鐢ㄩ�氱敤鎺ュ彛 `POST /infra/file/upload`锛屽弬鏁� `file` + `directory=purchase-request`锛岃繑鍥炴枃浠� URL銆傝瑙� `docs/file_upload_guide.md`銆�
+
+### 4.1 鍒涘缓閲囪喘鐢宠
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| requestTime | DateTime | 鏄� | 鐢宠鏃堕棿 |
+| requestReason | String | 鍚� | 鐢宠鐞嗙敱 |
+| supplierId | Long | 鍚� | 渚涘簲鍟嗙紪鍙凤紙鍙�夛級 |
+| discountPercent | BigDecimal | 鍚� | 浼樻儬鐜囷紝鐧惧垎姣� |
+| fileUrl | String | 鍚� | 闄勪欢鍦板潃 |
+| remark | String | 鍚� | 澶囨敞 |
+| items | List | 鏄� | 鐢宠鏄庣粏鍒楄〃 |
+
+**items 鏄庣粏鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| productId | Long | 鏄� | 浜у搧缂栧彿 |
+| productUnitId | Long | 鏄� | 浜у搧鍗曚綅缂栧彿 |
+| productPrice | BigDecimal | 鍚� | 浜у搧鍗曚环锛堝弬鑰冧环锛� |
+| count | BigDecimal | 鏄� | 鏁伴噺 |
+| taxPercent | BigDecimal | 鍚� | 绋庣巼锛岀櫨鍒嗘瘮 |
+| demandTime | Date | 鍚� | 闇�姹傛棩鏈� |
+| remark | String | 鍚� | 澶囨敞 |
+
+**鍝嶅簲锛�** `{ "code": 0, "data": 1001, "msg": "鎿嶄綔鎴愬姛" }`
+
+### 4.2 鎻愪氦閲囪喘鐢宠瀹℃壒
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘鐢宠缂栧彿 |
+| processDefinitionKey | String | 鏄� | 娴佺▼瀹氫箟 Key锛堢敤鎴蜂粠娴佺▼鍒楄〃涓�夋嫨锛� |
+
+**鍝嶅簲锛�** `{ "code": 0, "msg": "鎿嶄綔鎴愬姛" }`
+
+> **娴佺▼閫夋嫨**锛氭彁浜ゅ鎵瑰墠锛岄渶鍏堣皟鐢� `/erp/purchase-request/approve-process-list` 鑾峰彇鍙�夋祦绋嬪垪琛紝璁╃敤鎴烽�夋嫨鍚庝紶鍏� `processDefinitionKey`銆�
+
+### 4.3 鐢熸垚閲囪喘璁㈠崟
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘鐢宠缂栧彿 |
+| supplierId | Long | 鏄� | 渚涘簲鍟嗙紪鍙� |
+
+**鍝嶅簲锛�** `{ "code": 0, "data": 2001, "msg": "鎿嶄綔鎴愬姛" }`锛堣繑鍥為噰璐鍗曠紪鍙凤級
+
+### 4.4 鍒嗛〉鏌ヨ鍝嶅簲
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "no": "CGSQ20260701000001",
+ "status": 20,
+ "requestUserId": 1,
+ "requestUserName": "寮犱笁",
+ "requestDeptId": 100,
+ "requestDeptName": "鐢熶骇閮�",
+ "requestTime": "2026-07-01 10:00:00",
+ "requestReason": "鐢熶骇闇�瑕�",
+ "supplierId": 1,
+ "supplierName": "渚涘簲鍟咥",
+ "totalCount": 100.00,
+ "totalPrice": 10000.00,
+ "productNames": "浜у搧A,浜у搧B",
+ "orderId": 2001,
+ "orderNo": "CGDD20260701000001",
+ "createTime": "2026-07-01 10:00:00"
+ }
+ ],
+ "total": 1
+ },
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 浜斻�佺姸鎬佹祦杞�
+
+| 鐘舵�佸�� | 鐘舵�佸悕 | 璇存槑 |
+|--------|--------|------|
+| 0 | 鏈彁浜� | 鑽夌鐘舵�侊紝鍙紪杈戙�佸彲鍒犻櫎 |
+| 10 | 瀹℃壒涓� | 宸叉彁浜ゅ鎵癸紝涓嶅彲淇敼 |
+| 20 | 瀹℃牳閫氳繃 | 瀹℃壒閫氳繃锛屽彲鐢熸垚閲囪喘璁㈠崟 |
+| 30 | 瀹℃牳涓嶉�氳繃 | 瀹℃壒琚嫆缁濓紝鍙噸鏂版彁浜� |
+| 40 | 宸插彇娑� | 娴佺▼琚彇娑� |
+
+**鐘舵�佹祦杞浘锛�**
+```
+鑽夌(0) -> 鎻愪氦 -> 瀹℃壒涓�(10) -> 瀹℃壒閫氳繃(20) -> 鐢熸垚閲囪喘璁㈠崟
+ |
+ +-> 瀹℃壒涓嶉�氳繃(30) -> 閲嶆柊鎻愪氦 -> 瀹℃壒涓�(10)
+ |
+ +-> 宸插彇娑�(40)
+```
+
+---
+
+## 鍏�佸墠绔慨鏀圭偣
+
+### 6.1 鍒楄〃椤垫寜閽�
+
+```html
+<!-- 鎼滅储鏍� -->
+<el-form :model="queryParams" ref="queryFormRef" :inline="true">
+ <el-form-item label="鐢宠鍗曞彿" prop="no">
+ <el-input v-model="queryParams.no" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable />
+ </el-form-item>
+ <el-form-item label="鐢宠浜�" prop="requestUserId">
+ <el-select v-model="queryParams.requestUserId" placeholder="璇烽�夋嫨鐢宠浜�" clearable>
+ <!-- 鐢ㄦ埛鍒楄〃 -->
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瀹℃壒鐘舵��" prop="status">
+ <el-select v-model="queryParams.status" placeholder="璇烽�夋嫨鐘舵��" clearable>
+ <el-option label="鏈彁浜�" :value="0" />
+ <el-option label="瀹℃壒涓�" :value="10" />
+ <el-option label="瀹℃牳閫氳繃" :value="20" />
+ <el-option label="瀹℃牳涓嶉�氳繃" :value="30" />
+ <el-option label="宸插彇娑�" :value="40" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐢宠鏃堕棿" prop="requestTime">
+ <el-date-picker v-model="queryParams.requestTime" type="daterange" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+</el-form>
+
+<!-- 鎿嶄綔鏍� -->
+<el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" @click="handleAdd" v-hasPermi="['erp:purchase-request:create']">鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="success" @click="handleExport" v-hasPermi="['erp:purchase-request:export']">瀵煎嚭</el-button>
+ </el-col>
+</el-row>
+
+<!-- 琛ㄦ牸鎿嶄綔鍒� -->
+<el-table-column label="鎿嶄綔" align="center" width="200">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleDetail(scope.row)">璇︽儏</el-button>
+ <el-button link type="primary" @click="handleUpdate(scope.row)"
+ v-if="scope.row.status === 0" v-hasPermi="['erp:purchase-request:update']">缂栬緫</el-button>
+ <el-button link type="primary" @click="handleSubmit(scope.row)"
+ v-if="scope.row.status === 0" v-hasPermi="['erp:purchase-request:submit']">鎻愪氦</el-button>
+ <el-button link type="primary" @click="handleGenerateOrder(scope.row)"
+ v-if="scope.row.status === 20 && !scope.row.orderId" v-hasPermi="['erp:purchase-request:generate-order']">鐢熸垚璁㈠崟</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)"
+ v-if="scope.row.status === 0" v-hasPermi="['erp:purchase-request:delete']">鍒犻櫎</el-button>
+ </template>
+</el-table-column>
+```
+
+### 6.2 data 鏁版嵁
+
+```js
+data() {
+ return {
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNo: 1,
+ pageSize: 10,
+ no: undefined,
+ requestUserId: undefined,
+ requestDeptId: undefined,
+ requestTime: [],
+ status: undefined,
+ productId: undefined,
+ },
+ // 鐘舵�佸瓧鍏�
+ statusOptions: [
+ { value: 0, label: '鏈彁浜�' },
+ { value: 10, label: '瀹℃壒涓�' },
+ { value: 20, label: '瀹℃牳閫氳繃' },
+ { value: 30, label: '瀹℃牳涓嶉�氳繃' },
+ { value: 40, label: '宸插彇娑�' },
+ ],
+ }
+}
+```
+
+### 6.3 鏂规硶
+
+```js
+methods: {
+ // 鎻愪氦瀹℃壒
+ async handleSubmit(row) {
+ try {
+ await this.$modal.confirm('纭鎻愪氦璇ラ噰璐敵璇疯繘琛屽鎵癸紵')
+ await submitPurchaseRequest(row.id)
+ this.$modal.msgSuccess('鎻愪氦鎴愬姛')
+ this.getList()
+ } catch {}
+ },
+
+ // 鐢熸垚閲囪喘璁㈠崟
+ async handleGenerateOrder(row) {
+ try {
+ // 寮圭獥閫夋嫨渚涘簲鍟�
+ const { value: supplierId } = await this.$prompt('璇烽�夋嫨渚涘簲鍟�', '鐢熸垚閲囪喘璁㈠崟', {
+ inputType: 'select',
+ inputOptions: this.supplierOptions,
+ inputValidator: (value) => {
+ if (!value) return '璇烽�夋嫨渚涘簲鍟�'
+ return true
+ }
+ })
+ const orderId = await generatePurchaseOrder(row.id, supplierId)
+ this.$modal.msgSuccess(`鐢熸垚閲囪喘璁㈠崟鎴愬姛锛岃鍗曠紪鍙凤細${orderId}`)
+ this.getList()
+ } catch {}
+ },
+}
+```
+
+### 6.4 鏂囦欢涓婁紶缁勪欢
+
+> 浣跨敤閫氱敤鏂囦欢涓婁紶鎺ュ彛 `POST /infra/file/upload`锛岃瑙� `docs/file_upload_guide.md`銆�
+
+```html
+<!-- 琛ㄥ崟涓殑闄勪欢涓婁紶 -->
+<el-form-item label="闄勪欢" prop="fileUrl">
+ <el-upload
+ :action="uploadUrl"
+ :headers="headers"
+ :data="{ directory: 'purchase-request' }"
+ :on-success="handleUploadSuccess"
+ :before-upload="beforeUpload"
+ :file-list="fileList"
+ :limit="1"
+ >
+ <el-button type="primary">鐐瑰嚮涓婁紶</el-button>
+ <template #tip>
+ <div class="el-upload__tip">鏀寔 pdf銆乨oc銆乨ocx銆亁ls銆亁lsx 鏍煎紡锛屾渶澶� 10MB</div>
+ </template>
+ </el-upload>
+</el-form-item>
+```
+
+```js
+data() {
+ return {
+ // 鏂囦欢涓婁紶閰嶇疆锛堜娇鐢ㄩ�氱敤鎺ュ彛锛�
+ uploadUrl: process.env.VUE_APP_BASE_API + '/infra/file/upload',
+ headers: { Authorization: 'Bearer ' + getToken() },
+ fileList: [],
+ }
+},
+methods: {
+ handleUploadSuccess(response) {
+ if (response.code === 0) {
+ this.form.fileUrl = response.data
+ this.$modal.msgSuccess('涓婁紶鎴愬姛')
+ } else {
+ this.$modal.msgError(response.msg || '涓婁紶澶辫触')
+ }
+ },
+ beforeUpload(file) {
+ const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
+ const isAllowed = allowedTypes.includes(file.type) || file.name.endsWith('.pdf') || file.name.endsWith('.doc') || file.name.endsWith('.docx')
+ || file.name.endsWith('.xls') || file.name.endsWith('.xlsx')
+ if (!isAllowed) {
+ this.$message.error('鍙敮鎸� pdf銆乨oc銆乨ocx銆亁ls銆亁lsx 鏍煎紡!')
+ return false
+ }
+ const isLt10M = file.size / 1024 / 1024 < 10
+ if (!isLt10M) {
+ this.$message.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!')
+ return false
+ }
+ return true
+ },
+}
+```
+
+---
+
+## 涓冦�佹潈闄愰厤缃�
+
+| 鏉冮檺鏍囪瘑 | 璇存槑 |
+|---------|------|
+| erp:purchase-request:query | 鏌ヨ閲囪喘鐢宠 |
+| erp:purchase-request:create | 鏂板閲囪喘鐢宠 |
+| erp:purchase-request:update | 淇敼閲囪喘鐢宠 |
+| erp:purchase-request:delete | 鍒犻櫎閲囪喘鐢宠 |
+| erp:purchase-request:submit | 鎻愪氦瀹℃壒 |
+| erp:purchase-request:export | 瀵煎嚭閲囪喘鐢宠 |
+| erp:purchase-request:generate-order | 鐢熸垚閲囪喘璁㈠崟 |
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. **鐘舵�侀檺鍒�**锛氬彧鏈夎崏绋跨姸鎬侊紙status=0锛夌殑鐢宠鍗曟墠鑳界紪杈戝拰鍒犻櫎
+2. **瀹℃壒娴佺▼**锛氭彁浜ゅ鎵瑰墠璇风‘淇� BPM 娴佺▼宸查厤缃畬鎴�
+3. **鐢熸垚璁㈠崟**锛氬彧鏈夊鎵归�氳繃锛坰tatus=20锛変笖鏈敓鎴愯鍗曠殑鐢宠鍗曟墠鑳界敓鎴愰噰璐鍗�
+4. **渚涘簲鍟嗛�夋嫨**锛氱敓鎴愰噰璐鍗曟椂蹇呴』閫夋嫨渚涘簲鍟嗭紝鐢宠鏃跺彲棰勫厛鎸囧畾渚涘簲鍟�
+5. **鏁版嵁瀹屾暣鎬�**锛氱敵璇锋槑缁嗛渶濉啓瀹屾暣鐨勪骇鍝佷俊鎭拰鏁伴噺
diff --git a/docs/file_upload_guide.md b/docs/file_upload_guide.md
new file mode 100644
index 0000000..c8a659e
--- /dev/null
+++ b/docs/file_upload_guide.md
@@ -0,0 +1,244 @@
+# 鏂囦欢涓婁紶闆嗘垚鎸囧崡
+
+## 姒傝堪
+
+鏈郴缁熸彁渚涗簡**缁熶竴鐨勬枃浠朵笂浼犳湇鍔�**锛屾敮鎸佸绉嶅瓨鍌ㄦ柟寮忥紙鏈湴銆丼3/OSS銆佹暟鎹簱銆丗TP/SFTP锛夈��
+
+> **閲嶈**锛氭墍鏈変笟鍔℃ā鍧楀繀椤讳娇鐢ㄩ�氱敤鏂囦欢涓婁紶鎺ュ彛锛�**绂佹涓烘瘡涓ā鍧楁柊澧炰笂浼犳帴鍙�**銆�
+
+---
+
+## 涓�銆侀�氱敤涓婁紶鎺ュ彛
+
+### 1.1 鎺ュ彛淇℃伅
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/infra/file/upload` | 閫氱敤鏂囦欢涓婁紶 |
+
+### 1.2 璇锋眰鍙傛暟
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| file | MultipartFile | 鏄� | 涓婁紶鐨勬枃浠� |
+| directory | String | 鍚� | 瀛樺偍鐩綍锛屽 `purchase-request`銆乣contract` |
+
+### 1.3 鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 0,
+ "data": "https://xxx.com/xxx.pdf",
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+杩斿洖鍊� `data` 鍗充负鏂囦欢璁块棶 URL锛屽彲鐩存帴瀛樺偍鍒版暟鎹簱銆�
+
+---
+
+## 浜屻�佸墠绔泦鎴�
+
+### 2.1 涓婁紶缁勪欢绀轰緥
+
+```html
+<el-upload
+ :action="uploadUrl"
+ :headers="headers"
+ :data="{ directory: 'purchase-request' }"
+ :on-success="handleUploadSuccess"
+ :before-upload="beforeUpload"
+>
+ <el-button type="primary">鐐瑰嚮涓婁紶</el-button>
+</el-upload>
+```
+
+### 2.2 閰嶇疆璇存槑
+
+```js
+data() {
+ return {
+ // 閫氱敤涓婁紶鎺ュ彛
+ uploadUrl: process.env.VUE_APP_BASE_API + '/infra/file/upload',
+ // 璁よ瘉澶�
+ headers: { Authorization: 'Bearer ' + getToken() },
+ }
+},
+methods: {
+ handleUploadSuccess(response) {
+ if (response.code === 0) {
+ this.form.fileUrl = response.data // 淇濆瓨鏂囦欢 URL
+ }
+ },
+ beforeUpload(file) {
+ // 涓氬姟灞傛牎楠屾枃浠剁被鍨嬪拰澶у皬
+ const isLt10M = file.size / 1024 / 1024 < 10
+ if (!isLt10M) {
+ this.$message.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!')
+ }
+ return isLt10M
+ }
+}
+```
+
+---
+
+## 涓夈�佺洰褰曡鑼�
+
+鍚勪笟鍔℃ā鍧楅�氳繃 `directory` 鍙傛暟鍖哄垎瀛樺偍鐩綍锛�**绂佹鏂板涓氬姟涓婁紶鎺ュ彛**锛�
+
+| 妯″潡 | directory | 璇存槑 |
+|------|-----------|------|
+| 鐢ㄦ埛澶村儚 | `avatar` | 鐢ㄦ埛澶村儚鍥剧墖 |
+| 鍚堝悓闄勪欢 | `contract` | 鍚堝悓鐩稿叧鏂囦欢 |
+| 閲囪喘鐢宠 | `purchase-request` | 閲囪喘鐢宠闄勪欢 |
+| 閲囪喘璁㈠崟 | `purchase-order` | 閲囪喘璁㈠崟闄勪欢 |
+| 浜у搧鍥剧墖 | `product` | 浜у搧鐩稿叧鍥剧墖 |
+| 閫氱敤闄勪欢 | `attachment` | 閫氱敤涓氬姟闄勪欢 |
+
+> **鍛藉悕瑙勮寖**锛氫娇鐢ㄥ皬鍐欏瓧姣嶅拰杩炲瓧绗︼紝濡� `purchase-request`锛屼笌妯″潡鍚嶄繚鎸佷竴鑷淬��
+
+---
+
+## 鍥涖�佸悗绔泦鎴愶紙鐗规畩鍦烘櫙锛�
+
+### 4.1 鍚庣闇�瑕佷富鍔ㄤ笂浼犳枃浠舵椂
+
+鏋佸皯鏁板満鏅笅锛屽悗绔渶瑕佷富鍔ㄤ笂浼犳枃浠讹紙濡傜敓鎴愭姤琛ㄥ悗淇濆瓨锛夛紝鍙敞鍏� `FileApi`锛�
+
+```java
+@Resource
+private FileApi fileApi;
+
+// 涓婁紶鏂囦欢
+String url = fileApi.createFile(bytes, "鎶ヨ〃.xlsx", "report", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+```
+
+### 4.2 鑾峰彇绉佹湁鏂囦欢棰勭鍚嶅湴鍧�
+
+```java
+// 鐢熸垚涓存椂璁块棶鍦板潃锛�1灏忔椂鏈夋晥
+String presignedUrl = fileApi.presignGetUrl(fileUrl, 3600);
+```
+
+---
+
+## 浜斻�佸瓨鍌ㄩ厤缃�
+
+瀛樺偍閰嶇疆閫氳繃绠$悊鍚庡彴鍔ㄦ�侀厤缃紝鏃犻渶淇敼浠g爜锛�
+
+1. 璁块棶 **鍩虹璁炬柦 > 鏂囦欢绠$悊 > 鏂囦欢閰嶇疆**
+2. 鏂板閰嶇疆锛岄�夋嫨瀛樺偍绫诲瀷骞跺~鍐欏弬鏁�
+3. 鐐瑰嚮"璁剧疆涓轰富閰嶇疆"婵�娲讳娇鐢�
+
+### 鏀寔鐨勫瓨鍌ㄧ被鍨�
+
+| 绫诲瀷 | 璇存槑 | 閫傜敤鍦烘櫙 |
+|------|------|----------|
+| DB | 瀛樺偍鍒版暟鎹簱 | 灏忔枃浠躲�佷复鏃舵枃浠� |
+| LOCAL | 鏈湴纾佺洏瀛樺偍 | 鍐呯綉閮ㄧ讲銆佸紑鍙戠幆澧� |
+| FTP/SFTP | FTP 鏈嶅姟鍣� | 鍏煎鑰佺郴缁� |
+| S3 | 闃块噷浜� OSS / 鑵捐浜� COS / MinIO | 鐢熶骇鐜 |
+
+---
+
+## 鍏�佹枃浠跺瓨鍌ㄦ柟寮�
+
+### 6.1 澶氭枃浠跺瓨鍌紙鎺ㄨ崘锛�
+
+浣跨敤 `List<String>` + `StringListTypeHandler`锛屽弬鑰� CRM 璺熻繘璁板綍妯″潡锛�
+
+**瀹炰綋绫伙細**
+
+```java
+@TableName(value = "crm_follow_up_record", autoResultMap = true)
+public class CrmFollowUpRecordDO extends BaseDO {
+
+ /**
+ * 鍥剧墖
+ */
+ @TableField(typeHandler = StringListTypeHandler.class)
+ private List<String> picUrls;
+
+ /**
+ * 闄勪欢
+ */
+ @TableField(typeHandler = StringListTypeHandler.class)
+ private List<String> fileUrls;
+}
+```
+
+**鏁版嵁搴撳瓧娈�**锛歚VARCHAR`锛屽瓨鍌� JSON 鏁扮粍鏍煎紡濡� `["url1","url2","url3"]`
+
+**VO 绫伙細**
+
+```java
+@Schema(description = "闄勪欢")
+private List<String> fileUrls;
+```
+
+**鍓嶇浣跨敤锛�**
+
+```js
+// 涓婁紶鎴愬姛鍚庤拷鍔�
+handleUploadSuccess(response) {
+ if (response.code === 0) {
+ if (!this.form.fileUrls) {
+ this.form.fileUrls = []
+ }
+ this.form.fileUrls.push(response.data)
+ }
+}
+
+// 鍒犻櫎鏂囦欢
+handleRemove(index) {
+ this.form.fileUrls.splice(index, 1)
+}
+```
+
+### 6.2 鍗曟枃浠跺瓨鍌�
+
+```java
+// 瀹炰綋绫�
+private String fileUrl;
+
+// VO 绫�
+@Schema(description = "闄勪欢鍦板潃")
+private String fileUrl;
+```
+
+### 6.3 `infra_file` 琛ㄧ殑浣滅敤
+
+绯荤粺 `infra_file` 琛ㄨ褰曟墍鏈変笂浼犳枃浠剁殑鍏冩暟鎹紙name銆乸ath銆乽rl銆乼ype銆乻ize锛夛紝涓嶄笌涓氬姟鍏宠仈锛屼粎鐢ㄤ簬锛�
+- 鏂囦欢绠$悊鍚庡彴鏌ョ湅涓婁紶璁板綍
+- 缁熻瀛樺偍浣跨敤鎯呭喌
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **浣跨敤閫氱敤鎺ュ彛**锛氭墍鏈夋枃浠朵笂浼犲繀椤讳娇鐢� `/infra/file/upload`锛岀姝㈡柊澧炰笟鍔′笂浼犳帴鍙�
+2. **鍖哄垎鐩綍**锛氶�氳繃 `directory` 鍙傛暟鍖哄垎涓氬姟妯″潡锛屼究浜庣鐞嗗拰娓呯悊
+3. **鏂囦欢澶у皬**锛氶粯璁ら檺鍒� 10MB锛屽彲鍦� Nginx 鎴栧簲鐢ㄥ眰璋冩暣
+4. **绫诲瀷鏍¢獙**锛氬墠绔簲鏍¢獙鍏佽鐨勬枃浠剁被鍨嬶紝闃叉涓婁紶鎭舵剰鏂囦欢
+5. **URL 瀛樺偍**锛氫笂浼犳垚鍔熷悗锛屽皢杩斿洖鐨� URL 瀛樺偍鍒颁笟鍔¤〃鐨� `fileUrl` 瀛楁
+
+---
+
+## 涓冦�佸父瑙侀棶棰�
+
+**Q: 涓轰粈涔堜笉鑳戒负姣忎釜妯″潡鏂板涓婁紶鎺ュ彛锛�**
+
+A: 缁熶竴鎺ュ彛渚夸簬锛�
+- 缁熶竴绠$悊瀛樺偍閰嶇疆
+- 缁熶竴鏉冮檺鎺у埗鍜屽璁�
+- 閬垮厤浠g爜閲嶅
+- 鍚庣画缁存姢鍜岃縼绉�
+
+**Q: 濡備綍鍖哄垎涓嶅悓妯″潡鐨勬枃浠讹紵**
+
+A: 浣跨敤 `directory` 鍙傛暟锛屾枃浠朵細瀛樺偍鍦ㄥ搴旂洰褰曚笅銆�
+
+**Q: 鍓嶇濡備綍闄愬埗鏂囦欢绫诲瀷锛�**
+
+A: 鍦� `beforeUpload` 鏂规硶涓牎楠� `file.type` 鎴� `file.name` 鍚庣紑銆�
diff --git a/docs/im_module_tables.sql b/docs/im_module_tables.sql
new file mode 100644
index 0000000..82cf1b2
--- /dev/null
+++ b/docs/im_module_tables.sql
@@ -0,0 +1,401 @@
+-- IM 妯″潡 MySQL 琛ㄧ粨鏋�
+-- 鍩轰簬 yudao-module-im 妯″潡瀹炰綋绫绘彁鍙�
+-- 鐢熸垚鏃堕棿: 2026-06-30
+
+-- ========================================
+-- 1. im_channel - IM 棰戦亾琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_channel`;
+CREATE TABLE `im_channel` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `code` VARCHAR(64) NOT NULL COMMENT '棰戦亾涓氬姟鐮�',
+ `name` VARCHAR(128) NOT NULL COMMENT '棰戦亾鍚嶇О',
+ `avatar` VARCHAR(512) DEFAULT NULL COMMENT '棰戦亾澶村儚',
+ `sort` INT DEFAULT 0 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`),
+ UNIQUE KEY `uk_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 棰戦亾琛�';
+
+-- ========================================
+-- 2. im_channel_material - IM 棰戦亾绱犳潗琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_channel_material`;
+CREATE TABLE `im_channel_material` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `channel_id` BIGINT NOT NULL COMMENT '棰戦亾缂栧彿',
+ `type` TINYINT NOT NULL COMMENT '绱犳潗鍐呭绫诲瀷',
+ `title` VARCHAR(256) NOT NULL COMMENT '鏍囬',
+ `cover_url` VARCHAR(512) DEFAULT NULL COMMENT '灏侀潰鍥�',
+ `summary` VARCHAR(1024) DEFAULT NULL COMMENT '鎽樿',
+ `content` TEXT DEFAULT NULL COMMENT '瀵屾枃鏈� HTML',
+ `url` VARCHAR(512) 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_channel_id` (`channel_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 棰戦亾绱犳潗琛�';
+
+-- ========================================
+-- 3. im_conversation_read - IM 浼氳瘽璇讳綅缃〃
+-- ========================================
+DROP TABLE IF EXISTS `im_conversation_read`;
+CREATE TABLE `im_conversation_read` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `user_id` BIGINT NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `conversation_type` TINYINT NOT NULL COMMENT '浼氳瘽绫诲瀷锛�1绉佽亰 2缇よ亰 3棰戦亾锛�',
+ `target_id` BIGINT NOT NULL COMMENT '鐩爣缂栧彿',
+ `message_id` BIGINT NOT NULL 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`),
+ UNIQUE KEY `uk_user_conv` (`user_id`, `conversation_type`, `target_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 浼氳瘽璇讳綅缃〃';
+
+-- ========================================
+-- 4. im_face_pack - IM 琛ㄦ儏鍖呰〃
+-- ========================================
+DROP TABLE IF EXISTS `im_face_pack`;
+CREATE TABLE `im_face_pack` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `name` VARCHAR(128) NOT NULL COMMENT '琛ㄦ儏鍖呭悕绉�',
+ `icon` VARCHAR(512) DEFAULT NULL COMMENT '琛ㄦ儏鍖呭浘鏍�',
+ `sort` INT DEFAULT 0 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`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 琛ㄦ儏鍖呰〃';
+
+-- ========================================
+-- 5. im_face_pack_item - IM 琛ㄦ儏鍖呴」琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_face_pack_item`;
+CREATE TABLE `im_face_pack_item` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `pack_id` BIGINT NOT NULL COMMENT '鎵�灞炶〃鎯呭寘缂栧彿',
+ `url` VARCHAR(512) NOT NULL COMMENT '琛ㄦ儏鍥� URL',
+ `name` VARCHAR(64) DEFAULT NULL COMMENT '琛ㄦ儏鍚�',
+ `width` INT DEFAULT NULL COMMENT '娓叉煋瀹藉害锛堝儚绱狅級',
+ `height` INT DEFAULT NULL COMMENT '娓叉煋楂樺害锛堝儚绱狅級',
+ `sort` INT DEFAULT 0 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_pack_id` (`pack_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 琛ㄦ儏鍖呴」琛�';
+
+-- ========================================
+-- 6. im_face_user_item - IM 鐢ㄦ埛绉佹湁琛ㄦ儏琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_face_user_item`;
+CREATE TABLE `im_face_user_item` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `user_id` BIGINT NOT NULL COMMENT '鎵�灞炵敤鎴风紪鍙�',
+ `url` VARCHAR(512) NOT NULL COMMENT '琛ㄦ儏鍥� URL',
+ `name` VARCHAR(64) DEFAULT NULL COMMENT '琛ㄦ儏鍚�',
+ `width` INT DEFAULT NULL COMMENT '娓叉煋瀹藉害锛堝儚绱狅級',
+ `height` INT DEFAULT NULL COMMENT '娓叉煋楂樺害锛堝儚绱狅級',
+ `sort` INT 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`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 鐢ㄦ埛绉佹湁琛ㄦ儏琛�';
+
+-- ========================================
+-- 7. im_friend - IM 濂藉弸鍏崇郴琛�
+-- ========================================
+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 '濂藉弸鐢ㄦ埛缂栧彿',
+ `silent` BIT(1) DEFAULT b'0' COMMENT '鏄惁鍏嶆墦鎵�',
+ `display_name` VARCHAR(128) DEFAULT NULL COMMENT '濂藉弸灞曠ず澶囨敞',
+ `add_source` TINYINT DEFAULT NULL COMMENT '娣诲姞鏉ユ簮',
+ `pinned` BIT(1) DEFAULT b'0' COMMENT '鏄惁缃《鑱旂郴浜�',
+ `blocked` BIT(1) DEFAULT b'0' COMMENT '鏄惁鎷夐粦',
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '濂藉弸鐘舵�侊紙0姝e父 1绂佺敤/宸插垹闄わ級',
+ `add_time` DATETIME DEFAULT NULL COMMENT '娣诲姞濂藉弸鏃堕棿',
+ `delete_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`),
+ UNIQUE KEY `uk_user_friend` (`user_id`, `friend_user_id`),
+ KEY `idx_friend_user_id` (`friend_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 濂藉弸鍏崇郴琛�';
+
+-- ========================================
+-- 8. im_friend_request - IM 濂藉弸鐢宠璁板綍琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_friend_request`;
+CREATE TABLE `im_friend_request` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `from_user_id` BIGINT NOT NULL COMMENT '鍙戣捣鏂圭敤鎴风紪鍙�',
+ `to_user_id` BIGINT NOT NULL COMMENT '鎺ユ敹鏂圭敤鎴风紪鍙�',
+ `apply_content` VARCHAR(256) DEFAULT NULL COMMENT '鐢宠鐞嗙敱',
+ `display_name` VARCHAR(128) DEFAULT NULL COMMENT '鍙戣捣鏂瑰鎺ユ敹鏂圭殑澶囨敞',
+ `add_source` TINYINT DEFAULT NULL COMMENT '娣诲姞鏉ユ簮',
+ `handle_result` TINYINT DEFAULT NULL COMMENT '澶勭悊缁撴灉',
+ `handle_content` VARCHAR(256) 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_from_user_id` (`from_user_id`),
+ KEY `idx_to_user_id` (`to_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 濂藉弸鐢宠璁板綍琛�';
+
+-- ========================================
+-- 9. im_group - IM 缇や俊鎭〃
+-- ========================================
+DROP TABLE IF EXISTS `im_group`;
+CREATE TABLE `im_group` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `name` VARCHAR(128) NOT NULL COMMENT '缇ゅ悕绉�',
+ `owner_user_id` BIGINT NOT NULL COMMENT '缇や富鐢ㄦ埛缂栧彿',
+ `avatar` VARCHAR(512) DEFAULT NULL COMMENT '缇ゅご鍍�',
+ `notice` VARCHAR(1024) DEFAULT NULL COMMENT '缇ゅ叕鍛�',
+ `join_approval` BIT(1) DEFAULT b'0' COMMENT '杩涚兢鏄惁闇�瀹℃壒',
+ `banned` BIT(1) DEFAULT b'0' COMMENT '鏄惁灏佺',
+ `banned_reason` VARCHAR(256) DEFAULT NULL COMMENT '灏佺鍘熷洜',
+ `banned_time` DATETIME DEFAULT NULL COMMENT '灏佺鏃堕棿',
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '缇ょ姸鎬侊紙0姝e父 1宸茶В鏁o級',
+ `dissolved_time` DATETIME DEFAULT NULL COMMENT '瑙f暎鏃堕棿',
+ `muted_all` BIT(1) DEFAULT b'0' COMMENT '鏄惁鍏ㄧ兢绂佽█',
+ `pinned_message_ids` VARCHAR(1024) 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_owner_user_id` (`owner_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 缇や俊鎭〃';
+
+-- ========================================
+-- 10. im_group_member - IM 缇ゆ垚鍛樿〃
+-- ========================================
+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 '鐢ㄦ埛缂栧彿',
+ `display_user_name` VARCHAR(128) DEFAULT NULL COMMENT '缁勫唴鏄剧ず鍚�',
+ `group_remark` VARCHAR(256) DEFAULT NULL COMMENT '缇ゅ娉�',
+ `silent` BIT(1) DEFAULT b'0' COMMENT '鏄惁鍏嶆墦鎵�',
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '鎴愬憳鐘舵�侊紙0姝e父 1绂佺敤锛�',
+ `role` TINYINT NOT NULL DEFAULT 0 COMMENT '鎴愬憳瑙掕壊锛�0鏅�氭垚鍛� 1绠$悊鍛� 2缇や富锛�',
+ `join_time` DATETIME DEFAULT NULL COMMENT '鍏ョ兢鏃堕棿',
+ `add_source` TINYINT DEFAULT NULL COMMENT '鍔犲叆鏉ユ簮',
+ `inviter_user_id` BIGINT DEFAULT NULL COMMENT '閭�璇蜂汉鐢ㄦ埛缂栧彿',
+ `quit_time` DATETIME DEFAULT NULL COMMENT '閫�缇ゆ椂闂�',
+ `mute_end_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`),
+ UNIQUE KEY `uk_group_user` (`group_id`, `user_id`),
+ KEY `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 缇ゆ垚鍛樿〃';
+
+-- ========================================
+-- 11. im_group_request - IM 鍔犵兢鐢宠璁板綍琛�
+-- ========================================
+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 '鐢宠浜�/琚個璇蜂汉鐢ㄦ埛缂栧彿',
+ `inviter_user_id` BIGINT DEFAULT NULL COMMENT '閭�璇蜂汉鐢ㄦ埛缂栧彿',
+ `apply_content` VARCHAR(256) DEFAULT NULL COMMENT '鐢宠鐞嗙敱',
+ `add_source` TINYINT DEFAULT NULL COMMENT '鍔犲叆鏉ユ簮',
+ `handle_result` TINYINT DEFAULT NULL COMMENT '澶勭悊缁撴灉',
+ `handle_user_id` BIGINT DEFAULT NULL COMMENT '澶勭悊浜虹敤鎴风紪鍙�',
+ `handle_content` VARCHAR(256) 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 鍔犵兢鐢宠璁板綍琛�';
+
+-- ========================================
+-- 12. im_channel_message - IM 棰戦亾娑堟伅琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_channel_message`;
+CREATE TABLE `im_channel_message` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `channel_id` BIGINT NOT NULL COMMENT '棰戦亾缂栧彿',
+ `material_id` BIGINT DEFAULT NULL COMMENT '鍏宠仈绱犳潗缂栧彿',
+ `type` TINYINT NOT NULL COMMENT '娑堟伅绫诲瀷',
+ `content` TEXT DEFAULT NULL COMMENT '娑堟伅鍐呭 JSON',
+ `receiver_user_ids` VARCHAR(1024) DEFAULT NULL COMMENT '鎺ユ敹浜虹紪鍙峰垪琛�',
+ `send_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_channel_id` (`channel_id`),
+ KEY `idx_send_time` (`send_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 棰戦亾娑堟伅琛�';
+
+-- ========================================
+-- 13. im_group_message - IM 缇よ亰娑堟伅琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_group_message`;
+CREATE TABLE `im_group_message` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `client_message_id` VARCHAR(64) DEFAULT NULL COMMENT '瀹㈡埛绔秷鎭紪鍙�',
+ `sender_id` BIGINT NOT NULL COMMENT '鍙戦�佷汉缂栧彿',
+ `group_id` BIGINT NOT NULL COMMENT '缇ょ紪鍙�',
+ `type` TINYINT NOT NULL COMMENT '娑堟伅绫诲瀷',
+ `content` TEXT DEFAULT NULL COMMENT '娑堟伅鍐呭 JSON',
+ `status` TINYINT DEFAULT NULL COMMENT '娑堟伅鐘舵��',
+ `send_time` DATETIME DEFAULT NULL COMMENT '鍙戦�佹椂闂�',
+ `receiver_user_ids` VARCHAR(1024) DEFAULT NULL COMMENT '瀹氬悜鎺ユ敹鐢ㄦ埛缂栧彿鍒楄〃',
+ `at_user_ids` VARCHAR(1024) DEFAULT NULL COMMENT '@鐩爣鐢ㄦ埛缂栧彿鍒楄〃',
+ `receipt_status` TINYINT 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_client_message_id` (`client_message_id`),
+ KEY `idx_group_id` (`group_id`),
+ KEY `idx_sender_id` (`sender_id`),
+ KEY `idx_send_time` (`send_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 缇よ亰娑堟伅琛�';
+
+-- ========================================
+-- 14. im_private_message - IM 绉佽亰娑堟伅琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_private_message`;
+CREATE TABLE `im_private_message` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `client_message_id` VARCHAR(64) DEFAULT NULL COMMENT '瀹㈡埛绔秷鎭紪鍙�',
+ `sender_id` BIGINT NOT NULL COMMENT '鍙戦�佷汉缂栧彿',
+ `receiver_id` BIGINT NOT NULL COMMENT '鎺ユ敹浜虹紪鍙�',
+ `type` TINYINT NOT NULL COMMENT '娑堟伅绫诲瀷',
+ `content` TEXT DEFAULT NULL COMMENT '娑堟伅鍐呭 JSON',
+ `status` TINYINT DEFAULT NULL COMMENT '娑堟伅鐘舵��',
+ `receipt_status` TINYINT DEFAULT NULL COMMENT '鍥炴墽鐘舵��',
+ `send_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`),
+ UNIQUE KEY `uk_client_message_id` (`client_message_id`),
+ KEY `idx_sender_id` (`sender_id`),
+ KEY `idx_receiver_id` (`receiver_id`),
+ KEY `idx_send_time` (`send_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 绉佽亰娑堟伅琛�';
+
+-- ========================================
+-- 15. im_rtc_call - IM 閫氳瘽璁板綍琛�
+-- ========================================
+DROP TABLE IF EXISTS `im_rtc_call`;
+CREATE TABLE `im_rtc_call` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `room` VARCHAR(64) NOT NULL COMMENT '涓氬姟閫氳瘽缂栧彿锛圲UID锛�',
+ `conversation_type` TINYINT NOT NULL COMMENT '浼氳瘽绫诲瀷',
+ `media_type` TINYINT NOT NULL COMMENT '濯掍綋绫诲瀷',
+ `inviter_user_id` BIGINT NOT NULL COMMENT '鍙戣捣浜虹敤鎴风紪鍙�',
+ `group_id` BIGINT DEFAULT NULL COMMENT '缇ょ紪鍙�',
+ `status` TINYINT NOT NULL COMMENT '閫氳瘽鐘舵��',
+ `end_reason` TINYINT DEFAULT NULL COMMENT '缁撴潫鍘熷洜',
+ `start_time` DATETIME DEFAULT NULL COMMENT '鍙戣捣鏃堕棿',
+ `accept_time` DATETIME DEFAULT NULL COMMENT '鎺ラ�氭椂闂�',
+ `end_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`),
+ UNIQUE KEY `uk_room` (`room`),
+ KEY `idx_inviter_user_id` (`inviter_user_id`),
+ KEY `idx_group_id` (`group_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 閫氳瘽璁板綍琛�';
+
+-- ========================================
+-- 16. im_rtc_participant - IM 閫氳瘽鍙備笌鑰呰〃
+-- ========================================
+DROP TABLE IF EXISTS `im_rtc_participant`;
+CREATE TABLE `im_rtc_participant` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `call_id` BIGINT NOT NULL COMMENT '閫氳瘽缂栧彿',
+ `room` VARCHAR(64) NOT NULL COMMENT '涓氬姟閫氳瘽缂栧彿',
+ `user_id` BIGINT NOT NULL COMMENT '鍙備笌鑰呯敤鎴风紪鍙�',
+ `role` TINYINT NOT NULL COMMENT '鍙備笌瑙掕壊',
+ `status` TINYINT NOT NULL COMMENT '鍙備笌鐘舵��',
+ `invite_time` DATETIME DEFAULT NULL COMMENT '琚個璇锋椂闂�',
+ `accept_time` DATETIME DEFAULT NULL COMMENT '鎺ュ惉鏃堕棿',
+ `leave_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_call_id` (`call_id`),
+ KEY `idx_room` (`room`),
+ KEY `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 閫氳瘽鍙備笌鑰呰〃';
+
+-- ========================================
+-- 17. im_sensitive_word - IM 鏁忔劅璇嶈〃
+-- ========================================
+DROP TABLE IF EXISTS `im_sensitive_word`;
+CREATE TABLE `im_sensitive_word` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `word` VARCHAR(128) NOT 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`),
+ UNIQUE KEY `uk_word` (`word`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IM 鏁忔劅璇嶈〃';
\ No newline at end of file
diff --git a/docs/marketing_report_frontend.md b/docs/marketing_report_frontend.md
new file mode 100644
index 0000000..63ed031
--- /dev/null
+++ b/docs/marketing_report_frontend.md
@@ -0,0 +1,361 @@
+# CRM 钀ラ攢鏁版嵁鎶ヨ〃 - 鍓嶇鏂囨。
+
+## 姒傝堪
+
+鏈鏀归�犲疄鐜拌惀閿�鏁版嵁鎶ヨ〃鍔熻兘锛屾彁渚涘鎴峰垎鏋愩�佸晢鏈哄垎鏋愩�佸悎鍚屽垎鏋愩�侀攢鍞紡鏂椼�侀攢鍞笟缁╃瓑澶氱淮搴︾粺璁″垎鏋愩��
+
+---
+
+## 涓�銆佹敼閫犳竻鍗�
+
+### 鍚庣鏀归�狅紙宸插畬鎴愶級
+
+| 鏂囦欢 | 鏀瑰姩璇存槑 |
+|------|----------|
+| `CrmReportApi.java` | 钀ラ攢鎶ヨ〃 API 鎺ュ彛 |
+| `CrmCustomerAnalyzeRespDTO.java` | 瀹㈡埛鍒嗘瀽缁熻 DTO |
+| `CrmBusinessAnalyzeRespDTO.java` | 鍟嗘満鍒嗘瀽缁熻 DTO |
+| `CrmContractAnalyzeRespDTO.java` | 鍚堝悓鍒嗘瀽缁熻 DTO |
+| `CrmSalesFunnelRespDTO.java` | 閿�鍞紡鏂楃粺璁� DTO |
+| `CrmSalesPerformanceRespDTO.java` | 閿�鍞笟缁╃粺璁� DTO |
+| `CrmReportApiImpl.java` | 鎶ヨ〃 API 瀹炵幇绫� |
+| `CrmCustomerMapper.java` | 鏂板缁熻鏂规硶 |
+| `CrmBusinessMapper.java` | 鏂板缁熻鏂规硶 |
+| `CrmContractMapper.java` | 鏂板缁熻鏂规硶 |
+| `CrmReceivableMapper.java` | 鏂板缁熻鏂规硶 |
+| `CrmReceivableService.java` | 鏂板缁熻鏂规硶 |
+
+---
+
+## 浜屻�佹秹鍙婇〉闈�
+
+### CRM 鎶ヨ〃妯″潡
+
+| 椤甸潰 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 鎶ヨ〃姒傝 | `/crm/report/dashboard` | 鏁版嵁姒傝椤甸潰 |
+| 瀹㈡埛鍒嗘瀽 | `/crm/report/customer` | 瀹㈡埛缁熻鍒嗘瀽 |
+| 鍟嗘満鍒嗘瀽 | `/crm/report/business` | 鍟嗘満缁熻鍒嗘瀽 |
+| 鍚堝悓鍒嗘瀽 | `/crm/report/contract` | 鍚堝悓缁熻鍒嗘瀽 |
+| 閿�鍞紡鏂� | `/crm/report/funnel` | 閿�鍞紡鏂楀浘 |
+| 閿�鍞笟缁� | `/crm/report/performance` | 閿�鍞笟缁╂帓琛� |
+
+---
+
+## 涓夈�丄PI 鎺ュ彛
+
+### CRM 钀ラ攢鎶ヨ〃 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /rpc-api/crm/report/get-customer-analyze | 鑾峰彇瀹㈡埛鍒嗘瀽缁熻 |
+| GET | /rpc-api/crm/report/get-business-analyze | 鑾峰彇鍟嗘満鍒嗘瀽缁熻 |
+| GET | /rpc-api/crm/report/get-contract-analyze | 鑾峰彇鍚堝悓鍒嗘瀽缁熻 |
+| GET | /rpc-api/crm/report/get-sales-funnel | 鑾峰彇閿�鍞紡鏂楃粺璁� |
+| GET | /rpc-api/crm/report/get-sales-performance | 鑾峰彇閿�鍞笟缁╃粺璁� |
+
+### 璇锋眰鍙傛暟
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| startTime | LocalDateTime | 鍚� | 寮�濮嬫椂闂� |
+| endTime | LocalDateTime | 鍚� | 缁撴潫鏃堕棿 |
+
+### 鍝嶅簲绀轰緥
+
+**瀹㈡埛鍒嗘瀽锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "totalCount": 1000,
+ "todayCount": 15,
+ "dealCount": 300,
+ "dealRate": 30.00,
+ "lockCount": 50,
+ "poolCount": 200
+ }
+}
+```
+
+**鍟嗘満鍒嗘瀽锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "totalCount": 500,
+ "todayCount": 8,
+ "totalPrice": 5000000.00,
+ "successCount": 150,
+ "successPrice": 2000000.00,
+ "successRate": 30.00,
+ "processingCount": 250,
+ "failCount": 100
+ }
+}
+```
+
+**鍚堝悓鍒嗘瀽锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "totalCount": 400,
+ "todayCount": 5,
+ "totalPrice": 3000000.00,
+ "approveCount": 300,
+ "approvePrice": 2500000.00,
+ "processCount": 20,
+ "receivablePrice": 1500000.00,
+ "receivableRate": 60.00
+ }
+}
+```
+
+**閿�鍞紡鏂楋細**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "stages": [
+ { "name": "瀹㈡埛", "count": 1000, "price": 0, "conversionRate": 100.00 },
+ { "name": "鍟嗘満", "count": 500, "price": 5000000.00, "conversionRate": 50.00 },
+ { "name": "鍚堝悓", "count": 300, "price": 2500000.00, "conversionRate": 30.00 },
+ { "name": "鍥炴", "count": null, "price": 1500000.00, "conversionRate": 15.00 }
+ ]
+ }
+}
+```
+
+**閿�鍞笟缁╋細**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "items": [
+ {
+ "userId": 1,
+ "userName": "寮犱笁",
+ "deptName": "閿�鍞竴閮�",
+ "contractCount": 50,
+ "contractPrice": 500000.00,
+ "receivablePrice": 300000.00,
+ "businessCount": 80,
+ "businessPrice": 800000.00
+ }
+ ]
+ }
+}
+```
+
+---
+
+## 鍥涖�佸墠绔慨鏀圭偣
+
+### 4.1 鎶ヨ〃姒傝椤甸潰
+
+**鏂囦欢璺緞锛�** `/src/views/crm/report/dashboard.vue`
+
+```html
+<template>
+ <div class="report-dashboard">
+ <!-- 缁熻鍗$墖 -->
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-card class="stat-card">
+ <div class="stat-title">瀹㈡埛鎬绘暟</div>
+ <div class="stat-value">{{ customerAnalyze.totalCount }}</div>
+ <div class="stat-footer">浠婃棩鏂板锛歿{ customerAnalyze.todayCount }}</div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stat-card">
+ <div class="stat-title">鍟嗘満鎬绘暟</div>
+ <div class="stat-value">{{ businessAnalyze.totalCount }}</div>
+ <div class="stat-footer">杞寲鐜囷細{{ businessAnalyze.successRate }}%</div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stat-card">
+ <div class="stat-title">鍚堝悓閲戦</div>
+ <div class="stat-value">楼{{ contractAnalyze.totalPrice }}</div>
+ <div class="stat-footer">宸插洖娆撅細楼{{ contractAnalyze.receivablePrice }}</div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stat-card">
+ <div class="stat-title">鍥炴鐜�</div>
+ <div class="stat-value">{{ contractAnalyze.receivableRate }}%</div>
+ <div class="stat-footer">瀹℃壒閫氳繃锛歿{ contractAnalyze.approveCount }}浠�</div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 閿�鍞紡鏂� -->
+ <el-card header="閿�鍞紡鏂�">
+ <FunnelChart :data="funnelData" />
+ </el-card>
+
+ <!-- 閿�鍞笟缁╂帓琛� -->
+ <el-card header="閿�鍞笟缁╂帓琛�">
+ <el-table :data="performanceData" stripe>
+ <el-table-column prop="userName" label="閿�鍞汉鍛�" />
+ <el-table-column prop="deptName" label="閮ㄩ棬" />
+ <el-table-column prop="contractCount" label="鍚堝悓鏁伴噺" width="100" />
+ <el-table-column prop="contractPrice" label="鍚堝悓閲戦" width="150" />
+ <el-table-column prop="receivablePrice" label="鍥炴閲戦" width="150" />
+ <el-table-column prop="businessCount" label="鍟嗘満鏁伴噺" width="100" />
+ <el-table-column prop="businessPrice" label="鍟嗘満閲戦" width="150" />
+ </el-table>
+ </el-card>
+ </div>
+</template>
+```
+
+### 4.2 閿�鍞紡鏂楃粍浠�
+
+**鏂囦欢璺緞锛�** `/src/components/FunnelChart.vue`
+
+```html
+<template>
+ <div class="funnel-chart">
+ <div v-for="(stage, index) in data" :key="index" class="funnel-stage">
+ <div class="funnel-bar" :style="getBarStyle(stage, index)">
+ <span class="stage-name">{{ stage.name }}</span>
+ <span class="stage-value">{{ stage.price > 0 ? '楼' + stage.price : stage.count }}</span>
+ </div>
+ <div class="conversion-rate">杞寲鐜囷細{{ stage.conversionRate }}%</div>
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ props: {
+ data: Array
+ },
+ methods: {
+ getBarStyle(stage, index) {
+ const maxWidth = 100
+ const width = maxWidth - (index * 15)
+ return {
+ width: `${width}%`,
+ backgroundColor: this.getColor(index)
+ }
+ },
+ getColor(index) {
+ const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C']
+ return colors[index] || '#909399'
+ }
+ }
+}
+</script>
+```
+
+### 4.3 API 瀹氫箟
+
+**鏂囦欢璺緞锛�** `/src/api/crm/report.js`
+
+```js
+import request from '@/utils/request'
+
+// 鑾峰彇瀹㈡埛鍒嗘瀽缁熻
+export function getCustomerAnalyze(startTime, endTime) {
+ return request({
+ url: '/crm/report/get-customer-analyze',
+ method: 'get',
+ params: { startTime, endTime }
+ })
+}
+
+// 鑾峰彇鍟嗘満鍒嗘瀽缁熻
+export function getBusinessAnalyze(startTime, endTime) {
+ return request({
+ url: '/crm/report/get-business-analyze',
+ method: 'get',
+ params: { startTime, endTime }
+ })
+}
+
+// 鑾峰彇鍚堝悓鍒嗘瀽缁熻
+export function getContractAnalyze(startTime, endTime) {
+ return request({
+ url: '/crm/report/get-contract-analyze',
+ method: 'get',
+ params: { startTime, endTime }
+ })
+}
+
+// 鑾峰彇閿�鍞紡鏂楃粺璁�
+export function getSalesFunnel() {
+ return request({
+ url: '/crm/report/get-sales-funnel',
+ method: 'get'
+ })
+}
+
+// 鑾峰彇閿�鍞笟缁╃粺璁�
+export function getSalesPerformance(startTime, endTime) {
+ return request({
+ url: '/crm/report/get-sales-performance',
+ method: 'get',
+ params: { startTime, endTime }
+ })
+}
+```
+
+---
+
+## 浜斻�佸浘琛ㄥ睍绀哄缓璁�
+
+### 5.1 瀹㈡埛鍒嗘瀽鍥捐〃
+
+- **瀹㈡埛澧為暱瓒嬪娍鍥�**锛氭姌绾垮浘灞曠ず姣忔棩/姣忓懆/姣忔湀鏂板瀹㈡埛
+- **瀹㈡埛鎴愪氦鐜囬ゼ鍥�**锛氬凡鎴愪氦 vs 鏈垚浜ゅ鎴锋瘮渚�
+- **瀹㈡埛鏉ユ簮鍒嗗竷**锛氶ゼ鍥惧睍绀轰笉鍚屾潵婧愮殑瀹㈡埛鍗犳瘮
+
+### 5.2 鍟嗘満鍒嗘瀽鍥捐〃
+
+- **鍟嗘満闃舵鍒嗗竷**锛氶ゼ鍥惧睍绀轰笉鍚岀姸鎬佺殑鍟嗘満鏁伴噺
+- **鍟嗘満閲戦鍒嗗竷**锛氭煴鐘跺浘灞曠ず涓嶅悓閲戦鍖洪棿鐨勫晢鏈烘暟閲�
+- **鍟嗘満杞寲瓒嬪娍**锛氭姌绾垮浘灞曠ず杞寲鐜囧彉鍖�
+
+### 5.3 鍚堝悓鍒嗘瀽鍥捐〃
+
+- **鍚堝悓閲戦瓒嬪娍**锛氭姌绾垮浘灞曠ず鍚堝悓閲戦鍙樺寲
+- **鍚堝悓鐘舵�佸垎甯�**锛氶ゼ鍥惧睍绀轰笉鍚屽鎵圭姸鎬佺殑鍚堝悓
+- **鍥炴杩涘害鍥�**锛氳繘搴︽潯灞曠ず鍥炴瀹屾垚搴�
+
+### 5.4 閿�鍞紡鏂楀浘
+
+- 浣跨敤妯悜婕忔枟鍥惧睍绀哄悇闃舵杞寲鎯呭喌
+- 姣忎釜闃舵鏄剧ず鏁伴噺鍜岄噾棰�
+- 鑷姩璁$畻杞寲鐜�
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. **鏁版嵁鏉冮檺**锛氭姤琛ㄦ暟鎹渶瑕佹牴鎹敤鎴锋潈闄愯繃婊�
+2. **缁熻鍙e緞**锛氭槑纭悇绫荤粺璁℃暟鎹殑璁$畻閫昏緫
+3. **鏃堕棿鑼冨洿**锛氭敮鎸佽嚜瀹氫箟鏃堕棿鑼冨洿鏌ヨ
+4. **鏁版嵁鍒锋柊**锛氳�冭檻鏁版嵁缂撳瓨鍜屽埛鏂扮瓥鐣�
+5. **瀵煎嚭鍔熻兘**锛氭敮鎸佹姤琛ㄦ暟鎹鍑轰负 Excel
+
+---
+
+## 涓冦�佸悗缁鍒�
+
+| Phase | 鍐呭 | 鐘舵�� |
+|-------|------|------|
+| Phase 1 | 瀹㈡埛鏁版嵁缁熶竴 - ERP 浣跨敤 CRM 瀹㈡埛 | **宸插畬鎴�** |
+| Phase 2 | 浜у搧鏁版嵁缁熶竴 - CRM/ERP 浣跨敤 MDM 鐗╂枡 | **宸插畬鎴�** |
+| Phase 3 | 鍚堝悓鐢熸垚閿�鍞鍗� | **宸插畬鎴�** |
+| Phase 4 | 閿�鍞叏娴佺▼杩芥函 | **宸插畬鎴�** |
+| Phase 5 | 钀ラ攢鏁版嵁鎶ヨ〃 | **宸插畬鎴�** |
+| Phase 6 | 鏁版嵁杩佺Щ涓庢竻鐞� | 寰呭紑濮� |
\ 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/mdm_frontend_change_phase1.md b/docs/mdm_frontend_change_phase1.md
new file mode 100644
index 0000000..1882c37
--- /dev/null
+++ b/docs/mdm_frontend_change_phase1.md
@@ -0,0 +1,173 @@
+# MDM 涓绘暟鎹ā鍧楁敼閫� - 鍓嶇鍙樻洿鏂囨。锛堢涓�闃舵锛�
+
+## 姒傝堪
+
+鏈鏀归�犲皢 ERP銆丮ES銆乄MS 涓変釜妯″潡鐨勭墿鏂�/浜у搧鏁版嵁缁熶竴鍒� MDM锛堜富鏁版嵁绠$悊锛夋ā鍧椼��
+
+**鏀归�犺寖鍥�**锛氬熀纭�鏁版嵁锛堝崟浣嶏級
+
+**娉ㄦ剰**锛�
+- 鐗╂枡绫诲瀷锛坕temType锛変负鏋氫妇瀛楁锛�1:鐗╂枡, 2:浜у搧, 3:鍗婃垚鍝侊級锛屼笉闇�瑕佸崟鐙淮鎶ら〉闈�
+- 鍝佺墝鏁版嵁鐢� WMS 缁存姢锛屼笉闇�瑕佸崟鐙殑 MDM 鍝佺墝椤甸潰
+
+---
+
+## 涓�銆丮DM 璁¢噺鍗曚綅椤甸潰
+
+### 娑夊強椤甸潰
+
+- 鏂板椤甸潰锛歚/mdm/unit` - 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 | 鑾峰彇鍗曚綅鍒楄〃 |
+
+**璇锋眰鍙傛暟锛堝垱寤�/鏇存柊锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 鍗曚綅缂栧彿锛堟洿鏂版椂蹇呭~锛� |
+| code | String | 鏄� | 鍗曚綅缂栫爜 |
+| name | String | 鏄� | 鍗曚綅鍚嶇О |
+| primaryFlag | Boolean | 鍚� | 鏄惁涓诲崟浣� |
+| primaryId | Long | 鍚� | 涓诲崟浣嶇紪鍙� |
+| changeRate | BigDecimal | 鍚� | 涓庝富鍗曚綅鎹㈢畻姣斾緥 |
+| status | Integer | 鏄� | 鐘舵�侊紙0:鍚敤, 1:绂佺敤锛� |
+| remark | String | 鍚� | 澶囨敞 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "id": 1,
+ "code": "PCS",
+ "name": "涓�",
+ "primaryFlag": true,
+ "primaryId": null,
+ "changeRate": 1,
+ "status": 0,
+ "remark": "",
+ "createTime": "2024-01-01 12:00:00"
+ },
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 浜屻�丒RP 浜у搧绠$悊椤甸潰淇敼
+
+### 娑夊強椤甸潰
+
+- `/erp/product` - ERP 浜у搧绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+
+### 鍓嶇淇敼鐐�
+
+鐗╂枡绫诲瀷鍥哄畾涓� `itemType = 2`锛堜骇鍝侊級锛屽墠绔棤闇�缁存姢璇ュ瓧娈点��
+
+---
+
+## 涓夈�丮ES 鐗╂枡绠$悊椤甸潰淇敼
+
+### 娑夊強椤甸潰
+
+- `/mes/md/item` - MES 鐗╂枡绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+
+### 鍓嶇淇敼鐐�
+
+鐗╂枡绫诲瀷鍥哄畾涓� `itemType = 1`锛堢墿鏂欙級锛屽墠绔棤闇�缁存姢璇ュ瓧娈点��
+
+---
+
+## 鍥涖�乄MS 鍟嗗搧绠$悊椤甸潰淇敼
+
+### 娑夊強椤甸潰
+
+- `/wms/item` - WMS 鍟嗗搧绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+
+### 鍓嶇淇敼鐐�
+
+鐗╂枡绫诲瀷鍥哄畾涓� `itemType = 1`锛堢墿鏂欙級锛屽墠绔棤闇�缁存姢璇ュ瓧娈点��
+
+---
+
+## 浜斻�佹柊澧炲墠绔� API 鏂囦欢
+
+### 鏂囦欢璺緞锛歚src/api/mdm/unit.js`
+
+```js
+import request from '@/utils/request'
+
+// 鍒涘缓璁¢噺鍗曚綅
+export function createUnit(data) {
+ return request({
+ url: '/mdm/unit/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 鏇存柊璁¢噺鍗曚綅
+export function updateUnit(data) {
+ return request({
+ url: '/mdm/unit/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鍒犻櫎璁¢噺鍗曚綅
+export function deleteUnit(id) {
+ return request({
+ url: '/mdm/unit/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 鑾峰彇璁¢噺鍗曚綅
+export function getUnit(id) {
+ return request({
+ url: '/mdm/unit/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 鑾峰彇璁¢噺鍗曚綅鍒嗛〉
+export function getUnitPage(params) {
+ return request({
+ url: '/mdm/unit/page',
+ method: 'get',
+ params: params
+ })
+}
+
+// 鑾峰彇璁¢噺鍗曚綅鍒楄〃
+export function getUnitListByIds(ids) {
+ return request({
+ url: '/mdm/unit/list-by-ids?ids=' + ids,
+ method: 'get'
+ })
+}
+```
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. **鏁版嵁鍏煎**锛氳縼绉绘湡闂达紝鍘熸ā鍧楅〉闈繚鐣欙紝浣嗘暟鎹簮鍙樻洿涓� MDM
+2. **鐗╂枡绫诲瀷瀛楁**锛歚itemType` 涓烘灇涓惧瓧娈碉紝涓嶉渶瑕佸崟鐙淮鎶ら〉闈�
+ - ERP 浜у搧锛歚itemType = 2`锛堜骇鍝侊級
+ - MES 鐗╂枡锛歚itemType = 1`锛堢墿鏂欙級
+ - WMS 鍟嗗搧锛歚itemType = 1`锛堢墿鏂欙級
+3. **鍝佺墝鏁版嵁**锛氱敱 WMS 妯″潡缁存姢锛屼笉闇�瑕佸崟鐙殑 MDM 鍝佺墝椤甸潰
+4. **ID 鏄犲皠**锛氶儴鍒嗘暟鎹� ID 鏈夊亸绉伙紝娉ㄦ剰鏌ヨ鍏宠仈鏁版嵁
+5. **鏉冮檺閰嶇疆**锛氶渶瑕佷负鏂伴〉闈㈤厤缃彍鍗曞拰鏉冮檺
\ No newline at end of file
diff --git a/docs/mdm_frontend_change_phase2.md b/docs/mdm_frontend_change_phase2.md
new file mode 100644
index 0000000..0d0ef68
--- /dev/null
+++ b/docs/mdm_frontend_change_phase2.md
@@ -0,0 +1,556 @@
+# MDM 涓绘暟鎹ā鍧楁敼閫� - 鍓嶇鍙樻洿鏂囨。锛堢浜岄樁娈碉級
+
+## 姒傝堪
+
+鏈鏀归�犲皢 ERP銆丮ES銆乄MS 涓変釜妯″潡鐨勭墿鏂�/浜у搧鏁版嵁缁熶竴鍒� MDM锛堜富鏁版嵁绠$悊锛夋ā鍧椼��
+
+**鏀归�犺寖鍥�**锛氱墿鏂�/浜у搧鏁版嵁杩佺Щ鍙� SKU 绠$悊
+
+---
+
+## 涓�銆丮DM 鐗╂枡椤甸潰
+
+### 娑夊強椤甸潰
+
+- 鏂板椤甸潰锛歚/mdm/item` - 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 | 鍚� | 瀹夊叏搴撳瓨 |
+| minStock | BigDecimal | 鍚� | 鏈�浣庡簱瀛� |
+| maxStock | BigDecimal | 鍚� | 鏈�楂樺簱瀛� |
+| expiryDay | Integer | 鍚� | 淇濊川鏈熷ぉ鏁� |
+| isBatchManaged | Boolean | 鍚� | 鏄惁鎵规绠$悊 |
+| weight | BigDecimal | 鍚� | 閲嶉噺锛坘g锛� |
+| remark | String | 鍚� | 澶囨敞 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "id": 1,
+ "code": "ITEM001",
+ "name": "铻轰笣",
+ "barCode": "12345678",
+ "specification": "M8*20",
+ "categoryId": 1,
+ "categoryName": "浜旈噾閰嶄欢",
+ "unitMeasureId": 1,
+ "unitMeasureName": "涓�",
+ "brandId": null,
+ "brandName": null,
+ "itemType": 1,
+ "status": 0,
+ "purchasePrice": 10.00,
+ "salesPrice": 15.00,
+ "costPrice": 8.00,
+ "safetyStock": 100,
+ "expiryDay": null,
+ "isBatchManaged": false,
+ "weight": 0.01,
+ "remark": "",
+ "createTime": "2024-01-01 12:00:00"
+ },
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 浜屻�丒RP 浜у搧椤甸潰淇敼
+
+### 娑夊強椤甸潰
+
+- `/erp/product` - ERP 浜у搧绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+
+### 鍓嶇淇敼鐐�
+
+#### 1. API 璋冪敤鍙樻洿
+
+```js
+// 鍘熻皟鐢� ERP 浜у搧 API
+import { getProductPage, getProduct, createProduct, updateProduct } from '@/api/erp/product'
+
+// 鏀逛负璋冪敤 MDM 鐗╂枡 API锛坕temType=2 浜у搧锛�
+import { getItemPage, getItem, createItem, updateItem } from '@/api/mdm/item'
+```
+
+#### 2. 瀛楁鏄犲皠
+
+| ERP 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 缂栧彿 |
+| barCode | barCode | 鏉$爜锛堝師 ERP 鐢� barCode锛岀幇缁熶竴锛� |
+| barCode | code | 缂栫爜锛堝師鏉$爜鏀逛负缂栫爜锛� |
+| name | name | 鍚嶇О |
+| standard | specification | 瑙勬牸鍨嬪彿 |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| unitId | unitMeasureId | 鍗曚綅缂栧彿 |
+| status | status | 鐘舵�� |
+| purchasePrice | purchasePrice | 閲囪喘浠锋牸 |
+| salePrice | salesPrice | 閿�鍞环鏍� |
+| minPrice | minPrice | 鏈�浣庝环鏍� |
+| expiryDay | expiryDay | 淇濊川鏈熷ぉ鏁� |
+| weight | weight | 閲嶉噺 |
+| remark | remark | 澶囨敞 |
+| - | itemType = 2 | 浜у搧绫诲瀷 |
+
+#### 3. data 鏁版嵁淇敼
+
+```js
+data() {
+ return {
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNo: 1,
+ pageSize: 10,
+ code: '',
+ name: '',
+ categoryId: null,
+ itemType: 2, // 鍥哄畾涓轰骇鍝佺被鍨�
+ status: null
+ }
+ }
+}
+```
+
+#### 4. 鏌ヨ鏂规硶淇敼
+
+```js
+getList() {
+ this.loading = true
+ getItemPage({
+ ...this.queryParams,
+ itemType: 2 // 浜у搧绫诲瀷
+ }).then(response => {
+ this.list = response.data.list
+ this.total = response.data.total
+ this.loading = false
+ })
+}
+```
+
+#### 5. 琛ㄥ崟瀛楁鏄犲皠淇敼
+
+```html
+<!-- 鍘熷瓧娈靛悕 -->
+<el-input v-model="form.barCode" placeholder="浜у搧鏉$爜" />
+
+<!-- 鏀逛负 -->
+<el-input v-model="form.barCode" placeholder="浜у搧鏉$爜" />
+<el-input v-model="form.code" placeholder="浜у搧缂栫爜" />
+```
+
+#### 6. 琛ㄦ牸鍒椾慨鏀�
+
+```html
+<!-- 鍘熻〃鏍煎垪 -->
+<el-table-column prop="barCode" label="浜у搧鏉$爜" />
+<el-table-column prop="standard" label="瑙勬牸" />
+
+<!-- 鏀逛负 -->
+<el-table-column prop="code" label="浜у搧缂栫爜" />
+<el-table-column prop="barCode" label="浜у搧鏉$爜" />
+<el-table-column prop="specification" label="瑙勬牸鍨嬪彿" />
+```
+
+---
+
+## 涓夈�丮ES 鐗╂枡椤甸潰淇敼
+
+### 娑夊強椤甸潰
+
+- `/mes/md/item` - MES 鐗╂枡绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+
+### 鍓嶇淇敼鐐�
+
+#### 1. API 璋冪敤鍙樻洿
+
+```js
+// 鍘熻皟鐢� MES 鐗╂枡 API
+import { getItemPage, getItem, createItem, updateItem } from '@/api/mes/md/item'
+
+// 鏀逛负璋冪敤 MDM 鐗╂枡 API锛坕temType=1 鐗╂枡锛�
+import { getItemPage, getItem, createItem, updateItem } from '@/api/mdm/item'
+```
+
+#### 2. 瀛楁鏄犲皠
+
+| MES 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 鐗╂枡缂栧彿 |
+| code | code | 鐗╂枡缂栫爜 |
+| name | name | 鐗╂枡鍚嶇О |
+| specification | specification | 瑙勬牸鍨嬪彿 |
+| itemTypeId | categoryId | 鍒嗙被缂栧彿 |
+| unitMeasureId | unitMeasureId | 鍗曚綅缂栧彿 |
+| status | status | 鐘舵�� |
+| minStock | minStock | 鏈�浣庡簱瀛� |
+| maxStock | maxStock | 鏈�楂樺簱瀛� |
+| batchFlag | isBatchManaged | 鏄惁鎵规绠$悊 |
+| highValue | highValue | 鏄惁楂樺�肩墿鏂� |
+| safeStockFlag | - | 鏄惁鍚敤瀹夊叏搴撳瓨锛堣绠� safetyStock锛� |
+| remark | remark | 澶囨敞 |
+| - | itemType = 1 | 鐗╂枡绫诲瀷 |
+
+#### 3. MES 鐗规湁鍔熻兘淇濈暀
+
+浠ヤ笅鍔熻兘浠嶇敱 MES 妯″潡绠$悊锛屼笉鍦� MDM 涓細
+
+- **鎵规灞炴�ч厤缃�**锛歚/mes/md/item/batch-config` - 鐗╂枡鎵规灞炴�ч厤缃�
+- **BOM 閰嶇疆**锛歚/mes/md/product-bom` - 浜у搧 BOM 閰嶇疆
+- **SOP 閰嶇疆**锛歚/mes/md/product-sop` - 浜у搧 SOP 閰嶇疆
+- **SIP 閰嶇疆**锛歚/mes/md/product-sip` - 浜у搧 SIP 閰嶇疆
+
+杩欎簺椤甸潰闇�瑕侀�氳繃鐗╂枡 ID 鍏宠仈 MDM 鐗╂枡鏁版嵁锛屾煡璇㈡椂浣跨敤 `list-by-ids` 鎺ュ彛鑾峰彇鐗╂枡鍩虹淇℃伅銆�
+
+#### 4. 鎵规閰嶇疆椤甸潰淇敼
+
+```js
+// 鑾峰彇鐗╂枡淇℃伅鏃朵娇鐢� MDM API
+import { getItemListByIds } from '@/api/mdm/item'
+
+// 鎵规閰嶇疆鍒楄〃椤甸潰
+methods: {
+ // 鑾峰彇鐗╂枡鍚嶇О
+ getItemNames(itemIds) {
+ getItemListByIds(itemIds).then(response => {
+ const itemMap = {}
+ response.data.forEach(item => {
+ itemMap[item.id] = item.name
+ })
+ this.itemMap = itemMap
+ })
+ }
+}
+```
+
+---
+
+## 鍥涖�乄MS 鍟嗗搧/SKU 椤甸潰淇敼
+
+### 娑夊強椤甸潰
+
+- `/wms/item` - WMS 鍟嗗搧绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+- `/wms/item-sku` - WMS SKU 绠$悊锛堜繚鐣欙紝鏁版嵁婧愬彉鏇达級
+
+### 鍓嶇淇敼鐐�
+
+#### 1. API 璋冪敤鍙樻洿
+
+```js
+// 鍘熻皟鐢� WMS 鍟嗗搧 API
+import { getItemPage, getItem } from '@/api/wms/item'
+
+// 鏀逛负璋冪敤 MDM 鐗╂枡 API
+import { getItemPage, getItem } from '@/api/mdm/item'
+
+// SKU 鏁版嵁
+import { getSkuPage, getSku } from '@/api/mdm/item-sku'
+```
+
+#### 2. 瀛楁鏄犲皠
+
+| WMS 鍟嗗搧瀛楁 | MDM 鐗╂枡瀛楁 | 璇存槑 |
+|--------------|--------------|------|
+| id | id | 鍟嗗搧缂栧彿 |
+| code | code | 鍟嗗搧缂栫爜 |
+| name | name | 鍟嗗搧鍚嶇О |
+| unit | - | 鍗曚綅锛堥渶浠� MDM 鍗曚綅琛ㄨ幏鍙栧悕绉帮級 |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| brandId | brandId | 鍝佺墝缂栧彿 |
+| remark | remark | 澶囨敞 |
+
+| WMS SKU 瀛楁 | MDM SKU 瀛楁 | 璇存槑 |
+|--------------|--------------|------|
+| id | id | SKU 缂栧彿 |
+| itemId | itemId | 鍟嗗搧缂栧彿 |
+| code | code | SKU 缂栫爜 |
+| name | name | SKU 鍚嶇О |
+| barCode | barCode | SKU 鏉$爜 |
+| length | length | 闀垮害锛坈m锛� |
+| width | width | 瀹藉害锛坈m锛� |
+| height | height | 楂樺害锛坈m锛� |
+| grossWeight | grossWeight | 姣涢噸锛坘g锛� |
+| netWeight | netWeight | 鍑�閲嶏紙kg锛� |
+| costPrice | costPrice | 鎴愭湰浠� |
+| sellingPrice | salePrice | 閿�鍞环 |
+
+#### 3. 鍗曚綅鍚嶇О鑾峰彇
+
+```js
+// WMS 鍟嗗搧鍘熸潵 unit 鏄瓧绗︿覆锛岀幇鍦ㄩ渶瑕佷粠 MDM 鍗曚綅鑾峰彇
+import { getUnitListByIds } from '@/api/mdm/unit'
+
+methods: {
+ // 鑾峰彇鍗曚綅鍚嶇О
+ loadUnitNames() {
+ const unitIds = this.list.map(item => item.unitMeasureId).filter(id => id)
+ if (unitIds.length > 0) {
+ getUnitListByIds(unitIds.join(',')).then(response => {
+ const unitMap = {}
+ response.data.forEach(unit => {
+ unitMap[unit.id] = unit.name
+ })
+ this.unitMap = unitMap
+ })
+ }
+ }
+}
+```
+
+---
+
+## 浜斻�佹柊澧炲墠绔� API 鏂囦欢
+
+### 鏂囦欢璺緞锛歚src/api/mdm/item.js`
+
+```js
+import request from '@/utils/request'
+
+// 鍒涘缓鐗╂枡
+export function createItem(data) {
+ return request({
+ url: '/mdm/item/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 鏇存柊鐗╂枡
+export function updateItem(data) {
+ return request({
+ url: '/mdm/item/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鏇存柊鐗╂枡鐘舵��
+export function updateItemStatus(id, status) {
+ return request({
+ url: '/mdm/item/update-status?id=' + id + '&status=' + status,
+ method: 'put'
+ })
+}
+
+// 鍒犻櫎鐗╂枡
+export function deleteItem(id) {
+ return request({
+ url: '/mdm/item/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 鑾峰彇鐗╂枡
+export function getItem(id) {
+ return request({
+ url: '/mdm/item/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 鑾峰彇鐗╂枡鍒嗛〉
+export function getItemPage(params) {
+ return request({
+ url: '/mdm/item/page',
+ method: 'get',
+ params: params
+ })
+}
+
+// 鑾峰彇鐗╂枡鍒楄〃
+export function getItemListByIds(ids) {
+ return request({
+ url: '/mdm/item/list-by-ids?ids=' + ids,
+ method: 'get'
+ })
+}
+```
+
+### 鏂囦欢璺緞锛歚src/api/mdm/item-sku.js`
+
+```js
+import request from '@/utils/request'
+
+// 鍒涘缓 SKU
+export function createSku(data) {
+ return request({
+ url: '/mdm/item-sku/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 鏇存柊 SKU
+export function updateSku(data) {
+ return request({
+ url: '/mdm/item-sku/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鍒犻櫎 SKU
+export function deleteSku(id) {
+ return request({
+ url: '/mdm/item-sku/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 鑾峰彇 SKU
+export function getSku(id) {
+ return request({
+ url: '/mdm/item-sku/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 鑾峰彇 SKU 鍒嗛〉
+export function getSkuPage(params) {
+ return request({
+ url: '/mdm/item-sku/page',
+ method: 'get',
+ params: params
+ })
+}
+
+// 鑾峰彇鐗╂枡鐨� SKU 鍒楄〃
+export function getSkuListByItemId(itemId) {
+ return request({
+ url: '/mdm/item-sku/list-by-item-id?itemId=' + itemId,
+ method: 'get'
+ })
+}
+```
+
+---
+
+## 鍏�侀�夋嫨鍣ㄧ粍浠朵慨鏀�
+
+### 鐗╂枡閫夋嫨鍣ㄧ粍浠�
+
+鍚勬ā鍧椾娇鐢ㄧ墿鏂欓�夋嫨鍣ㄦ椂锛岄渶瑕佷紶鍏� `itemType` 鍙傛暟锛�
+
+```vue
+<!-- ERP 浜у搧閫夋嫨鍣� -->
+<mdm-item-selector :item-type="2" @select="handleProductSelect" />
+
+<!-- MES 鐗╂枡閫夋嫨鍣� -->
+<mdm-item-selector :item-type="1" @select="handleItemSelect" />
+
+<!-- WMS 鍟嗗搧閫夋嫨鍣� -->
+<mdm-item-selector :item-type="1" @select="handleItemSelect" />
+```
+
+### 缁勪欢瀹炵幇绀轰緥
+
+```vue
+<template>
+ <el-dialog title="閫夋嫨鐗╂枡" :visible.sync="visible" width="800px">
+ <el-table :data="list" @row-click="handleRowClick">
+ <el-table-column prop="code" label="缂栫爜" width="120" />
+ <el-table-column prop="name" label="鍚嶇О" width="180" />
+ <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" width="120" />
+ <el-table-column prop="categoryName" label="鍒嗙被" width="120" />
+ <el-table-column prop="unitMeasureName" label="鍗曚綅" width="80" />
+ </el-table>
+ <pagination :total="total" :page.sync="queryParams.pageNo" @pagination="getList" />
+ </el-dialog>
+</template>
+
+<script>
+import { getItemPage } from '@/api/mdm/item'
+
+export default {
+ name: 'MdmItemSelector',
+ props: {
+ itemType: {
+ type: Number,
+ default: null
+ }
+ },
+ data() {
+ return {
+ visible: false,
+ list: [],
+ total: 0,
+ queryParams: {
+ pageNo: 1,
+ pageSize: 10
+ }
+ }
+ },
+ methods: {
+ show() {
+ this.visible = true
+ this.getList()
+ },
+ getList() {
+ getItemPage({
+ ...this.queryParams,
+ itemType: this.itemType,
+ status: 0 // 鍙�夋嫨鍚敤鐨�
+ }).then(response => {
+ this.list = response.data.list
+ this.total = response.data.total
+ })
+ },
+ handleRowClick(row) {
+ this.$emit('select', row)
+ this.visible = false
+ }
+ }
+}
+</script>
+```
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **鐗╂枡绫诲瀷鍖哄垎**锛�
+ - `itemType=1`锛氱墿鏂欙紙MES 浣跨敤锛�
+ - `itemType=2`锛氫骇鍝侊紙ERP 浣跨敤锛�
+ - `itemType=3`锛氬崐鎴愬搧
+
+2. **鍒犻櫎鎿嶄綔**锛歁DM 鐗╂枡鍒犻櫎瀹為檯涓虹鐢ㄦ搷浣滐紝涓嶅仛鐗╃悊鍒犻櫎
+
+3. **SKU 妯″紡**锛氭墍鏈夌墿鏂欏繀椤婚厤缃� SKU锛孍RP/MES 鏁版嵁杩佺Щ鏃惰嚜鍔ㄥ垱寤洪粯璁� SKU
+
+4. **鍚戝悗鍏煎**锛氬師妯″潡鐨� API 鎺ュ彛淇濈暀锛屽唴閮ㄨ皟鐢� MDM API
+
+5. **鏁版嵁鍏宠仈**锛�
+ - MES 鎵规閰嶇疆銆丅OM 绛夐�氳繃鐗╂枡 ID 鍏宠仈
+ - WMS 搴撳瓨銆佸嚭鍏ュ簱绛夐�氳繃 SKU ID 鍏宠仈
+
+6. **鏉冮檺閰嶇疆**锛氶渶瑕佷负鏂伴〉闈㈤厤缃彍鍗曞拰鏉冮檺
\ No newline at end of file
diff --git a/docs/mdm_migration_plan.md b/docs/mdm_migration_plan.md
new file mode 100644
index 0000000..64bf1cf
--- /dev/null
+++ b/docs/mdm_migration_plan.md
@@ -0,0 +1,260 @@
+# MDM 涓绘暟鎹ā鍧楁敼閫犺鍒�
+
+## 涓�銆佹敼閫犺儗鏅�
+
+褰撳墠 ERP銆丮ES銆乄MS 涓変釜妯″潡鍚勮嚜缁存姢浜у搧/鐗╂枡鏁版嵁锛屽瓨鍦ㄦ暟鎹宀涢棶棰橈細
+- **ERP**锛氱淮鎶や骇鍝佹暟鎹紙erp_product锛夈�佷骇鍝佸垎绫伙紙erp_product_category锛夈�佽閲忓崟浣嶏紙erp_product_unit锛�
+- **MES**锛氱淮鎶ょ墿鏂欐暟鎹紙mes_md_item锛夈�佺墿鏂欏垎绫伙紙mes_md_item_type锛夈�佽閲忓崟浣嶏紙mes_md_unit_measure锛�
+- **WMS**锛氱淮鎶ゅ晢鍝佹暟鎹紙wms_item锛夈�佸晢鍝佸垎绫伙紙wms_item_category锛夈�佸搧鐗岋紙wms_item_brand锛夈�丼KU锛坵ms_item_sku锛�
+
+鏀归�犵洰鏍囷細灏嗗熀纭�浜у搧鏁版嵁缁熶竴鍒� MDM 妯″潡绠$悊锛屼笁涓ā鍧楅�氳繃 API 璋冪敤鑾峰彇鏁版嵁銆�
+
+---
+
+## 浜屻�佹敼閫犻樁娈佃鍒�
+
+### 绗竴闃舵锛氬熀纭�鏁版嵁杩佺Щ锛堥璁� 1 澶╋級
+
+**鐩爣**锛氬畬鎴愬垎绫汇�佸崟浣嶃�佸搧鐗岀瓑鍩虹鏁版嵁鐨勮縼绉�
+
+| 搴忓彿 | 浠诲姟 | 妯″潡 | 浼樺厛绾� |
+|------|------|------|--------|
+| 1.1 | 杩佺Щ ERP 浜у搧鍒嗙被鍒� MDM 鐗╂枡鍒嗙被 | ERP | P0 |
+| 1.2 | 杩佺Щ MES 鐗╂枡鍒嗙被鍒� MDM 鐗╂枡鍒嗙被 | MES | P0 |
+| 1.3 | 杩佺Щ WMS 鍟嗗搧鍒嗙被鍒� MDM 鐗╂枡鍒嗙被 | WMS | P0 |
+| 1.4 | 杩佺Щ ERP 璁¢噺鍗曚綅鍒� MDM 璁¢噺鍗曚綅 | ERP | P0 |
+| 1.5 | 杩佺Щ MES 璁¢噺鍗曚綅鍒� MDM 璁¢噺鍗曚綅 | MES | P0 |
+| 1.6 | 杩佺Щ WMS 鍟嗗搧鍝佺墝鍒� MDM 鍝佺墝 | WMS | P0 |
+
+**浜у嚭鐗�**锛�
+- SQL 鏁版嵁杩佺Щ鑴氭湰
+- 鍓嶇鍙樻洿鏂囨。
+
+---
+
+### 绗簩闃舵锛氱墿鏂欐暟鎹縼绉伙紙棰勮 1 澶╋級
+
+**鐩爣**锛氬畬鎴愮墿鏂�/浜у搧涓绘暟鎹縼绉�
+
+| 搴忓彿 | 浠诲姟 | 妯″潡 | 浼樺厛绾� |
+|------|------|------|--------|
+| 2.1 | 杩佺Щ MES 鐗╂枡鍒� MDM锛坕temType=1 鐗╂枡锛� | MES | P0 |
+| 2.2 | 杩佺Щ ERP 浜у搧鍒� MDM锛坕temType=2 浜у搧锛� | ERP | P0 |
+| 2.3 | 杩佺Щ WMS 鍟嗗搧鍒� MDM | WMS | P0 |
+| 2.4 | 鍒涘缓鐗╂枡 SKU 鏁版嵁 | MDM | P0 |
+
+**浜у嚭鐗�**锛�
+- SQL 鏁版嵁杩佺Щ鑴氭湰
+- 鍓嶇鍙樻洿鏂囨。
+
+---
+
+### 绗笁闃舵锛氭ā鍧楅�傞厤鏀归�狅紙棰勮 4 澶╋級
+
+**鐩爣**锛欵RP/MES/WMS 妯″潡閫傞厤 MDM 鏁版嵁婧�
+
+| 搴忓彿 | 浠诲姟 | 妯″潡 | 浼樺厛绾� |
+|------|------|------|--------|
+| 3.1 | ERP 妯″潡寮曠敤 MDM API锛屼慨鏀� Service 灞� | ERP | P0 |
+| 3.2 | ERP 妯″潡淇敼 Controller 灞傚搷搴旂粨鏋� | ERP | P1 |
+| 3.3 | MES 妯″潡寮曠敤 MDM API锛屼慨鏀� Service 灞� | MES | P0 |
+| 3.4 | MES 妯″潡淇敼 Controller 灞傚搷搴旂粨鏋� | MES | P1 |
+| 3.5 | WMS 妯″潡寮曠敤 MDM API锛屼慨鏀� Service 灞� | WMS | P0 |
+| 3.6 | WMS 妯″潡淇敼 Controller 灞傚搷搴旂粨鏋� | WMS | P1 |
+
+**浜у嚭鐗�**锛�
+- 鍚庣浠g爜鏀归��
+- 鍓嶇鍙樻洿鏂囨。
+
+---
+
+### 绗洓闃舵锛氬墠绔�傞厤锛堥璁� 3 澶╋級
+
+**鐩爣**锛氬墠绔〉闈㈤�傞厤鏂版暟鎹粨鏋�
+
+| 搴忓彿 | 浠诲姟 | 妯″潡 | 浼樺厛绾� |
+|------|------|------|--------|
+| 4.1 | ERP 浜у搧绠$悊椤甸潰閫傞厤 | ERP | P1 |
+| 4.2 | MES 鐗╂枡绠$悊椤甸潰閫傞厤 | MES | P1 |
+| 4.3 | WMS 鍟嗗搧绠$悊椤甸潰閫傞厤 | WMS | P1 |
+| 4.4 | 鍚勬ā鍧楅�夋嫨鍣ㄧ粍浠堕�傞厤 | 鍏ㄩ儴 | P1 |
+
+**浜у嚭鐗�**锛�
+- 鍓嶇浠g爜鏀归��
+- 鍓嶇鍙樻洿鏂囨。
+
+---
+
+## 涓夈�佹暟鎹瓧娈垫槧灏�
+
+### 3.1 ERP 浜у搧 鈫� MDM 鐗╂枡
+
+| ERP 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 浜у搧缂栧彿 |
+| name | name | 浜у搧鍚嶇О |
+| barCode | barCode | 浜у搧鏉$爜 |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| unitId | unitMeasureId | 鍗曚綅缂栧彿 |
+| standard | specification | 瑙勬牸鍨嬪彿 |
+| status | status | 鐘舵�� |
+| purchasePrice | purchasePrice | 閲囪喘浠锋牸 |
+| salePrice | salesPrice | 閿�鍞环鏍� |
+| minPrice | minPrice | 鏈�浣庝环鏍� |
+| expiryDay | expiryDay | 淇濊川鏈熷ぉ鏁� |
+| weight | weight | 閲嶉噺 |
+| remark | remark | 澶囨敞 |
+| - | itemType = 2 | 浜у搧绫诲瀷 |
+
+### 3.2 MES 鐗╂枡 鈫� MDM 鐗╂枡
+
+| MES 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 鐗╂枡缂栧彿 |
+| code | code | 鐗╂枡缂栫爜 |
+| name | name | 鐗╂枡鍚嶇О |
+| specification | specification | 瑙勬牸鍨嬪彿 |
+| itemTypeId | categoryId | 鍒嗙被缂栧彿 |
+| unitMeasureId | unitMeasureId | 鍗曚綅缂栧彿 |
+| status | status | 鐘舵�� |
+| minStock | minStock | 鏈�浣庡簱瀛� |
+| maxStock | maxStock | 鏈�楂樺簱瀛� |
+| batchFlag | isBatchManaged | 鏄惁鎵规绠$悊 |
+| highValue | highValue | 鏄惁楂樺�肩墿鏂� |
+| safeStockFlag | - | 鏄惁鍚敤瀹夊叏搴撳瓨锛堣浆涓� safetyStock 璁$畻锛� |
+| remark | remark | 澶囨敞 |
+| - | itemType = 1 | 鐗╂枡绫诲瀷 |
+
+### 3.3 WMS 鍟嗗搧 鈫� MDM 鐗╂枡
+
+| WMS 瀛楁 | MDM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 鍟嗗搧缂栧彿 |
+| code | code | 鍟嗗搧缂栫爜 |
+| name | name | 鍟嗗搧鍚嶇О |
+| unit | - | 鍗曚綅锛堥渶鍏宠仈 MDM 鍗曚綅锛� |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| brandId | brandId | 鍝佺墝缂栧彿 |
+| remark | remark | 澶囨敞 |
+| - | itemType = 1 | 榛樿鐗╂枡绫诲瀷 |
+
+### 3.4 WMS SKU 鈫� MDM SKU
+
+| WMS SKU 瀛楁 | MDM SKU 瀛楁 | 璇存槑 |
+|--------------|--------------|------|
+| id | id | SKU 缂栧彿 |
+| itemId | itemId | 鐗╂枡缂栧彿 |
+| code | code | SKU 缂栫爜 |
+| name | name | SKU 鍚嶇О |
+| barCode | barCode | SKU 鏉$爜 |
+| length | length | 闀垮害 |
+| width | width | 瀹藉害 |
+| height | height | 楂樺害 |
+| grossWeight | grossWeight | 姣涢噸 |
+| netWeight | netWeight | 鍑�閲� |
+| costPrice | costPrice | 鎴愭湰浠� |
+| sellingPrice | salePrice | 閿�鍞环 |
+
+---
+
+## 鍥涖�佹敞鎰忎簨椤�
+
+1. **鏁版嵁鍚堝苟绛栫暐**锛氬垎绫汇�佸崟浣嶇瓑鍩虹鏁版嵁鍙兘瀛樺湪閲嶅锛岄渶鎸夌紪鐮佸幓閲嶅悎骞�
+2. **ID 鍏煎**锛氳縼绉诲悗淇濇寔鍘� ID 涓嶅彉锛岄伩鍏嶅叧鑱斿け鏁�
+3. **鍚戝悗鍏煎**锛氬師妯″潡 API 淇濈暀锛屽唴閮ㄨ皟鐢� MDM API 瀹炵幇
+4. **鏁版嵁楠岃瘉**锛氭瘡闃舵瀹屾垚鍚庤繘琛屾暟鎹畬鏁存�ч獙璇�
+
+---
+
+## 浜斻�侀闄╄瘎浼�
+
+| 椋庨櫓椤� | 椋庨櫓绛夌骇 | 搴斿鎺柦 |
+|--------|----------|----------|
+| 鏁版嵁鍚堝苟鍐茬獊 | 涓� | 鍒跺畾鍚堝苟瑙勫垯锛屼汉宸ュ鏍稿啿绐佹暟鎹� |
+| 鍓嶇閫傞厤閬楁紡 | 涓� | 缂栧啓璇︾粏鍙樻洿鏂囨。锛岄�愰〉闈㈡牳瀵� |
+| 鎬ц兘涓嬮檷 | 浣� | MDM API 澧炲姞缂撳瓨鏈哄埗 |
+
+---
+
+## 鍏�佷骇鍑虹墿娓呭崟
+
+| 鏂囦欢 | 璇存槑 | 璺緞 |
+|------|------|------|
+| mdm_migration_plan.md | 鏀归�犺鍒掓�绘枃妗� | docs/ |
+| mdm_migration_01_basic_data.sql | 绗竴闃舵 SQL锛氬熀纭�鏁版嵁杩佺Щ | docs/sql/ |
+| mdm_migration_02_item_data.sql | 绗簩闃舵 SQL锛氱墿鏂欐暟鎹縼绉� | docs/sql/ |
+| mdm_frontend_change_phase1.md | 绗竴闃舵鍓嶇鍙樻洿鏂囨。 | docs/ |
+| mdm_frontend_change_phase2.md | 绗簩闃舵鍓嶇鍙樻洿鏂囨。 | docs/ |
+| mdm_frontend_change.md | MDM API 鎬昏鏂囨。 | docs/ |
+
+---
+
+## 涓冦�佽繘搴﹁窡韪�
+
+| 闃舵 | 寮�濮嬫棩鏈� | 缁撴潫鏃ユ湡 | 鐘舵�� | 瀹屾垚鐜� |
+|------|----------|----------|------|--------|
+| 绗竴闃舵 | SQL鑴氭湰宸插噯澶� | - | 鉁� 宸插畬鎴愶紙闇�鎵嬪姩鎵цSQL锛� | 100% |
+| 绗簩闃舵 | SQL鑴氭湰宸插噯澶� | - | 鉁� 宸插畬鎴愶紙闇�鎵嬪姩鎵цSQL锛� | 100% |
+| 绗笁闃舵 | 鍚庣浠g爜鏀归�犲畬鎴� | - | 鉁� 宸插畬鎴� | 100% |
+| 绗洓闃舵 | - | - | 鉂� 鏈紑濮嬶紙闇�鍓嶇寮�鍙戯級 | 0% |
+
+---
+
+## 鍏�佹敼閫犺鎯�
+
+### 绗竴闃舵瀹屾垚鎯呭喌
+
+| 搴忓彿 | 浠诲姟 | 鐘舵�� |
+|------|------|------|
+| 1.1 | 杩佺Щ ERP 浜у搧鍒嗙被鍒� MDM 鐗╂枡鍒嗙被 | 鉁� SQL鑴氭湰宸插噯澶� |
+| 1.2 | 杩佺Щ MES 鐗╂枡鍒嗙被鍒� MDM 鐗╂枡鍒嗙被 | 鉁� SQL鑴氭湰宸插噯澶� |
+| 1.3 | 杩佺Щ WMS 鍟嗗搧鍒嗙被鍒� MDM 鐗╂枡鍒嗙被 | 鉁� SQL鑴氭湰宸插噯澶� |
+| 1.4 | 杩佺Щ ERP 璁¢噺鍗曚綅鍒� MDM 璁¢噺鍗曚綅 | 鉁� SQL鑴氭湰宸插噯澶� |
+| 1.5 | 杩佺Щ MES 璁¢噺鍗曚綅鍒� MDM 璁¢噺鍗曚綅 | 鉁� SQL鑴氭湰宸插噯澶� |
+| 1.6 | 杩佺Щ WMS 鍟嗗搧鍝佺墝鍒� MDM 鍝佺墝 | 鉁� SQL鑴氭湰宸插噯澶� |
+
+**浜у嚭鐗�**锛歚docs/sql/mdm_migration_01_basic_data.sql`
+
+### 绗簩闃舵瀹屾垚鎯呭喌
+
+| 搴忓彿 | 浠诲姟 | 鐘舵�� |
+|------|------|------|
+| 2.1 | 杩佺Щ MES 鐗╂枡鍒� MDM锛坕temType=1 鐗╂枡锛� | 鉁� SQL鑴氭湰宸插噯澶� |
+| 2.2 | 杩佺Щ ERP 浜у搧鍒� MDM锛坕temType=2 浜у搧锛� | 鉁� SQL鑴氭湰宸插噯澶� |
+| 2.3 | 杩佺Щ WMS 鍟嗗搧鍒� MDM | 鉁� SQL鑴氭湰宸插噯澶� |
+| 2.4 | 鍒涘缓鐗╂枡 SKU 鏁版嵁 | 鉁� SQL鑴氭湰宸插噯澶� |
+
+**浜у嚭鐗�**锛歚docs/sql/mdm_migration_02_item_data.sql`
+
+### 绗笁闃舵瀹屾垚鎯呭喌
+
+| 搴忓彿 | 浠诲姟 | 鐘舵�� |
+|------|------|------|
+| 3.1 | ERP 妯″潡寮曠敤 MDM API锛屼慨鏀� Service 灞� | 鉁� 宸插畬鎴� |
+| 3.2 | ERP 妯″潡淇敼 Controller 灞傚搷搴旂粨鏋� | 鉁� 淇濇寔鍘熺粨鏋勶紝鏃犻渶淇敼 |
+| 3.3 | MES 妯″潡寮曠敤 MDM API锛屼慨鏀� Service 灞� | 鉁� 宸插畬鎴� |
+| 3.4 | MES 妯″潡淇敼 Controller 灞傚搷搴旂粨鏋� | 鉁� 淇濇寔鍘熺粨鏋勶紝鏃犻渶淇敼 |
+| 3.5 | WMS 妯″潡寮曠敤 MDM API锛屼慨鏀� Service 灞� | 鉁� 宸插畬鎴� |
+| 3.6 | WMS 妯″潡淇敼 Controller 灞傚搷搴旂粨鏋� | 鉁� 淇濇寔鍘熺粨鏋勶紝鏃犻渶淇敼 |
+
+**鏀瑰姩鏂囦欢**锛�
+- `yudao-module-erp/pom.xml` - 娣诲姞 MDM 渚濊禆
+- `yudao-module-erp/.../ErpProductServiceImpl.java` - 鏀逛负璋冪敤 MDM API
+- `yudao-module-mes/pom.xml` - 娣诲姞 MDM 渚濊禆
+- `yudao-module-mes/.../MesMdItemServiceImpl.java` - 鏀逛负璋冪敤 MDM API
+- `yudao-module-wms/pom.xml` - 娣诲姞 MDM 渚濊禆
+- `yudao-module-wms/.../WmsItemServiceImpl.java` - 鏀逛负璋冪敤 MDM API
+- `yudao-module-mdm/.../MdmItemRespDTO.java` - 娣诲姞 minStock銆乵axStock 瀛楁
+
+### 绗洓闃舵寰呭姙浜嬮」
+
+| 搴忓彿 | 浠诲姟 | 鐘舵�� | 澶囨敞 |
+|------|------|------|------|
+| 4.1 | ERP 浜у搧绠$悊椤甸潰閫傞厤 | 鉂� 寰呭紑濮� | 闇�鍓嶇寮�鍙� |
+| 4.2 | MES 鐗╂枡绠$悊椤甸潰閫傞厤 | 鉂� 寰呭紑濮� | 闇�鍓嶇寮�鍙� |
+| 4.3 | WMS 鍟嗗搧绠$悊椤甸潰閫傞厤 | 鉂� 寰呭紑濮� | 闇�鍓嶇寮�鍙� |
+| 4.4 | 鍚勬ā鍧楅�夋嫨鍣ㄧ粍浠堕�傞厤 | 鉂� 寰呭紑濮� | 闇�鍓嶇寮�鍙� |
+
+**鍓嶇鍙樻洿鏂囨。**锛�
+- `docs/mdm_frontend_change_phase1.md`
+- `docs/mdm_frontend_change_phase2.md`
\ No newline at end of file
diff --git a/docs/mdm_product_integration_frontend.md b/docs/mdm_product_integration_frontend.md
new file mode 100644
index 0000000..d0305e9
--- /dev/null
+++ b/docs/mdm_product_integration_frontend.md
@@ -0,0 +1,276 @@
+# MDM 浜у搧鏁版嵁涓�浣撳寲鏀归�� - 鍓嶇鏂囨。
+
+## 姒傝堪
+
+鏈鏀归�犲皢 MDM 鐗╂枡浣滀负缁熶竴浜у搧涓绘暟鎹紝CRM 鍜� ERP 妯″潡鐨勪骇鍝佹暟鎹粺涓�浣跨敤 MDM 鐗╂枡锛坕temType=2 浜у搧绫诲瀷锛夈��
+
+---
+
+## 涓�銆佹敼閫犳竻鍗�
+
+### 鍚庣鏀归�狅紙宸插畬鎴愶級
+
+| 鏂囦欢 | 鏀瑰姩璇存槑 |
+|------|----------|
+| `yudao-module-mdm-api/` | MDM 鐗╂枡 API 妯″潡 |
+| `MdmItemApi.java` | 鐗╂枡 API 鎺ュ彛 |
+| `MdmItemRespDTO.java` | 鐗╂枡鍝嶅簲 DTO |
+| `MdmItemCreateReqDTO.java` | 鐗╂枡鍒涘缓璇锋眰 DTO |
+| `MdmItemUpdateReqDTO.java` | 鐗╂枡鏇存柊璇锋眰 DTO |
+| `MdmItemTypeEnum.java` | 鐗╂枡绫诲瀷鏋氫妇 |
+| `ErpProductServiceImpl.java` | 浣跨敤 MDM 鐗╂枡 API |
+| `CrmProductServiceImpl.java` | 浣跨敤 MDM 鐗╂枡 API |
+
+---
+
+## 浜屻�佹秹鍙婇〉闈�
+
+### ERP 浜у搧绠$悊
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 浜у搧鍒楄〃 | `/erp/product/index` | 鏃犻渶鏀瑰姩锛屾帴鍙e吋瀹� |
+| 浜у搧琛ㄥ崟 | `/erp/product/form` | 鏃犻渶鏀瑰姩锛屾帴鍙e吋瀹� |
+
+### CRM 浜у搧绠$悊
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 浜у搧鍒楄〃 | `/crm/product/index` | 鏃犻渶鏀瑰姩锛屾帴鍙e吋瀹� |
+| 浜у搧琛ㄥ崟 | `/crm/product/form` | 鏃犻渶鏀瑰姩锛屾帴鍙e吋瀹� |
+
+### MDM 鐗╂枡绠$悊
+
+| 椤甸潰 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 鐗╂枡鍒楄〃 | `/mdm/item/index` | 缁熶竴浜у搧涓绘暟鎹叆鍙� |
+| 鐗╂枡琛ㄥ崟 | `/mdm/item/form` | 鍒涘缓/缂栬緫鐗╂枡 |
+
+---
+
+## 涓夈�佹暟鎹ā鍨嬫槧灏�
+
+### MDM 鐗╂枡绫诲瀷
+
+| 绫诲瀷鍊� | 璇存槑 | 鐢ㄩ�� |
+|--------|------|------|
+| 1 | 鐗╂枡 | 鍘熸潗鏂欍�佽緟鏂欑瓑 |
+| 2 | 浜у搧 | 閿�鍞骇鍝侊紙CRM銆丒RP 浣跨敤锛� |
+| 3 | 鍗婃垚鍝� | 鐢熶骇涓棿浠� |
+
+### 瀛楁鏄犲皠锛圡DM -> ERP锛�
+
+| MDM 瀛楁 | ERP 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 缂栧彿 |
+| name | name | 鍚嶇О |
+| barCode | barCode | 鏉$爜 |
+| specification | standard | 瑙勬牸 |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| unitMeasureId | unitId | 鍗曚綅缂栧彿 |
+| status | status | 鐘舵�� |
+| purchasePrice | purchasePrice | 閲囪喘浠锋牸 |
+| salesPrice | salePrice | 閿�鍞环鏍� |
+| costPrice | minPrice | 鎴愭湰浠� -> 鏈�浣庝环 |
+| expiryDay | expiryDay | 淇濊川鏈� |
+| weight | weight | 閲嶉噺 |
+| remark | remark | 澶囨敞 |
+
+### 瀛楁鏄犲皠锛圡DM -> CRM锛�
+
+| MDM 瀛楁 | CRM 瀛楁 | 璇存槑 |
+|----------|----------|------|
+| id | id | 缂栧彿 |
+| code | no | 缂栫爜 |
+| name | name | 鍚嶇О |
+| unitMeasureId | unit | 鍗曚綅 |
+| salesPrice | price | 浠锋牸 |
+| status | status | 鐘舵�� |
+| categoryId | categoryId | 鍒嗙被缂栧彿 |
+| remark | description | 鎻忚堪 |
+
+---
+
+## 鍥涖�丄PI 鎺ュ彛
+
+### MDM 鐗╂枡 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /rpc-api/mdm/item/create | 鍒涘缓鐗╂枡 |
+| PUT | /rpc-api/mdm/item/update | 鏇存柊鐗╂枡 |
+| GET | /rpc-api/mdm/item/get | 鑾峰彇鐗╂枡 |
+| POST | /rpc-api/mdm/item/list | 鑾峰彇鐗╂枡鍒楄〃 |
+| POST | /rpc-api/mdm/item/page | 鑾峰彇鐗╂枡鍒嗛〉 |
+| GET | /rpc-api/mdm/item/validate-exists | 鏍¢獙鐗╂枡瀛樺湪 |
+| GET | /rpc-api/mdm/item/get-by-code | 鏍规嵁缂栫爜鑾峰彇鐗╂枡 |
+| GET | /rpc-api/mdm/item/get-by-bar-code | 鏍规嵁鏉$爜鑾峰彇鐗╂枡 |
+
+### ERP 浜у搧 API锛堜繚鎸佷笉鍙橈級
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/product/create | 鍒涘缓浜у搧 |
+| PUT | /erp/product/update | 鏇存柊浜у搧 |
+| DELETE | /erp/product/delete | 鍒犻櫎浜у搧 |
+| GET | /erp/product/get | 鑾峰彇浜у搧 |
+| GET | /erp/product/page | 鑾峰彇浜у搧鍒嗛〉 |
+| GET | /erp/product/simple-list | 鑾峰彇浜у搧绮剧畝鍒楄〃 |
+
+### CRM 浜у搧 API锛堜繚鎸佷笉鍙橈級
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /crm/product/create | 鍒涘缓浜у搧 |
+| PUT | /crm/product/update | 鏇存柊浜у搧 |
+| DELETE | /crm/product/delete | 鍒犻櫎浜у搧 |
+| GET | /crm/product/get | 鑾峰彇浜у搧 |
+| GET | /crm/product/page | 鑾峰彇浜у搧鍒嗛〉 |
+| GET | /crm/product/simple-list | 鑾峰彇浜у搧绮剧畝鍒楄〃 |
+
+---
+
+## 浜斻�佸墠绔慨鏀圭偣
+
+### 5.1 浜у搧閫夋嫨缁勪欢锛堝彲閫変紭鍖栵級
+
+濡傛灉闇�瑕佺粺涓�浜у搧閫夋嫨浣撻獙锛屽彲浠ュ垱寤� MDM 鐗╂枡閫夋嫨缁勪欢锛�
+
+```html
+<!-- MdmItemSelect.vue -->
+<template>
+ <el-select v-model="selectedValue" placeholder="璇烽�夋嫨浜у搧" filterable>
+ <el-option
+ v-for="item in itemList"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id"
+ >
+ <span>{{ item.name }}</span>
+ <span style="color: #999; margin-left: 8px;">{{ item.barCode }}</span>
+ </el-option>
+ </el-select>
+</template>
+
+<script>
+import { getItemList } from '@/api/mdm/item'
+
+export default {
+ props: {
+ value: [Number, String],
+ itemType: {
+ type: Number,
+ default: 2 // 榛樿浜у搧绫诲瀷
+ }
+ },
+ data() {
+ return {
+ itemList: []
+ }
+ },
+ computed: {
+ selectedValue: {
+ get() {
+ return this.value
+ },
+ set(val) {
+ this.$emit('input', val)
+ }
+ }
+ },
+ created() {
+ this.loadItemList()
+ },
+ methods: {
+ async loadItemList() {
+ const { data } = await getItemList({ itemType: this.itemType, status: 0 })
+ this.itemList = data
+ }
+ }
+}
+</script>
+```
+
+### 5.2 浣跨敤鏂瑰紡
+
+```html
+<!-- ERP 閿�鍞鍗曚腑閫夋嫨浜у搧 -->
+<MdmItemSelect v-model="form.productId" :item-type="2" />
+
+<!-- 閲囪喘璁㈠崟涓�夋嫨鐗╂枡 -->
+<MdmItemSelect v-model="form.itemId" :item-type="1" />
+```
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+### 6.1 鏁版嵁杩佺Щ
+
+濡傛灉宸叉湁 ERP 鎴� CRM 浜у搧鏁版嵁锛岄渶瑕佽縼绉诲埌 MDM 鐗╂枡琛細
+
+```sql
+-- 杩佺Щ ERP 浜у搧鍒� MDM 鐗╂枡锛堢ず渚嬶級
+INSERT INTO mdm_item (id, code, name, bar_code, specification, category_id, unit_measure_id,
+ item_type, status, purchase_price, sales_price, cost_price, expiry_day, weight, remark,
+ creator, create_time, updater, update_time, deleted, tenant_id)
+SELECT
+ id,
+ bar_code AS code,
+ name,
+ bar_code,
+ standard AS specification,
+ category_id,
+ unit_id AS unit_measure_id,
+ 2 AS item_type, -- 浜у搧绫诲瀷
+ status,
+ purchase_price,
+ sale_price AS sales_price,
+ min_price AS cost_price,
+ expiry_day,
+ weight,
+ remark,
+ creator,
+ create_time,
+ updater,
+ update_time,
+ deleted,
+ tenant_id
+FROM erp_product
+WHERE deleted = 0;
+
+-- 杩佺Щ CRM 浜у搧鍒� MDM 鐗╂枡锛堥渶瑕佸鐞� ID 鍐茬獊锛�
+-- 娉ㄦ剰锛氬鏋� ID 涓� ERP 浜у搧鍐茬獊锛岄渶瑕侀噸鏂板垎閰� ID 骞舵洿鏂板叧鑱旀暟鎹�
+```
+
+### 6.2 鎺ュ彛鍏煎鎬�
+
+- ERP 鍜� CRM 鐨勪骇鍝佹帴鍙d繚鎸佷笉鍙橈紝鍓嶇鏃犻渶淇敼
+- 鏁版嵁瀹為檯浠� MDM 鑾峰彇锛屽鍓嶇閫忔槑
+- 鍒涘缓/鏇存柊鎿嶄綔閫氳繃 MDM API 鎵ц
+
+### 6.3 鏉冮檺绠$悊
+
+- CRM 浜у搧淇濈暀鏈湴璁板綍鐢ㄤ簬鏉冮檺绠$悊
+- 浜у搧 ID 涓� MDM 鐗╂枡 ID 淇濇寔涓�鑷�
+- 鏉冮檺楠岃瘉浠嶇劧閫氳繃 CRM 鏉冮檺绯荤粺
+
+### 6.4 搴熷純璇存槑
+
+- `erp_product` 琛細淇濈暀浣嗕笉鍐嶄綔涓轰富鏁版嵁婧�
+- `crm_product` 琛細淇濈暀鐢ㄤ簬鏉冮檺绠$悊锛屾暟鎹悓姝ヨ嚜 MDM
+- 寤鸿鍚庣画鐗堟湰鍦ㄥ墠绔彍鍗曚腑缁熶竴浣跨敤 MDM 鐗╂枡绠$悊
+
+---
+
+## 涓冦�佸悗缁鍒�
+
+| Phase | 鍐呭 | 鐘舵�� |
+|-------|------|------|
+| Phase 1 | 瀹㈡埛鏁版嵁缁熶竴 - ERP 浣跨敤 CRM 瀹㈡埛 | **宸插畬鎴�** |
+| Phase 2 | 浜у搧鏁版嵁缁熶竴 - CRM/ERP 浣跨敤 MDM 鐗╂枡 | **宸插畬鎴�** |
+| Phase 3 | 鍚堝悓鐢熸垚閿�鍞鍗� | 寰呭紑濮� |
+| Phase 4 | 閿�鍞叏娴佺▼杩芥函 | 寰呭紑濮� |
+| Phase 5 | 钀ラ攢鏁版嵁鎶ヨ〃 | 寰呭紑濮� |
+| Phase 6 | 鏁版嵁杩佺Щ涓庢竻鐞� | 寰呭紑濮� |
+
diff --git a/docs/sales_traceability_frontend.md b/docs/sales_traceability_frontend.md
new file mode 100644
index 0000000..4913aba
--- /dev/null
+++ b/docs/sales_traceability_frontend.md
@@ -0,0 +1,349 @@
+# CRM 閿�鍞叏娴佺▼杩芥函 - 鍓嶇鏂囨。
+
+## 姒傝堪
+
+鏈鏀归�犲疄鐜伴攢鍞叏娴佺▼杩芥函鍔熻兘锛屽彲浠庡晢鏈恒�佸悎鍚屻�佸鎴风瓑澶氫釜缁村害鏌ョ湅瀹屾暣鐨勯攢鍞祦绋嬩俊鎭紝鍖呮嫭鍟嗘満 -> 鍚堝悓 -> 閿�鍞鍗� -> 鍑哄簱 -> 鍥炴 -> 閫�璐х殑鍏ㄩ摼璺暟鎹��
+
+---
+
+## 涓�銆佹敼閫犳竻鍗�
+
+### 鍚庣鏀归�狅紙宸插畬鎴愶級
+
+| 鏂囦欢 | 鏀瑰姩璇存槑 |
+|------|----------|
+| `CrmSaleTraceApi.java` | 閿�鍞拷婧� API 鎺ュ彛 |
+| `CrmSaleTraceRespDTO.java` | 閿�鍞拷婧搷搴� DTO |
+| `CrmSaleTraceApiImpl.java` | 閿�鍞拷婧� API 瀹炵幇 |
+| `CrmBusinessDO.java` | 鏂板 orderId銆乷rderNo 瀛楁 |
+| `CrmBusinessRespVO.java` | 鏂板 orderId銆乷rderNo 瀛楁 |
+| `CrmContractMapper.java` | 鏂板 selectByBusinessId銆乻electListByCustomerId 鏂规硶 |
+| `CrmReceivableService.java` | 鏂板 getReceivablePriceByContractId 鏂规硶 |
+
+---
+
+## 浜屻�佹秹鍙婇〉闈�
+
+### CRM 妯″潡
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 鍟嗘満璇︽儏 | `/crm/business/detail` | 娣诲姞閿�鍞祦绋嬭拷婧叆鍙� |
+| 鍚堝悓璇︽儏 | `/crm/contract/detail` | 娣诲姞閿�鍞祦绋嬭拷婧叆鍙� |
+| 瀹㈡埛璇︽儏 | `/crm/customer/detail` | 娣诲姞閿�鍞祦绋嬭拷婧垪琛� |
+
+### 鏂板椤甸潰
+
+| 椤甸潰 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 閿�鍞拷婧鎯� | `/crm/sale-trace/detail` | 閿�鍞祦绋嬭拷婧鎯呴〉 |
+
+---
+
+## 涓夈�丄PI 鎺ュ彛
+
+### CRM 閿�鍞拷婧� API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /rpc-api/crm/sale-trace/get-by-business-id | 鏍规嵁鍟嗘満缂栧彿鑾峰彇杩芥函淇℃伅 |
+| GET | /rpc-api/crm/sale-trace/get-by-contract-id | 鏍规嵁鍚堝悓缂栧彿鑾峰彇杩芥函淇℃伅 |
+| GET | /rpc-api/crm/sale-trace/get-list-by-customer-id | 鏍规嵁瀹㈡埛缂栧彿鑾峰彇杩芥函鍒楄〃 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "businessId": 1,
+ "businessName": "鍟嗘満A",
+ "businessPrice": 100000,
+ "businessStatus": "杩涜涓�",
+ "businessCreateTime": "2024-01-01 10:00:00",
+ "customerId": 1,
+ "customerName": "瀹㈡埛A",
+ "contractId": 1,
+ "contractName": "鍚堝悓A",
+ "contractPrice": 95000,
+ "contractStatus": 20,
+ "contractCreateTime": "2024-01-15 10:00:00",
+ "orderId": 1,
+ "orderNo": "SO001",
+ "orderPrice": 95000,
+ "orderStatus": 0,
+ "orderCreateTime": "2024-01-16 10:00:00",
+ "receivablePrice": 50000,
+ "receivableItems": [
+ {
+ "receivableId": 1,
+ "receivableNo": "RK001",
+ "price": 30000,
+ "receivableTime": "2024-02-01"
+ },
+ {
+ "receivableId": 2,
+ "receivableNo": "RK002",
+ "price": 20000,
+ "receivableTime": "2024-03-01"
+ }
+ ]
+ }
+}
+```
+
+---
+
+## 鍥涖�佸墠绔慨鏀圭偣
+
+### 4.1 鍟嗘満璇︽儏椤甸潰
+
+**鏂囦欢璺緞锛�** `/src/views/crm/business/detail.vue`
+
+**鏂板缁勪欢锛�**
+
+```html
+<!-- 閿�鍞祦绋嬭拷婧崱鐗� -->
+<el-card class="sale-trace-card" header="閿�鍞祦绋嬭拷婧�">
+ <div v-if="traceInfo">
+ <!-- 娴佺▼鏃堕棿绾� -->
+ <el-timeline>
+ <el-timeline-item
+ :timestamp="traceInfo.businessCreateTime"
+ placement="top"
+ color="#409EFF"
+ >
+ <el-card>
+ <h4>鍟嗘満鍒涘缓</h4>
+ <p>鍟嗘満鍚嶇О锛歿{ traceInfo.businessName }}</p>
+ <p>鍟嗘満閲戦锛毬{ traceInfo.businessPrice }}</p>
+ </el-card>
+ </el-timeline-item>
+
+ <el-timeline-item
+ v-if="traceInfo.contractId"
+ :timestamp="traceInfo.contractCreateTime"
+ placement="top"
+ color="#67C23A"
+ >
+ <el-card>
+ <h4>鍚堝悓绛捐</h4>
+ <p>鍚堝悓鍚嶇О锛歿{ traceInfo.contractName }}</p>
+ <p>鍚堝悓閲戦锛毬{ traceInfo.contractPrice }}</p>
+ <p>鍚堝悓鐘舵�侊細{{ getContractStatusText(traceInfo.contractStatus) }}</p>
+ </el-card>
+ </el-timeline-item>
+
+ <el-timeline-item
+ v-if="traceInfo.orderId"
+ :timestamp="traceInfo.orderCreateTime"
+ placement="top"
+ color="#E6A23C"
+ >
+ <el-card>
+ <h4>閿�鍞鍗�</h4>
+ <p>璁㈠崟鍙凤細<el-link type="primary" @click="viewOrder(traceInfo.orderId)">{{ traceInfo.orderNo }}</el-link></p>
+ <p>璁㈠崟閲戦锛毬{ traceInfo.orderPrice }}</p>
+ <p>璁㈠崟鐘舵�侊細{{ getOrderStatusText(traceInfo.orderStatus) }}</p>
+ </el-card>
+ </el-timeline-item>
+
+ <el-timeline-item
+ v-if="traceInfo.receivablePrice > 0"
+ placement="top"
+ color="#F56C6C"
+ >
+ <el-card>
+ <h4>鍥炴璁板綍</h4>
+ <p>宸插洖娆鹃噾棰濓細楼{{ traceInfo.receivablePrice }}</p>
+ <el-table v-if="traceInfo.receivableItems" :data="traceInfo.receivableItems" size="small">
+ <el-table-column prop="receivableNo" label="鍥炴鍗曞彿" />
+ <el-table-column prop="price" label="鍥炴閲戦" />
+ <el-table-column prop="receivableTime" label="鍥炴鏃堕棿" />
+ </el-table>
+ </el-card>
+ </el-timeline-item>
+ </el-timeline>
+ </div>
+ <div v-else>
+ <el-empty description="鏆傛棤閿�鍞祦绋嬫暟鎹�" />
+ </div>
+</el-card>
+```
+
+**鏂板鏂规硶锛�**
+
+```js
+import { getTraceByBusinessId } from '@/api/crm/saleTrace'
+
+data() {
+ return {
+ traceInfo: null
+ }
+},
+
+methods: {
+ async loadTraceInfo() {
+ const { data } = await getTraceByBusinessId(this.businessId)
+ this.traceInfo = data
+ },
+ getContractStatusText(status) {
+ const statusMap = { 0: '鑽夌', 10: '瀹℃壒涓�', 20: '瀹℃壒閫氳繃', 30: '瀹℃壒涓嶉�氳繃' }
+ return statusMap[status] || '鏈煡'
+ },
+ getOrderStatusText(status) {
+ const statusMap = { 0: '鏈鏍�', 10: '宸插鏍�' }
+ return statusMap[status] || '鏈煡'
+ },
+ viewOrder(orderId) {
+ this.$router.push({ path: '/erp/sale/order/detail', query: { id: orderId } })
+ }
+}
+```
+
+### 4.2 鍚堝悓璇︽儏椤甸潰
+
+**鏂囦欢璺緞锛�** `/src/views/crm/contract/detail.vue`
+
+**鏂板鏂规硶锛�**
+
+```js
+import { getTraceByContractId } from '@/api/crm/saleTrace'
+
+methods: {
+ async loadTraceInfo() {
+ const { data } = await getTraceByContractId(this.contractId)
+ this.traceInfo = data
+ }
+}
+```
+
+### 4.3 瀹㈡埛璇︽儏椤甸潰
+
+**鏂囦欢璺緞锛�** `/src/views/crm/customer/detail.vue`
+
+**鏂板鍒楄〃锛�**
+
+```html
+<el-card header="閿�鍞祦绋嬭拷婧�">
+ <el-table :data="traceList" size="small">
+ <el-table-column prop="businessName" label="鍟嗘満鍚嶇О">
+ <template #default="{ row }">
+ <el-link v-if="row.businessId" type="primary" @click="viewBusiness(row.businessId)">
+ {{ row.businessName }}
+ </el-link>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="contractName" label="鍚堝悓鍚嶇О">
+ <template #default="{ row }">
+ <el-link v-if="row.contractId" type="primary" @click="viewContract(row.contractId)">
+ {{ row.contractName }}
+ </el-link>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="contractPrice" label="鍚堝悓閲戦" width="120" />
+ <el-table-column prop="orderNo" label="閿�鍞鍗�">
+ <template #default="{ row }">
+ <el-link v-if="row.orderId" type="primary" @click="viewOrder(row.orderId)">
+ {{ row.orderNo }}
+ </el-link>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="receivablePrice" label="宸插洖娆�" width="120" />
+ <el-table-column label="鍥炴鐜�" width="100">
+ <template #default="{ row }">
+ <span v-if="row.contractPrice > 0">
+ {{ ((row.receivablePrice / row.contractPrice) * 100).toFixed(1) }}%
+ </span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ </el-table>
+</el-card>
+```
+
+### 4.4 API 瀹氫箟
+
+**鏂囦欢璺緞锛�** `/src/api/crm/saleTrace.js`
+
+```js
+import request from '@/utils/request'
+
+// 鏍规嵁鍟嗘満缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭�
+export function getTraceByBusinessId(businessId) {
+ return request({
+ url: '/crm/sale-trace/get-by-business-id',
+ method: 'get',
+ params: { businessId }
+ })
+}
+
+// 鏍规嵁鍚堝悓缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭�
+export function getTraceByContractId(contractId) {
+ return request({
+ url: '/crm/sale-trace/get-by-contract-id',
+ method: 'get',
+ params: { contractId }
+ })
+}
+
+// 鏍规嵁瀹㈡埛缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧垪琛�
+export function getTraceListByCustomerId(customerId) {
+ return request({
+ url: '/crm/sale-trace/get-list-by-customer-id',
+ method: 'get',
+ params: { customerId }
+ })
+}
+```
+
+---
+
+## 浜斻�佹暟鎹ā鍨嬪彉鏇�
+
+### CRM 鍟嗘満琛ㄥ彉鏇�
+
+```sql
+ALTER TABLE `crm_business`
+ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '鍏宠仈 ERP 閿�鍞鍗曠紪鍙�' AFTER `total_price`,
+ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 閿�鍞鍗曞崟鍙�' AFTER `order_id`;
+```
+
+---
+
+## 鍏�佷笟鍔℃祦绋嬪浘
+
+```
+鍟嗘満鍒涘缓 鈫� 鍟嗘満璺熻繘 鈫� 鍚堝悓绛捐 鈫� 鍚堝悓瀹℃壒 鈫� 鐢熸垚璁㈠崟 鈫� 璁㈠崟瀹℃壒 鈫� 閿�鍞嚭搴� 鈫� 瀹㈡埛鍥炴
+ 鈫� 鈫� 鈫� 鈫� 鈫� 鈫� 鈫� 鈫�
+ 10:00 12:00 15:00 16:00 17:00 18:00 20:00 25:00
+
+ 锛堥噾棰濆彉鍖栬拷韪級
+鍟嗘満閲戦(100K) 鈫� 鍚堝悓閲戦(95K) 鈫� 璁㈠崟閲戦(95K) 鈫� 鍑哄簱閲戦(95K) 鈫� 鍥炴閲戦(50K)
+```
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **鏁版嵁鍏宠仈**锛氳拷婧暟鎹潵婧愪簬澶氫釜妯″潡锛圕RM銆丒RP锛夛紝闇�纭繚鏁版嵁涓�鑷存��
+2. **鏉冮檺鎺у埗**锛氳拷婧煡璇㈤渶瑕佸搴旀ā鍧楃殑鏌ョ湅鏉冮檺
+3. **鏁版嵁瀹屾暣鎬�**锛氶儴鍒嗕笟鍔″彲鑳藉彧鍒版煇涓幆鑺傦紙濡傚彧绛惧悎鍚屾湭鐢熸垚璁㈠崟锛�
+4. **鏃堕棿绾垮睍绀�**锛氭寜鏃堕棿椤哄簭灞曠ず鍚勭幆鑺備俊鎭�
+5. **閲戦姹囨��**锛氬睍绀烘瘡涓幆鑺傜殑閲戦鍙樺寲鎯呭喌
+
+---
+
+## 鍏�佸悗缁鍒�
+
+| Phase | 鍐呭 | 鐘舵�� |
+|-------|------|------|
+| Phase 1 | 瀹㈡埛鏁版嵁缁熶竴 - ERP 浣跨敤 CRM 瀹㈡埛 | **宸插畬鎴�** |
+| Phase 2 | 浜у搧鏁版嵁缁熶竴 - CRM/ERP 浣跨敤 MDM 鐗╂枡 | **宸插畬鎴�** |
+| Phase 3 | 鍚堝悓鐢熸垚閿�鍞鍗� | **宸插畬鎴�** |
+| Phase 4 | 閿�鍞叏娴佺▼杩芥函 | **宸插畬鎴�** |
+| Phase 5 | 钀ラ攢鏁版嵁鎶ヨ〃 | 寰呭紑濮� |
+| Phase 6 | 鏁版嵁杩佺Щ涓庢竻鐞� | 寰呭紑濮� |
\ No newline at end of file
diff --git a/docs/sql/bpm_purchase_approval_init.sql b/docs/sql/bpm_purchase_approval_init.sql
new file mode 100644
index 0000000..5bc5712
--- /dev/null
+++ b/docs/sql/bpm_purchase_approval_init.sql
@@ -0,0 +1,86 @@
+-- =====================================================
+-- BPM 閲囪喘瀹℃牳娴佺▼ - SQL 鍒濆鍖�
+-- =====================================================
+
+-- =====================================================
+-- 01. 鍒涘缓娴佺▼鍒嗙被锛堥噰璐鐞嗭級
+-- =====================================================
+
+INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
+VALUES (100, '閲囪喘瀹℃牳', 'purchase_approve', '閲囪喘璁㈠崟瀹℃壒娴佺▼鍒嗙被', 0, 10, '1', NOW(), '1', NOW(), 0)
+ON DUPLICATE KEY UPDATE name = VALUES(name), code = VALUES(code);
+
+-- =====================================================
+-- 02. 鍒涘缓閲囪喘璁㈠崟瀹℃壒琛ㄥ崟
+-- =====================================================
+
+INSERT INTO bpm_form (id, name, status, conf, fields, remark, creator, create_time, updater, update_time, deleted)
+VALUES (
+ 100,
+ '閲囪喘璁㈠崟瀹℃壒琛ㄥ崟',
+ 0,
+ '{"formRef":"elForm","size":"medium","labelPosition":"right","labelWidth":100,"formRules":"rules","gutter":15,"disabled":false,"span":24,"formBtns":true}',
+ '[
+ {"type":"input","label":"璁㈠崟缂栧彿","placeholder":"璇疯緭鍏ヨ鍗曠紪鍙�","field":"orderNo","defaultValue":"","required":true},
+ {"type":"input","label":"渚涘簲鍟嗗悕绉�","placeholder":"璇疯緭鍏ヤ緵搴斿晢鍚嶇О","field":"supplierName","defaultValue":"","required":true},
+ {"type":"input","label":"閲囪喘閲戦","placeholder":"璇疯緭鍏ラ噰璐噾棰�","field":"amount","defaultValue":"","required":true},
+ {"type":"input","label":"璁㈠崟鏁伴噺","placeholder":"璇疯緭鍏ヨ鍗曟暟閲�","field":"quantity","defaultValue":"","required":true},
+ {"type":"textarea","label":"璁㈠崟鏄庣粏","placeholder":"璇疯緭鍏ヨ鍗曟槑缁�","field":"orderDetail","defaultValue":"","required":false},
+ {"type":"textarea","label":"澶囨敞","placeholder":"璇疯緭鍏ュ娉�","field":"remark","defaultValue":"","required":false}
+ ]',
+ '閲囪喘璁㈠崟瀹℃壒琛ㄥ崟',
+ '1',
+ NOW(),
+ '1',
+ NOW(),
+ 0
+) ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- =====================================================
+-- 03. 璇存槑锛氭祦绋嬫ā鍨嬪拰瀹氫箟闇�瑕侀�氳繃鍓嶇鐣岄潰鍒涘缓
+-- =====================================================
+--
+-- 閲囪喘瀹℃壒娴佺▼寤鸿閰嶇疆锛�
+--
+-- 娴佺▼鍚嶇О锛氶噰璐鍗曞鎵�
+-- 娴佺▼鍒嗙被锛氶噰璐鏍� (code = purchase_approve)
+-- 琛ㄥ崟绫诲瀷锛氬姩鎬佽〃鍗曪紙鍏宠仈涓婇潰鐨勮〃鍗� id=100锛�
+--
+-- 娴佺▼鑺傜偣閰嶇疆锛圫imple 璁捐鍣級锛�
+--
+-- 1. 寮�濮嬭妭鐐�
+-- - 鍙戣捣浜猴細鍏ㄥ憳鍙彂璧�
+--
+-- 2. 涓荤瀹℃壒鑺傜偣
+-- - 瀹℃壒浜猴細鍙戣捣浜虹殑鐩村睘涓荤
+-- - 瀹℃壒绫诲瀷锛氬鎵�
+--
+-- 3. 閲囪喘缁忕悊瀹℃壒鑺傜偣锛堥噾棰� > 5000 鏃讹級
+-- - 鏉′欢鍒嗘敮锛氶噰璐噾棰� > 5000
+-- - 瀹℃壒浜猴細閲囪喘缁忕悊瑙掕壊鎴栨寚瀹氱敤鎴�
+-- - 瀹℃壒绫诲瀷锛氬鎵�
+--
+-- 4. 璐㈠姟瀹℃壒鑺傜偣锛堥噾棰� > 10000 鏃讹級
+-- - 鏉′欢鍒嗘敮锛氶噰璐噾棰� > 10000
+-- - 瀹℃壒浜猴細璐㈠姟缁忕悊瑙掕壊鎴栨寚瀹氱敤鎴�
+-- - 瀹℃壒绫诲瀷锛氬鎵�
+--
+-- 5. 鎬荤粡鐞嗗鎵硅妭鐐癸紙閲戦 > 50000 鏃讹級
+-- - 鏉′欢鍒嗘敮锛氶噰璐噾棰� > 50000
+-- - 瀹℃壒浜猴細鎬荤粡鐞嗚鑹叉垨鎸囧畾鐢ㄦ埛
+-- - 瀹℃壒绫诲瀷锛氬鎵�
+--
+-- 6. 缁撴潫鑺傜偣
+-- - 瀹℃壒閫氳繃鍚庤嚜鍔ㄧ粨鏉�
+--
+-- =====================================================
+
+-- =====================================================
+-- 04. 鏌ヨ楠岃瘉
+-- =====================================================
+
+-- 楠岃瘉鍒嗙被鍒涘缓
+SELECT * FROM bpm_category WHERE code = 'purchase_approve';
+
+-- 楠岃瘉琛ㄥ崟鍒涘缓
+SELECT * FROM bpm_form WHERE name = '閲囪喘璁㈠崟瀹℃壒琛ㄥ崟';
\ No newline at end of file
diff --git a/docs/sql/erp_purchase_enhance.sql b/docs/sql/erp_purchase_enhance.sql
new file mode 100644
index 0000000..9a115d4
--- /dev/null
+++ b/docs/sql/erp_purchase_enhance.sql
@@ -0,0 +1,96 @@
+-- =====================================================
+-- ERP 閲囪喘绠$悊妯″潡瀹屽杽 - SQL 鑴氭湰
+-- =====================================================
+
+-- =====================================================
+-- 00. 鍒涘缓 BPM 閲囪喘瀹℃牳鍒嗙被锛堝繀椤诲湪閲囪喘璁㈠崟鍓嶅垱寤猴級
+-- =====================================================
+INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
+VALUES (100, '閲囪喘瀹℃牳', 'purchase_approve', '閲囪喘璁㈠崟瀹℃壒娴佺▼鍒嗙被', 0, 10, '1', NOW(), '1', NOW(), 0)
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- =====================================================
+-- 01. 閲囪喘鍏ュ簱琛ㄥ鍔犻獙鏀跺瓧娈�
+-- =====================================================
+ALTER TABLE `erp_purchase_in` ADD COLUMN IF NOT EXISTS `inspection_status` tinyint DEFAULT 0 COMMENT '楠屾敹鐘舵�侊紙0:寰呴獙鏀� 1:宸查獙鏀� 2:楠屾敹涓嶅悎鏍硷級';
+ALTER TABLE `erp_purchase_in` ADD COLUMN IF NOT EXISTS `inspection_time` datetime DEFAULT NULL COMMENT '楠屾敹鏃堕棿';
+ALTER TABLE `erp_purchase_in` ADD COLUMN IF NOT EXISTS `inspector_id` bigint DEFAULT NULL COMMENT '楠屾敹浜哄憳缂栧彿';
+ALTER TABLE `erp_purchase_in` ADD COLUMN IF NOT EXISTS `inspection_result` varchar(500) DEFAULT NULL COMMENT '楠屾敹缁撴灉';
+
+-- =====================================================
+-- 02. 渚涘簲鍟嗚〃澧炲姞瀛楁
+-- =====================================================
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `credit_code` varchar(50) DEFAULT NULL COMMENT '缁熶竴绀句細淇$敤浠g爜';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `bank_name` varchar(100) DEFAULT NULL COMMENT '寮�鎴烽摱琛�';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `bank_account` varchar(50) DEFAULT NULL COMMENT '閾惰璐﹀彿';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `address` varchar(200) DEFAULT NULL COMMENT '鍦板潃';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `contact` varchar(50) DEFAULT NULL COMMENT '鑱旂郴浜�';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `phone` varchar(20) DEFAULT NULL COMMENT '鑱旂郴鐢佃瘽';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `email` varchar(100) DEFAULT NULL COMMENT '閭';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `tax_no` varchar(50) DEFAULT NULL COMMENT '绋庡彿';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `qualification` varchar(500) DEFAULT NULL COMMENT '璧勮川淇℃伅';
+ALTER TABLE `erp_supplier` ADD COLUMN IF NOT EXISTS `cooperation_time` date DEFAULT NULL COMMENT '鍚堜綔寮�濮嬫椂闂�';
+
+-- =====================================================
+-- 03. 閲囪喘璁㈠崟琛ㄥ鍔犲鎵瑰瓧娈�
+-- =====================================================
+ALTER TABLE `erp_purchase_order` ADD COLUMN IF NOT EXISTS `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒鏃堕棿';
+ALTER TABLE `erp_purchase_order` ADD COLUMN IF NOT EXISTS `approver_id` bigint DEFAULT NULL COMMENT '瀹℃壒浜哄憳缂栧彿';
+
+-- =====================================================
+-- 04. 鍒濆鍖栬彍鍗曟暟鎹�
+-- =====================================================
+
+-- 閲囪喘绠$悊鐩綍
+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 ('閲囪喘绠$悊', '', 1, 10, 0, '/erp/purchase', 'ep:goods', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 鑾峰彇閲囪喘绠$悊鑿滃崟ID
+SET @purchase_menu_id = LAST_INSERT_ID();
+
+-- 渚涘簲鍟嗙鐞嗚彍鍗�
+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, 1, @purchase_menu_id, 'supplier', 'ep:office-building', 'erp/purchase/supplier/index', 'ErpSupplier', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+SET @supplier_menu_id = LAST_INSERT_ID();
+
+-- 渚涘簲鍟嗘寜閽潈闄�
+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
+('渚涘簲鍟嗘煡璇�', 'erp:supplier:query', 3, 1, @supplier_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('渚涘簲鍟嗘柊澧�', 'erp:supplier:create', 3, 2, @supplier_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('渚涘簲鍟嗕慨鏀�', 'erp:supplier:update', 3, 3, @supplier_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('渚涘簲鍟嗗垹闄�', 'erp:supplier:delete', 3, 4, @supplier_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('渚涘簲鍟嗗鍑�', 'erp:supplier:export', 3, 5, @supplier_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), 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 ('閲囪喘璁㈠崟', '', 2, 2, @purchase_menu_id, 'order', 'ep:document-checked', 'erp/purchase/order/index', 'ErpPurchaseOrder', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+SET @order_menu_id = LAST_INSERT_ID();
+
+-- 閲囪喘璁㈠崟鎸夐挳鏉冮檺
+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
+('閲囪喘璁㈠崟鏌ヨ', 'erp:purchase-order:query', 3, 1, @order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('閲囪喘璁㈠崟鏂板', 'erp:purchase-order:create', 3, 2, @order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('閲囪喘璁㈠崟淇敼', 'erp:purchase-order:update', 3, 3, @order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('閲囪喘璁㈠崟鍒犻櫎', 'erp:purchase-order:delete', 3, 4, @order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('閲囪喘璁㈠崟瀹℃壒', 'erp:purchase-order:update-status', 3, 5, @order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('閲囪喘璁㈠崟瀵煎嚭', 'erp:purchase-order:export', 3, 6, @order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), 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 ('閲囪喘鍏ュ簱', '', 2, 3, @purchase_menu_id, 'in', 'ep:box', 'erp/purchase/in/index', 'ErpPurchaseIn', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), 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 ('閲囪喘閫�璐�', '', 2, 4, @purchase_menu_id, 'return', 'ep:back', 'erp/purchase/return/index', 'ErpPurchaseReturn', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), 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 ('閲囪喘缁熻', '', 2, 5, @purchase_menu_id, 'statistics', 'ep:data-analysis', 'erp/purchase/statistics/index', 'ErpPurchaseStatistics', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+SET @statistics_menu_id = LAST_INSERT_ID();
+
+-- 閲囪喘缁熻鎸夐挳鏉冮檺
+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
+('閲囪喘缁熻鏌ヨ', 'erp:purchase-statistics:query', 3, 1, @statistics_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('渚涘簲鍟嗕緵璐х粺璁�', 'erp:purchase-statistics:supplier', 3, 2, @statistics_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('閲囪喘浠锋牸鍒嗘瀽', 'erp:purchase-statistics:price', 3, 3, @statistics_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+('鏈畬鎴愯鍗曡窡韪�', 'erp:purchase-statistics:uncompleted', 3, 4, @statistics_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
diff --git a/docs/sql/mdm_migration_01_basic_data.sql b/docs/sql/mdm_migration_01_basic_data.sql
new file mode 100644
index 0000000..5d3792d
--- /dev/null
+++ b/docs/sql/mdm_migration_01_basic_data.sql
@@ -0,0 +1,191 @@
+-- =====================================================
+-- MDM 涓绘暟鎹縼绉� - 绗竴闃舵锛氬熀纭�鏁版嵁杩佺Щ
+-- 鎵ц椤哄簭锛�01 -> 02 -> 03 -> 04 -> 05 -> 06
+-- 閫傜敤鍦烘櫙锛氭柊椤圭洰鍒濆鍖栵紝鏃犲巻鍙蹭笟鍔℃暟鎹�
+-- =====================================================
+
+-- =====================================================
+-- 00. 鍒涘缓榛樿鍗曚綅锛堢‘淇� WMS 鍟嗗搧鍗曚綅涓嶄负绌猴級
+-- =====================================================
+
+-- 鍒涘缓涓�涓粯璁ゅ崟浣嶏紝鐢ㄤ簬 WMS 鍟嗗搧鍗曚綅鏃犳硶鍖归厤鐨勬儏鍐�
+INSERT INTO mdm_unit_measure (id, code, name, primary_flag, primary_id, change_rate, status, remark, creator, create_time, updater, update_time, deleted)
+VALUES (1, 'PCS', '涓�', TRUE, NULL, 1, 0, '榛樿鍗曚綅', '1', NOW(), '1', NOW(), 0)
+ON DUPLICATE KEY UPDATE name = VALUES(name);
+
+-- =====================================================
+-- 01. 杩佺Щ鍒嗙被鏁版嵁
+-- =====================================================
+
+-- 1.1 杩佺Щ ERP 浜у搧鍒嗙被鍒� MDM 鐗╂枡鍒嗙被
+-- ERP 鍒嗙被榛樿鍏宠仈浜у搧绫诲瀷锛坕temType=2锛�
+INSERT INTO mdm_item_category (id, parent_id, code, name, item_type, sort, status, creator, create_time, updater, update_time, deleted)
+SELECT
+ id,
+ COALESCE(parent_id, 0) AS parent_id,
+ code,
+ name,
+ 2 AS item_type, -- 浜у搧绫诲瀷
+ sort,
+ status,
+ '1' AS creator,
+ create_time,
+ '1' AS updater,
+ update_time,
+ 0 AS deleted
+FROM erp_product_category
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- 1.2 杩佺Щ MES 鐗╂枡鍒嗙被鍒� MDM 鐗╂枡鍒嗙被
+-- MES 鍒嗙被鏍规嵁 itemOrProduct 瀛楁鍒ゆ柇绫诲瀷
+INSERT INTO mdm_item_category (id, parent_id, code, name, item_type, sort, status, remark, creator, create_time, updater, update_time, deleted)
+SELECT
+ id + 10000 AS id, -- MES 鍒嗙被ID鍋忕Щ10000閬垮厤鍐茬獊
+ COALESCE(parent_id, 0) +
+ CASE WHEN parent_id IS NOT NULL AND parent_id > 0 THEN 10000 ELSE 0 END AS parent_id,
+ code,
+ name,
+ CASE item_or_product
+ WHEN 'ITEM' THEN 1 -- 鐗╂枡
+ WHEN 'PRODUCT' THEN 2 -- 浜у搧
+ ELSE NULL -- 鍏ㄩ儴
+ END AS item_type,
+ sort,
+ status,
+ remark,
+ '1' AS creator,
+ create_time,
+ '1' AS updater,
+ update_time,
+ 0 AS deleted
+FROM mes_md_item_type
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- 1.3 杩佺Щ WMS 鍟嗗搧鍒嗙被鍒� MDM 鐗╂枡鍒嗙被
+INSERT INTO mdm_item_category (id, parent_id, code, name, item_type, sort, status, creator, create_time, updater, update_time, deleted)
+SELECT
+ id + 20000 AS id, -- WMS 鍒嗙被ID鍋忕Щ20000閬垮厤鍐茬獊
+ COALESCE(parent_id, 0) +
+ CASE WHEN parent_id IS NOT NULL AND parent_id > 0 THEN 20000 ELSE 0 END AS parent_id,
+ code,
+ name,
+ 1 AS item_type, -- 榛樿鐗╂枡绫诲瀷
+ sort,
+ status,
+ '1' AS creator,
+ create_time,
+ '1' AS updater,
+ update_time,
+ 0 AS deleted
+FROM wms_item_category
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- =====================================================
+-- 02. 杩佺Щ璁¢噺鍗曚綅鏁版嵁
+-- =====================================================
+
+-- 2.1 杩佺Щ ERP 璁¢噺鍗曚綅鍒� MDM锛圛D 浠� 100 寮�濮嬶級
+INSERT INTO mdm_unit_measure (id, code, name, primary_flag, primary_id, change_rate, status, remark, creator, create_time, updater, update_time, deleted)
+SELECT
+ id + 100 AS id, -- ERP 鍗曚綅ID鍋忕Щ100閬垮厤涓庨粯璁ゅ崟浣嶅啿绐�
+ name AS code, -- ERP 鍗曚綅娌℃湁缂栫爜锛屼娇鐢ㄥ悕绉颁綔涓虹紪鐮�
+ name,
+ TRUE AS primary_flag, -- 榛樿涓轰富鍗曚綅
+ NULL AS primary_id,
+ 1 AS change_rate,
+ status,
+ NULL AS remark,
+ '1' AS creator,
+ create_time,
+ '1' AS updater,
+ update_time,
+ 0 AS deleted
+FROM erp_product_unit
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name);
+
+-- 2.2 杩佺Щ MES 璁¢噺鍗曚綅鍒� MDM锛圛D 浠� 1000 寮�濮嬶級
+INSERT INTO mdm_unit_measure (id, code, name, primary_flag, primary_id, change_rate, status, remark, creator, create_time, updater, update_time, deleted)
+SELECT
+ id + 1000 AS id, -- MES 鍗曚綅ID鍋忕Щ1000閬垮厤鍐茬獊
+ code,
+ name,
+ COALESCE(primary_flag, FALSE) AS primary_flag,
+ CASE WHEN primary_id IS NOT NULL THEN primary_id + 1000 ELSE NULL END AS primary_id,
+ change_rate,
+ status,
+ remark,
+ '1' AS creator,
+ create_time,
+ '1' AS updater,
+ update_time,
+ 0 AS deleted
+FROM mes_md_unit_measure
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- =====================================================
+-- 03. 杩佺Щ鍝佺墝鏁版嵁
+-- =====================================================
+
+-- 3.1 杩佺Щ WMS 鍝佺墝鍒� MDM
+INSERT INTO mdm_brand (id, code, name, status, remark, creator, create_time, updater, update_time, deleted)
+SELECT
+ id,
+ code,
+ name,
+ 0 AS status, -- 榛樿鍚敤
+ NULL AS remark,
+ '1' AS creator,
+ create_time,
+ '1' AS updater,
+ update_time,
+ 0 AS deleted
+FROM wms_item_brand
+WHERE deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- =====================================================
+-- 04. 鏁版嵁楠岃瘉
+-- =====================================================
+
+-- 楠岃瘉鍒嗙被鏁版嵁杩佺Щ
+SELECT 'ERP鍒嗙被杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM erp_product_category WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_item_category WHERE id < 10000 AND deleted = 0) AS migrated_count;
+
+SELECT 'MES鍒嗙被杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM mes_md_item_type WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_item_category WHERE id >= 10000 AND id < 20000 AND deleted = 0) AS migrated_count;
+
+SELECT 'WMS鍒嗙被杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM wms_item_category WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_item_category WHERE id >= 20000 AND deleted = 0) AS migrated_count;
+
+-- 楠岃瘉鍗曚綅鏁版嵁杩佺Щ
+SELECT 'ERP鍗曚綅杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM erp_product_unit WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_unit_measure WHERE id >= 100 AND id < 1000 AND deleted = 0) AS migrated_count;
+
+SELECT 'MES鍗曚綅杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM mes_md_unit_measure WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_unit_measure WHERE id >= 1000 AND deleted = 0) AS migrated_count;
+
+-- 楠岃瘉鍝佺墝鏁版嵁杩佺Щ
+SELECT 'WMS鍝佺墝杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM wms_item_brand WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_brand WHERE deleted = 0) AS migrated_count;
\ No newline at end of file
diff --git a/docs/sql/mdm_migration_02_item_data.sql b/docs/sql/mdm_migration_02_item_data.sql
new file mode 100644
index 0000000..ae7f251
--- /dev/null
+++ b/docs/sql/mdm_migration_02_item_data.sql
@@ -0,0 +1,291 @@
+-- =====================================================
+-- MDM 涓绘暟鎹縼绉� - 绗簩闃舵锛氱墿鏂欐暟鎹縼绉�
+-- 鎵ц椤哄簭锛氬厛鎵ц绗竴闃舵 SQL
+-- 閫傜敤鍦烘櫙锛氭柊椤圭洰鍒濆鍖栵紝鏃犲巻鍙蹭笟鍔℃暟鎹�
+-- =====================================================
+
+-- =====================================================
+-- 01. 杩佺Щ MES 鐗╂枡鏁版嵁
+-- =====================================================
+
+-- MES 鐗╂枡绫诲瀷涓� itemType=1锛堢墿鏂欙級
+INSERT INTO mdm_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
+ m.id AS id,
+ m.code,
+ m.name,
+ NULL AS bar_code, -- MES 鐗╂枡娌℃湁鏉$爜瀛楁
+ m.specification,
+ -- MES 鍒嗙被ID鍋忕Щ浜�10000
+ m.item_type_id + 10000 AS category_id,
+ -- MES 鍗曚綅ID鍋忕Щ浜�1000
+ m.unit_measure_id + 1000 AS unit_measure_id,
+ NULL AS brand_id,
+ 1 AS item_type, -- 鐗╂枡绫诲瀷
+ m.status,
+ NULL AS purchase_price,
+ NULL AS sales_price,
+ NULL AS cost_price,
+ NULL AS min_price,
+ NULL AS safety_stock,
+ m.min_stock,
+ m.max_stock,
+ NULL AS min_order_qty,
+ NULL AS lead_time,
+ COALESCE(m.batch_flag, FALSE) AS is_batch_managed,
+ FALSE AS is_serial_managed,
+ NULL AS barcode_rule_id,
+ NULL AS expiry_day,
+ COALESCE(m.high_value, FALSE) AS high_value,
+ NULL AS weight,
+ m.remark,
+ '1' AS creator,
+ m.create_time,
+ '1' AS updater,
+ m.update_time,
+ 0 AS deleted
+FROM mes_md_item m
+WHERE m.deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- =====================================================
+-- 02. 杩佺Щ ERP 浜у搧鏁版嵁
+-- =====================================================
+
+-- ERP 浜у搧绫诲瀷涓� itemType=2锛堜骇鍝侊級
+INSERT INTO mdm_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
+ p.id AS id,
+ p.bar_code AS code, -- ERP 浣跨敤鏉$爜浣滀负缂栫爜
+ p.name,
+ p.bar_code,
+ p.standard AS specification,
+ -- ERP 鍒嗙被ID鏃犲亸绉�
+ p.category_id,
+ -- ERP 鍗曚綅ID鍋忕Щ浜�100
+ p.unit_id + 100 AS unit_measure_id,
+ NULL AS brand_id,
+ 2 AS item_type, -- 浜у搧绫诲瀷
+ p.status,
+ p.purchase_price,
+ p.sale_price AS sales_price,
+ NULL AS cost_price,
+ p.min_price,
+ NULL AS safety_stock,
+ NULL AS min_stock,
+ NULL AS max_stock,
+ NULL AS min_order_qty,
+ NULL AS lead_time,
+ FALSE AS is_batch_managed,
+ FALSE AS is_serial_managed,
+ NULL AS barcode_rule_id,
+ p.expiry_day,
+ FALSE AS high_value,
+ p.weight,
+ p.remark,
+ '1' AS creator,
+ p.create_time,
+ '1' AS updater,
+ p.update_time,
+ 0 AS deleted
+FROM erp_product p
+WHERE p.deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ bar_code = VALUES(bar_code);
+
+-- =====================================================
+-- 03. 杩佺Щ WMS 鍟嗗搧鏁版嵁
+-- =====================================================
+
+-- WMS 鍟嗗搧绫诲瀷涓� itemType=1锛堢墿鏂欙級
+-- 娉ㄦ剰锛歐MS 鍟嗗搧鐨� unit 瀛楁鏄瓧绗︿覆锛岄渶瑕佸尮閰� MDM 鍗曚綅鍚嶇О锛屾壘涓嶅埌鍒欎娇鐢ㄩ粯璁ゅ崟浣嶏紙id=1锛�
+INSERT INTO mdm_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
+ w.id + 30000 AS id, -- WMS 鍟嗗搧ID鍋忕Щ30000閬垮厤鍐茬獊
+ w.code,
+ w.name,
+ NULL AS bar_code,
+ NULL AS specification,
+ -- WMS 鍒嗙被ID鍋忕Щ浜�20000
+ w.category_id + 20000 AS category_id,
+ -- WMS 鍟嗗搧鍗曚綅涓哄瓧绗︿覆锛岄渶鍖归厤MDM鍗曚綅鍚嶇О锛屾壘涓嶅埌鍒欎娇鐢ㄩ粯璁ゅ崟浣�
+ COALESCE(
+ (SELECT id FROM mdm_unit_measure WHERE name = w.unit AND deleted = 0 LIMIT 1),
+ 1 -- 榛樿鍗曚綅ID
+ ) AS unit_measure_id,
+ -- WMS 鍝佺墝ID鏃犲亸绉�
+ w.brand_id,
+ 1 AS item_type, -- 榛樿鐗╂枡绫诲瀷
+ 0 AS status, -- 榛樿鍚敤
+ NULL AS purchase_price,
+ NULL AS sales_price,
+ NULL AS cost_price,
+ NULL AS min_price,
+ NULL AS safety_stock,
+ NULL AS min_stock,
+ NULL AS max_stock,
+ NULL AS min_order_qty,
+ NULL AS lead_time,
+ FALSE AS is_batch_managed,
+ FALSE AS is_serial_managed,
+ NULL AS barcode_rule_id,
+ NULL AS expiry_day,
+ FALSE AS high_value,
+ NULL AS weight,
+ w.remark,
+ '1' AS creator,
+ w.create_time,
+ '1' AS updater,
+ w.update_time,
+ 0 AS deleted
+FROM wms_item w
+WHERE w.deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- =====================================================
+-- 04. 杩佺Щ WMS SKU 鏁版嵁骞跺垱寤� MDM SKU
+-- =====================================================
+
+-- 杩佺Щ WMS SKU 鍒� MDM SKU
+INSERT INTO mdm_item_sku (id, item_id, code, name, bar_code, length, width, height,
+ gross_weight, net_weight, cost_price, sale_price, status, remark,
+ creator, create_time, updater, update_time, deleted)
+SELECT
+ s.id + 40000 AS id, -- SKU ID鍋忕Щ40000閬垮厤鍐茬獊
+ -- WMS 鍟嗗搧ID鍋忕Щ浜�30000
+ s.item_id + 30000 AS item_id,
+ s.code,
+ s.name,
+ s.bar_code,
+ s.length,
+ s.width,
+ s.height,
+ s.gross_weight,
+ s.net_weight,
+ s.cost_price,
+ s.selling_price AS sale_price,
+ 0 AS status,
+ NULL AS remark,
+ '1' AS creator,
+ s.create_time,
+ '1' AS updater,
+ s.update_time,
+ 0 AS deleted
+FROM wms_item_sku s
+WHERE s.deleted = 0
+ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ code = VALUES(code);
+
+-- =====================================================
+-- 05. 涓� MES/ERP 鐗╂枡鍒涘缓榛樿 SKU
+-- =====================================================
+
+-- 涓� MES 鐗╂枡鍒涘缓榛樿 SKU锛堝鏋滀笉瀛樺湪锛�
+INSERT INTO mdm_item_sku (item_id, code, name, bar_code, length, width, height,
+ gross_weight, net_weight, cost_price, sale_price, status, remark,
+ creator, create_time, updater, update_time, deleted)
+SELECT
+ m.id AS item_id,
+ CONCAT(m.code, '-DEFAULT') AS code,
+ CONCAT(m.name, '-榛樿SKU') AS name,
+ NULL AS bar_code,
+ NULL AS length,
+ NULL AS width,
+ NULL AS height,
+ NULL AS gross_weight,
+ NULL AS net_weight,
+ NULL AS cost_price,
+ NULL AS sale_price,
+ 0 AS status,
+ '绯荤粺鑷姩鍒涘缓' AS remark,
+ '1' AS creator,
+ NOW() AS create_time,
+ '1' AS updater,
+ NOW() AS update_time,
+ 0 AS deleted
+FROM mdm_item m
+WHERE m.item_type = 1 -- MES 鐗╂枡
+ AND m.deleted = 0
+ AND m.id < 30000 -- 鎺掗櫎 WMS 鍟嗗搧锛堝凡鏈� SKU锛�
+ AND NOT EXISTS (
+ SELECT 1 FROM mdm_item_sku s WHERE s.item_id = m.id AND s.deleted = 0
+ );
+
+-- 涓� ERP 浜у搧鍒涘缓榛樿 SKU锛堝鏋滀笉瀛樺湪锛�
+INSERT INTO mdm_item_sku (item_id, code, name, bar_code, length, width, height,
+ gross_weight, net_weight, cost_price, sale_price, status, remark,
+ creator, create_time, updater, update_time, deleted)
+SELECT
+ p.id AS item_id,
+ CONCAT(p.bar_code, '-DEFAULT') AS code,
+ CONCAT(p.name, '-榛樿SKU') AS name,
+ p.bar_code,
+ NULL AS length,
+ NULL AS width,
+ NULL AS height,
+ NULL AS gross_weight,
+ NULL AS net_weight,
+ NULL AS cost_price,
+ NULL AS sale_price,
+ 0 AS status,
+ '绯荤粺鑷姩鍒涘缓' AS remark,
+ '1' AS creator,
+ NOW() AS create_time,
+ '1' AS updater,
+ NOW() AS update_time,
+ 0 AS deleted
+FROM mdm_item p
+WHERE p.item_type = 2 -- ERP 浜у搧
+ AND p.deleted = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM mdm_item_sku s WHERE s.item_id = p.id AND s.deleted = 0
+ );
+
+-- =====================================================
+-- 06. 鏁版嵁楠岃瘉
+-- =====================================================
+
+-- 楠岃瘉鐗╂枡鏁版嵁杩佺Щ
+SELECT 'MES鐗╂枡杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM mes_md_item WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_item WHERE item_type = 1 AND id < 30000 AND deleted = 0) AS migrated_count;
+
+SELECT 'ERP浜у搧杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM erp_product WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_item WHERE item_type = 2 AND deleted = 0) AS migrated_count;
+
+SELECT 'WMS鍟嗗搧杩佺Щ' AS source,
+ (SELECT COUNT(*) FROM wms_item WHERE deleted = 0) AS original_count,
+ (SELECT COUNT(*) FROM mdm_item WHERE id >= 30000 AND deleted = 0) AS migrated_count;
+
+-- 楠岃瘉 SKU 鏁版嵁
+SELECT 'SKU鎬绘暟' AS metric, COUNT(*) AS count FROM mdm_item_sku WHERE deleted = 0;
+SELECT '鏈塖KU鐨勭墿鏂欐暟' AS metric, COUNT(DISTINCT item_id) AS count FROM mdm_item_sku WHERE deleted = 0;
+
+-- =====================================================
+-- 07. 妫�鏌ヤ娇鐢ㄩ粯璁ゅ崟浣嶇殑 WMS 鍟嗗搧
+-- =====================================================
+
+-- 鏌ョ湅 WMS 鍟嗗搧涓崟浣嶆棤娉曞尮閰嶇殑鎯呭喌
+SELECT w.id, w.code, w.name, w.unit
+FROM wms_item w
+WHERE w.deleted = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM mdm_unit_measure u WHERE u.name = w.unit AND u.deleted = 0
+ )
+LIMIT 20;
\ No newline at end of file
diff --git a/docs/sql/mdm_migration_03_update_refs.sql b/docs/sql/mdm_migration_03_update_refs.sql
new file mode 100644
index 0000000..a565a2d
--- /dev/null
+++ b/docs/sql/mdm_migration_03_update_refs.sql
@@ -0,0 +1,50 @@
+-- =====================================================
+-- MDM 涓绘暟鎹縼绉� - 琛ュ厖锛氭坊鍔犵己澶辩殑瀛楁
+-- 鎵ц椤哄簭锛氬湪 mdm_migration_02_item_data.sql 涔嬪悗鎵ц
+-- =====================================================
+
+-- =====================================================
+-- 01. 妫�鏌ュ苟娣诲姞 mes_wm_material_stock 琛ㄧ殑 item_type_id 瀛楁
+-- =====================================================
+
+-- 娣诲姞 item_type_id 瀛楁锛堝鏋滀笉瀛樺湪锛�
+-- MySQL 涓嶆敮鎸� IF NOT EXISTS 鐢ㄤ簬 ADD COLUMN锛屼娇鐢ㄥ瓨鍌ㄨ繃绋嬫垨鐩存帴鎵ц
+-- 鍏堝皾璇曟坊鍔狅紝濡傛灉宸插瓨鍦ㄤ細鎶ラ敊锛屽彲浠ュ拷鐣�
+
+-- 鏂瑰紡1锛氱洿鎺ユ坊鍔狅紙濡傛灉瀛楁宸插瓨鍦ㄤ細鎶ラ敊锛屽拷鐣ュ嵆鍙級
+ALTER TABLE mes_wm_material_stock ADD COLUMN item_type_id bigint DEFAULT NULL COMMENT '鐗╂枡鍒嗙被缂栧彿' AFTER id;
+
+-- 鏂瑰紡2锛氬鏋滄柟寮�1鎶ラ敊璇村瓧娈靛凡瀛樺湪锛岃鏄庡瓧娈靛凡缁忓瓨鍦紝璺宠繃鍗冲彲
+
+
+-- =====================================================
+-- 02. 涓� item_type_id 鍒涘缓绱㈠紩锛堝彲閫夛級
+-- =====================================================
+
+-- 鍒涘缓绱㈠紩锛堝鏋滀笉瀛樺湪锛�
+-- CREATE INDEX idx_item_type_id ON mes_wm_material_stock(item_type_id);
+
+
+-- =====================================================
+-- 03. 鏇存柊搴撳瓨琛ㄤ腑鐨� item_type_id
+-- 鏍规嵁 item_id 鍏宠仈 mes_md_item 琛ㄨ幏鍙� item_type_id
+-- =====================================================
+
+-- 浠庣墿鏂欒〃鑾峰彇鍒嗙被ID骞舵洿鏂板埌搴撳瓨琛�
+UPDATE mes_wm_material_stock s
+INNER JOIN mes_md_item i ON s.item_id = i.id
+SET s.item_type_id = i.item_type_id + 10000 -- MDM鍒嗙被ID鍋忕Щ10000
+WHERE s.item_type_id IS NULL
+ AND s.deleted = 0;
+
+-- =====================================================
+-- 04. 楠岃瘉缁撴灉
+-- =====================================================
+
+-- 鏌ョ湅搴撳瓨琛ㄥ瓧娈�
+SELECT * FROM mes_wm_material_stock LIMIT 5;
+
+-- 楠岃瘉 item_type_id 鏄惁鏇存柊
+SELECT COUNT(*) AS updated_count
+FROM mes_wm_material_stock
+WHERE item_type_id IS NOT NULL AND item_type_id >= 10000 AND deleted = 0;
diff --git a/docs/sql/mdm_migration_04_alter_columns.sql b/docs/sql/mdm_migration_04_alter_columns.sql
new file mode 100644
index 0000000..ad8188b
--- /dev/null
+++ b/docs/sql/mdm_migration_04_alter_columns.sql
@@ -0,0 +1,11 @@
+-- =====================================================
+-- MDM 涓绘暟鎹縼绉� - 琛ュ厖锛氫慨鏀瑰瓧娈电害鏉�
+-- 鎵ц椤哄簭锛氬湪 mdm_schema.sql 涔嬪悗鎵ц锛堝鏋滆〃宸插瓨鍦級
+-- =====================================================
+
+-- 淇敼 mdm_item 琛ㄥ瓧娈电害鏉燂紝鍏佽 category_id 鍜� unit_measure_id 涓虹┖
+ALTER TABLE mdm_item MODIFY COLUMN category_id bigint DEFAULT NULL COMMENT '鐗╂枡鍒嗙被缂栧彿';
+ALTER TABLE mdm_item MODIFY COLUMN unit_measure_id bigint DEFAULT NULL COMMENT '璁¢噺鍗曚綅缂栧彿';
+
+-- 濡傛灉闇�瑕佸垹闄� brand_id 瀛楁
+ALTER TABLE mdm_item DROP COLUMN IF EXISTS brand_id;
\ No newline at end of file
diff --git a/docs/sql/mdm_schema.sql b/docs/sql/mdm_schema.sql
new file mode 100644
index 0000000..0a6b5df
--- /dev/null
+++ b/docs/sql/mdm_schema.sql
@@ -0,0 +1,194 @@
+-- =====================================================
+-- MDM 涓绘暟鎹ā鍧� - 鏁版嵁搴撹〃缁撴瀯
+-- 鎵ц椤哄簭锛氬厛鎵ц姝よ剼鏈垱寤鸿〃锛屽啀鎵ц鏁版嵁杩佺Щ鑴氭湰
+-- =====================================================
+
+-- =====================================================
+-- 01. 鐗╂枡鍒嗙被琛�
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_item_category`;
+CREATE TABLE `mdm_item_category` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍒嗙被缂栧彿',
+ `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '鐖跺垎绫荤紪鍙凤紙鏍硅妭鐐�=0锛�',
+ `code` varchar(50) DEFAULT NULL COMMENT '鍒嗙被缂栫爜',
+ `name` varchar(100) NOT NULL COMMENT '鍒嗙被鍚嶇О',
+ `item_type` tinyint DEFAULT NULL COMMENT '閫傜敤鐗╂枡绫诲瀷锛�1:鐗╂枡 2:浜у搧 3:鍗婃垚鍝� NULL:鍏ㄩ儴锛�',
+ `sort` int NOT NULL DEFAULT 0 COMMENT '鎺掑簭',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0姝e父 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`),
+ KEY `idx_parent_id` (`parent_id`),
+ KEY `idx_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 鐗╂枡鍒嗙被琛�';
+
+-- =====================================================
+-- 02. 璁¢噺鍗曚綅琛�
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_unit_measure`;
+CREATE TABLE `mdm_unit_measure` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍗曚綅缂栧彿',
+ `code` varchar(50) NOT NULL COMMENT '鍗曚綅缂栫爜',
+ `name` varchar(50) NOT NULL COMMENT '鍗曚綅鍚嶇О',
+ `primary_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁涓诲崟浣�',
+ `primary_id` bigint DEFAULT NULL COMMENT '涓诲崟浣嶇紪鍙�',
+ `change_rate` decimal(24,6) DEFAULT 1.000000 COMMENT '涓庝富鍗曚綅鎹㈢畻姣斾緥',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0姝e父 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`, `deleted`),
+ KEY `idx_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 璁¢噺鍗曚綅琛�';
+
+-- =====================================================
+-- 03. 鍝佺墝琛�
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_brand`;
+CREATE TABLE `mdm_brand` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍝佺墝缂栧彿',
+ `code` varchar(50) NOT NULL COMMENT '鍝佺墝缂栫爜',
+ `name` varchar(100) NOT NULL COMMENT '鍝佺墝鍚嶇О',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0姝e父 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`, `deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 鍝佺墝琛�';
+
+-- =====================================================
+-- 04. 鐗╂枡涓绘暟鎹〃
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_item`;
+CREATE TABLE `mdm_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐗╂枡缂栧彿',
+ `code` varchar(50) NOT NULL COMMENT '鐗╂枡缂栫爜',
+ `name` varchar(200) NOT NULL COMMENT '鐗╂枡鍚嶇О',
+ `bar_code` varchar(100) DEFAULT NULL COMMENT '鐗╂枡鏉$爜',
+ `specification` varchar(200) DEFAULT NULL COMMENT '瑙勬牸鍨嬪彿',
+ `category_id` bigint DEFAULT NULL COMMENT '鐗╂枡鍒嗙被缂栧彿',
+ `unit_measure_id` bigint DEFAULT NULL COMMENT '璁¢噺鍗曚綅缂栧彿',
+ `item_type` tinyint NOT NULL DEFAULT 1 COMMENT '鐗╂枡绫诲瀷锛�1:鐗╂枡 2:浜у搧 3:鍗婃垚鍝侊級',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ `purchase_price` decimal(24,2) DEFAULT NULL COMMENT '閲囪喘浠�',
+ `sales_price` decimal(24,2) DEFAULT NULL COMMENT '閿�鍞环',
+ `cost_price` decimal(24,2) DEFAULT NULL COMMENT '鎴愭湰浠�',
+ `min_price` decimal(24,2) 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) NOT NULL DEFAULT b'0' COMMENT '鏄惁鎵规绠$悊',
+ `is_serial_managed` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁搴忓垪鍙风鐞�',
+ `barcode_rule_id` bigint DEFAULT NULL COMMENT '鏉$爜瑙勫垯ID',
+ `expiry_day` int DEFAULT NULL COMMENT '淇濊川鏈熷ぉ鏁�',
+ `high_value` bit(1) NOT NULL 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`, `deleted`),
+ KEY `idx_name` (`name`),
+ KEY `idx_category_id` (`category_id`),
+ KEY `idx_item_type` (`item_type`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 鐗╂枡涓绘暟鎹〃';
+
+-- =====================================================
+-- 05. 鐗╂枡SKU琛�
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_item_sku`;
+CREATE TABLE `mdm_item_sku` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'SKU缂栧彿',
+ `item_id` bigint NOT NULL COMMENT '鐗╂枡缂栧彿',
+ `code` varchar(50) NOT NULL COMMENT 'SKU缂栫爜',
+ `name` varchar(200) NOT NULL COMMENT 'SKU鍚嶇О',
+ `bar_code` varchar(100) DEFAULT NULL COMMENT 'SKU鏉$爜',
+ `length` decimal(24,6) DEFAULT NULL COMMENT '闀垮害锛坈m锛�',
+ `width` decimal(24,6) DEFAULT NULL COMMENT '瀹藉害锛坈m锛�',
+ `height` decimal(24,6) DEFAULT NULL COMMENT '楂樺害锛坈m锛�',
+ `gross_weight` decimal(24,6) DEFAULT NULL COMMENT '姣涢噸锛坘g锛�',
+ `net_weight` decimal(24,6) DEFAULT NULL COMMENT '鍑�閲嶏紙kg锛�',
+ `cost_price` decimal(24,2) DEFAULT NULL COMMENT '鎴愭湰浠�',
+ `sale_price` decimal(24,2) DEFAULT NULL COMMENT '閿�鍞环',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0姝e父 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`, `deleted`),
+ KEY `idx_item_id` (`item_id`),
+ KEY `idx_bar_code` (`bar_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 鐗╂枡SKU琛�';
+
+-- =====================================================
+-- 06. 鐗╂枡鎵规灞炴�ч厤缃〃
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_item_batch_config`;
+CREATE TABLE `mdm_item_batch_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '閰嶇疆缂栧彿',
+ `item_id` bigint NOT NULL COMMENT '鐗╂枡缂栧彿',
+ `produce_date_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤鐢熶骇鏃ユ湡',
+ `expire_date_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤澶辨晥鏃ユ湡',
+ `receipt_date_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤鍏ュ簱鏃ユ湡',
+ `vendor_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤渚涘簲鍟�',
+ `client_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤瀹㈡埛',
+ `sales_order_code_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤閿�鍞鍗曞彿',
+ `purchase_order_code_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤閲囪喘璁㈠崟鍙�',
+ `work_order_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤鐢熶骇宸ュ崟',
+ `task_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤鐢熶骇浠诲姟',
+ `workstation_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤宸ヤ綅',
+ `tool_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤宸ヨ澶瑰叿',
+ `mold_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤妯″叿',
+ `lot_number_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤鎵规鍙�',
+ `quality_status_flag` bit(1) NOT NULL 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`, `deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 鐗╂枡鎵规灞炴�ч厤缃〃';
+
+-- =====================================================
+-- 07. 浠撳簱琛�
+-- =====================================================
+DROP TABLE IF EXISTS `mdm_warehouse`;
+CREATE TABLE `mdm_warehouse` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '浠撳簱缂栧彿',
+ `code` varchar(50) NOT NULL COMMENT '浠撳簱缂栫爜',
+ `name` varchar(100) NOT NULL COMMENT '浠撳簱鍚嶇О',
+ `type` tinyint DEFAULT NULL COMMENT '浠撳簱绫诲瀷',
+ `address` varchar(500) DEFAULT NULL COMMENT '浠撳簱鍦板潃',
+ `principal` varchar(64) DEFAULT NULL COMMENT '璐熻矗浜�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊紙0姝e父 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`, `deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MDM 浠撳簱琛�';
diff --git a/pom.xml b/pom.xml
index c8565b3..d8b83af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,11 +22,16 @@
<!-- <module>yudao-module-pay</module>-->
<!-- <module>yudao-module-mall</module>-->
<module>yudao-module-crm</module>
+ <module>yudao-module-crm-api</module>
<module>yudao-module-erp</module>
+ <module>yudao-module-erp-api</module>
<!-- <module>yudao-module-iot</module>-->
<module>yudao-module-mes</module>
<module>yudao-module-wms</module>
<module>yudao-module-im</module>
+ <!-- MDM 涓绘暟鎹鐞嗘ā鍧� -->
+ <module>yudao-module-mdm-api</module>
+ <module>yudao-module-mdm</module>
<!-- 璇峰弬鑰� https://doc.iocoder.cn/ai/build/ 鏂囨。锛屽畬鎴� AI 妯″潡鐨勫惎鍔紒锛侊紒 -->
<!-- <module>yudao-module-ai</module>-->
</modules>
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
index 33482d4..c58c0a2 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
@@ -1,8 +1,6 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
import java.util.Arrays;
@@ -11,8 +9,6 @@
*
* @author 鑺嬮亾婧愮爜
*/
-@RequiredArgsConstructor
-@Getter
public enum TerminalEnum implements ArrayValuable<Integer> {
UNKNOWN(0, "鏈煡"), // 鐩殑锛氬湪鏃犳硶瑙f瀽鍒� terminal 鏃讹紝浣跨敤瀹�
@@ -33,6 +29,19 @@
*/
private final String name;
+ TerminalEnum(Integer terminal, String name) {
+ this.terminal = terminal;
+ this.name = name;
+ }
+
+ public Integer getTerminal() {
+ return terminal;
+ }
+
+ public String getName() {
+ return name;
+ }
+
@Override
public Integer[] array() {
return ARRAYS;
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
index 6e2f2ea..55f8de7 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
@@ -24,4 +24,10 @@
new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId));
}
+ default List<BpmProcessDefinitionInfoDO> selectListByCategory(String category) {
+ return selectList(new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>()
+ .eq(BpmProcessDefinitionInfoDO::getCategory, category)
+ .eq(BpmProcessDefinitionInfoDO::getVisible, true));
+ }
+
}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
index f42e69e..a5efca9 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
@@ -178,4 +178,12 @@
*/
Deployment getDeployment(String id);
+ /**
+ * 鑾峰緱鎸囧畾鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅鍒楄〃
+ *
+ * @param category 鍒嗙被缂栫爜
+ * @return 娴佺▼瀹氫箟淇℃伅鍒楄〃
+ */
+ List<BpmProcessDefinitionInfoDO> getProcessDefinitionInfoListByCategory(String category);
+
}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
index 836b518..8171fb0 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -244,4 +244,9 @@
return query.list();
}
+ @Override
+ public List<BpmProcessDefinitionInfoDO> getProcessDefinitionInfoListByCategory(String category) {
+ return processDefinitionMapper.selectListByCategory(category);
+ }
+
}
diff --git a/yudao-module-crm-api/pom.xml b/yudao-module-crm-api/pom.xml
new file mode 100644
index 0000000..aada0ff
--- /dev/null
+++ b/yudao-module-crm-api/pom.xml
@@ -0,0 +1,33 @@
+<?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-crm-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ crm 妯″潡涓嬶紝瀹㈡埛鍏崇郴绠$悊鐨� API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
new file mode 100644
index 0000000..7aea86c
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.crm.api.customer;
+
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CRM 瀹㈡埛 API 鎺ュ彛
+ *
+ * 鎻愪緵缁欏叾瀹冩ā鍧楋紙濡� ERP锛夎皟鐢�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface CrmCustomerApi {
+
+ /**
+ * 鑾峰緱瀹㈡埛淇℃伅
+ *
+ * @param id 瀹㈡埛缂栧彿
+ * @return 瀹㈡埛淇℃伅
+ */
+ CrmCustomerRespDTO getCustomer(Long id);
+
+ /**
+ * 鑾峰緱瀹㈡埛淇℃伅鍒楄〃
+ *
+ * @param ids 瀹㈡埛缂栧彿鍒楄〃
+ * @return 瀹㈡埛淇℃伅鍒楄〃
+ */
+ List<CrmCustomerRespDTO> getCustomerList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱瀹㈡埛淇℃伅 Map
+ *
+ * @param ids 瀹㈡埛缂栧彿鍒楄〃
+ * @return 瀹㈡埛淇℃伅 Map
+ */
+ Map<Long, CrmCustomerRespDTO> getCustomerMap(Collection<Long> ids);
+
+ /**
+ * 鏍¢獙瀹㈡埛鏄惁瀛樺湪
+ *
+ * @param id 瀹㈡埛缂栧彿
+ * @return 瀹㈡埛淇℃伅锛堟牎楠岄�氳繃锛�
+ */
+ CrmCustomerRespDTO validateCustomer(Long id);
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java
new file mode 100644
index 0000000..d3237c7
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.crm.api.customer.dto;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * CRM 瀹㈡埛 Response DTO
+ *
+ * 鎻愪緵缁欏叾瀹冩ā鍧楋紙濡� ERP锛変娇鐢�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class CrmCustomerRespDTO {
+
+ /**
+ * 缂栧彿
+ */
+ private Long id;
+
+ /**
+ * 瀹㈡埛鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鎵嬫満
+ */
+ private String mobile;
+ /**
+ * 鐢佃瘽
+ */
+ private String telephone;
+ /**
+ * QQ
+ */
+ private String qq;
+ /**
+ * wechat
+ */
+ private String wechat;
+ /**
+ * email
+ */
+ private String email;
+
+ /**
+ * 鎵�鍦ㄥ湴
+ */
+ private Integer areaId;
+ /**
+ * 璇︾粏鍦板潃
+ */
+ private String detailAddress;
+
+ /**
+ * 鎵�灞炶涓�
+ */
+ private Integer industryId;
+ /**
+ * 瀹㈡埛绛夌骇
+ */
+ private Integer level;
+ /**
+ * 瀹㈡埛鏉ユ簮
+ */
+ private Integer source;
+
+ /**
+ * 璐熻矗浜虹殑鐢ㄦ埛缂栧彿
+ */
+ private Long ownerUserId;
+ /**
+ * 鎴愪负璐熻矗浜虹殑鏃堕棿
+ */
+ private LocalDateTime ownerTime;
+
+ /**
+ * 閿佸畾鐘舵��
+ */
+ private Boolean lockStatus;
+ /**
+ * 鎴愪氦鐘舵��
+ */
+ private Boolean dealStatus;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApi.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApi.java
new file mode 100644
index 0000000..e299c8d
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApi.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.crm.api.report;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmBusinessAnalyzeRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmContractAnalyzeRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmCustomerAnalyzeRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmSalesFunnelRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmSalesPerformanceRespDTO;
+
+import java.time.LocalDateTime;
+
+/**
+ * CRM 钀ラ攢鎶ヨ〃 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface CrmReportApi {
+
+ String PREFIX = "/rpc-api/crm/report";
+
+ /**
+ * 鑾峰彇瀹㈡埛鍒嗘瀽缁熻
+ *
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 缁熻鏁版嵁
+ */
+ CommonResult<CrmCustomerAnalyzeRespDTO> getCustomerAnalyze(LocalDateTime startTime, LocalDateTime endTime);
+
+ /**
+ * 鑾峰彇鍟嗘満鍒嗘瀽缁熻
+ *
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 缁熻鏁版嵁
+ */
+ CommonResult<CrmBusinessAnalyzeRespDTO> getBusinessAnalyze(LocalDateTime startTime, LocalDateTime endTime);
+
+ /**
+ * 鑾峰彇鍚堝悓鍒嗘瀽缁熻
+ *
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 缁熻鏁版嵁
+ */
+ CommonResult<CrmContractAnalyzeRespDTO> getContractAnalyze(LocalDateTime startTime, LocalDateTime endTime);
+
+ /**
+ * 鑾峰彇閿�鍞紡鏂楃粺璁�
+ *
+ * @return 缁熻鏁版嵁
+ */
+ CommonResult<CrmSalesFunnelRespDTO> getSalesFunnel();
+
+ /**
+ * 鑾峰彇閿�鍞笟缁╃粺璁�
+ *
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 缁熻鏁版嵁
+ */
+ CommonResult<CrmSalesPerformanceRespDTO> getSalesPerformance(LocalDateTime startTime, LocalDateTime endTime);
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmBusinessAnalyzeRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmBusinessAnalyzeRespDTO.java
new file mode 100644
index 0000000..8c74013
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmBusinessAnalyzeRespDTO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.api.report.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * CRM 鍟嗘満鍒嗘瀽缁熻 Response DTO
+ */
+@Schema(description = "RPC 鏈嶅姟 - CRM 鍟嗘満鍒嗘瀽缁熻 Response DTO")
+@Data
+public class CrmBusinessAnalyzeRespDTO {
+
+ @Schema(description = "鍟嗘満鎬绘暟")
+ private Long totalCount;
+
+ @Schema(description = "鏂板鍟嗘満鏁帮紙浠婃棩锛�")
+ private Long todayCount;
+
+ @Schema(description = "鍟嗘満鎬婚噾棰�")
+ private BigDecimal totalPrice;
+
+ @Schema(description = "鎴愪氦鍟嗘満鏁�")
+ private Long successCount;
+
+ @Schema(description = "鎴愪氦鍟嗘満閲戦")
+ private BigDecimal successPrice;
+
+ @Schema(description = "鍟嗘満杞寲鐜囷紙鐧惧垎姣旓級")
+ private BigDecimal successRate;
+
+ @Schema(description = "杩涜涓晢鏈烘暟")
+ private Long processingCount;
+
+ @Schema(description = "澶辫触鍟嗘満鏁�")
+ private Long failCount;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmContractAnalyzeRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmContractAnalyzeRespDTO.java
new file mode 100644
index 0000000..2284ea6
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmContractAnalyzeRespDTO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.api.report.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * CRM 鍚堝悓鍒嗘瀽缁熻 Response DTO
+ */
+@Schema(description = "RPC 鏈嶅姟 - CRM 鍚堝悓鍒嗘瀽缁熻 Response DTO")
+@Data
+public class CrmContractAnalyzeRespDTO {
+
+ @Schema(description = "鍚堝悓鎬绘暟")
+ private Long totalCount;
+
+ @Schema(description = "鏂板鍚堝悓鏁帮紙浠婃棩锛�")
+ private Long todayCount;
+
+ @Schema(description = "鍚堝悓鎬婚噾棰�")
+ private BigDecimal totalPrice;
+
+ @Schema(description = "瀹℃壒閫氳繃鍚堝悓鏁�")
+ private Long approveCount;
+
+ @Schema(description = "瀹℃壒閫氳繃鍚堝悓閲戦")
+ private BigDecimal approvePrice;
+
+ @Schema(description = "瀹℃壒涓悎鍚屾暟")
+ private Long processCount;
+
+ @Schema(description = "宸插洖娆鹃噾棰�")
+ private BigDecimal receivablePrice;
+
+ @Schema(description = "鍥炴鐜囷紙鐧惧垎姣旓級")
+ private BigDecimal receivableRate;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmCustomerAnalyzeRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmCustomerAnalyzeRespDTO.java
new file mode 100644
index 0000000..6919993
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmCustomerAnalyzeRespDTO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.crm.api.report.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * CRM 瀹㈡埛鍒嗘瀽缁熻 Response DTO
+ */
+@Schema(description = "RPC 鏈嶅姟 - CRM 瀹㈡埛鍒嗘瀽缁熻 Response DTO")
+@Data
+public class CrmCustomerAnalyzeRespDTO {
+
+ @Schema(description = "瀹㈡埛鎬绘暟")
+ private Long totalCount;
+
+ @Schema(description = "鏂板瀹㈡埛鏁帮紙浠婃棩锛�")
+ private Long todayCount;
+
+ @Schema(description = "鎴愪氦瀹㈡埛鏁�")
+ private Long dealCount;
+
+ @Schema(description = "鎴愪氦鐜囷紙鐧惧垎姣旓級")
+ private BigDecimal dealRate;
+
+ @Schema(description = "閿佸畾瀹㈡埛鏁�")
+ private Long lockCount;
+
+ @Schema(description = "鍏捣瀹㈡埛鏁�")
+ private Long poolCount;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesFunnelRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesFunnelRespDTO.java
new file mode 100644
index 0000000..b73eefb
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesFunnelRespDTO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.crm.api.report.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * CRM 閿�鍞紡鏂楃粺璁� Response DTO
+ */
+@Schema(description = "RPC 鏈嶅姟 - CRM 閿�鍞紡鏂楃粺璁� Response DTO")
+@Data
+public class CrmSalesFunnelRespDTO {
+
+ @Schema(description = "婕忔枟闃舵鍒楄〃")
+ private List<FunnelStage> stages;
+
+ @Schema(description = "婕忔枟闃舵")
+ @Data
+ public static class FunnelStage {
+
+ @Schema(description = "闃舵鍚嶇О")
+ private String name;
+
+ @Schema(description = "鏁伴噺")
+ private Long count;
+
+ @Schema(description = "閲戦")
+ private BigDecimal price;
+
+ @Schema(description = "杞寲鐜囷紙鐧惧垎姣旓級")
+ private BigDecimal conversionRate;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesPerformanceRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesPerformanceRespDTO.java
new file mode 100644
index 0000000..2bfd5ba
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/report/dto/CrmSalesPerformanceRespDTO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.crm.api.report.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * CRM 閿�鍞笟缁╃粺璁� Response DTO
+ */
+@Schema(description = "RPC 鏈嶅姟 - CRM 閿�鍞笟缁╃粺璁� Response DTO")
+@Data
+public class CrmSalesPerformanceRespDTO {
+
+ @Schema(description = "閿�鍞汉鍛樹笟缁╁垪琛�")
+ private List<SalesItem> items;
+
+ @Schema(description = "閿�鍞笟缁╅」")
+ @Data
+ public static class SalesItem {
+
+ @Schema(description = "閿�鍞汉鍛樼紪鍙�")
+ private Long userId;
+
+ @Schema(description = "閿�鍞汉鍛樺鍚�")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "鍚堝悓鏁伴噺")
+ private Long contractCount;
+
+ @Schema(description = "鍚堝悓閲戦")
+ private BigDecimal contractPrice;
+
+ @Schema(description = "鍥炴閲戦")
+ private BigDecimal receivablePrice;
+
+ @Schema(description = "鍟嗘満鏁伴噺")
+ private Long businessCount;
+
+ @Schema(description = "鍟嗘満閲戦")
+ private BigDecimal businessPrice;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApi.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApi.java
new file mode 100644
index 0000000..98e20ff
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApi.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.api.trace;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.crm.api.trace.dto.CrmSaleTraceRespDTO;
+
+/**
+ * CRM 閿�鍞拷婧� API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface CrmSaleTraceApi {
+
+ String PREFIX = "/rpc-api/crm/sale-trace";
+
+ /**
+ * 鏍规嵁鍟嗘満缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭�
+ *
+ * @param businessId 鍟嗘満缂栧彿
+ * @return 杩芥函淇℃伅
+ */
+ CommonResult<CrmSaleTraceRespDTO> getTraceByBusinessId(Long businessId);
+
+ /**
+ * 鏍规嵁鍚堝悓缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭�
+ *
+ * @param contractId 鍚堝悓缂栧彿
+ * @return 杩芥函淇℃伅
+ */
+ CommonResult<CrmSaleTraceRespDTO> getTraceByContractId(Long contractId);
+
+ /**
+ * 鏍规嵁瀹㈡埛缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭垪琛�
+ *
+ * @param customerId 瀹㈡埛缂栧彿
+ * @return 杩芥函淇℃伅鍒楄〃
+ */
+ CommonResult<java.util.List<CrmSaleTraceRespDTO>> getTraceListByCustomerId(Long customerId);
+
+}
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/dto/CrmSaleTraceRespDTO.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/dto/CrmSaleTraceRespDTO.java
new file mode 100644
index 0000000..656f723
--- /dev/null
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/trace/dto/CrmSaleTraceRespDTO.java
@@ -0,0 +1,125 @@
+package cn.iocoder.yudao.module.crm.api.trace.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * CRM 閿�鍞祦绋嬭拷婧� Response DTO
+ */
+@Schema(description = "RPC 鏈嶅姟 - CRM 閿�鍞祦绋嬭拷婧� Response DTO")
+@Data
+public class CrmSaleTraceRespDTO {
+
+ // ========== 鍟嗘満淇℃伅 ==========
+
+ @Schema(description = "鍟嗘満缂栧彿")
+ private Long businessId;
+
+ @Schema(description = "鍟嗘満鍚嶇О")
+ private String businessName;
+
+ @Schema(description = "鍟嗘満閲戦")
+ private BigDecimal businessPrice;
+
+ @Schema(description = "鍟嗘満鐘舵��")
+ private String businessStatus;
+
+ @Schema(description = "鍟嗘満鍒涘缓鏃堕棿")
+ private LocalDateTime businessCreateTime;
+
+ // ========== 瀹㈡埛淇℃伅 ==========
+
+ @Schema(description = "瀹㈡埛缂栧彿")
+ private Long customerId;
+
+ @Schema(description = "瀹㈡埛鍚嶇О")
+ private String customerName;
+
+ // ========== 鍚堝悓淇℃伅 ==========
+
+ @Schema(description = "鍚堝悓缂栧彿")
+ private Long contractId;
+
+ @Schema(description = "鍚堝悓鍚嶇О")
+ private String contractName;
+
+ @Schema(description = "鍚堝悓閲戦")
+ private BigDecimal contractPrice;
+
+ @Schema(description = "鍚堝悓鐘舵��")
+ private Integer contractStatus;
+
+ @Schema(description = "鍚堝悓鍒涘缓鏃堕棿")
+ private LocalDateTime contractCreateTime;
+
+ // ========== 閿�鍞鍗曚俊鎭� ==========
+
+ @Schema(description = "閿�鍞鍗曠紪鍙�")
+ private Long orderId;
+
+ @Schema(description = "閿�鍞鍗曞崟鍙�")
+ private String orderNo;
+
+ @Schema(description = "璁㈠崟閲戦")
+ private BigDecimal orderPrice;
+
+ @Schema(description = "璁㈠崟鐘舵��")
+ private Integer orderStatus;
+
+ @Schema(description = "璁㈠崟鍒涘缓鏃堕棿")
+ private LocalDateTime orderCreateTime;
+
+ // ========== 鍑哄簱淇℃伅 ==========
+
+ @Schema(description = "鍑哄簱鍗曠紪鍙�")
+ private Long outId;
+
+ @Schema(description = "鍑哄簱鍗曞彿")
+ private String outNo;
+
+ @Schema(description = "鍑哄簱閲戦")
+ private BigDecimal outPrice;
+
+ @Schema(description = "鍑哄簱鐘舵��")
+ private Integer outStatus;
+
+ @Schema(description = "鍑哄簱鏃堕棿")
+ private LocalDateTime outTime;
+
+ // ========== 鍥炴淇℃伅 ==========
+
+ @Schema(description = "鍥炴閲戦")
+ private BigDecimal receivablePrice;
+
+ @Schema(description = "鍥炴璁板綍鍒楄〃")
+ private List<ReceivableItem> receivableItems;
+
+ // ========== 閫�璐т俊鎭� ==========
+
+ @Schema(description = "閫�璐ч噾棰�")
+ private BigDecimal returnPrice;
+
+ /**
+ * 鍥炴璁板綍椤�
+ */
+ @Data
+ public static class ReceivableItem {
+
+ @Schema(description = "鍥炴缂栧彿")
+ private Long receivableId;
+
+ @Schema(description = "鍥炴鍗曞彿")
+ private String receivableNo;
+
+ @Schema(description = "鍥炴閲戦")
+ private BigDecimal price;
+
+ @Schema(description = "鍥炴鏃堕棿")
+ private LocalDateTime receivableTime;
+ }
+
+}
diff --git a/yudao-module-crm/pom.xml b/yudao-module-crm/pom.xml
index f26c14e..87a8ed6 100644
--- a/yudao-module-crm/pom.xml
+++ b/yudao-module-crm/pom.xml
@@ -18,6 +18,13 @@
</description>
<dependencies>
+ <!-- CRM API 妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-crm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system</artifactId>
@@ -34,6 +41,20 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- ERP 閿�鍞鍗� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-erp-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
new file mode 100644
index 0000000..7aea86c
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.crm.api.customer;
+
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CRM 瀹㈡埛 API 鎺ュ彛
+ *
+ * 鎻愪緵缁欏叾瀹冩ā鍧楋紙濡� ERP锛夎皟鐢�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface CrmCustomerApi {
+
+ /**
+ * 鑾峰緱瀹㈡埛淇℃伅
+ *
+ * @param id 瀹㈡埛缂栧彿
+ * @return 瀹㈡埛淇℃伅
+ */
+ CrmCustomerRespDTO getCustomer(Long id);
+
+ /**
+ * 鑾峰緱瀹㈡埛淇℃伅鍒楄〃
+ *
+ * @param ids 瀹㈡埛缂栧彿鍒楄〃
+ * @return 瀹㈡埛淇℃伅鍒楄〃
+ */
+ List<CrmCustomerRespDTO> getCustomerList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱瀹㈡埛淇℃伅 Map
+ *
+ * @param ids 瀹㈡埛缂栧彿鍒楄〃
+ * @return 瀹㈡埛淇℃伅 Map
+ */
+ Map<Long, CrmCustomerRespDTO> getCustomerMap(Collection<Long> ids);
+
+ /**
+ * 鏍¢獙瀹㈡埛鏄惁瀛樺湪
+ *
+ * @param id 瀹㈡埛缂栧彿
+ * @return 瀹㈡埛淇℃伅锛堟牎楠岄�氳繃锛�
+ */
+ CrmCustomerRespDTO validateCustomer(Long id);
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java
new file mode 100644
index 0000000..f6d901a
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.crm.api.customer;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CRM 瀹㈡埛 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+public class CrmCustomerApiImpl implements CrmCustomerApi {
+
+ @Resource
+ private CrmCustomerService customerService;
+
+ @Override
+ public CrmCustomerRespDTO getCustomer(Long id) {
+ CrmCustomerDO customer = customerService.getCustomer(id);
+ return BeanUtils.toBean(customer, CrmCustomerRespDTO.class);
+ }
+
+ @Override
+ public List<CrmCustomerRespDTO> getCustomerList(Collection<Long> ids) {
+ List<CrmCustomerDO> customers = customerService.getCustomerList(ids);
+ return BeanUtils.toBean(customers, CrmCustomerRespDTO.class);
+ }
+
+ @Override
+ public Map<Long, CrmCustomerRespDTO> getCustomerMap(Collection<Long> ids) {
+ List<CrmCustomerDO> customers = customerService.getCustomerList(ids);
+ return customers.stream()
+ .collect(java.util.stream.Collectors.toMap(
+ CrmCustomerDO::getId,
+ customer -> BeanUtils.toBean(customer, CrmCustomerRespDTO.class)));
+ }
+
+ @Override
+ public CrmCustomerRespDTO validateCustomer(Long id) {
+ customerService.validateCustomer(id);
+ return getCustomer(id);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java
new file mode 100644
index 0000000..d3237c7
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/dto/CrmCustomerRespDTO.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.crm.api.customer.dto;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * CRM 瀹㈡埛 Response DTO
+ *
+ * 鎻愪緵缁欏叾瀹冩ā鍧楋紙濡� ERP锛変娇鐢�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+public class CrmCustomerRespDTO {
+
+ /**
+ * 缂栧彿
+ */
+ private Long id;
+
+ /**
+ * 瀹㈡埛鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鎵嬫満
+ */
+ private String mobile;
+ /**
+ * 鐢佃瘽
+ */
+ private String telephone;
+ /**
+ * QQ
+ */
+ private String qq;
+ /**
+ * wechat
+ */
+ private String wechat;
+ /**
+ * email
+ */
+ private String email;
+
+ /**
+ * 鎵�鍦ㄥ湴
+ */
+ private Integer areaId;
+ /**
+ * 璇︾粏鍦板潃
+ */
+ private String detailAddress;
+
+ /**
+ * 鎵�灞炶涓�
+ */
+ private Integer industryId;
+ /**
+ * 瀹㈡埛绛夌骇
+ */
+ private Integer level;
+ /**
+ * 瀹㈡埛鏉ユ簮
+ */
+ private Integer source;
+
+ /**
+ * 璐熻矗浜虹殑鐢ㄦ埛缂栧彿
+ */
+ private Long ownerUserId;
+ /**
+ * 鎴愪负璐熻矗浜虹殑鏃堕棿
+ */
+ private LocalDateTime ownerTime;
+
+ /**
+ * 閿佸畾鐘舵��
+ */
+ private Boolean lockStatus;
+ /**
+ * 鎴愪氦鐘舵��
+ */
+ private Boolean dealStatus;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApiImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApiImpl.java
new file mode 100644
index 0000000..379c959
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/report/CrmReportApiImpl.java
@@ -0,0 +1,244 @@
+package cn.iocoder.yudao.module.crm.api.report;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmBusinessAnalyzeRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmContractAnalyzeRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmCustomerAnalyzeRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmSalesFunnelRespDTO;
+import cn.iocoder.yudao.module.crm.api.report.dto.CrmSalesPerformanceRespDTO;
+import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
+import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+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.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * CRM 钀ラ攢鎶ヨ〃 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(CrmReportApi.PREFIX)
+@Validated
+public class CrmReportApiImpl implements CrmReportApi {
+
+ @Resource
+ private CrmCustomerMapper customerMapper;
+ @Resource
+ private CrmBusinessMapper businessMapper;
+ @Resource
+ private CrmContractMapper contractMapper;
+ @Resource
+ private CrmReceivableService receivableService;
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+
+ @Override
+ public CommonResult<CrmCustomerAnalyzeRespDTO> getCustomerAnalyze(LocalDateTime startTime, LocalDateTime endTime) {
+ CrmCustomerAnalyzeRespDTO result = new CrmCustomerAnalyzeRespDTO();
+ // 瀹㈡埛鎬绘暟
+ result.setTotalCount(customerMapper.selectCount());
+ // 浠婃棩鏂板
+ result.setTodayCount(customerMapper.selectTodayCount());
+ // 鎴愪氦瀹㈡埛鏁�
+ result.setDealCount(customerMapper.selectDealCount());
+ // 鎴愪氦鐜�
+ if (result.getTotalCount() > 0) {
+ result.setDealRate(BigDecimal.valueOf(result.getDealCount())
+ .divide(BigDecimal.valueOf(result.getTotalCount()), 4, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100)));
+ } else {
+ result.setDealRate(BigDecimal.ZERO);
+ }
+ // 閿佸畾瀹㈡埛鏁�
+ result.setLockCount(customerMapper.selectLockCount());
+ // 鍏捣瀹㈡埛鏁�
+ result.setPoolCount(customerMapper.selectPoolCount());
+ return success(result);
+ }
+
+ @Override
+ public CommonResult<CrmBusinessAnalyzeRespDTO> getBusinessAnalyze(LocalDateTime startTime, LocalDateTime endTime) {
+ CrmBusinessAnalyzeRespDTO result = new CrmBusinessAnalyzeRespDTO();
+ // 鍟嗘満鎬绘暟
+ result.setTotalCount(businessMapper.selectCount());
+ // 浠婃棩鏂板
+ result.setTodayCount(businessMapper.selectTodayCount());
+ // 鍟嗘満鎬婚噾棰�
+ result.setTotalPrice(businessMapper.selectTotalPriceSum());
+ // 鎴愪氦鍟嗘満鏁�
+ result.setSuccessCount(businessMapper.selectSuccessCount());
+ // 鎴愪氦鍟嗘満閲戦
+ result.setSuccessPrice(businessMapper.selectSuccessPriceSum());
+ // 鍟嗘満杞寲鐜�
+ if (result.getTotalCount() > 0) {
+ result.setSuccessRate(BigDecimal.valueOf(result.getSuccessCount())
+ .divide(BigDecimal.valueOf(result.getTotalCount()), 4, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100)));
+ } else {
+ result.setSuccessRate(BigDecimal.ZERO);
+ }
+ // 杩涜涓晢鏈烘暟
+ result.setProcessingCount(businessMapper.selectProcessingCount());
+ // 澶辫触鍟嗘満鏁�
+ result.setFailCount(businessMapper.selectFailCount());
+ return success(result);
+ }
+
+ @Override
+ public CommonResult<CrmContractAnalyzeRespDTO> getContractAnalyze(LocalDateTime startTime, LocalDateTime endTime) {
+ CrmContractAnalyzeRespDTO result = new CrmContractAnalyzeRespDTO();
+ // 鍚堝悓鎬绘暟
+ result.setTotalCount(contractMapper.selectCount());
+ // 浠婃棩鏂板
+ result.setTodayCount(contractMapper.selectTodayCount());
+ // 鍚堝悓鎬婚噾棰�
+ result.setTotalPrice(contractMapper.selectTotalPriceSum());
+ // 瀹℃壒閫氳繃鍚堝悓鏁�
+ result.setApproveCount(contractMapper.selectApproveCount());
+ // 瀹℃壒閫氳繃鍚堝悓閲戦
+ result.setApprovePrice(contractMapper.selectApprovePriceSum());
+ // 瀹℃壒涓悎鍚屾暟
+ result.setProcessCount(contractMapper.selectProcessCount());
+ // 宸插洖娆鹃噾棰�
+ BigDecimal receivablePrice = receivableService.getReceivablePriceSum();
+ result.setReceivablePrice(receivablePrice != null ? receivablePrice : BigDecimal.ZERO);
+ // 鍥炴鐜�
+ if (result.getApprovePrice() != null && result.getApprovePrice().compareTo(BigDecimal.ZERO) > 0) {
+ result.setReceivableRate(result.getReceivablePrice()
+ .divide(result.getApprovePrice(), 4, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100)));
+ } else {
+ result.setReceivableRate(BigDecimal.ZERO);
+ }
+ return success(result);
+ }
+
+ @Override
+ public CommonResult<CrmSalesFunnelRespDTO> getSalesFunnel() {
+ CrmSalesFunnelRespDTO result = new CrmSalesFunnelRespDTO();
+ List<CrmSalesFunnelRespDTO.FunnelStage> stages = new ArrayList<>();
+
+ // 闃舵1锛氱嚎绱�/瀹㈡埛
+ Long customerCount = customerMapper.selectCount();
+ stages.add(createStage("瀹㈡埛", customerCount, BigDecimal.ZERO));
+
+ // 闃舵2锛氬晢鏈�
+ Long businessCount = businessMapper.selectCount();
+ BigDecimal businessPrice = businessMapper.selectTotalPriceSum();
+ stages.add(createStage("鍟嗘満", businessCount, businessPrice));
+
+ // 闃舵3锛氬悎鍚�
+ Long contractCount = contractMapper.selectApproveCount();
+ BigDecimal contractPrice = contractMapper.selectApprovePriceSum();
+ stages.add(createStage("鍚堝悓", contractCount, contractPrice));
+
+ // 闃舵4锛氬洖娆�
+ BigDecimal receivablePrice = receivableService.getReceivablePriceSum();
+ stages.add(createStage("鍥炴", null, receivablePrice));
+
+ // 璁$畻杞寲鐜�
+ for (int i = 0; i < stages.size(); i++) {
+ if (i == 0) {
+ stages.get(i).setConversionRate(BigDecimal.valueOf(100));
+ } else if (customerCount > 0 && stages.get(i).getCount() != null) {
+ stages.get(i).setConversionRate(BigDecimal.valueOf(stages.get(i).getCount())
+ .divide(BigDecimal.valueOf(customerCount), 4, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100)));
+ } else {
+ stages.get(i).setConversionRate(BigDecimal.ZERO);
+ }
+ }
+
+ result.setStages(stages);
+ return success(result);
+ }
+
+ @Override
+ public CommonResult<CrmSalesPerformanceRespDTO> getSalesPerformance(LocalDateTime startTime, LocalDateTime endTime) {
+ CrmSalesPerformanceRespDTO result = new CrmSalesPerformanceRespDTO();
+ List<CrmSalesPerformanceRespDTO.SalesItem> items = new ArrayList<>();
+
+ // 鑾峰彇鎵�鏈夐攢鍞汉鍛橈紙鏈夊悎鍚屾垨鍟嗘満鐨勮礋璐d汉锛�
+ List<Long> userIds = contractMapper.selectOwnerUserIds();
+ userIds.addAll(businessMapper.selectOwnerUserIds());
+ userIds = userIds.stream().distinct().collect(Collectors.toList());
+
+ if (CollUtil.isEmpty(userIds)) {
+ result.setItems(items);
+ return success(result);
+ }
+
+ // 鑾峰彇鐢ㄦ埛淇℃伅
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+
+ // 鑾峰彇閮ㄩ棬淇℃伅
+ List<Long> deptIds = userMap.values().stream()
+ .map(AdminUserRespDTO::getDeptId)
+ .filter(id -> id != null)
+ .distinct()
+ .collect(Collectors.toList());
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+
+ for (Long userId : userIds) {
+ AdminUserRespDTO user = userMap.get(userId);
+ if (user == null) {
+ continue;
+ }
+
+ CrmSalesPerformanceRespDTO.SalesItem item = new CrmSalesPerformanceRespDTO.SalesItem();
+ item.setUserId(userId);
+ item.setUserName(user.getNickname());
+ // 鑾峰彇閮ㄩ棬鍚嶇О
+ if (user.getDeptId() != null) {
+ DeptRespDTO dept = deptMap.get(user.getDeptId());
+ item.setDeptName(dept != null ? dept.getName() : "");
+ }
+
+ // 鍚堝悓鏁伴噺鍜岄噾棰�
+ item.setContractCount(contractMapper.selectCountByOwnerUserId(userId));
+ item.setContractPrice(contractMapper.selectPriceSumByOwnerUserId(userId));
+
+ // 鍥炴閲戦
+ item.setReceivablePrice(receivableService.getReceivablePriceByOwnerUserId(userId));
+
+ // 鍟嗘満鏁伴噺鍜岄噾棰�
+ item.setBusinessCount(businessMapper.selectCountByOwnerUserId(userId));
+ item.setBusinessPrice(businessMapper.selectPriceSumByOwnerUserId(userId));
+
+ items.add(item);
+ }
+
+ result.setItems(items);
+ return success(result);
+ }
+
+ private CrmSalesFunnelRespDTO.FunnelStage createStage(String name, Long count, BigDecimal price) {
+ CrmSalesFunnelRespDTO.FunnelStage stage = new CrmSalesFunnelRespDTO.FunnelStage();
+ stage.setName(name);
+ stage.setCount(count);
+ stage.setPrice(price != null ? price : BigDecimal.ZERO);
+ return stage;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApiImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApiImpl.java
new file mode 100644
index 0000000..c460c89
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/trace/CrmSaleTraceApiImpl.java
@@ -0,0 +1,190 @@
+package cn.iocoder.yudao.module.crm.api.trace;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.crm.api.trace.dto.CrmSaleTraceRespDTO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
+import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
+import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
+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.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * CRM 閿�鍞拷婧� API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(CrmSaleTraceApi.PREFIX)
+@Validated
+public class CrmSaleTraceApiImpl implements CrmSaleTraceApi {
+
+ @Resource
+ private CrmBusinessMapper businessMapper;
+ @Resource
+ private CrmContractMapper contractMapper;
+ @Resource
+ private CrmCustomerService customerService;
+ @Resource
+ private CrmReceivableService receivableService;
+ @Resource
+ private ErpSaleOrderApi saleOrderApi;
+
+ @Override
+ public CommonResult<CrmSaleTraceRespDTO> getTraceByBusinessId(Long businessId) {
+ CrmBusinessDO business = businessMapper.selectById(businessId);
+ if (business == null) {
+ return success(null);
+ }
+ CrmSaleTraceRespDTO trace = buildBaseTrace(business.getCustomerId());
+
+ // 濉厖鍟嗘満淇℃伅
+ trace.setBusinessId(business.getId());
+ trace.setBusinessName(business.getName());
+ trace.setBusinessPrice(business.getTotalPrice());
+ trace.setBusinessCreateTime(business.getCreateTime());
+
+ // 鏌ヨ鍏宠仈鍚堝悓
+ CrmContractDO contract = contractMapper.selectByBusinessId(businessId);
+ if (contract != null) {
+ fillContractInfo(trace, contract);
+ // 鏌ヨ鍏宠仈璁㈠崟
+ if (contract.getOrderId() != null) {
+ fillOrderInfo(trace, contract.getOrderId());
+ }
+ }
+
+ return success(trace);
+ }
+
+ @Override
+ public CommonResult<CrmSaleTraceRespDTO> getTraceByContractId(Long contractId) {
+ CrmContractDO contract = contractMapper.selectById(contractId);
+ if (contract == null) {
+ return success(null);
+ }
+ CrmSaleTraceRespDTO trace = buildBaseTrace(contract.getCustomerId());
+
+ // 濉厖鍚堝悓淇℃伅
+ fillContractInfo(trace, contract);
+
+ // 濉厖鍟嗘満淇℃伅
+ if (contract.getBusinessId() != null) {
+ CrmBusinessDO business = businessMapper.selectById(contract.getBusinessId());
+ if (business != null) {
+ trace.setBusinessId(business.getId());
+ trace.setBusinessName(business.getName());
+ trace.setBusinessPrice(business.getTotalPrice());
+ trace.setBusinessCreateTime(business.getCreateTime());
+ }
+ }
+
+ // 鏌ヨ鍏宠仈璁㈠崟
+ if (contract.getOrderId() != null) {
+ fillOrderInfo(trace, contract.getOrderId());
+ }
+
+ return success(trace);
+ }
+
+ @Override
+ public CommonResult<List<CrmSaleTraceRespDTO>> getTraceListByCustomerId(Long customerId) {
+ CrmCustomerDO customer = customerService.getCustomer(customerId);
+ if (customer == null) {
+ return success(new ArrayList<>());
+ }
+
+ // 鏌ヨ瀹㈡埛鎵�鏈夊悎鍚�
+ List<CrmContractDO> contracts = contractMapper.selectListByCustomerId(customerId);
+ if (CollUtil.isEmpty(contracts)) {
+ return success(new ArrayList<>());
+ }
+
+ List<CrmSaleTraceRespDTO> traces = new ArrayList<>();
+ for (CrmContractDO contract : contracts) {
+ CrmSaleTraceRespDTO trace = new CrmSaleTraceRespDTO();
+ trace.setCustomerId(customerId);
+ trace.setCustomerName(customer.getName());
+
+ fillContractInfo(trace, contract);
+
+ // 濉厖鍟嗘満淇℃伅
+ if (contract.getBusinessId() != null) {
+ CrmBusinessDO business = businessMapper.selectById(contract.getBusinessId());
+ if (business != null) {
+ trace.setBusinessId(business.getId());
+ trace.setBusinessName(business.getName());
+ trace.setBusinessPrice(business.getTotalPrice());
+ trace.setBusinessCreateTime(business.getCreateTime());
+ }
+ }
+
+ // 鏌ヨ鍏宠仈璁㈠崟
+ if (contract.getOrderId() != null) {
+ fillOrderInfo(trace, contract.getOrderId());
+ }
+
+ traces.add(trace);
+ }
+
+ return success(traces);
+ }
+
+ /**
+ * 鏋勫缓鍩虹杩芥函淇℃伅
+ */
+ private CrmSaleTraceRespDTO buildBaseTrace(Long customerId) {
+ CrmSaleTraceRespDTO trace = new CrmSaleTraceRespDTO();
+ if (customerId != null) {
+ CrmCustomerDO customer = customerService.getCustomer(customerId);
+ if (customer != null) {
+ trace.setCustomerId(customerId);
+ trace.setCustomerName(customer.getName());
+ }
+ }
+ return trace;
+ }
+
+ /**
+ * 濉厖鍚堝悓淇℃伅
+ */
+ private void fillContractInfo(CrmSaleTraceRespDTO trace, CrmContractDO contract) {
+ trace.setContractId(contract.getId());
+ trace.setContractName(contract.getName());
+ trace.setContractPrice(contract.getTotalPrice());
+ trace.setContractStatus(contract.getAuditStatus());
+ trace.setContractCreateTime(contract.getCreateTime());
+
+ // 璁$畻鍥炴閲戦
+ BigDecimal receivablePrice = receivableService.getReceivablePriceByContractId(contract.getId());
+ trace.setReceivablePrice(receivablePrice != null ? receivablePrice : BigDecimal.ZERO);
+ }
+
+ /**
+ * 濉厖璁㈠崟淇℃伅
+ */
+ private void fillOrderInfo(CrmSaleTraceRespDTO trace, Long orderId) {
+ ErpSaleOrderRespDTO order = saleOrderApi.getSaleOrder(orderId).getCheckedData();
+ if (order != null) {
+ trace.setOrderId(orderId);
+ trace.setOrderNo(order.getNo());
+ trace.setOrderPrice(order.getTotalPrice());
+ trace.setOrderStatus(order.getStatus());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
index d94c856..c00b813 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
@@ -87,6 +87,12 @@
@ExcelProperty("鍟嗘満鎬婚噾棰�")
private BigDecimal totalPrice;
+ @Schema(description = "鍏宠仈 ERP 閿�鍞鍗曠紪鍙�", example = "12345")
+ private Long orderId;
+ @Schema(description = "ERP 閿�鍞鍗曞崟鍙�", example = "SO001")
+ @ExcelProperty("閿�鍞鍗曞崟鍙�")
+ private String orderNo;
+
@Schema(description = "澶囨敞", example = "闅忎究")
@ExcelProperty("澶囨敞")
private String remark;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
index b9fd295..a2a70b4 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -253,4 +253,12 @@
.setTotalReceivablePrice(receivablePriceMap.getOrDefault(contract.getId(), BigDecimal.ZERO))));
}
+ @PostMapping("/generate-sale-order")
+ @Operation(summary = "鏍规嵁鍚堝悓鐢熸垚閿�鍞鍗�")
+ @Parameter(name = "id", description = "鍚堝悓缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('crm:contract:update')")
+ public CommonResult<Long> generateSaleOrder(@RequestParam("id") Long id) {
+ return success(contractService.generateSaleOrder(id, getLoginUserId()));
+ }
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
index 307a747..ca5f36e 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
@@ -92,6 +92,12 @@
@ExcelProperty("宸插洖娆鹃噾棰�")
private BigDecimal totalReceivablePrice;
+ @Schema(description = "鍏宠仈 ERP 閿�鍞鍗曠紪鍙�", example = "12345")
+ private Long orderId;
+ @Schema(description = "ERP 閿�鍞鍗曞崟鍙�", example = "SO001")
+ @ExcelProperty("閿�鍞鍗曞崟鍙�")
+ private String orderNo;
+
@Schema(description = "瀹㈡埛绛剧害浜虹紪鍙�", example = "18546")
private Long signContactId;
@Schema(description = "瀹㈡埛绛剧害浜�", example = "灏忚眴")
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java
index a30ece2..cea6882 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java
@@ -104,6 +104,14 @@
*/
private BigDecimal totalPrice;
/**
+ * 鍏宠仈 ERP 閿�鍞鍗曠紪鍙�
+ */
+ private Long orderId;
+ /**
+ * ERP 閿�鍞鍗曞崟鍙�
+ */
+ private String orderNo;
+ /**
* 澶囨敞
*/
private String remark;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
index 89f4fd9..e7e3bae 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
@@ -104,6 +104,14 @@
*/
private BigDecimal totalPrice;
/**
+ * 鍏宠仈 ERP 閿�鍞鍗曠紪鍙�
+ */
+ private Long orderId;
+ /**
+ * ERP 閿�鍞鍗曞崟鍙�
+ */
+ private String orderNo;
+ /**
* 瀹㈡埛绛剧害浜猴紝闈炲繀椤�
*
* 鍏宠仈 {@link CrmContactDO#getId()}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
index cc6d598..6d1814d 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.dal.mysql.business;
+import cn.hutool.core.date.LocalDateTimeUtil;
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;
@@ -7,11 +8,14 @@
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -75,4 +79,70 @@
.betweenIfPresent(CrmBusinessDO::getCreateTime, pageVO.getTimes()));
}
+ // ========== 缁熻鏂规硶 ==========
+
+ default Long selectTodayCount() {
+ LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
+ LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
+ return selectCount(new LambdaQueryWrapperX<CrmBusinessDO>()
+ .between(CrmBusinessDO::getCreateTime, beginOfToday, endOfToday));
+ }
+
+ default BigDecimal selectTotalPriceSum() {
+ LambdaQueryWrapperX<CrmBusinessDO> query = new LambdaQueryWrapperX<>();
+ query.select(CrmBusinessDO::getTotalPrice);
+ List<CrmBusinessDO> list = selectList(query);
+ return list.stream()
+ .map(CrmBusinessDO::getTotalPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ default Long selectSuccessCount() {
+ return selectCount(new LambdaQueryWrapperX<CrmBusinessDO>()
+ .eq(CrmBusinessDO::getEndStatus, CrmBusinessEndStatusEnum.WIN.getStatus()));
+ }
+
+ default BigDecimal selectSuccessPriceSum() {
+ LambdaQueryWrapperX<CrmBusinessDO> query = new LambdaQueryWrapperX<>();
+ query.eq(CrmBusinessDO::getEndStatus, CrmBusinessEndStatusEnum.WIN.getStatus())
+ .select(CrmBusinessDO::getTotalPrice);
+ List<CrmBusinessDO> list = selectList(query);
+ return list.stream()
+ .map(CrmBusinessDO::getTotalPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ default Long selectProcessingCount() {
+ return selectCount(new LambdaQueryWrapperX<CrmBusinessDO>()
+ .isNull(CrmBusinessDO::getEndStatus));
+ }
+
+ default Long selectFailCount() {
+ return selectCount(new LambdaQueryWrapperX<CrmBusinessDO>()
+ .eq(CrmBusinessDO::getEndStatus, CrmBusinessEndStatusEnum.LOSE.getStatus()));
+ }
+
+ default List<Long> selectOwnerUserIds() {
+ LambdaQueryWrapperX<CrmBusinessDO> query = new LambdaQueryWrapperX<>();
+ query.select(CrmBusinessDO::getOwnerUserId).isNotNull(CrmBusinessDO::getOwnerUserId);
+ List<CrmBusinessDO> list = selectList(query);
+ return list.stream().map(CrmBusinessDO::getOwnerUserId).distinct().toList();
+ }
+
+ default Long selectCountByOwnerUserId(Long ownerUserId) {
+ return selectCount(CrmBusinessDO::getOwnerUserId, ownerUserId);
+ }
+
+ default BigDecimal selectPriceSumByOwnerUserId(Long ownerUserId) {
+ LambdaQueryWrapperX<CrmBusinessDO> query = new LambdaQueryWrapperX<>();
+ query.eq(CrmBusinessDO::getOwnerUserId, ownerUserId).select(CrmBusinessDO::getTotalPrice);
+ List<CrmBusinessDO> list = selectList(query);
+ return list.stream()
+ .map(CrmBusinessDO::getTotalPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
index 1303112..a61c925 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
@@ -14,6 +14,7 @@
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import org.apache.ibatis.annotations.Mapper;
+import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@@ -113,4 +114,71 @@
.eq(CrmContractDO::getOwnerUserId, ownerUserId));
}
+ default CrmContractDO selectByBusinessId(Long businessId) {
+ return selectOne(CrmContractDO::getBusinessId, businessId);
+ }
+
+ default List<CrmContractDO> selectListByCustomerId(Long customerId) {
+ return selectList(CrmContractDO::getCustomerId, customerId);
+ }
+
+ // ========== 缁熻鏂规硶 ==========
+
+ default Long selectTodayCount() {
+ LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
+ LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
+ return selectCount(new LambdaQueryWrapperX<CrmContractDO>()
+ .between(CrmContractDO::getCreateTime, beginOfToday, endOfToday));
+ }
+
+ default BigDecimal selectTotalPriceSum() {
+ LambdaQueryWrapperX<CrmContractDO> query = new LambdaQueryWrapperX<>();
+ query.select(CrmContractDO::getTotalPrice);
+ List<CrmContractDO> list = selectList(query);
+ return list.stream()
+ .map(CrmContractDO::getTotalPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ default Long selectApproveCount() {
+ return selectCount(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus());
+ }
+
+ default BigDecimal selectApprovePriceSum() {
+ LambdaQueryWrapperX<CrmContractDO> query = new LambdaQueryWrapperX<>();
+ query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
+ .select(CrmContractDO::getTotalPrice);
+ List<CrmContractDO> list = selectList(query);
+ return list.stream()
+ .map(CrmContractDO::getTotalPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ default Long selectProcessCount() {
+ return selectCount(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus());
+ }
+
+ default List<Long> selectOwnerUserIds() {
+ LambdaQueryWrapperX<CrmContractDO> query = new LambdaQueryWrapperX<>();
+ query.select(CrmContractDO::getOwnerUserId).isNotNull(CrmContractDO::getOwnerUserId);
+ List<CrmContractDO> list = selectList(query);
+ return list.stream().map(CrmContractDO::getOwnerUserId).distinct().toList();
+ }
+
+ default Long selectCountByOwnerUserId(Long ownerUserId) {
+ return selectCount(CrmContractDO::getOwnerUserId, ownerUserId);
+ }
+
+ default BigDecimal selectPriceSumByOwnerUserId(Long ownerUserId) {
+ LambdaQueryWrapperX<CrmContractDO> query = new LambdaQueryWrapperX<>();
+ query.eq(CrmContractDO::getOwnerUserId, ownerUserId).select(CrmContractDO::getTotalPrice);
+ List<CrmContractDO> list = selectList(query);
+ return list.stream()
+ .map(CrmContractDO::getTotalPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
index 2322603..e6a1991 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -180,4 +180,26 @@
return selectCount(query);
}
+ // ========== 缁熻鏂规硶 ==========
+
+ default Long selectTodayCount() {
+ LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
+ LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
+ return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
+ .between(CrmCustomerDO::getCreateTime, beginOfToday, endOfToday));
+ }
+
+ default Long selectDealCount() {
+ return selectCount(CrmCustomerDO::getDealStatus, true);
+ }
+
+ default Long selectLockCount() {
+ return selectCount(CrmCustomerDO::getLockStatus, true);
+ }
+
+ default Long selectPoolCount() {
+ return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
+ .isNull(CrmCustomerDO::getOwnerUserId));
+ }
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
index 904357e..433648b 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
@@ -94,4 +94,35 @@
return selectCount(CrmReceivableDO::getContractId, contractId);
}
+ // ========== 缁熻鏂规硶 ==========
+
+ default BigDecimal selectReceivablePriceSum() {
+ LambdaQueryWrapperX<CrmReceivableDO> query = new LambdaQueryWrapperX<>();
+ query.in(CrmReceivableDO::getAuditStatus,
+ CrmAuditStatusEnum.DRAFT.getStatus(),
+ CrmAuditStatusEnum.PROCESS.getStatus(),
+ CrmAuditStatusEnum.APPROVE.getStatus())
+ .select(CrmReceivableDO::getPrice);
+ List<CrmReceivableDO> list = selectList(query);
+ return list.stream()
+ .map(CrmReceivableDO::getPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ default BigDecimal selectReceivablePriceSumByOwnerUserId(Long ownerUserId) {
+ LambdaQueryWrapperX<CrmReceivableDO> query = new LambdaQueryWrapperX<>();
+ query.eq(CrmReceivableDO::getOwnerUserId, ownerUserId)
+ .in(CrmReceivableDO::getAuditStatus,
+ CrmAuditStatusEnum.DRAFT.getStatus(),
+ CrmAuditStatusEnum.PROCESS.getStatus(),
+ CrmAuditStatusEnum.APPROVE.getStatus())
+ .select(CrmReceivableDO::getPrice);
+ List<CrmReceivableDO> list = selectList(query);
+ return list.stream()
+ .map(CrmReceivableDO::getPrice)
+ .filter(p -> p != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index 5c1eb8d..fa1c157 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -16,6 +16,9 @@
ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "鏇存柊鍚堝悓瀹℃牳鐘舵�佸け璐ワ紝鍘熷洜锛氬悎鍚屼笉鏄鏍镐腑鐘舵��");
ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "鐢熸垚鍚堝悓搴忓垪鍙烽噸澶嶏紝璇烽噸璇�");
ErrorCode CONTRACT_DELETE_FAIL = new ErrorCode(1_020_000_005, "鍒犻櫎鍚堝悓澶辫触锛屽師鍥狅細鏈夎鍥炴鎵�浣跨敤");
+ ErrorCode CONTRACT_GENERATE_ORDER_FAIL_NOT_APPROVE = new ErrorCode(1_020_000_006, "鐢熸垚閿�鍞鍗曞け璐ワ紝鍘熷洜锛氬悎鍚屾湭瀹℃壒閫氳繃");
+ ErrorCode CONTRACT_GENERATE_ORDER_FAIL_EXISTS_ORDER = new ErrorCode(1_020_000_007, "鐢熸垚閿�鍞鍗曞け璐ワ紝鍘熷洜锛氬凡鍏宠仈閿�鍞鍗曘�恵}銆�");
+ ErrorCode CONTRACT_GENERATE_ORDER_FAIL_NO_PRODUCT = new ErrorCode(1_020_000_008, "鐢熸垚閿�鍞鍗曞け璐ワ紝鍘熷洜锛氬悎鍚屾病鏈変骇鍝佷俊鎭�");
// ========== 绾跨储绠$悊 1-020-001-000 ==========
ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "绾跨储涓嶅瓨鍦�");
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
index 0d8964f..a3e6ba0 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
@@ -202,4 +202,13 @@
*/
List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
+ /**
+ * 鏍规嵁鍚堝悓鐢熸垚閿�鍞鍗�
+ *
+ * @param id 鍚堝悓缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 閿�鍞鍗曠紪鍙�
+ */
+ Long generateSaleOrder(Long id, Long userId);
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index d25d7c0..b997a26 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -31,6 +31,9 @@
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
+import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderCreateReqDTO;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
@@ -95,6 +98,8 @@
private AdminUserApi adminUserApi;
@Resource
private BpmProcessInstanceApi bpmProcessInstanceApi;
+ @Resource
+ private ErpSaleOrderApi saleOrderApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -413,4 +418,66 @@
return contractMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @LogRecord(type = CRM_CONTRACT_TYPE, subType = "鐢熸垚閿�鍞鍗�", bizNo = "{{#id}}",
+ success = "鍚堝悓銆恵{#contractName}}銆戠敓鎴愰攢鍞鍗曘�恵{#orderNo}}銆戞垚鍔�")
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
+ public Long generateSaleOrder(Long id, Long userId) {
+ // 1. 鏍¢獙鍚堝悓瀛樺湪涓斿凡瀹℃壒閫氳繃
+ CrmContractDO contract = validateContractExists(id);
+ if (!CrmAuditStatusEnum.APPROVE.getStatus().equals(contract.getAuditStatus())) {
+ throw exception(CONTRACT_GENERATE_ORDER_FAIL_NOT_APPROVE);
+ }
+ // 2. 鏍¢獙鏄惁宸茬敓鎴愰攢鍞鍗�
+ ErpSaleOrderRespDTO existOrder = saleOrderApi.getSaleOrderByContractId(id).getCheckedData();
+ if (existOrder != null) {
+ throw exception(CONTRACT_GENERATE_ORDER_FAIL_EXISTS_ORDER, existOrder.getNo());
+ }
+
+ // 3. 鑾峰彇鍚堝悓浜у搧鍒楄〃
+ List<CrmContractProductDO> contractProducts = contractProductMapper.selectListByContractId(id);
+ if (CollUtil.isEmpty(contractProducts)) {
+ throw exception(CONTRACT_GENERATE_ORDER_FAIL_NO_PRODUCT);
+ }
+
+ // 4. 鏋勫缓閿�鍞鍗曞垱寤鸿姹�
+ ErpSaleOrderCreateReqDTO createReqDTO = new ErpSaleOrderCreateReqDTO();
+ createReqDTO.setCustomerId(contract.getCustomerId());
+ createReqDTO.setContractId(contract.getId());
+ createReqDTO.setContractNo(contract.getNo());
+ createReqDTO.setSaleUserId(contract.getOwnerUserId());
+ createReqDTO.setOrderTime(LocalDateTime.now().toString());
+ createReqDTO.setDiscountPercent(contract.getDiscountPercent());
+ createReqDTO.setRemark("鐢卞悎鍚屻��" + contract.getNo() + "銆戣嚜鍔ㄧ敓鎴�");
+
+ // 5. 鏋勫缓璁㈠崟椤�
+ List<ErpSaleOrderCreateReqDTO.Item> items = contractProducts.stream()
+ .map(cp -> {
+ ErpSaleOrderCreateReqDTO.Item item = new ErpSaleOrderCreateReqDTO.Item();
+ item.setProductId(cp.getProductId());
+ item.setProductPrice(cp.getContractPrice());
+ item.setCount(cp.getCount());
+ return item;
+ })
+ .toList();
+ createReqDTO.setItems(items);
+
+ // 6. 鍒涘缓閿�鍞鍗�
+ Long orderId = saleOrderApi.createSaleOrder(createReqDTO).getCheckedData();
+ ErpSaleOrderRespDTO order = saleOrderApi.getSaleOrder(orderId).getCheckedData();
+
+ // 7. 鏇存柊鍚堝悓鍏宠仈璁㈠崟淇℃伅
+ contractMapper.updateById(new CrmContractDO()
+ .setId(id)
+ .setOrderId(orderId)
+ .setOrderNo(order.getNo()));
+
+ // 8. 璁板綍鎿嶄綔鏃ュ織
+ LogRecordContext.putVariable("contractName", contract.getName());
+ LogRecordContext.putVariable("orderNo", order.getNo());
+
+ return orderId;
+ }
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
index e45c4db..04dc3f5 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.service.product;
import cn.hutool.core.collection.CollUtil;
+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.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
@@ -14,6 +15,11 @@
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+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.enums.MdmItemTypeEnum;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
@@ -23,10 +29,8 @@
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.Map;
+import java.util.*;
+import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -36,6 +40,11 @@
/**
* CRM 浜у搧 Service 瀹炵幇绫�
+ *
+ * 鏀归�犺鏄庯細浜у搧鏁版嵁缁熶竴浣跨敤 MDM 涓绘暟鎹鐞嗭紙itemType=2 浜у搧绫诲瀷锛�
+ * - 鍒涘缓銆佹洿鏂版搷浣滈�氳繃 MDM API 杩涜
+ * - 鏌ヨ鎿嶄綔浠� MDM 鑾峰彇鏁版嵁
+ * - CRM 浜у搧琛ㄤ繚鐣欑敤浜庢潈闄愮鐞�
*
* @author ZanGe涓�
*/
@@ -54,6 +63,9 @@
@Resource
private AdminUserApi adminUserApi;
+ @Resource
+ private MdmItemApi mdmItemApi;
+
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_CREATE_SUB_TYPE, bizNo = "{{#productId}}",
@@ -64,16 +76,38 @@
validateProductNoDuplicate(null, createReqVO.getNo());
validateProductCategoryExists(createReqVO.getCategoryId());
- // 2. 鎻掑叆浜у搧
- CrmProductDO product = BeanUtils.toBean(createReqVO, CrmProductDO.class);
+ // 2. 閫氳繃 MDM API 鍒涘缓浜у搧
+ MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
+ createReqDTO.setCode(createReqVO.getNo());
+ createReqDTO.setName(createReqVO.getName());
+ createReqDTO.setBarCode(createReqVO.getNo()); // 浣跨敤缂栫爜浣滀负鏉$爜
+ createReqDTO.setCategoryId(createReqVO.getCategoryId());
+ createReqDTO.setUnitMeasureId(createReqVO.getUnit() != null ? createReqVO.getUnit().longValue() : null);
+ createReqDTO.setItemType(MdmItemTypeEnum.PRODUCT.getType()); // 浜у搧绫诲瀷
+ createReqDTO.setStatus(createReqVO.getStatus() != null ? createReqVO.getStatus() : CommonStatusEnum.ENABLE.getStatus());
+ createReqDTO.setSalesPrice(createReqVO.getPrice());
+ createReqDTO.setRemark(createReqVO.getDescription());
+ Long itemId = mdmItemApi.createItem(createReqDTO).getCheckedData();
+
+ // 3. 鎻掑叆鏈湴浜у搧璁板綍锛堢敤浜庢潈闄愮鐞嗭級
+ CrmProductDO product = new CrmProductDO();
+ product.setId(itemId);
+ product.setName(createReqVO.getName());
+ product.setNo(createReqVO.getNo());
+ product.setUnit(createReqVO.getUnit());
+ product.setPrice(createReqVO.getPrice());
+ product.setStatus(createReqVO.getStatus());
+ product.setCategoryId(createReqVO.getCategoryId());
+ product.setDescription(createReqVO.getDescription());
+ product.setOwnerUserId(createReqVO.getOwnerUserId());
productMapper.insert(product);
- // 3. 鎻掑叆鏁版嵁鏉冮檺
+ // 4. 鎻掑叆鏁版嵁鏉冮檺
permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(product.getOwnerUserId())
.setBizType(CrmBizTypeEnum.CRM_PRODUCT.getType()).setBizId(product.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
- // 4. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+ // 5. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
LogRecordContext.putVariable("productId", product.getId());
return product.getId();
}
@@ -89,26 +123,47 @@
validateProductNoDuplicate(updateReqVO.getId(), updateReqVO.getNo());
validateProductCategoryExists(updateReqVO.getCategoryId());
- // 2. 鏇存柊浜у搧
+ // 2. 閫氳繃 MDM API 鏇存柊浜у搧
+ MdmItemUpdateReqDTO updateReqDTO = new MdmItemUpdateReqDTO();
+ updateReqDTO.setId(updateReqVO.getId());
+ updateReqDTO.setCode(updateReqVO.getNo());
+ updateReqDTO.setName(updateReqVO.getName());
+ updateReqDTO.setBarCode(updateReqVO.getNo());
+ updateReqDTO.setCategoryId(updateReqVO.getCategoryId());
+ updateReqDTO.setUnitMeasureId(updateReqVO.getUnit() != null ? updateReqVO.getUnit().longValue() : null);
+ updateReqDTO.setItemType(MdmItemTypeEnum.PRODUCT.getType());
+ updateReqDTO.setStatus(updateReqVO.getStatus());
+ updateReqDTO.setSalesPrice(updateReqVO.getPrice());
+ updateReqDTO.setRemark(updateReqVO.getDescription());
+ mdmItemApi.updateItem(updateReqDTO);
+
+ // 3. 鏇存柊鏈湴浜у搧璁板綍
CrmProductDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductDO.class);
productMapper.updateById(updateObj);
- // 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+ // 4. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(crmProductDO, CrmProductSaveReqVO.class));
}
private CrmProductDO validateProductExists(Long id) {
+ // 浠� MDM 鏍¢獙
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
+ throw exception(PRODUCT_NOT_EXISTS);
+ }
+ // 浠庢湰鍦拌幏鍙� CRM 浜у搧璁板綍
CrmProductDO product = productMapper.selectById(id);
if (product == null) {
- throw exception(PRODUCT_NOT_EXISTS);
+ // 濡傛灉鏈湴涓嶅瓨鍦紝鍒涘缓涓�涓悓姝ヨ褰�
+ product = convertToCrmProduct(item);
+ productMapper.insert(product);
}
return product;
}
private void validateProductNoDuplicate(Long id, String no) {
- CrmProductDO product = productMapper.selectByNo(no);
- if (product == null
- || product.getId().equals(id)) {
+ MdmItemRespDTO item = mdmItemApi.getItemByCode(no).getCheckedData();
+ if (item == null || item.getId().equals(id)) {
return;
}
throw exception(PRODUCT_NO_EXISTS);
@@ -128,29 +183,65 @@
public void deleteProduct(Long id) {
// 鏍¢獙瀛樺湪
validateProductExists(id);
- // 鍒犻櫎
+ // 閫氳繃 MDM API 鍒犻櫎锛堟洿鏂扮姸鎬佷负绂佺敤锛�
+ mdmItemApi.updateItemStatus(id, CommonStatusEnum.DISABLE.getStatus());
+ // 鍒犻櫎鏈湴璁板綍
productMapper.deleteById(id);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmProductDO getProduct(Long id) {
- return productMapper.selectById(id);
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null || !MdmItemTypeEnum.PRODUCT.getType().equals(item.getItemType())) {
+ return null;
+ }
+ // 鑾峰彇鏈湴 CRM 浜у搧璁板綍锛堢敤浜庢潈闄愶級
+ CrmProductDO product = productMapper.selectById(id);
+ if (product == null) {
+ // 鍚屾鍒涘缓鏈湴璁板綍
+ product = convertToCrmProduct(item);
+ productMapper.insert(product);
+ } else {
+ // 鏇存柊鏈湴璁板綍鐨勬暟鎹�
+ product.setName(item.getName());
+ product.setNo(item.getCode());
+ product.setPrice(item.getSalesPrice());
+ product.setStatus(item.getStatus());
+ product.setCategoryId(item.getCategoryId());
+ product.setDescription(item.getRemark());
+ }
+ return product;
}
@Override
public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO) {
- return productMapper.selectPage(pageReqVO);
+ // 浠� MDM 鑾峰彇浜у搧鍒嗛〉鏁版嵁
+ cn.iocoder.yudao.framework.common.pojo.PageResult<MdmItemRespDTO> mdmPageResult =
+ mdmItemApi.getItemPage(MdmItemTypeEnum.PRODUCT.getType(), null,
+ pageReqVO.getPageNo(), pageReqVO.getPageSize()).getCheckedData();
+ // 杞崲涓� CRM 浜у搧
+ List<CrmProductDO> list = mdmPageResult.getList().stream()
+ .map(this::convertToCrmProduct)
+ .collect(Collectors.toList());
+ return new PageResult<>(list, mdmPageResult.getTotal());
}
@Override
public Long getProductByCategoryId(Long categoryId) {
- return productMapper.selectCountByCategoryId(categoryId);
+ return mdmItemApi.getItemCountByCategoryId(categoryId).getCheckedData();
}
@Override
public List<CrmProductDO> getProductListByStatus(Integer status) {
- return productMapper.selectListByStatus(status);
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁
+ List<MdmItemRespDTO> items = mdmItemApi.getItemListByStatus(status).getCheckedData();
+ // 杩囨护浜у搧绫诲瀷骞惰浆鎹�
+ return items.stream()
+ .filter(item -> MdmItemTypeEnum.PRODUCT.getType().equals(item.getItemType()))
+ .map(this::convertToCrmProduct)
+ .collect(Collectors.toList());
}
@Override
@@ -158,18 +249,20 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- List<CrmProductDO> list = productMapper.selectByIds(ids);
- Map<Long, CrmProductDO> productMap = convertMap(list, CrmProductDO::getId);
+ List<MdmItemRespDTO> items = mdmItemApi.getItemList(ids).getCheckedData();
+ Map<Long, MdmItemRespDTO> itemMap = convertMap(items, MdmItemRespDTO::getId);
+ List<CrmProductDO> result = new ArrayList<>();
for (Long id : ids) {
- CrmProductDO product = productMap.get(id);
- if (productMap.get(id) == null) {
+ MdmItemRespDTO item = itemMap.get(id);
+ if (item == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
- if (CrmProductStatusEnum.isDisable(product.getStatus())) {
- throw exception(PRODUCT_NOT_ENABLE, product.getName());
+ if (CrmProductStatusEnum.isDisable(item.getStatus())) {
+ throw exception(PRODUCT_NOT_ENABLE, item.getName());
}
+ result.add(convertToCrmProduct(item));
}
- return list;
+ return result;
}
@Override
@@ -177,7 +270,27 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- return productMapper.selectByIds(ids);
+ List<MdmItemRespDTO> items = mdmItemApi.getItemList(ids).getCheckedData();
+ return items.stream()
+ .filter(item -> MdmItemTypeEnum.PRODUCT.getType().equals(item.getItemType()))
+ .map(this::convertToCrmProduct)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 灏� MDM 鐗╂枡杞崲涓� CRM 浜у搧
+ */
+ private CrmProductDO convertToCrmProduct(MdmItemRespDTO item) {
+ CrmProductDO product = new CrmProductDO();
+ product.setId(item.getId());
+ product.setName(item.getName());
+ product.setNo(item.getCode());
+ product.setUnit(item.getUnitMeasureId() != null ? item.getUnitMeasureId().intValue() : null);
+ product.setPrice(item.getSalesPrice());
+ product.setStatus(item.getStatus());
+ product.setCategoryId(item.getCategoryId());
+ product.setDescription(item.getRemark());
+ return product;
}
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
index f6ac8c3..bd0d5dc 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
@@ -130,4 +130,27 @@
*/
Long getReceivableCountByContractId(Long contractId);
+ /**
+ * 鏍规嵁鍚堝悓缂栧彿鏌ヨ宸插洖娆鹃噾棰�
+ *
+ * @param contractId 鍚堝悓缂栧彿
+ * @return 宸插洖娆鹃噾棰�
+ */
+ BigDecimal getReceivablePriceByContractId(Long contractId);
+
+ /**
+ * 鑾峰彇鎬诲洖娆鹃噾棰�
+ *
+ * @return 鎬诲洖娆鹃噾棰�
+ */
+ BigDecimal getReceivablePriceSum();
+
+ /**
+ * 鏍规嵁璐熻矗浜烘煡璇㈠洖娆鹃噾棰�
+ *
+ * @param ownerUserId 璐熻矗浜虹紪鍙�
+ * @return 鍥炴閲戦
+ */
+ BigDecimal getReceivablePriceByOwnerUserId(Long ownerUserId);
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
index 642c8e6..0c68f1e 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -307,4 +307,21 @@
return receivableMapper.selectCountByContractId(contractId);
}
+ @Override
+ public BigDecimal getReceivablePriceByContractId(Long contractId) {
+ Map<Long, BigDecimal> priceMap = receivableMapper.selectReceivablePriceMapByContractId(
+ java.util.Collections.singleton(contractId));
+ return priceMap.getOrDefault(contractId, BigDecimal.ZERO);
+ }
+
+ @Override
+ public BigDecimal getReceivablePriceSum() {
+ return receivableMapper.selectReceivablePriceSum();
+ }
+
+ @Override
+ public BigDecimal getReceivablePriceByOwnerUserId(Long ownerUserId) {
+ return receivableMapper.selectReceivablePriceSumByOwnerUserId(ownerUserId);
+ }
+
}
diff --git a/yudao-module-erp-api/pom.xml b/yudao-module-erp-api/pom.xml
new file mode 100644
index 0000000..7386f6a
--- /dev/null
+++ b/yudao-module-erp-api/pom.xml
@@ -0,0 +1,33 @@
+<?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-erp-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ erp 妯″潡涓嬶紝浼佷笟璧勬簮绠$悊鐨� API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApi.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApi.java
new file mode 100644
index 0000000..cdc79e4
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApi.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.erp.api.sale;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderCreateReqDTO;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
+
+/**
+ * ERP 閿�鍞鍗� API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface ErpSaleOrderApi {
+
+ String PREFIX = "/rpc-api/erp/sale-order";
+
+ /**
+ * 鍒涘缓閿�鍞鍗�
+ *
+ * @param createReqDTO 鍒涘缓淇℃伅
+ * @return 璁㈠崟缂栧彿
+ */
+ CommonResult<Long> createSaleOrder(ErpSaleOrderCreateReqDTO createReqDTO);
+
+ /**
+ * 鑾峰彇閿�鍞鍗�
+ *
+ * @param id 璁㈠崟缂栧彿
+ * @return 閿�鍞鍗�
+ */
+ CommonResult<ErpSaleOrderRespDTO> getSaleOrder(Long id);
+
+ /**
+ * 鏍规嵁鍚堝悓缂栧彿鑾峰彇閿�鍞鍗�
+ *
+ * @param contractId 鍚堝悓缂栧彿
+ * @return 閿�鍞鍗�
+ */
+ CommonResult<ErpSaleOrderRespDTO> getSaleOrderByContractId(Long contractId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderCreateReqDTO.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderCreateReqDTO.java
new file mode 100644
index 0000000..14042b8
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderCreateReqDTO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.erp.api.sale.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "RPC 鏈嶅姟 - ERP 閿�鍞鍗曞垱寤� Request DTO")
+@Data
+public class ErpSaleOrderCreateReqDTO {
+
+ @Schema(description = "瀹㈡埛缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long customerId;
+
+ @Schema(description = "鍏宠仈鍚堝悓缂栧彿", example = "1")
+ private Long contractId;
+
+ @Schema(description = "鍚堝悓鍗曞彿", example = "HT001")
+ private String contractNo;
+
+ @Schema(description = "閿�鍞憳缂栧彿", example = "1")
+ private Long saleUserId;
+
+ @Schema(description = "缁撶畻璐︽埛缂栧彿", example = "1")
+ private Long accountId;
+
+ @Schema(description = "涓嬪崟鏃堕棿")
+ private String orderTime;
+
+ @Schema(description = "浼樻儬鐜囷紝鐧惧垎姣�", example = "10")
+ private BigDecimal discountPercent;
+
+ @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", example = "100")
+ private BigDecimal depositPrice;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ @Schema(description = "璁㈠崟椤瑰垪琛�")
+ private java.util.List<Item> items;
+
+ @Schema(description = "璁㈠崟椤�")
+ @Data
+ public static class Item {
+
+ @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long productId;
+
+ @Schema(description = "浜у搧鍗曚綅缂栧彿", example = "1")
+ private Long productUnitId;
+
+ @Schema(description = "浜у搧鍗曚环锛屽崟浣嶏細鍏�", example = "100")
+ private BigDecimal productPrice;
+
+ @Schema(description = "鏁伴噺", example = "10")
+ private BigDecimal count;
+
+ @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮", example = "10")
+ private BigDecimal taxPercent;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞")
+ private String remark;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderRespDTO.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderRespDTO.java
new file mode 100644
index 0000000..1557099
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/sale/dto/ErpSaleOrderRespDTO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.api.sale.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "RPC 鏈嶅姟 - ERP 閿�鍞鍗� Response DTO")
+@Data
+public class ErpSaleOrderRespDTO {
+
+ @Schema(description = "璁㈠崟缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "閿�鍞鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "SO001")
+ private String no;
+
+ @Schema(description = "瀹㈡埛缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long customerId;
+
+ @Schema(description = "鍏宠仈鍚堝悓缂栧彿", example = "1")
+ private Long contractId;
+
+ @Schema(description = "鍚堝悓鍗曞彿", example = "HT001")
+ private String contractNo;
+
+ @Schema(description = "璁㈠崟鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍚堣閲戦锛屽崟浣嶏細鍏�", example = "1000")
+ private java.math.BigDecimal totalPrice;
+
+}
diff --git a/yudao-module-erp/pom.xml b/yudao-module-erp/pom.xml
index ce377ff..ec7a802 100644
--- a/yudao-module-erp/pom.xml
+++ b/yudao-module-erp/pom.xml
@@ -24,6 +24,34 @@
<version>${revision}</version>
</dependency>
+ <!-- BPM 宸ヤ綔娴� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-bpm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- CRM 瀹㈡埛 API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-crm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- ERP 閿�鍞鍗� API锛堟湰妯″潡 API锛� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-erp-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- MDM 涓绘暟鎹鐞� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApiImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApiImpl.java
new file mode 100644
index 0000000..4e902eb
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/sale/ErpSaleOrderApiImpl.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.erp.api.sale;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderCreateReqDTO;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper;
+import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * ERP 閿�鍞鍗� API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(ErpSaleOrderApi.PREFIX)
+@Validated
+public class ErpSaleOrderApiImpl implements ErpSaleOrderApi {
+
+ @Resource
+ private ErpSaleOrderService saleOrderService;
+
+ @Resource
+ private ErpSaleOrderMapper saleOrderMapper;
+
+ @Override
+ public CommonResult<Long> createSaleOrder(ErpSaleOrderCreateReqDTO createReqDTO) {
+ // 杞崲涓� SaveReqVO
+ ErpSaleOrderSaveReqVO saveReqVO = BeanUtils.toBean(createReqDTO, ErpSaleOrderSaveReqVO.class);
+ // 杞崲璁㈠崟椤�
+ if (createReqDTO.getItems() != null) {
+ saveReqVO.setItems(createReqDTO.getItems().stream()
+ .map(item -> BeanUtils.toBean(item, ErpSaleOrderSaveReqVO.Item.class))
+ .toList());
+ }
+ return success(saleOrderService.createSaleOrder(saveReqVO));
+ }
+
+ @Override
+ public CommonResult<ErpSaleOrderRespDTO> getSaleOrder(Long id) {
+ ErpSaleOrderDO order = saleOrderService.getSaleOrder(id);
+ return success(BeanUtils.toBean(order, ErpSaleOrderRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<ErpSaleOrderRespDTO> getSaleOrderByContractId(Long contractId) {
+ ErpSaleOrderDO order = saleOrderMapper.selectByContractId(contractId);
+ return success(BeanUtils.toBean(order, ErpSaleOrderRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java
index 04f9068..0c3f727 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java
@@ -67,8 +67,8 @@
@Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:product:query')")
public CommonResult<ErpProductRespVO> getProduct(@RequestParam("id") Long id) {
- ErpProductDO product = productService.getProduct(id);
- return success(BeanUtils.toBean(product, ErpProductRespVO.class));
+ ErpProductRespVO product = productService.getProductVO(id);
+ return success(product);
}
@GetMapping("/page")
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
new file mode 100644
index 0000000..49d540e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -0,0 +1,210 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestRespVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
+import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+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.servlet.http.HttpServletResponse;
+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.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+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.convertMultiMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDeptId;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠")
+@RestController
+@RequestMapping("/erp/purchase-request")
+@Validated
+public class ErpPurchaseRequestController {
+
+ @Resource
+ private ErpPurchaseRequestService purchaseRequestService;
+ @Resource
+ private ErpProductService productService;
+ @Resource
+ private ErpSupplierService supplierService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓閲囪喘鐢宠")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:create')")
+ public CommonResult<Long> createPurchaseRequest(@Valid @RequestBody ErpPurchaseRequestSaveReqVO createReqVO) {
+ return success(purchaseRequestService.createPurchaseRequest(createReqVO, getLoginUserId(), getLoginUserDeptId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊閲囪喘鐢宠")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:update')")
+ public CommonResult<Boolean> updatePurchaseRequest(@Valid @RequestBody ErpPurchaseRequestSaveReqVO updateReqVO) {
+ purchaseRequestService.updatePurchaseRequest(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/submit")
+ @Operation(summary = "鎻愪氦閲囪喘鐢宠瀹℃壒")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:submit')")
+ public CommonResult<Boolean> submitPurchaseRequest(@RequestParam("id") Long id,
+ @RequestParam("processDefinitionKey") String processDefinitionKey) {
+ purchaseRequestService.submitPurchaseRequest(id, processDefinitionKey, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎閲囪喘鐢宠")
+ @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:delete')")
+ public CommonResult<Boolean> deletePurchaseRequest(@RequestParam("ids") List<Long> ids) {
+ purchaseRequestService.deletePurchaseRequest(ids);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱閲囪喘鐢宠")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
+ public CommonResult<ErpPurchaseRequestRespVO> getPurchaseRequest(@RequestParam("id") Long id) {
+ ErpPurchaseRequestDO purchaseRequest = purchaseRequestService.getPurchaseRequest(id);
+ if (purchaseRequest == null) {
+ return success(null);
+ }
+ List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestId(id);
+ Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
+ convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
+ return success(BeanUtils.toBean(purchaseRequest, ErpPurchaseRequestRespVO.class, vo -> {
+ vo.setItems(BeanUtils.toBean(purchaseRequestItemList, ErpPurchaseRequestRespVO.Item.class, item ->
+ MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
+ .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
+ // 濉厖鍏宠仈瀛楁
+ fillPurchaseRequestRelatedFields(vo);
+ }));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱閲囪喘鐢宠鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
+ public CommonResult<PageResult<ErpPurchaseRequestRespVO>> getPurchaseRequestPage(@Valid ErpPurchaseRequestPageReqVO pageReqVO) {
+ PageResult<ErpPurchaseRequestDO> pageResult = purchaseRequestService.getPurchaseRequestPage(pageReqVO);
+ return success(buildPurchaseRequestVOPageResult(pageResult));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭閲囪喘鐢宠 Excel")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportPurchaseRequestExcel(@Valid ErpPurchaseRequestPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<ErpPurchaseRequestRespVO> list = buildPurchaseRequestVOPageResult(
+ purchaseRequestService.getPurchaseRequestPage(pageReqVO)).getList();
+ ExcelUtils.write(response, "閲囪喘鐢宠.xls", "鏁版嵁", ErpPurchaseRequestRespVO.class, list);
+ }
+
+ @PostMapping("/generate-order")
+ @Operation(summary = "鐢熸垚閲囪喘璁㈠崟")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:generate-order')")
+ public CommonResult<Long> generatePurchaseOrder(@RequestParam("id") Long id,
+ @RequestParam("supplierId") Long supplierId) {
+ return success(purchaseRequestService.generatePurchaseOrder(id, supplierId, getLoginUserId()));
+ }
+
+ @GetMapping("/approve-process-list")
+ @Operation(summary = "鑾峰彇閲囪喘鐢宠瀹℃壒娴佺▼鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
+ public CommonResult<List<Map<String, Object>>> getPurchaseRequestApproveProcessList() {
+ return success(purchaseRequestService.getPurchaseRequestApproveProcessDefinitionList());
+ }
+
+ private PageResult<ErpPurchaseRequestRespVO> buildPurchaseRequestVOPageResult(PageResult<ErpPurchaseRequestDO> pageResult) {
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return PageResult.empty(pageResult.getTotal());
+ }
+ // 1.1 鐢宠鏄庣粏
+ List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestIds(
+ convertSet(pageResult.getList(), ErpPurchaseRequestDO::getId));
+ Map<Long, List<ErpPurchaseRequestItemDO>> purchaseRequestItemMap = convertMultiMap(purchaseRequestItemList, ErpPurchaseRequestItemDO::getRequestId);
+ // 1.2 浜у搧淇℃伅
+ Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
+ convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
+ // 1.3 渚涘簲鍟嗕俊鎭�
+ Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
+ convertSet(pageResult.getList(), ErpPurchaseRequestDO::getSupplierId));
+ // 1.4 鐢ㄦ埛淇℃伅
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+ convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestUserId));
+ // 1.5 閮ㄩ棬淇℃伅
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+ convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestDeptId));
+
+ // 2. 寮�濮嬫嫾鎺�
+ return BeanUtils.toBean(pageResult, ErpPurchaseRequestRespVO.class, vo -> {
+ vo.setItems(BeanUtils.toBean(purchaseRequestItemMap.get(vo.getId()), ErpPurchaseRequestRespVO.Item.class,
+ item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
+ .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
+ vo.setProductNames(CollUtil.join(vo.getItems(), "锛�", ErpPurchaseRequestRespVO.Item::getProductName));
+ MapUtils.findAndThen(supplierMap, vo.getSupplierId(), supplier -> vo.setSupplierName(supplier.getName()));
+ MapUtils.findAndThen(userMap, vo.getRequestUserId(), user -> vo.setRequestUserName(user.getNickname()));
+ MapUtils.findAndThen(deptMap, vo.getRequestDeptId(), dept -> vo.setRequestDeptName(dept.getName()));
+ });
+ }
+
+ private void fillPurchaseRequestRelatedFields(ErpPurchaseRequestRespVO vo) {
+ // 濉厖鐢宠浜哄悕绉�
+ if (vo.getRequestUserId() != null) {
+ AdminUserRespDTO user = adminUserApi.getUser(vo.getRequestUserId());
+ if (user != null) {
+ vo.setRequestUserName(user.getNickname());
+ }
+ }
+ // 濉厖閮ㄩ棬鍚嶇О
+ if (vo.getRequestDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(vo.getRequestDeptId());
+ if (dept != null) {
+ vo.setRequestDeptName(dept.getName());
+ }
+ }
+ // 濉厖渚涘簲鍟嗗悕绉�
+ if (vo.getSupplierId() != null) {
+ ErpSupplierDO supplier = supplierService.getSupplier(vo.getSupplierId());
+ if (supplier != null) {
+ vo.setSupplierName(supplier.getName());
+ }
+ }
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
new file mode 100644
index 0000000..e67c4c5
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
+
+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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ErpPurchaseRequestPageReqVO extends PageParam {
+
+ @Schema(description = "閲囪喘鐢宠鍗曞彿", example = "CGSQ001")
+ private String no;
+
+ @Schema(description = "鐢宠浜虹紪鍙�", example = "1")
+ private Long requestUserId;
+
+ @Schema(description = "鐢宠閮ㄩ棬缂栧彿", example = "1")
+ private Long requestDeptId;
+
+ @Schema(description = "鐢宠鏃堕棿")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] requestTime;
+
+ @Schema(description = "瀹℃壒鐘舵��", example = "20")
+ private Integer status;
+
+ @Schema(description = "鐢宠鐞嗙敱", example = "鐢熶骇闇�瑕�")
+ private String requestReason;
+
+ @Schema(description = "浜у搧缂栧彿", example = "1")
+ private Long productId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
new file mode 100644
index 0000000..47ee75e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -0,0 +1,156 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ErpPurchaseRequestRespVO {
+
+ @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
+ @ExcelProperty("缂栧彿")
+ private Long id;
+
+ @Schema(description = "閲囪喘鐢宠鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "CGSQ001")
+ @ExcelProperty("閲囪喘鐢宠鍗曞彿")
+ private String no;
+
+ @Schema(description = "瀹℃壒鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+ @ExcelProperty("瀹℃壒鐘舵��")
+ private Integer status;
+
+ @Schema(description = "鐢宠浜虹紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long requestUserId;
+ @Schema(description = "鐢宠浜哄悕绉�", example = "寮犱笁")
+ @ExcelProperty("鐢宠浜�")
+ private String requestUserName;
+
+ @Schema(description = "鐢宠閮ㄩ棬缂栧彿", example = "1")
+ private Long requestDeptId;
+ @Schema(description = "鐢宠閮ㄩ棬鍚嶇О", example = "鐢熶骇閮�")
+ @ExcelProperty("鐢宠閮ㄩ棬")
+ private String requestDeptName;
+
+ @Schema(description = "鐢宠鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("鐢宠鏃堕棿")
+ private LocalDateTime requestTime;
+
+ @Schema(description = "鐢宠鐞嗙敱", example = "鐢熶骇闇�瑕�")
+ @ExcelProperty("鐢宠鐞嗙敱")
+ private String requestReason;
+
+ @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "1724")
+ private Long supplierId;
+ @Schema(description = "渚涘簲鍟嗗悕绉�", example = "渚涘簲鍟咥")
+ @ExcelProperty("渚涘簲鍟嗗悕绉�")
+ private String supplierName;
+
+ @Schema(description = "鍚堣鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ @ExcelProperty("鍚堣鏁伴噺")
+ private BigDecimal totalCount;
+
+ @Schema(description = "鍚堣浜у搧浠锋牸", example = "10000.00")
+ private BigDecimal totalProductPrice;
+
+ @Schema(description = "鍚堣绋庨", example = "1300.00")
+ private BigDecimal totalTaxPrice;
+
+ @Schema(description = "鏈�缁堝悎璁′环鏍�", requiredMode = Schema.RequiredMode.REQUIRED, example = "11300.00")
+ @ExcelProperty("鏈�缁堝悎璁′环鏍�")
+ private BigDecimal totalPrice;
+
+ @Schema(description = "浼樻儬鐜囷紝鐧惧垎姣�", example = "10.00")
+ private BigDecimal discountPercent;
+
+ @Schema(description = "浼樻儬閲戦", example = "1130.00")
+ private BigDecimal discountPrice;
+
+ @Schema(description = "宸ヤ綔娴佺紪鍙�", example = "xxx-xxx")
+ private String processInstanceId;
+
+ @Schema(description = "鍏宠仈閲囪喘璁㈠崟缂栧彿", example = "100")
+ private Long orderId;
+ @Schema(description = "鍏宠仈閲囪喘璁㈠崟鍗曞彿", example = "CGDD001")
+ @ExcelProperty("閲囪喘璁㈠崟鍗曞彿")
+ private String orderNo;
+
+ @Schema(description = "鐢熸垚閲囪喘璁㈠崟鏃堕棿")
+ private LocalDateTime orderTime;
+
+ @Schema(description = "闄勪欢鍦板潃", example = "https://www.iocoder.cn")
+ @ExcelProperty("闄勪欢鍦板潃")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞", example = "浣犵寽")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓浜�", example = "1")
+ private String creator;
+ @Schema(description = "鍒涘缓浜哄悕绉�", example = "鑺嬮亾")
+ private String creatorName;
+
+ @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ private LocalDateTime createTime;
+
+ @Schema(description = "鐢宠鏄庣粏鍒楄〃")
+ private List<Item> items;
+
+ @Schema(description = "浜у搧淇℃伅")
+ @ExcelProperty("浜у搧淇℃伅")
+ private String productNames;
+
+ @Data
+ public static class Item {
+
+ @Schema(description = "鐢宠椤圭紪鍙�", example = "11756")
+ private Long id;
+
+ @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
+ private Long productId;
+
+ @Schema(description = "浜у搧鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
+ private Long productUnitId;
+
+ @Schema(description = "浜у搧鍗曚环锛堝弬鑰冧环锛�", example = "100.00")
+ private BigDecimal productPrice;
+
+ @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal count;
+
+ @Schema(description = "鎬讳环", example = "10000.00")
+ private BigDecimal totalPrice;
+
+ @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮", example = "13.00")
+ private BigDecimal taxPercent;
+
+ @Schema(description = "绋庨", example = "1300.00")
+ private BigDecimal taxPrice;
+
+ @Schema(description = "闇�姹傛棩鏈�", example = "2024-01-01")
+ private LocalDate demandTime;
+
+ @Schema(description = "澶囨敞", example = "闅忎究")
+ private String remark;
+
+ // ========== 鍏宠仈瀛楁 ==========
+
+ @Schema(description = "浜у搧鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "宸у厠鍔�")
+ private String productName;
+ @Schema(description = "浜у搧鏉$爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
+ private String productBarCode;
+ @Schema(description = "浜у搧鍗曚綅鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鐩�")
+ private String productUnitName;
+
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
new file mode 100644
index 0000000..d1a01ad
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠鏂板/淇敼 Request VO")
+@Data
+public class ErpPurchaseRequestSaveReqVO {
+
+ @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
+ private Long id;
+
+ @Schema(description = "鐢宠鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐢宠鏃堕棿涓嶈兘涓虹┖")
+ private LocalDateTime requestTime;
+
+ @Schema(description = "鐢宠鐞嗙敱", example = "鐢熶骇闇�瑕�")
+ private String requestReason;
+
+ @Schema(description = "渚涘簲鍟嗙紪鍙凤紙鍙�夛級", example = "1724")
+ private Long supplierId;
+
+ @Schema(description = "浼樻儬鐜囷紝鐧惧垎姣�", example = "99.88")
+ private BigDecimal discountPercent;
+
+ @Schema(description = "闄勪欢鍦板潃", example = "https://www.iocoder.cn")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞", example = "浣犵寽")
+ private String remark;
+
+ @Schema(description = "鐢宠鏄庣粏鍒楄〃")
+ @NotEmpty(message = "鐢宠鏄庣粏鍒楄〃涓嶈兘涓虹┖")
+ @Valid
+ private List<Item> items;
+
+ @Data
+ public static class Item {
+
+ @Schema(description = "鐢宠椤圭紪鍙�", example = "11756")
+ private Long id;
+
+ @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
+ @NotNull(message = "浜у搧缂栧彿涓嶈兘涓虹┖")
+ private Long productId;
+
+ @Schema(description = "浜у搧鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
+ @NotNull(message = "浜у搧鍗曚綅缂栧彿涓嶈兘涓虹┖")
+ private Long productUnitId;
+
+ @Schema(description = "浜у搧鍗曚环锛堝弬鑰冧环锛�", example = "100.00")
+ private BigDecimal productPrice;
+
+ @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ @NotNull(message = "鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal count;
+
+ @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮", example = "13.00")
+ private BigDecimal taxPercent;
+
+ @Schema(description = "闇�姹傛棩鏈�", example = "2024-01-01")
+ private LocalDate demandTime;
+
+ @Schema(description = "澶囨敞", example = "闅忎究")
+ private String remark;
+
+ }
+
+}
\ No newline at end of file
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 eee8cd9..383a7d9 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
@@ -8,15 +8,15 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
-import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
-import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -54,7 +54,7 @@
@Resource
private ErpProductService productService;
@Resource
- private ErpCustomerService customerService;
+ private CrmCustomerApi customerApi;
@Resource
private AdminUserApi adminUserApi;
@@ -144,8 +144,8 @@
// 1.2 浜у搧淇℃伅
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId));
- // 1.3 瀹㈡埛淇℃伅
- Map<Long, ErpCustomerDO> customerMap = customerService.getCustomerMap(
+ // 1.3 瀹㈡埛淇℃伅锛堜娇鐢� CRM 瀹㈡埛 API锛�
+ Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(
convertSet(pageResult.getList(), ErpSaleOrderDO::getCustomerId));
// 1.4 绠$悊鍛樹俊鎭�
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
index e23a1fa..95dcd7a 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
@@ -19,6 +19,12 @@
@NotNull(message = "瀹㈡埛缂栧彿涓嶈兘涓虹┖")
private Long customerId;
+ @Schema(description = "鍏宠仈鍚堝悓缂栧彿", example = "1")
+ private Long contractId;
+
+ @Schema(description = "鍚堝悓鍗曞彿", example = "HT001")
+ private String contractNo;
+
@Schema(description = "涓嬪崟鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "涓嬪崟鏃堕棿涓嶈兘涓虹┖")
private LocalDateTime orderTime;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java
index 46a5595..624ee2e 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java
@@ -53,7 +53,7 @@
/**
* 瀹㈡埛缂栧彿
*
- * 鍏宠仈 {@link ErpCustomerDO#getId()}
+ * 鍏宠仈 CRM 瀹㈡埛 {@link cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO#getId()}
*/
private Long customerId;
/**
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
new file mode 100644
index 0000000..cb51196
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
@@ -0,0 +1,122 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
+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;
+
+/**
+ * ERP 閲囪喘鐢宠 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName(value = "erp_purchase_request")
+@KeySequence("erp_purchase_request_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpPurchaseRequestDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+ /**
+ * 閲囪喘鐢宠鍗曞彿
+ */
+ private String no;
+ /**
+ * 瀹℃壒鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 鐢宠浜虹紪鍙�
+ */
+ private Long requestUserId;
+ /**
+ * 鐢宠閮ㄩ棬缂栧彿
+ */
+ private Long requestDeptId;
+ /**
+ * 鐢宠鏃堕棿
+ */
+ private LocalDateTime requestTime;
+ /**
+ * 鐢宠鐞嗙敱
+ */
+ private String requestReason;
+
+ /**
+ * 渚涘簲鍟嗙紪鍙�
+ *
+ * 鍏宠仈 {@link ErpSupplierDO#getId()}
+ */
+ private Long supplierId;
+
+ /**
+ * 鍚堣鏁伴噺
+ */
+ private BigDecimal totalCount;
+ /**
+ * 鍚堣浜у搧浠锋牸锛屽崟浣嶏細鍏�
+ */
+ private BigDecimal totalProductPrice;
+ /**
+ * 鍚堣绋庨锛屽崟浣嶏細鍏�
+ */
+ private BigDecimal totalTaxPrice;
+ /**
+ * 鏈�缁堝悎璁′环鏍硷紝鍗曚綅锛氬厓
+ *
+ * totalPrice = totalProductPrice + totalTaxPrice - discountPrice
+ */
+ private BigDecimal totalPrice;
+ /**
+ * 浼樻儬鐜囷紝鐧惧垎姣�
+ */
+ private BigDecimal discountPercent;
+ /**
+ * 浼樻儬閲戦锛屽崟浣嶏細鍏�
+ *
+ * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent
+ */
+ private BigDecimal discountPrice;
+
+ /**
+ * 宸ヤ綔娴佺紪鍙�
+ */
+ private String processInstanceId;
+
+ /**
+ * 鍏宠仈閲囪喘璁㈠崟缂栧彿锛堝鎵归�氳繃鍚庣敓鎴愶級
+ *
+ * 鍏宠仈 {@link ErpPurchaseOrderDO#getId()}
+ */
+ private Long orderId;
+ /**
+ * 鐢熸垚閲囪喘璁㈠崟鏃堕棿
+ */
+ private LocalDateTime orderTime;
+
+ /**
+ * 闄勪欢鍦板潃
+ */
+ private String fileUrl;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
new file mode 100644
index 0000000..e5fcb71
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
+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.LocalDate;
+
+/**
+ * ERP 閲囪喘鐢宠鏄庣粏 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("erp_purchase_request_items")
+@KeySequence("erp_purchase_request_items_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpPurchaseRequestItemDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+ /**
+ * 閲囪喘鐢宠缂栧彿
+ *
+ * 鍏宠仈 {@link ErpPurchaseRequestDO#getId()}
+ */
+ private Long requestId;
+ /**
+ * 浜у搧缂栧彿
+ *
+ * 鍏宠仈 {@link ErpProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 浜у搧鍗曚綅缂栧彿
+ *
+ * 鍐椾綑 {@link ErpProductDO#getUnitId()}
+ */
+ private Long productUnitId;
+
+ /**
+ * 浜у搧鍗曚环锛堝弬鑰冧环锛夛紝鍗曚綅锛氬厓
+ */
+ private BigDecimal productPrice;
+ /**
+ * 鏁伴噺
+ */
+ private BigDecimal count;
+ /**
+ * 鎬讳环锛屽崟浣嶏細鍏�
+ *
+ * totalPrice = productPrice * count
+ */
+ private BigDecimal totalPrice;
+ /**
+ * 绋庣巼锛岀櫨鍒嗘瘮
+ */
+ private BigDecimal taxPercent;
+ /**
+ * 绋庨锛屽崟浣嶏細鍏�
+ *
+ * taxPrice = totalPrice * taxPercent
+ */
+ private BigDecimal taxPrice;
+
+ /**
+ * 闇�姹傛棩鏈�
+ */
+ private LocalDate demandTime;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
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..77dfa18 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
@@ -43,10 +43,18 @@
/**
* 瀹㈡埛缂栧彿
*
- * 鍏宠仈 {@link ErpCustomerDO#getId()}
+ * 鍏宠仈 CRM 瀹㈡埛 {@link cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO#getId()}
*/
private Long customerId;
/**
+ * 鍏宠仈 CRM 鍚堝悓缂栧彿
+ */
+ private Long contractId;
+ /**
+ * CRM 鍚堝悓鍗曞彿
+ */
+ private String contractNo;
+ /**
* 缁撶畻璐︽埛缂栧彿
*
* 鍏宠仈 {@link ErpAccountDO#getId()}
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..117cebd 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
@@ -43,7 +43,7 @@
/**
* 瀹㈡埛缂栧彿
*
- * 鍏宠仈 {@link ErpCustomerDO#getId()}
+ * 鍏宠仈 CRM 瀹㈡埛 {@link cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO#getId()}
*/
private Long customerId;
/**
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java
index ba41ac9..10f0dff 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java
@@ -43,7 +43,7 @@
/**
* 瀹㈡埛缂栧彿
*
- * 鍏宠仈 {@link ErpCustomerDO#getId()}
+ * 鍏宠仈 CRM 瀹㈡埛 {@link cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO#getId()}
*/
private Long customerId;
/**
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java
new file mode 100644
index 0000000..b08e726
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘鐢宠鏄庣粏 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface ErpPurchaseRequestItemMapper extends BaseMapperX<ErpPurchaseRequestItemDO> {
+
+ default List<ErpPurchaseRequestItemDO> selectListByRequestId(Long requestId) {
+ return selectList(ErpPurchaseRequestItemDO::getRequestId, requestId);
+ }
+
+ default List<ErpPurchaseRequestItemDO> selectListByRequestIds(Collection<Long> requestIds) {
+ return selectList(ErpPurchaseRequestItemDO::getRequestId, requestIds);
+ }
+
+ default int deleteByRequestId(Long requestId) {
+ return delete(ErpPurchaseRequestItemDO::getRequestId, requestId);
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
new file mode 100644
index 0000000..d44d171
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
+
+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.MPJLambdaWrapperX;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * ERP 閲囪喘鐢宠 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface ErpPurchaseRequestMapper extends BaseMapperX<ErpPurchaseRequestDO> {
+
+ default PageResult<ErpPurchaseRequestDO> selectPage(ErpPurchaseRequestPageReqVO reqVO) {
+ MPJLambdaWrapperX<ErpPurchaseRequestDO> query = new MPJLambdaWrapperX<ErpPurchaseRequestDO>()
+ .likeIfPresent(ErpPurchaseRequestDO::getNo, reqVO.getNo())
+ .eqIfPresent(ErpPurchaseRequestDO::getRequestUserId, reqVO.getRequestUserId())
+ .eqIfPresent(ErpPurchaseRequestDO::getRequestDeptId, reqVO.getRequestDeptId())
+ .betweenIfPresent(ErpPurchaseRequestDO::getRequestTime, reqVO.getRequestTime())
+ .eqIfPresent(ErpPurchaseRequestDO::getStatus, reqVO.getStatus())
+ .likeIfPresent(ErpPurchaseRequestDO::getRequestReason, reqVO.getRequestReason())
+ .orderByDesc(ErpPurchaseRequestDO::getId);
+ if (reqVO.getProductId() != null) {
+ query.leftJoin(ErpPurchaseRequestItemDO.class, ErpPurchaseRequestItemDO::getRequestId, ErpPurchaseRequestDO::getId)
+ .eq(ErpPurchaseRequestItemDO::getProductId, reqVO.getProductId())
+ .groupBy(ErpPurchaseRequestDO::getId);
+ }
+ return selectJoinPage(reqVO, ErpPurchaseRequestDO.class, query);
+ }
+
+ default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseRequestDO updateObj) {
+ return update(updateObj, new LambdaUpdateWrapper<ErpPurchaseRequestDO>()
+ .eq(ErpPurchaseRequestDO::getId, id).eq(ErpPurchaseRequestDO::getStatus, status));
+ }
+
+ default ErpPurchaseRequestDO selectByNo(String no) {
+ return selectOne(ErpPurchaseRequestDO::getNo, no);
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
index 8ed3b6f..0b3b2ee 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
@@ -73,4 +73,8 @@
return selectOne(ErpSaleOrderDO::getNo, no);
}
+ default ErpSaleOrderDO selectByContractId(Long contractId) {
+ return selectOne(ErpSaleOrderDO::getContractId, contractId);
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
index b6777d2..ae6f218 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
@@ -56,6 +56,10 @@
*/
public static final String PURCHASE_ORDER_NO_PREFIX = "CGDD";
/**
+ * 閲囪喘鐢宠 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO}
+ */
+ public static final String PURCHASE_REQUEST_NO_PREFIX = "CGSQ";
+ /**
* 閲囪喘鍏ュ簱 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO}
*/
public static final String PURCHASE_IN_NO_PREFIX = "CGRK";
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
new file mode 100644
index 0000000..b27025a
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.erp.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * ERP 閲囪喘鐢宠瀹℃壒鐘舵�佹灇涓�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RequiredArgsConstructor
+@Getter
+public enum ErpPurchaseRequestStatusEnum implements ArrayValuable<Integer> {
+
+ DRAFT(0, "鏈彁浜�"),
+ PROCESS(10, "瀹℃壒涓�"),
+ APPROVE(20, "瀹℃牳閫氳繃"),
+ REJECT(30, "瀹℃牳涓嶉�氳繃"),
+ CANCEL(40, "宸插彇娑�");
+
+ private final Integer status;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(ErpPurchaseRequestStatusEnum::getStatus).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
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..c7ccb51 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_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_101_011, "閲囪喘瀹℃牳鍒嗙被涓嶅瓨鍦紝璇峰厛鍦˙PM妯″潡鍒涘缓閲囪喘瀹℃牳娴佺▼鍒嗙被");
+ ErrorCode PURCHASE_ORDER_BPM_PROCESS_DEFINITION_NOT_EXISTS = 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,18 @@
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, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑浠樻鍗�");
+
+ // ========== ERP 閲囪喘鐢宠锛�1-030-104-000锛� ==========
+ ErrorCode PURCHASE_REQUEST_NOT_EXISTS = new ErrorCode(1_030_104_000, "閲囪喘鐢宠鍗曚笉瀛樺湪");
+ ErrorCode PURCHASE_REQUEST_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_001, "閲囪喘鐢宠鍗�({})涓嶆槸鑽夌鐘舵�侊紝鏃犳硶鍒犻櫎");
+ ErrorCode PURCHASE_REQUEST_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_002, "閲囪喘鐢宠鍗�({})涓嶆槸鑽夌鐘舵�侊紝鏃犳硶淇敼");
+ ErrorCode PURCHASE_REQUEST_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_003, "鎻愪氦澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑鐢宠鍗曟墠鑳芥彁浜�");
+ ErrorCode PURCHASE_REQUEST_NO_EXISTS = new ErrorCode(1_030_104_004, "鐢熸垚鐢宠鍗曞彿澶辫触锛岃閲嶆柊鎻愪氦");
+ ErrorCode PURCHASE_REQUEST_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_104_005, "鏇存柊瀹℃壒鐘舵�佸け璐ワ紝鍙湁瀹℃壒涓殑鐢宠鍗曟墠鑳芥洿鏂�");
+ ErrorCode PURCHASE_REQUEST_NOT_APPROVE = new ErrorCode(1_030_104_006, "閲囪喘鐢宠鍗曟湭瀹℃牳閫氳繃锛屾棤娉曟搷浣�");
+ ErrorCode PURCHASE_REQUEST_ORDER_EXISTS = new ErrorCode(1_030_104_007, "閲囪喘鐢宠鍗曞凡鐢熸垚閲囪喘璁㈠崟锛岃鍕块噸澶嶆搷浣�");
+ ErrorCode PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_104_008, "閲囪喘鐢宠瀹℃壒鍒嗙被涓嶅瓨鍦紝璇峰厛鍦˙PM妯″潡鍒涘缓閲囪喘鐢宠瀹℃壒娴佺▼鍒嗙被");
+ ErrorCode PURCHASE_REQUEST_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_104_009, "閲囪喘鐢宠瀹℃壒鍒嗙被涓嬫病鏈夊彲鐢ㄧ殑娴佺▼妯″瀷锛岃鍏堝垱寤哄苟鍙戝竷娴佺▼");
// ========== ERP 閲囪喘閫�璐э紙1-030-103-000锛� ==========
ErrorCode PURCHASE_RETURN_NOT_EXISTS = new ErrorCode(1_030_103_000, "閲囪喘閫�璐у崟涓嶅瓨鍦�");
@@ -130,6 +144,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/finance/ErpFinanceReceiptServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java
index 4b7484d..20fb4ae 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO;
@@ -17,7 +18,6 @@
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.common.ErpBizTypeEnum;
-import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutService;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleReturnService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -55,7 +55,7 @@
private ErpNoRedisDAO noRedisDAO;
@Resource
- private ErpCustomerService customerService;
+ private CrmCustomerApi customerApi;
@Resource
private ErpAccountService accountService;
@Resource
@@ -72,8 +72,8 @@
// 1.1 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
List<ErpFinanceReceiptItemDO> receiptItems = validateFinanceReceiptItems(
createReqVO.getCustomerId(), createReqVO.getItems());
- // 1.2 鏍¢獙瀹㈡埛
- customerService.validateCustomer(createReqVO.getCustomerId());
+ // 1.2 鏍¢獙瀹㈡埛锛堜娇鐢� CRM 瀹㈡埛 API锛�
+ customerApi.validateCustomer(createReqVO.getCustomerId());
// 1.3 鏍¢獙缁撶畻璐︽埛
if (createReqVO.getAccountId() != null) {
accountService.validateAccount(createReqVO.getAccountId());
@@ -110,8 +110,8 @@
if (ErpAuditStatus.APPROVE.getStatus().equals(receipt.getStatus())) {
throw exception(FINANCE_RECEIPT_UPDATE_FAIL_APPROVE, receipt.getNo());
}
- // 1.2 鏍¢獙瀹㈡埛
- customerService.validateCustomer(updateReqVO.getCustomerId());
+ // 1.2 鏍¢獙瀹㈡埛锛堜娇鐢� CRM 瀹㈡埛 API锛�
+ customerApi.validateCustomer(updateReqVO.getCustomerId());
// 1.3 鏍¢獙缁撶畻璐︽埛
if (updateReqVO.getAccountId() != null) {
accountService.validateAccount(updateReqVO.getAccountId());
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java
index 778c7f2..0227ef3 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java
@@ -59,6 +59,14 @@
ErpProductDO getProduct(Long id);
/**
+ * 鑾峰緱浜у搧 VO
+ *
+ * @param id 缂栧彿
+ * @return 浜у搧 VO
+ */
+ ErpProductRespVO getProductVO(Long id);
+
+ /**
* 鑾峰緱鎸囧畾鐘舵�佺殑浜у搧 VO 鍒楄〃
*
* @param status 鐘舵��
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..247295e 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
@@ -4,14 +4,16 @@
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.enums.MdmItemTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -25,6 +27,10 @@
/**
* ERP 浜у搧 Service 瀹炵幇绫�
*
+ * 鏀归�犺鏄庯細浜у搧鏁版嵁缁熶竴浣跨敤 MDM 涓绘暟鎹鐞嗭紙itemType=2 浜у搧绫诲瀷锛�
+ * - 鍒涘缓銆佹洿鏂般�佸垹闄ゆ搷浣滈�氳繃 MDM API 杩涜
+ * - 鏌ヨ鎿嶄綔浠� MDM 鑾峰彇鏁版嵁
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@@ -35,36 +41,77 @@
private ErpProductMapper productMapper;
@Resource
- private ErpProductCategoryService productCategoryService;
- @Resource
- private ErpProductUnitService productUnitService;
+ private MdmItemApi mdmItemApi;
@Override
public Long createProduct(ProductSaveReqVO createReqVO) {
- // TODO 鑺嬭壙锛氭牎楠屽垎绫�
- // 鎻掑叆
- ErpProductDO product = BeanUtils.toBean(createReqVO, ErpProductDO.class);
- productMapper.insert(product);
- // 杩斿洖
- return product.getId();
+ // 1. 鏍¢獙鏉$爜鏄惁閲嶅
+ MdmItemRespDTO existItem = mdmItemApi.getItemByBarCode(createReqVO.getBarCode()).getCheckedData();
+ if (existItem != null) {
+ throw exception(PRODUCT_BAR_CODE_EXISTS);
+ }
+ // 2. 閫氳繃 MDM API 鍒涘缓浜у搧
+ MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
+ createReqDTO.setCode(createReqVO.getBarCode()); // 浣跨敤鏉$爜浣滀负缂栫爜
+ createReqDTO.setName(createReqVO.getName());
+ createReqDTO.setBarCode(createReqVO.getBarCode());
+ createReqDTO.setSpecification(createReqVO.getStandard());
+ createReqDTO.setCategoryId(createReqVO.getCategoryId());
+ createReqDTO.setUnitMeasureId(createReqVO.getUnitId());
+ createReqDTO.setItemType(MdmItemTypeEnum.PRODUCT.getType()); // 浜у搧绫诲瀷
+ createReqDTO.setStatus(createReqVO.getStatus() != null ? createReqVO.getStatus() : CommonStatusEnum.ENABLE.getStatus());
+ createReqDTO.setPurchasePrice(createReqVO.getPurchasePrice());
+ createReqDTO.setSalesPrice(createReqVO.getSalePrice());
+ createReqDTO.setCostPrice(createReqVO.getMinPrice());
+ createReqDTO.setExpiryDay(createReqVO.getExpiryDay());
+ createReqDTO.setWeight(createReqVO.getWeight());
+ createReqDTO.setRemark(createReqVO.getRemark());
+ return mdmItemApi.createItem(createReqDTO).getCheckedData();
}
@Override
public void updateProduct(ProductSaveReqVO updateReqVO) {
- // TODO 鑺嬭壙锛氭牎楠屽垎绫�
- // 鏍¢獙瀛樺湪
- validateProductExists(updateReqVO.getId());
- // 鏇存柊
- ErpProductDO updateObj = BeanUtils.toBean(updateReqVO, ErpProductDO.class);
- productMapper.updateById(updateObj);
+ // 1. 鏍¢獙瀛樺湪
+ MdmItemRespDTO existItem = mdmItemApi.getItem(updateReqVO.getId()).getCheckedData();
+ if (existItem == null) {
+ throw exception(PRODUCT_NOT_EXISTS);
+ }
+ // 2. 鏍¢獙鏉$爜鏄惁閲嶅
+ if (!existItem.getBarCode().equals(updateReqVO.getBarCode())) {
+ MdmItemRespDTO barCodeItem = mdmItemApi.getItemByBarCode(updateReqVO.getBarCode()).getCheckedData();
+ if (barCodeItem != null) {
+ throw exception(PRODUCT_BAR_CODE_EXISTS);
+ }
+ }
+ // 3. 閫氳繃 MDM API 鏇存柊浜у搧
+ MdmItemUpdateReqDTO updateReqDTO = new MdmItemUpdateReqDTO();
+ updateReqDTO.setId(updateReqVO.getId());
+ updateReqDTO.setCode(updateReqVO.getBarCode());
+ updateReqDTO.setName(updateReqVO.getName());
+ updateReqDTO.setBarCode(updateReqVO.getBarCode());
+ updateReqDTO.setSpecification(updateReqVO.getStandard());
+ updateReqDTO.setCategoryId(updateReqVO.getCategoryId());
+ updateReqDTO.setUnitMeasureId(updateReqVO.getUnitId());
+ updateReqDTO.setItemType(MdmItemTypeEnum.PRODUCT.getType());
+ updateReqDTO.setStatus(updateReqVO.getStatus());
+ updateReqDTO.setPurchasePrice(updateReqVO.getPurchasePrice());
+ updateReqDTO.setSalesPrice(updateReqVO.getSalePrice());
+ updateReqDTO.setCostPrice(updateReqVO.getMinPrice());
+ updateReqDTO.setExpiryDay(updateReqVO.getExpiryDay());
+ updateReqDTO.setWeight(updateReqVO.getWeight());
+ updateReqDTO.setRemark(updateReqVO.getRemark());
+ mdmItemApi.updateItem(updateReqDTO);
}
@Override
public void deleteProduct(Long id) {
// 鏍¢獙瀛樺湪
- validateProductExists(id);
- // 鍒犻櫎
- productMapper.deleteById(id);
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
+ throw exception(PRODUCT_NOT_EXISTS);
+ }
+ // 閫氳繃 MDM API 鍒犻櫎锛堟洿鏂扮姸鎬佷负绂佺敤锛�
+ mdmItemApi.updateItemStatus(id, CommonStatusEnum.DISABLE.getStatus());
}
@Override
@@ -72,35 +119,90 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- List<ErpProductDO> list = productMapper.selectByIds(ids);
- Map<Long, ErpProductDO> productMap = convertMap(list, ErpProductDO::getId);
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁
+ List<MdmItemRespDTO> items = mdmItemApi.getItemList(ids).getCheckedData();
+ Map<Long, MdmItemRespDTO> itemMap = convertMap(items, MdmItemRespDTO::getId);
+ List<ErpProductDO> result = new ArrayList<>();
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());
}
+ // 杞崲涓� ErpProductDO
+ result.add(convertToErpProduct(item));
}
- return list;
+ return result;
}
private void validateProductExists(Long id) {
- if (productMapper.selectById(id) == null) {
+ // 浠� MDM 鏍¢獙
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
}
@Override
public ErpProductDO getProduct(Long id) {
- return productMapper.selectById(id);
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
+ return null;
+ }
+ // 妫�鏌ユ槸鍚︿负浜у搧绫诲瀷
+ if (!MdmItemTypeEnum.PRODUCT.getType().equals(item.getItemType())) {
+ return null;
+ }
+ return convertToErpProduct(item);
+ }
+
+ @Override
+ public ErpProductRespVO getProductVO(Long id) {
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
+ return null;
+ }
+ // 妫�鏌ユ槸鍚︿负浜у搧绫诲瀷
+ if (!MdmItemTypeEnum.PRODUCT.getType().equals(item.getItemType())) {
+ return null;
+ }
+ return convertToErpProductVO(item);
+ }
+
+ /**
+ * 灏� MDM 鐗╂枡杞崲涓� ERP 浜у搧 VO
+ */
+ private ErpProductRespVO convertToErpProductVO(MdmItemRespDTO item) {
+ ErpProductRespVO vo = new ErpProductRespVO();
+ vo.setId(item.getId());
+ vo.setName(item.getName());
+ vo.setBarCode(item.getBarCode());
+ vo.setStandard(item.getSpecification());
+ vo.setCategoryId(item.getCategoryId());
+ vo.setUnitId(item.getUnitMeasureId());
+ vo.setStatus(item.getStatus());
+ vo.setExpiryDay(item.getExpiryDay());
+ vo.setWeight(item.getWeight());
+ vo.setPurchasePrice(item.getPurchasePrice());
+ vo.setSalePrice(item.getSalesPrice());
+ vo.setMinPrice(item.getCostPrice());
+ vo.setRemark(item.getRemark());
+ return vo;
}
@Override
public List<ErpProductRespVO> getProductVOListByStatus(Integer status) {
- List<ErpProductDO> list = productMapper.selectListByStatus(status);
- return buildProductVOList(list);
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁锛坕temType=2锛�
+ List<MdmItemRespDTO> items = mdmItemApi.getItemListByStatus(status).getCheckedData();
+ // 杩囨护浜у搧绫诲瀷
+ items = items.stream()
+ .filter(item -> MdmItemTypeEnum.PRODUCT.getType().equals(item.getItemType()))
+ .toList();
+ return buildProductVOListFromMdm(items);
}
@Override
@@ -108,40 +210,78 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- List<ErpProductDO> list = productMapper.selectByIds(ids);
- return buildProductVOList(list);
+ // 浠� MDM 鑾峰彇浜у搧鏁版嵁
+ List<MdmItemRespDTO> items = mdmItemApi.getItemList(ids).getCheckedData();
+ return buildProductVOListFromMdm(items);
}
@Override
public PageResult<ErpProductRespVO> getProductVOPage(ErpProductPageReqVO pageReqVO) {
- PageResult<ErpProductDO> pageResult = productMapper.selectPage(pageReqVO);
- return new PageResult<>(buildProductVOList(pageResult.getList()), pageResult.getTotal());
+ // 浠� MDM 鑾峰彇浜у搧鍒嗛〉鏁版嵁
+ cn.iocoder.yudao.framework.common.pojo.PageResult<MdmItemRespDTO> mdmPageResult =
+ mdmItemApi.getItemPage(MdmItemTypeEnum.PRODUCT.getType(), null,
+ pageReqVO.getPageNo(), pageReqVO.getPageSize()).getCheckedData();
+ return new PageResult<>(buildProductVOListFromMdm(mdmPageResult.getList()), mdmPageResult.getTotal());
}
- private List<ErpProductRespVO> buildProductVOList(List<ErpProductDO> list) {
- if (CollUtil.isEmpty(list)) {
+ /**
+ * 浠� MDM 鏁版嵁鏋勫缓 ERP 浜у搧 VO 鍒楄〃
+ */
+ private List<ErpProductRespVO> buildProductVOListFromMdm(List<MdmItemRespDTO> items) {
+ if (CollUtil.isEmpty(items)) {
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> result = new ArrayList<>();
+ for (MdmItemRespDTO item : items) {
+ ErpProductRespVO vo = new ErpProductRespVO();
+ vo.setId(item.getId());
+ vo.setName(item.getName());
+ vo.setBarCode(item.getBarCode());
+ vo.setStandard(item.getSpecification());
+ vo.setCategoryId(item.getCategoryId());
+ vo.setUnitId(item.getUnitMeasureId());
+ vo.setStatus(item.getStatus());
+ vo.setExpiryDay(item.getExpiryDay());
+ vo.setWeight(item.getWeight());
+ vo.setPurchasePrice(item.getPurchasePrice());
+ vo.setSalePrice(item.getSalesPrice());
+ vo.setMinPrice(item.getCostPrice()); // 浣跨敤鎴愭湰浠蜂綔涓烘渶浣庝环
+ vo.setRemark(item.getRemark());
+ // 鍒嗙被鍚嶇О鍜屽崟浣嶅悕绉伴渶瑕侀澶栨煡璇紙绠�鍖栧鐞嗭紝鍚庣画鍙紭鍖栵級
+ result.add(vo);
+ }
+ return result;
+ }
+
+ /**
+ * 灏� MDM 鐗╂枡杞崲涓� ERP 浜у搧 DO
+ */
+ private ErpProductDO convertToErpProduct(MdmItemRespDTO item) {
+ ErpProductDO product = new ErpProductDO();
+ product.setId(item.getId());
+ product.setName(item.getName());
+ product.setBarCode(item.getBarCode());
+ product.setStandard(item.getSpecification());
+ product.setCategoryId(item.getCategoryId());
+ product.setUnitId(item.getUnitMeasureId());
+ product.setStatus(item.getStatus());
+ product.setExpiryDay(item.getExpiryDay());
+ product.setWeight(item.getWeight());
+ product.setPurchasePrice(item.getPurchasePrice());
+ product.setSalePrice(item.getSalesPrice());
+ product.setMinPrice(item.getCostPrice());
+ product.setRemark(item.getRemark());
+ return product;
}
@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();
}
}
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..28bc81b 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
@@ -22,19 +22,17 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
-// TODO 鑺嬭壙锛氳褰曟搷浣滄棩蹇�
-
/**
* ERP 閲囪喘璁㈠崟 Service 瀹炵幇绫�
+ *
+ * 娉ㄦ剰锛氶噰璐鍗曚笉闇�瑕� BPM 瀹℃壒锛屽鎵规祦绋嬪湪閲囪喘鐢宠闃舵瀹屾垚銆�
+ * 閲囪喘璁㈠崟鐢遍噰璐敵璇风敓鎴愶紝鎴栫洿鎺ュ垱寤猴紙鐘舵�佷负宸插鏍革級銆�
*
* @author 鑺嬮亾婧愮爜
*/
@@ -74,9 +72,9 @@
throw exception(PURCHASE_ORDER_NO_EXISTS);
}
- // 2.1 鎻掑叆璁㈠崟
+ // 2.1 鎻掑叆璁㈠崟锛堥噰璐鍗曚笉闇�瑕佸鎵癸紝鐩存帴涓哄凡瀹℃牳鐘舵�侊級
ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
- .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()));
+ .setNo(no).setStatus(ErpAuditStatus.APPROVE.getStatus()));
calculateTotalPrice(purchaseOrder, purchaseOrderItems);
purchaseOrderMapper.insert(purchaseOrder);
// 2.2 鎻掑叆璁㈠崟椤�
@@ -90,6 +88,7 @@
public void updatePurchaseOrder(ErpPurchaseOrderSaveReqVO updateReqVO) {
// 1.1 鏍¢獙瀛樺湪
ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(updateReqVO.getId());
+ // 閲囪喘璁㈠崟涓嶅厑璁镐慨鏀癸紙宸插鏍哥姸鎬侊級
if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseOrder.getStatus())) {
throw exception(PURCHASE_ORDER_UPDATE_FAIL_APPROVE, purchaseOrder.getNo());
}
@@ -126,6 +125,8 @@
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePurchaseOrderStatus(Long id, Integer status) {
+ // 閲囪喘璁㈠崟涓嶅啀鏀寔鐘舵�佹祦杞紙涓嶉渶瑕佸鎵癸級
+ // 姝ゆ柟娉曚繚鐣欑敤浜庣壒娈婃儏鍐碉紙濡傚弽瀹℃牳鍚庨噸鏂板鏍革級
boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
// 1.1 鏍¢獙瀛樺湪
ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
@@ -133,12 +134,14 @@
if (purchaseOrder.getStatus().equals(status)) {
throw exception(approve ? PURCHASE_ORDER_APPROVE_FAIL : PURCHASE_ORDER_PROCESS_FAIL);
}
- // 1.3 瀛樺湪閲囪喘鍏ュ崟锛屾棤娉曞弽瀹℃牳
- if (!approve && purchaseOrder.getInCount().compareTo(BigDecimal.ZERO) > 0) {
+ // 1.3 瀛樺湪閲囪喘鍏ュ簱鍗曪紝鏃犳硶鍙嶅鏍�
+ BigDecimal inCount = purchaseOrder.getInCount();
+ if (!approve && inCount != null && inCount.compareTo(BigDecimal.ZERO) > 0) {
throw exception(PURCHASE_ORDER_PROCESS_FAIL_EXISTS_IN);
}
// 1.4 瀛樺湪閲囪喘閫�璐у崟锛屾棤娉曞弽瀹℃牳
- if (!approve && purchaseOrder.getReturnCount().compareTo(BigDecimal.ZERO) > 0) {
+ BigDecimal returnCount = purchaseOrder.getReturnCount();
+ if (!approve && returnCount != null && returnCount.compareTo(BigDecimal.ZERO) > 0) {
throw exception(PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN);
}
@@ -171,7 +174,7 @@
private void updatePurchaseOrderItemList(Long id, List<ErpPurchaseOrderItemDO> newList) {
// 绗竴姝ワ紝瀵规瘮鏂拌�佹暟鎹紝鑾峰緱娣诲姞銆佷慨鏀广�佸垹闄ょ殑鍒楄〃
List<ErpPurchaseOrderItemDO> oldList = purchaseOrderItemMapper.selectListByOrderId(id);
- List<List<ErpPurchaseOrderItemDO>> diffList = diffList(oldList, newList, // id 涓嶅悓锛屽氨璁や负鏄笉鍚岀殑璁板綍
+ List<List<ErpPurchaseOrderItemDO>> diffList = diffList(oldList, newList,
(oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
// 绗簩姝ワ紝鎵归噺娣诲姞銆佷慨鏀广�佸垹闄�
@@ -241,11 +244,9 @@
}
});
- // 2. 閬嶅巻鍒犻櫎锛屽苟璁板綍鎿嶄綔鏃ュ織
+ // 2. 閬嶅巻鍒犻櫎
purchaseOrders.forEach(purchaseOrder -> {
- // 2.1 鍒犻櫎璁㈠崟
purchaseOrderMapper.deleteById(purchaseOrder.getId());
- // 2.2 鍒犻櫎璁㈠崟椤�
purchaseOrderItemMapper.deleteByOrderId(purchaseOrder.getId());
});
}
@@ -292,4 +293,4 @@
return purchaseOrderItemMapper.selectListByOrderIds(orderIds);
}
-}
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java
new file mode 100644
index 0000000..f66ead5
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java
@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.module.erp.service.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘鐢宠 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface ErpPurchaseRequestService {
+
+ /**
+ * 鍒涘缓閲囪喘鐢宠
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @param userId 鐢ㄦ埛缂栧彿锛堢敵璇蜂汉锛�
+ * @param deptId 閮ㄩ棬缂栧彿锛堢敵璇烽儴闂級
+ * @return 缂栧彿
+ */
+ Long createPurchaseRequest(@Valid ErpPurchaseRequestSaveReqVO createReqVO, Long userId, Long deptId);
+
+ /**
+ * 鏇存柊閲囪喘鐢宠
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updatePurchaseRequest(@Valid ErpPurchaseRequestSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎閲囪喘鐢宠
+ *
+ * @param ids 缂栧彿鏁扮粍
+ */
+ void deletePurchaseRequest(List<Long> ids);
+
+ /**
+ * 鑾峰緱閲囪喘鐢宠
+ *
+ * @param id 缂栧彿
+ * @return 閲囪喘鐢宠
+ */
+ ErpPurchaseRequestDO getPurchaseRequest(Long id);
+
+ /**
+ * 鑾峰緱閲囪喘鐢宠鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 閲囪喘鐢宠鍒嗛〉
+ */
+ PageResult<ErpPurchaseRequestDO> getPurchaseRequestPage(ErpPurchaseRequestPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱閲囪喘鐢宠鏄庣粏鍒楄〃
+ *
+ * @param requestId 閲囪喘鐢宠缂栧彿
+ * @return 閲囪喘鐢宠鏄庣粏鍒楄〃
+ */
+ List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestId(Long requestId);
+
+ /**
+ * 鑾峰緱閲囪喘鐢宠鏄庣粏鍒楄〃
+ *
+ * @param requestIds 閲囪喘鐢宠缂栧彿鏁扮粍
+ * @return 閲囪喘鐢宠鏄庣粏鍒楄〃
+ */
+ List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestIds(Collection<Long> requestIds);
+
+ /**
+ * 鎻愪氦閲囪喘鐢宠瀹℃壒
+ *
+ * @param id 缂栧彿
+ * @param processDefinitionKey 娴佺▼瀹氫箟 Key锛堢敤鎴烽�夋嫨锛�
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId);
+
+ /**
+ * 鏇存柊閲囪喘鐢宠瀹℃壒鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult);
+
+ /**
+ * 鏍规嵁閲囪喘鐢宠鐢熸垚閲囪喘璁㈠崟
+ *
+ * @param id 閲囪喘鐢宠缂栧彿
+ * @param supplierId 渚涘簲鍟嗙紪鍙�
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 閲囪喘璁㈠崟缂栧彿
+ */
+ Long generatePurchaseOrder(Long id, Long supplierId, Long userId);
+
+ /**
+ * 鑾峰彇閲囪喘鐢宠瀹℃壒娴佺▼鍒楄〃
+ *
+ * @return 娴佺▼瀹氫箟鍒楄〃
+ */
+ List<java.util.Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList();
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
new file mode 100644
index 0000000..766a564
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -0,0 +1,403 @@
+package cn.iocoder.yudao.module.erp.service.purchase;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+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.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
+import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
+import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
+
+/**
+ * ERP 閲囪喘鐢宠 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+@Slf4j
+public class ErpPurchaseRequestServiceImpl implements ErpPurchaseRequestService {
+
+ /**
+ * 閲囪喘鐢宠 BPM 鍒嗙被缂栫爜
+ */
+ private static final String PURCHASE_REQUEST_APPROVE_CATEGORY_CODE = "purchase_request_approve";
+
+ @Resource
+ private ErpPurchaseRequestMapper purchaseRequestMapper;
+ @Resource
+ private ErpPurchaseRequestItemMapper purchaseRequestItemMapper;
+
+ @Resource
+ private ErpNoRedisDAO noRedisDAO;
+
+ @Resource
+ private ErpProductService productService;
+ @Resource
+ private ErpSupplierService supplierService;
+
+ @Resource
+ private BpmCategoryService bpmCategoryService;
+ @Resource
+ private BpmProcessDefinitionService bpmProcessDefinitionService;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+
+ @Resource
+ private ErpPurchaseOrderService purchaseOrderService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createPurchaseRequest(ErpPurchaseRequestSaveReqVO createReqVO, Long userId, Long deptId) {
+ // 1.1 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
+ List<ErpPurchaseRequestItemDO> purchaseRequestItems = validatePurchaseRequestItems(createReqVO.getItems());
+ // 1.2 鐢熸垚鐢宠鍗曞彿锛屽苟鏍¢獙鍞竴鎬�
+ String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_REQUEST_NO_PREFIX);
+ if (purchaseRequestMapper.selectByNo(no) != null) {
+ throw exception(PURCHASE_REQUEST_NO_EXISTS);
+ }
+
+ // 鏃ュ織锛氭鏌ラ儴闂ㄥ拰鐢ㄦ埛淇℃伅
+ log.info("[createPurchaseRequest] 鍒涘缓閲囪喘鐢宠锛寀serId={}, deptId={}", userId, deptId);
+
+ // 2.1 鎻掑叆鐢宠鍗�
+ ErpPurchaseRequestDO purchaseRequest = BeanUtils.toBean(createReqVO, ErpPurchaseRequestDO.class, req -> req
+ .setNo(no)
+ .setStatus(ErpPurchaseRequestStatusEnum.DRAFT.getStatus())
+ .setRequestUserId(userId)
+ .setRequestDeptId(deptId));
+ calculateTotalPrice(purchaseRequest, purchaseRequestItems);
+ purchaseRequestMapper.insert(purchaseRequest);
+ // 2.2 鎻掑叆鐢宠鏄庣粏
+ purchaseRequestItems.forEach(o -> o.setRequestId(purchaseRequest.getId()));
+ purchaseRequestItemMapper.insertBatch(purchaseRequestItems);
+ return purchaseRequest.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updatePurchaseRequest(ErpPurchaseRequestSaveReqVO updateReqVO) {
+ // 1.1 鏍¢獙瀛樺湪
+ ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(updateReqVO.getId());
+ // 1.2 鏍¢獙鐘舵�侊紝鍙湁鑽夌鐘舵�佹墠鑳戒慨鏀�
+ if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
+ throw exception(PURCHASE_REQUEST_UPDATE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
+ }
+ // 1.3 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
+ List<ErpPurchaseRequestItemDO> purchaseRequestItems = validatePurchaseRequestItems(updateReqVO.getItems());
+
+ // 2.1 鏇存柊鐢宠鍗�
+ ErpPurchaseRequestDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseRequestDO.class);
+ calculateTotalPrice(updateObj, purchaseRequestItems);
+ purchaseRequestMapper.updateById(updateObj);
+ // 2.2 鏇存柊鐢宠鏄庣粏
+ updatePurchaseRequestItemList(updateReqVO.getId(), purchaseRequestItems);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deletePurchaseRequest(List<Long> ids) {
+ // 1. 鏍¢獙涓嶅浜庨潪鑽夌鐘舵��
+ List<ErpPurchaseRequestDO> purchaseRequests = purchaseRequestMapper.selectByIds(ids);
+ if (CollUtil.isEmpty(purchaseRequests)) {
+ return;
+ }
+ purchaseRequests.forEach(purchaseRequest -> {
+ if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
+ throw exception(PURCHASE_REQUEST_DELETE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
+ }
+ });
+
+ // 2. 閬嶅巻鍒犻櫎
+ purchaseRequests.forEach(purchaseRequest -> {
+ // 2.1 鍒犻櫎鐢宠鍗�
+ purchaseRequestMapper.deleteById(purchaseRequest.getId());
+ // 2.2 鍒犻櫎鐢宠鏄庣粏
+ purchaseRequestItemMapper.deleteByRequestId(purchaseRequest.getId());
+ });
+ }
+
+ private ErpPurchaseRequestDO validatePurchaseRequestExists(Long id) {
+ ErpPurchaseRequestDO purchaseRequest = purchaseRequestMapper.selectById(id);
+ if (purchaseRequest == null) {
+ throw exception(PURCHASE_REQUEST_NOT_EXISTS);
+ }
+ return purchaseRequest;
+ }
+
+ private List<ErpPurchaseRequestItemDO> validatePurchaseRequestItems(List<ErpPurchaseRequestSaveReqVO.Item> list) {
+ // 1. 鏍¢獙浜у搧瀛樺湪
+ List<ErpProductDO> productList = productService.validProductList(
+ convertSet(list, ErpPurchaseRequestSaveReqVO.Item::getProductId));
+ Map<Long, ErpProductDO> productMap = convertMap(productList, ErpProductDO::getId);
+ // 2. 杞寲涓� ErpPurchaseRequestItemDO 鍒楄〃
+ return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseRequestItemDO.class, item -> {
+ item.setProductUnitId(productMap.get(item.getProductId()).getUnitId());
+ if (item.getProductPrice() != null && item.getCount() != null) {
+ item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
+ if (item.getTaxPercent() != null) {
+ item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
+ }
+ }
+ }));
+ }
+
+ private void updatePurchaseRequestItemList(Long id, List<ErpPurchaseRequestItemDO> newList) {
+ // 绗竴姝ワ紝瀵规瘮鏂拌�佹暟鎹紝鑾峰緱娣诲姞銆佷慨鏀广�佸垹闄ょ殑鍒楄〃
+ List<ErpPurchaseRequestItemDO> oldList = purchaseRequestItemMapper.selectListByRequestId(id);
+ List<List<ErpPurchaseRequestItemDO>> diffList = diffList(oldList, newList,
+ (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
+
+ // 绗簩姝ワ紝鎵归噺娣诲姞銆佷慨鏀广�佸垹闄�
+ if (CollUtil.isNotEmpty(diffList.get(0))) {
+ diffList.get(0).forEach(o -> o.setRequestId(id));
+ purchaseRequestItemMapper.insertBatch(diffList.get(0));
+ }
+ if (CollUtil.isNotEmpty(diffList.get(1))) {
+ purchaseRequestItemMapper.updateBatch(diffList.get(1));
+ }
+ if (CollUtil.isNotEmpty(diffList.get(2))) {
+ purchaseRequestItemMapper.deleteByIds(convertList(diffList.get(2), ErpPurchaseRequestItemDO::getId));
+ }
+ }
+
+ private void calculateTotalPrice(ErpPurchaseRequestDO purchaseRequest, List<ErpPurchaseRequestItemDO> items) {
+ purchaseRequest.setTotalCount(getSumValue(items, ErpPurchaseRequestItemDO::getCount, BigDecimal::add));
+ purchaseRequest.setTotalProductPrice(getSumValue(items, ErpPurchaseRequestItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
+ purchaseRequest.setTotalTaxPrice(getSumValue(items, ErpPurchaseRequestItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
+ purchaseRequest.setTotalPrice(purchaseRequest.getTotalProductPrice().add(purchaseRequest.getTotalTaxPrice()));
+ // 璁$畻浼樻儬浠锋牸
+ if (purchaseRequest.getDiscountPercent() == null) {
+ purchaseRequest.setDiscountPercent(BigDecimal.ZERO);
+ }
+ purchaseRequest.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseRequest.getTotalPrice(), purchaseRequest.getDiscountPercent()));
+ purchaseRequest.setTotalPrice(purchaseRequest.getTotalPrice().subtract(purchaseRequest.getDiscountPrice()));
+ }
+
+ @Override
+ public ErpPurchaseRequestDO getPurchaseRequest(Long id) {
+ return purchaseRequestMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<ErpPurchaseRequestDO> getPurchaseRequestPage(ErpPurchaseRequestPageReqVO pageReqVO) {
+ return purchaseRequestMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestId(Long requestId) {
+ return purchaseRequestItemMapper.selectListByRequestId(requestId);
+ }
+
+ @Override
+ public List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestIds(Collection<Long> requestIds) {
+ if (CollUtil.isEmpty(requestIds)) {
+ return Collections.emptyList();
+ }
+ return purchaseRequestItemMapper.selectListByRequestIds(requestIds);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId) {
+ // 1. 鏍¢獙鐢宠鍗曞瓨鍦ㄤ笖涓鸿崏绋跨姸鎬�
+ ErpPurchaseRequestDO request = validatePurchaseRequestExists(id);
+ if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(request.getStatus())) {
+ throw exception(PURCHASE_REQUEST_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ validatePurchaseRequestApproveCategoryAndProcessDefinition();
+
+ // 3. 鏍¢獙浼犲叆鐨勬祦绋嬪畾涔� Key 鏄惁灞炰簬璇ュ垎绫讳笖澶勪簬婵�娲荤姸鎬�
+ ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+ if (processDefinition == null) {
+ throw exception(PURCHASE_REQUEST_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+
+ // 4. 鍒涘缓娴佺▼瀹炰緥锛堜娇鐢ㄧ敤鎴烽�夋嫨鐨勬祦绋嬪畾涔� Key锛�
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+ new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(processDefinitionKey)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 5. 鏇存柊鐢宠鍗曠姸鎬�
+ purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+ .setId(id)
+ .setProcessInstanceId(processInstanceId)
+ .setStatus(ErpPurchaseRequestStatusEnum.PROCESS.getStatus()));
+ }
+
+ /**
+ * 鏍¢獙 BPM 閲囪喘鐢宠瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ */
+ private void validatePurchaseRequestApproveCategoryAndProcessDefinition() {
+ // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+ List<String> codes = Collections.singletonList(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE);
+ Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes);
+ if (!categoryMap.containsKey(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE)) {
+ throw exception(PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS);
+ }
+
+ // 2. 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鍙敤鐨勬祦绋嬪畾涔�
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE);
+ if (CollUtil.isEmpty(definitionInfoList)) {
+ throw exception(PURCHASE_REQUEST_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+
+ // 3. 鏍¢獙鏄惁鏈夋縺娲荤姸鎬佺殑娴佺▼瀹氫箟
+ Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+ List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+ boolean hasActiveDefinition = processDefinitions.stream()
+ .anyMatch(pd -> !pd.isSuspended());
+ if (!hasActiveDefinition) {
+ throw exception(PURCHASE_REQUEST_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙鐢宠鍗曞瓨鍦ㄤ笖涓哄鎵逛腑鐘舵��
+ ErpPurchaseRequestDO request = validatePurchaseRequestExists(id);
+ if (!ErpPurchaseRequestStatusEnum.PROCESS.getStatus().equals(request.getStatus())) {
+ log.error("[updatePurchaseRequestAuditStatus] 鐢宠鍗�({}) 涓嶅浜庡鎵逛腑", id);
+ throw exception(PURCHASE_REQUEST_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+
+ // 2. 杞崲瀹℃壒缁撴灉
+ Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+ purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+ .setId(id)
+ .setStatus(auditStatus));
+ }
+
+ /**
+ * BPM 瀹℃壒缁撴灉杞崲
+ */
+ private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ Integer auditStatus = BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)
+ ? ErpPurchaseRequestStatusEnum.APPROVE.getStatus()
+ : BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)
+ ? ErpPurchaseRequestStatusEnum.REJECT.getStatus()
+ : BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)
+ ? ErpPurchaseRequestStatusEnum.CANCEL.getStatus() : null;
+ Assert.notNull(auditStatus, "BPM 瀹℃壒缁撴灉({}) 杞崲澶辫触", bpmResult);
+ return auditStatus;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long generatePurchaseOrder(Long id, Long supplierId, Long userId) {
+ // 1. 鏍¢獙鐢宠鍗曞瓨鍦ㄤ笖宸插鎵归�氳繃
+ ErpPurchaseRequestDO request = validatePurchaseRequestExists(id);
+ if (!ErpPurchaseRequestStatusEnum.APPROVE.getStatus().equals(request.getStatus())) {
+ throw exception(PURCHASE_REQUEST_NOT_APPROVE);
+ }
+ // 鏍¢獙鏄惁宸茬敓鎴愰噰璐鍗�
+ if (request.getOrderId() != null) {
+ throw exception(PURCHASE_REQUEST_ORDER_EXISTS);
+ }
+
+ // 2. 鏍¢獙渚涘簲鍟�
+ supplierService.validateSupplier(supplierId);
+
+ // 3. 鑾峰彇鐢宠鏄庣粏
+ List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectListByRequestId(id);
+
+ // 4. 鏋勫缓閲囪喘璁㈠崟 VO
+ ErpPurchaseOrderSaveReqVO orderVO = new ErpPurchaseOrderSaveReqVO();
+ orderVO.setSupplierId(supplierId);
+ orderVO.setOrderTime(java.time.LocalDateTime.now());
+ orderVO.setRemark("鐢遍噰璐敵璇峰崟[" + request.getNo() + "]鐢熸垚");
+ // 杞崲鏄庣粏椤�
+ orderVO.setItems(convertList(requestItems, item -> {
+ ErpPurchaseOrderSaveReqVO.Item orderItem = new ErpPurchaseOrderSaveReqVO.Item();
+ orderItem.setProductId(item.getProductId());
+ orderItem.setProductUnitId(item.getProductUnitId());
+ orderItem.setProductPrice(item.getProductPrice());
+ orderItem.setCount(item.getCount());
+ orderItem.setTaxPercent(item.getTaxPercent());
+ orderItem.setRemark(item.getRemark());
+ return orderItem;
+ }));
+
+ // 5. 鍒涘缓閲囪喘璁㈠崟
+ Long orderId = purchaseOrderService.createPurchaseOrder(orderVO);
+
+ // 6. 鏇存柊鐢宠鍗曞叧鑱斾俊鎭�
+ purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+ .setId(id)
+ .setOrderId(orderId)
+ .setOrderTime(java.time.LocalDateTime.now()));
+
+ return orderId;
+ }
+
+ @Override
+ public List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList() {
+ // 1. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ validatePurchaseRequestApproveCategoryAndProcessDefinition();
+
+ // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE);
+
+ // 3. 鑾峰彇娴佺▼瀹氫箟璇︽儏
+ Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+ List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+
+ // 4. 杩囨护锛氬彧淇濈暀婵�娲荤姸鎬侊紝涓旂浉鍚� key 鍙繚鐣欐渶鏂扮増鏈�
+ Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+ for (ProcessDefinition pd : processDefinitions) {
+ if (pd.isSuspended()) {
+ continue; // 璺宠繃鎸傝捣鐨勬祦绋�
+ }
+ ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+ if (existing == null || pd.getVersion() > existing.getVersion()) {
+ latestVersionMap.put(pd.getKey(), pd);
+ }
+ }
+
+ // 5. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+ List<Map<String, Object>> result = new ArrayList<>();
+ for (ProcessDefinition pd : latestVersionMap.values()) {
+ Map<String, Object> item = new HashMap<>();
+ item.put("id", pd.getId());
+ item.put("key", pd.getKey());
+ item.put("name", pd.getName());
+ item.put("version", pd.getVersion());
+ result.add(item);
+ }
+ return result;
+ }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java
new file mode 100644
index 0000000..44a4008
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.erp.service.purchase.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閲囪喘鐢宠瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * 閫氳繃 businessKey锛堥噰璐敵璇稩D锛夊尮閰嶏紝鑰屼笉鏄浐瀹氱殑娴佺▼瀹氫箟 Key锛�
+ * 杩欐牱鍙互鏀寔鍒嗙被涓嬩换鎰� Key 鐨勬祦绋嬫ā鍨嬨��
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+@Slf4j
+public class ErpPurchaseRequestStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private ErpPurchaseRequestService purchaseRequestService;
+ @Resource
+ private ErpPurchaseRequestMapper purchaseRequestMapper;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ閲囪喘鐢宠鍗�
+ Long requestId;
+ try {
+ requestId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+ }
+
+ ErpPurchaseRequestDO request = purchaseRequestMapper.selectById(requestId);
+ if (request == null) {
+ return; // 涓嶆槸閲囪喘鐢宠锛屽拷鐣�
+ }
+
+ // 鍙湁瀹℃壒涓姸鎬佺殑鐢宠鍗曟墠澶勭悊
+ if (!ErpPurchaseRequestStatusEnum.PROCESS.getStatus().equals(request.getStatus())) {
+ log.warn("[onApplicationEvent] 閲囪喘鐢宠鍗�({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", requestId, request.getStatus());
+ return;
+ }
+
+ log.info("[onApplicationEvent] 閲囪喘鐢宠鍗�({}) 瀹℃壒鐘舵�佹洿鏂�: {}", requestId, event.getStatus());
+ // 鏇存柊瀹℃壒鐘舵��
+ purchaseRequestService.updatePurchaseRequestAuditStatus(requestId, event.getStatus());
+ }
+
+}
\ No newline at end of file
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..c3bf261 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,7 @@
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.crm.api.customer.CrmCustomerApi;
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;
@@ -54,7 +55,7 @@
@Resource
private ErpProductService productService;
@Resource
- private ErpCustomerService customerService;
+ private CrmCustomerApi customerApi;
@Resource
private ErpAccountService accountService;
@@ -66,8 +67,8 @@
public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) {
// 1.1 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(createReqVO.getItems());
- // 1.2 鏍¢獙瀹㈡埛
- customerService.validateCustomer(createReqVO.getCustomerId());
+ // 1.2 鏍¢獙瀹㈡埛锛堜娇鐢� CRM 瀹㈡埛 API锛�
+ customerApi.validateCustomer(createReqVO.getCustomerId());
// 1.3 鏍¢獙缁撶畻璐︽埛
if (createReqVO.getAccountId() != null) {
accountService.validateAccount(createReqVO.getAccountId());
@@ -101,8 +102,8 @@
if (ErpAuditStatus.APPROVE.getStatus().equals(saleOrder.getStatus())) {
throw exception(SALE_ORDER_UPDATE_FAIL_APPROVE, saleOrder.getNo());
}
- // 1.2 鏍¢獙瀹㈡埛
- customerService.validateCustomer(updateReqVO.getCustomerId());
+ // 1.2 鏍¢獙瀹㈡埛锛堜娇鐢� CRM 瀹㈡埛 API锛�
+ customerApi.validateCustomer(updateReqVO.getCustomerId());
// 1.3 鏍¢獙缁撶畻璐︽埛
if (updateReqVO.getAccountId() != null) {
accountService.validateAccount(updateReqVO.getAccountId());
diff --git a/yudao-module-mdm-api/pom.xml b/yudao-module-mdm-api/pom.xml
new file mode 100644
index 0000000..0a90b63
--- /dev/null
+++ b/yudao-module-mdm-api/pom.xml
@@ -0,0 +1,33 @@
+<?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>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java b/yudao-module-mdm-api/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-api/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-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
new file mode 100644
index 0000000..51bc623
--- /dev/null
+++ b/yudao-module-mdm-api/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;
+
+}
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
new file mode 100644
index 0000000..5cd624c
--- /dev/null
+++ b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
@@ -0,0 +1,75 @@
+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 = "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 highValue;
+
+ @Schema(description = "閲嶉噺", example = "0.5")
+ private BigDecimal weight;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java b/yudao-module-mdm-api/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-api/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-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
new file mode 100644
index 0000000..442634d
--- /dev/null
+++ b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mdm.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * MDM 鐗╂枡绫诲瀷鏋氫妇
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum MdmItemTypeEnum {
+
+ MATERIAL(1, "鐗╂枡"),
+ PRODUCT(2, "浜у搧"),
+ SEMI_FINISHED(3, "鍗婃垚鍝�");
+
+ /**
+ * 绫诲瀷缂栫爜
+ */
+ 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;
+ }
+
+}
diff --git a/yudao-module-mdm/pom.xml b/yudao-module-mdm/pom.xml
new file mode 100644
index 0000000..f8bf40f
--- /dev/null
+++ b/yudao-module-mdm/pom.xml
@@ -0,0 +1,69 @@
+<?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-mdm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <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..b3c1993
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
@@ -0,0 +1,75 @@
+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 = "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 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..790b91d
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java
@@ -0,0 +1,95 @@
+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));
+ }
+
+ @GetMapping("/list-by-ids")
+ @Operation(summary = "鑾峰緱鍝佺墝鍒楄〃")
+ @Parameter(name = "ids", description = "缂栧彿鍒楄〃", required = true, example = "1,2,3")
+ @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
+ public CommonResult<List<MdmBrandDO>> getBrandListByIds(@RequestParam("ids") List<Long> ids) {
+ return success(brandService.getBrandList(ids));
+ }
+
+}
\ 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..e964cf3
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java
@@ -0,0 +1,99 @@
+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/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));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "鑾峰緱鍚敤鐨勫垎绫诲垪琛�")
+ public CommonResult<List<MdmItemCategoryRespVO>> getSimpleItemList() {
+ List<MdmItemCategoryDO> list = itemCategoryService.getSimpleItemList();
+ 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..f12ff9e
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -0,0 +1,110 @@
+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.controller.admin.item.vo.MdmItemStatusUpdateReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
+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 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 = "绠$悊鍚庡彴 - MDM 鐗╂枡涓绘暟鎹�")
+@RestController
+@RequestMapping("/mdm/item")
+@Validated
+public class MdmItemController {
+
+ @Resource
+ private MdmItemService itemService;
+
+ @Resource
+ private MdmUnitMeasureService unitMeasureService;
+
+ @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 = "鏇存柊鐗╂枡鐘舵��")
+ @PreAuthorize("@ss.hasPermission('mdm:item:update')")
+ public CommonResult<Boolean> updateItemStatus(@RequestBody MdmItemStatusUpdateReqVO reqVO) {
+ itemService.updateItemStatus(reqVO.getId(), reqVO.getStatus());
+ 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);
+ // 鍏宠仈鏌ヨ璁¢噺鍗曚綅鍚嶇О
+ Map<Long, MdmUnitMeasureDO> unitMap = unitMeasureService.getUnitMeasureMap(
+ convertSet(pageResult.getList(), MdmItemDO::getUnitMeasureId));
+ // 杞崲
+ List<MdmItemRespVO> respList = BeanUtils.toBean(pageResult.getList(), MdmItemRespVO.class);
+ respList.forEach(resp -> {
+ MdmUnitMeasureDO unit = unitMap.get(resp.getUnitMeasureId());
+ if (unit != null) {
+ resp.setUnitMeasureName(unit.getName());
+ }
+ });
+ return success(new PageResult<>(respList, pageResult.getTotal()));
+ }
+
+ @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..35723c4
--- /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 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;
+
+ @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..c241974
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
@@ -0,0 +1,72 @@
+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 = "鐗╂枡鍒嗙被缂栧彿", example = "1")
+ private Long categoryId;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栧彿", example = "1")
+ private Long unitMeasureId;
+
+ @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/item/vo/MdmItemStatusUpdateReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemStatusUpdateReqVO.java
new file mode 100644
index 0000000..4954ed9
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemStatusUpdateReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡鐘舵�佹洿鏂� Request VO")
+@Data
+public class MdmItemStatusUpdateReqVO {
+
+ @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "鐗╂枡缂栧彿涓嶈兘涓虹┖")
+ private Long id;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+ 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/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..87c4dd1
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
@@ -0,0 +1,96 @@
+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")
+@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));
+ }
+
+ @GetMapping("/list-by-ids")
+ @Operation(summary = "鑾峰緱璁¢噺鍗曚綅鍒楄〃")
+ @Parameter(name = "ids", description = "缂栧彿鍒楄〃", required = true, example = "1,2,3")
+ @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
+ public CommonResult<List<MdmUnitMeasureDO>> getUnitMeasureListByIds(@RequestParam("ids") List<Long> ids) {
+ return success(unitMeasureService.getUnitMeasureList(ids));
+ }
+
+}
\ 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..7a4cba4
--- /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("mdm_brand")
+@KeySequence("mdm_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..e54c823
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java
@@ -0,0 +1,72 @@
+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("mdm_item_category")
+@KeySequence("mdm_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: 鍗婃垚鍝�
+ * 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..f3243fe
--- /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("mdm_item_batch_config")
+@KeySequence("mdm_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..d231469
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java
@@ -0,0 +1,176 @@
+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("mdm_item")
+@KeySequence("mdm_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: 鍗婃垚鍝�
+ */
+ 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..c58f062
--- /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("mdm_unit_measure")
+@KeySequence("mdm_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..6312adf
--- /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("mdm_warehouse")
+@KeySequence("mdm_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..56d2559
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mdm.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * MDM 鐗╂枡绫诲瀷鏋氫妇
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Getter
+@AllArgsConstructor
+public enum MdmItemTypeEnum {
+
+ MATERIAL(1, "鐗╂枡"),
+ PRODUCT(2, "浜у搧"),
+ SEMI_FINISHED(3, "鍗婃垚鍝�");
+
+ /**
+ * 绫诲瀷缂栫爜
+ */
+ 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..d82d6a1
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java
@@ -0,0 +1,109 @@
+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);
+
+ /**
+ * 鑾峰緱鍚敤鐨勫垎绫诲垪琛�
+ *
+ * @return 鍒嗙被鍒楄〃
+ */
+ List<MdmItemCategoryDO> getSimpleItemList();
+
+}
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..ecb240b
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java
@@ -0,0 +1,158 @@
+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);
+ }
+ }
+ }
+
+ @Override
+ public List<MdmItemCategoryDO> getSimpleItemList() {
+ return itemCategoryMapper.selectList(new MdmItemCategoryDO().setStatus(0));
+ }
+
+}
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..13e8dec
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -0,0 +1,181 @@
+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());
+ }
+
+ @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..cb3d2de 100644
--- a/yudao-module-mes/pom.xml
+++ b/yudao-module-mes/pom.xml
@@ -24,6 +24,13 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
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..23f6e22 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
@@ -38,6 +38,8 @@
/**
* MES 鐗╂枡浜у搧 Service 瀹炵幇绫�
*
+ * 娉ㄦ剰锛氬綋鍓嶄繚鎸佷娇鐢� MES 鏈湴琛紝寰� MDM 鏁版嵁杩佺Щ瀹屾垚鍚庡啀鍒囨崲鍒� MDM API
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@@ -362,4 +364,4 @@
return respVO;
}
-}
+}
\ No newline at end of file
diff --git a/yudao-module-wms/pom.xml b/yudao-module-wms/pom.xml
index 9365862..82adcc2 100644
--- a/yudao-module-wms/pom.xml
+++ b/yudao-module-wms/pom.xml
@@ -24,6 +24,13 @@
<version>${revision}</version>
</dependency>
+ <!-- MDM 涓绘暟鎹鐞� API -->
+ <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/service/md/item/WmsItemServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java
index 191ca86..5ded7db 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
@@ -4,6 +4,9 @@
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.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
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;
@@ -15,6 +18,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -25,6 +29,9 @@
/**
* WMS 鍟嗗搧 Service 瀹炵幇绫�
*
+ * 鏀归�犺鏄庯細鍟嗗搧鍩虹鏁版嵁浠� MDM 妯″潡鑾峰彇锛坕temType=1 鐗╂枡绫诲瀷锛�
+ * WMS 鐗规湁鍔熻兘锛圫KU銆佸簱瀛樼瓑锛変粛淇濈暀 WMS 绠$悊
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@@ -33,6 +40,10 @@
@Resource
private WmsItemMapper itemMapper;
+
+ @Resource
+ private MdmItemApi mdmItemApi;
+
@Resource
@Lazy // 寤惰繜鍔犺浇锛岄伩鍏嶅惊鐜緷璧�
private WmsItemCategoryService categoryService;
@@ -48,7 +59,7 @@
// 鏍¢獙鏁版嵁
validateItemSaveData(createReqVO);
- // 鎻掑叆鍟嗗搧
+ // 鎻掑叆鍟嗗搧锛堟殏鏃朵繚鐣欏師閫昏緫锛屽悗缁彲瀹屽叏杩佺Щ鍒� MDM锛�
WmsItemDO item = BeanUtils.toBean(createReqVO, WmsItemDO.class);
itemMapper.insert(item);
// 鎻掑叆 SKU
@@ -64,7 +75,7 @@
// 鏍¢獙鏁版嵁
validateItemSaveData(updateReqVO);
- // 鏇存柊鍟嗗搧
+ // 鏇存柊鍟嗗搧锛堟殏鏃朵繚鐣欏師閫昏緫锛�
WmsItemDO updateObj = BeanUtils.toBean(updateReqVO, WmsItemDO.class);
itemMapper.updateById(updateObj);
// 鏇存柊 SKU
@@ -83,11 +94,9 @@
}
private void validateItemCodeUnique(Long id, String code) {
- WmsItemDO item = itemMapper.selectByCode(code);
- if (item == null) {
- return;
- }
- if (id == null || ObjectUtil.notEqual(item.getId(), id)) {
+ // 妫�鏌� MDM 涓紪鐮佸敮涓�鎬�
+ MdmItemRespDTO item = mdmItemApi.getItemByCode(code).getCheckedData();
+ if (item != null && ObjectUtil.notEqual(item.getId(), id)) {
throw exception(ITEM_CODE_DUPLICATE);
}
}
@@ -126,28 +135,58 @@
@Override
public WmsItemDO validateItemExists(Long id) {
- WmsItemDO item = itemMapper.selectById(id);
+ // 浠� MDM 鏍¢獙鐗╂枡瀛樺湪
+ MdmItemRespDTO item = mdmItemApi.validateItemExists(id).getCheckedData();
if (item == null) {
- throw exception(ITEM_NOT_EXISTS);
+ // 濡傛灉 MDM 涓笉瀛樺湪锛屾鏌ユ湰鍦拌〃
+ WmsItemDO localItem = itemMapper.selectById(id);
+ if (localItem == null) {
+ throw exception(ITEM_NOT_EXISTS);
+ }
+ return localItem;
}
- return item;
+ return convertToWmsItem(item);
}
@Override
public WmsItemDO getItem(Long id) {
- return itemMapper.selectById(id);
+ // 浠� MDM 鑾峰彇鐗╂枡鏁版嵁锛堟敞鎰� WMS ID 鍋忕Щ 30000锛�
+ MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
+ if (item == null) {
+ // 濡傛灉 MDM 涓笉瀛樺湪锛屾煡璇㈡湰鍦拌〃
+ return itemMapper.selectById(id);
+ }
+ return convertToWmsItem(item);
}
@Override
public PageResult<WmsItemDO> getItemPage(WmsItemPageReqVO pageReqVO) {
- return itemMapper.selectPage(pageReqVO,
- categoryService.getSelfAndChildItemCategoryIdList(pageReqVO.getCategoryId()));
+ // 浠� MDM 鑾峰彇鐗╂枡鍒嗛〉鏁版嵁锛坕temType=1 鐗╂枡绫诲瀷锛�
+ cn.iocoder.yudao.framework.common.pojo.PageResult<MdmItemRespDTO> mdmPageResult =
+ mdmItemApi.getItemPage(MdmItemTypeEnum.MATERIAL.getType(), null,
+ pageReqVO.getPageNo(), pageReqVO.getPageSize()).getCheckedData();
+
+ // 杞崲涓� WMS 鍟嗗搧 DO
+ List<WmsItemDO> list = new ArrayList<>();
+ for (MdmItemRespDTO item : mdmPageResult.getList()) {
+ list.add(convertToWmsItem(item));
+ }
+ return new PageResult<>(list, mdmPageResult.getTotal());
}
@Override
public List<WmsItemDO> getItemList(WmsItemListReqVO listReqVO) {
- return itemMapper.selectList(listReqVO,
- categoryService.getSelfAndChildItemCategoryIdList(listReqVO.getCategoryId()));
+ // 浠� MDM 鑾峰彇鐗╂枡鍒楄〃锛坕temType=1 鐗╂枡绫诲瀷锛�
+ List<MdmItemRespDTO> items = mdmItemApi.getItemListByStatus(null).getCheckedData();
+
+ List<WmsItemDO> result = new ArrayList<>();
+ for (MdmItemRespDTO item : items) {
+ // 鍙繑鍥炵墿鏂欑被鍨�
+ if (MdmItemTypeEnum.MATERIAL.getType().equals(item.getItemType())) {
+ result.add(convertToWmsItem(item));
+ }
+ }
+ return result;
}
@Override
@@ -155,17 +194,42 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- return itemMapper.selectByIds(ids);
+ // 浠� MDM 鑾峰彇鐗╂枡鍒楄〃
+ List<MdmItemRespDTO> items = mdmItemApi.getItemList(ids).getCheckedData();
+ List<WmsItemDO> result = new ArrayList<>();
+ for (MdmItemRespDTO item : items) {
+ result.add(convertToWmsItem(item));
+ }
+ return result;
+ }
+
+ /**
+ * 灏� MDM 鐗╂枡杞崲涓� WMS 鍟嗗搧 DO
+ */
+ private WmsItemDO convertToWmsItem(MdmItemRespDTO item) {
+ WmsItemDO wmsItem = new WmsItemDO();
+ wmsItem.setId(item.getId());
+ wmsItem.setCode(item.getCode());
+ wmsItem.setName(item.getName());
+ // MDM categoryId 鏄犲皠鍒� WMS categoryId锛堟敞鎰� ID 鍋忕Щ锛�
+ wmsItem.setCategoryId(item.getCategoryId() != null ? item.getCategoryId() - 20000 : null);
+ wmsItem.setBrandId(item.getBrandId());
+ // 鍗曚綅鍚嶇О闇�瑕佷粠 MDM 鍗曚綅琛ㄨ幏鍙栵紝杩欓噷鏆傛椂鐣欑┖
+ wmsItem.setRemark(item.getRemark());
+ return wmsItem;
}
@Override
public long getItemCountByCategoryId(Long categoryId) {
- return itemMapper.selectCountByCategoryId(categoryId);
+ // 浠� MDM 鑾峰彇鍒嗙被鐗╂枡鏁伴噺锛堟敞鎰� ID 鍋忕Щ锛�
+ return mdmItemApi.getItemCountByCategoryId(categoryId + 20000).getCheckedData();
}
@Override
public long getItemCountByBrandId(Long brandId) {
+ // 浠� MDM 鑾峰彇鍝佺墝鐗╂枡鏁伴噺
+ // 娉ㄦ剰锛歁DM 鐩墠娌℃湁鎸夊搧鐗岀粺璁$殑 API锛屾殏鏃惰繑鍥炴湰鍦版暟鎹�
return itemMapper.selectCountByBrandId(brandId);
}
-}
+}
\ No newline at end of file
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index fcac3d7..52b4bfe 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -136,6 +136,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