2026-06-29 940c8481d2fdcf51341db4ccd6fd6fcc2d43debb
初始化项目
已添加119个文件
已修改53个文件
13475 ■■■■■ 文件已修改
docs/batch1/database_changes.sql 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch1/frontend_changes.md 321 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch2/database_changes.sql 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch2/frontend_changes.md 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch3/database_changes.sql 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch3/frontend_changes.md 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch4/database_changes.sql 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch4/frontend_changes.md 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch5/database_changes.sql 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch5/frontend_changes.md 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch6/database_changes.sql 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch6/frontend_changes.md 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/cross_module_integration_plan.md 531 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/im_module.sql 353 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/im_module_unlock.md 399 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mdm_frontend_change.md 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/workflow_guide.md 639 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mdm_migration.sql 405 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mdm_tables.sql 334 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java 279 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseInStatusListener.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOutStatusListener.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java 139 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm-api/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/pom.xml 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApi.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/MdmBrandApiImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/brand/dto/MdmBrandRespDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApi.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/MdmItemCategoryApiImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/category/dto/MdmItemCategoryRespDTO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApi.java 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemApiImpl.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApi.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSkuApiImpl.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSkuRespDTO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApi.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/MdmUnitMeasureApiImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/unit/dto/MdmUnitMeasureRespDTO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApi.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/MdmWarehouseApiImpl.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/warehouse/dto/MdmWarehouseRespDTO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandPageReqVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/vo/MdmBrandSaveReqVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategoryRespVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/vo/MdmItemCategorySaveReqVO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemPageReqVO.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasurePageReqVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehousePageReqVO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseRespVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/vo/MdmWarehouseSaveReqVO.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/brand/MdmBrandDO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemSkuDO.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/brand/MdmBrandMapper.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/category/MdmItemCategoryMapper.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemBatchConfigMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemSkuMapper.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/unit/MdmUnitMeasureMapper.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/warehouse/MdmWarehouseMapper.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandService.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/brand/MdmBrandServiceImpl.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuService.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemSkuServiceImpl.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseService.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/warehouse/MdmWarehouseServiceImpl.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/pom.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/listener/MesProWorkOrderStatusListener.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApi.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApiImpl.java 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryQueryReqDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryRespDTO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptReqDTO.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptResultDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReserveReqDTO.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentReqDTO.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentResultDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsUseReserveReqDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/dto/StockRecordCreateDTO.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/WmsInventoryReserveController.java 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReservePageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReserveRespVO.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryReserveDO.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryTransactionDO.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryReserveMapper.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryTransactionMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsInventoryBizTypeEnum.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveBizTypeEnum.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveStatusEnum.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsTransactionTypeEnum.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/stock/StockBizTypeEnum.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveService.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveServiceImpl.java 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch1/database_changes.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,166 @@
-- =====================================================
-- Batch 1: WMS API æ¨¡å—建设 - æ•°æ®åº“变更脚本
-- ç‰ˆæœ¬: v1.0
-- æ—¥æœŸ: 2026-06-26
-- è¯´æ˜Ž: æ–°å¢žåº“存预留相关表,扩展库存字段
-- =====================================================
-- =====================================================
-- 1. æ–°å¢žåº“存预留表
-- =====================================================
-- åº“存预留表(用于销售订单等业务预留库存)
CREATE TABLE IF NOT EXISTS `wms_inventory_reserve` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号',
  `reserve_no` VARCHAR(64) NOT NULL COMMENT '预留单号',
  `biz_type` INT NOT NULL COMMENT '业务类型(10=销售预留 20=生产预留)',
  `biz_id` BIGINT NOT NULL COMMENT '业务单据ID',
  `biz_no` VARCHAR(64) DEFAULT NULL COMMENT '业务单据号',
  `sku_id` BIGINT NOT NULL COMMENT 'SKU编号',
  `warehouse_id` BIGINT NOT NULL COMMENT '仓库编号',
  `reserve_quantity` DECIMAL(24,6) NOT NULL COMMENT '预留数量',
  `used_quantity` DECIMAL(24,6) DEFAULT 0 COMMENT '已使用数量',
  `status` INT NOT NULL DEFAULT 0 COMMENT '状态(0=预留中 1=部分使用 2=已使用 3=已释放)',
  `expire_time` DATETIME DEFAULT NULL COMMENT '过期时间',
  `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
  `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_reserve_no` (`reserve_no`),
  KEY `idx_biz` (`biz_type`, `biz_id`),
  KEY `idx_sku_warehouse` (`sku_id`, `warehouse_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存预留表';
-- =====================================================
-- 2. æ–°å¢žåº“存事务流水表
-- =====================================================
CREATE TABLE IF NOT EXISTS `wms_inventory_transaction` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号',
  `transaction_no` VARCHAR(64) NOT NULL COMMENT '事务流水号',
  `transaction_type` INT NOT NULL COMMENT '事务类型(10=入库 20=出库 30=预留 40=释放 50=盘点 60=调拨)',
  `biz_type` INT NOT NULL COMMENT '业务类型',
  `biz_id` BIGINT NOT NULL COMMENT '业务单据ID',
  `biz_no` VARCHAR(64) DEFAULT NULL COMMENT '业务单据号',
  `sku_id` BIGINT NOT NULL COMMENT 'SKU编号',
  `warehouse_id` BIGINT NOT NULL COMMENT '仓库编号',
  `quantity` DECIMAL(24,6) NOT NULL COMMENT '变动数量(正数入库,负数出库)',
  `before_quantity` DECIMAL(24,6) DEFAULT NULL COMMENT '变动前数量',
  `after_quantity` DECIMAL(24,6) DEFAULT NULL COMMENT '变动后数量',
  `reserve_id` BIGINT DEFAULT NULL COMMENT '关联预留ID',
  `reserve_no` VARCHAR(64) DEFAULT NULL COMMENT '关联预留单号',
  `transaction_time` DATETIME NOT NULL COMMENT '事务时间',
  `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
  `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_transaction_no` (`transaction_no`),
  KEY `idx_biz` (`biz_type`, `biz_id`),
  KEY `idx_sku` (`sku_id`),
  KEY `idx_transaction_time` (`transaction_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存事务流水表';
-- =====================================================
-- 3. æ•°æ®å­—典新增
-- =====================================================
-- åº“存预留业务类型
INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES ('WMS åº“存预留业务类型', 'wms_reserve_biz_type', 0, '库存预留的业务来源类型', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name` = 'WMS åº“存预留业务类型';
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES
(10, '销售预留', '10', 'wms_reserve_biz_type', 0, 'primary', '', '销售订单预留库存', '1', NOW(), '1', NOW(), b'0'),
(20, '生产预留', '20', 'wms_reserve_biz_type', 0, 'success', '', '生产工单预留库存', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
-- åº“存事务类型
INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES ('WMS åº“存事务类型', 'wms_transaction_type', 0, '库存事务流水类型', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name` = 'WMS åº“存事务类型';
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES
(10, '入库', '10', 'wms_transaction_type', 0, 'success', '', '库存入库', '1', NOW(), '1', NOW(), b'0'),
(20, '出库', '20', 'wms_transaction_type', 0, 'warning', '', '库存出库', '1', NOW(), '1', NOW(), b'0'),
(30, '预留', '30', 'wms_transaction_type', 0, 'primary', '', '库存预留', '1', NOW(), '1', NOW(), b'0'),
(40, '释放', '40', 'wms_transaction_type', 0, 'info', '', '预留释放', '1', NOW(), '1', NOW(), b'0'),
(50, '盘点', '50', 'wms_transaction_type', 0, 'default', '', '库存盘点', '1', NOW(), '1', NOW(), b'0'),
(60, '调拨', '60', 'wms_transaction_type', 0, 'default', '', '库存调拨', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
-- åº“存预留状态
INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES ('WMS åº“存预留状态', 'wms_reserve_status', 0, '库存预留状态', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name` = 'WMS åº“存预留状态';
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES
(0, '预留中', '0', 'wms_reserve_status', 0, 'primary', '', '库存预留中', '1', NOW(), '1', NOW(), b'0'),
(1, '部分使用', '1', 'wms_reserve_status', 0, 'warning', '', '部分已使用', '1', NOW(), '1', NOW(), b'0'),
(2, '已使用', '2', 'wms_reserve_status', 0, 'success', '', '已全部使用', '1', NOW(), '1', NOW(), b'0'),
(3, '已释放', '3', 'wms_reserve_status', 0, 'info', '', '预留已释放', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
-- =====================================================
-- 4. èœå•新增(库存预留管理)
-- =====================================================
-- åº“存预留放在"库存管理"菜单下(ID: 6440)
-- å¦‚果库存管理菜单不存在,则放在 WMS ç³»ç»Ÿèœå•下(ID: 6400)
-- æ–¹æ¡ˆä¸€ï¼šä½¿ç”¨å›ºå®šçš„库存管理菜单ID(推荐,如果数据库结构一致)
-- SET @inventory_menu_id = 6440;
-- æ–¹æ¡ˆäºŒï¼šåŠ¨æ€æŸ¥è¯¢åº“å­˜ç®¡ç†èœå•ID(更安全)
SET @inventory_menu_id = (SELECT `id` FROM `system_menu` WHERE `name` = '库存管理' AND `parent_id` = 6400 AND `deleted` = b'0' LIMIT 1);
-- å¦‚果库存管理菜单不存在,使用 WMS ç³»ç»Ÿèœå•ID
SET @inventory_menu_id = IFNULL(@inventory_menu_id, 6400);
-- æ–°å¢žåº“存预留菜单
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES ('库存预留', '', 2, 30, @inventory_menu_id, 'reserve', 'ep:lock', 'wms/inventory/reserve/index', 'WmsInventoryReserve', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name` = '库存预留';
-- èŽ·å–èœå•ID
SET @reserve_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'reserve' AND `parent_id` = @inventory_menu_id AND `deleted` = b'0');
-- æŸ¥è¯¢æŒ‰é’®
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES ('库存预留查询', 'wms:inventory-reserve:query', 3, 1, @reserve_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `permission` = 'wms:inventory-reserve:query';
-- é‡Šæ”¾æŒ‰é’®
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES ('库存预留释放', 'wms:inventory-reserve:release', 3, 2, @reserve_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `permission` = 'wms:inventory-reserve:release';
-- =====================================================
-- å›žæ»šè„šæœ¬ï¼ˆå¦‚éœ€å›žæ»šï¼Œæ‰§è¡Œä»¥ä¸‹è¯­å¥ï¼‰
-- =====================================================
/*
-- åˆ é™¤æ–°å¢žè¡¨
DROP TABLE IF EXISTS `wms_inventory_reserve`;
DROP TABLE IF EXISTS `wms_inventory_transaction`;
-- åˆ é™¤å­—å…¸
DELETE FROM `system_dict_type` WHERE `type` = 'wms_reserve_biz_type';
DELETE FROM `system_dict_data` WHERE `dict_type` = 'wms_reserve_biz_type';
DELETE FROM `system_dict_type` WHERE `type` = 'wms_transaction_type';
DELETE FROM `system_dict_data` WHERE `dict_type` = 'wms_transaction_type';
DELETE FROM `system_dict_type` WHERE `type` = 'wms_reserve_status';
DELETE FROM `system_dict_data` WHERE `dict_type` = 'wms_reserve_status';
-- åˆ é™¤èœå•
DELETE FROM `system_menu` WHERE `path` = 'reserve' AND `name` = '库存预留';
DELETE FROM `system_menu` WHERE `permission` LIKE 'wms:inventory-reserve:%';
*/
docs/batch1/frontend_changes.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,321 @@
# Batch 1: WMS API æ¨¡å—建设 - å‰ç«¯ä¿®æ”¹æ–‡æ¡£
## æ¶‰åŠé¡µé¢
- æ–°å¢žé¡µé¢ï¼šåº“存预留管理页面
- ä¿®æ”¹é¡µé¢ï¼šåº“存查询页面(新增预留数量显示)
---
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /wms/inventory-reserve/page | åº“存预留分页查询 |
| POST | /wms/inventory-reserve/release | é‡Šæ”¾åº“存预留 |
| GET | /wms/inventory-reserve/get | èŽ·å–é¢„ç•™è¯¦æƒ… |
**请求参数(分页查询):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo | Integer | æ˜¯ | é¡µç  |
| pageSize | Integer | æ˜¯ | æ¯é¡µæ•°é‡ |
| reserveNo | String | å¦ | é¢„留单号 |
| bizType | Integer | å¦ | ä¸šåŠ¡ç±»åž‹ |
| bizNo | String | å¦ | ä¸šåŠ¡å•æ®å· |
| status | Integer | å¦ | çŠ¶æ€ |
**响应:**
```json
{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 1,
        "reserveNo": "RES202606260001",
        "bizType": 10,
        "bizTypeName": "销售预留",
        "bizId": 100,
        "bizNo": "SO202606260001",
        "skuId": 1,
        "skuName": "商品A-红色-L",
        "warehouseId": 1,
        "warehouseName": "主仓库",
        "reserveQuantity": 100.00,
        "usedQuantity": 0.00,
        "status": 0,
        "statusName": "预留中",
        "expireTime": "2026-06-30 00:00:00",
        "createTime": "2026-06-26 10:00:00"
      }
    ],
    "total": 1
  }
}
```
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. æ–°å¢žåº“存预留管理页面
**文件路径:** `src/views/wms/inventory/reserve/index.vue`
```vue
<template>
  <ContentWrap>
    <!-- æœç´¢ -->
    <el-form
      class="-mb-15px"
      :model="queryParams"
      :inline="true"
      label-width="68px"
    >
      <el-form-item label="预留单号" prop="reserveNo">
        <el-input
          v-model="queryParams.reserveNo"
          placeholder="请输入预留单号"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="业务类型" prop="bizType">
        <el-select
          v-model="queryParams.bizType"
          placeholder="请选择业务类型"
          clearable
          class="!w-240px"
        >
          <el-option
            v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RESERVE_BIZ_TYPE)"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select
          v-model="queryParams.status"
          placeholder="请选择状态"
          clearable
          class="!w-240px"
        >
          <el-option
            v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RESERVE_STATUS)"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <!-- åˆ—表 -->
  <ContentWrap class="mt-10px">
    <el-table v-loading="loading" :data="list" :stripe="true">
      <el-table-column label="预留单号" align="center" prop="reserveNo" width="180" />
      <el-table-column label="业务类型" align="center" prop="bizTypeName" width="100" />
      <el-table-column label="业务单据号" align="center" prop="bizNo" width="180" />
      <el-table-column label="商品SKU" align="center" prop="skuName" width="200" />
      <el-table-column label="仓库" align="center" prop="warehouseName" width="120" />
      <el-table-column label="预留数量" align="center" prop="reserveQuantity" width="100" />
      <el-table-column label="已使用数量" align="center" prop="usedQuantity" width="100" />
      <el-table-column label="状态" align="center" prop="statusName" width="100">
        <template #default="scope">
          <dict-tag :type="DICT_TYPE.WMS_RESERVE_STATUS" :value="scope.row.status" />
        </template>
      </el-table-column>
      <el-table-column
        label="过期时间"
        align="center"
        prop="expireTime"
        :formatter="dateFormatter"
        width="180"
      />
      <el-table-column label="操作" align="center" fixed="right" width="100">
        <template #default="scope">
          <el-button
            link
            type="primary"
            @click="handleRelease(scope.row)"
            v-hasPermi="['wms:inventory-reserve:release']"
            v-if="scope.row.status === 0 || scope.row.status === 1"
          >
            é‡Šæ”¾
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </ContentWrap>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as ReserveApi from '@/api/wms/inventory/reserve'
defineOptions({ name: 'WmsInventoryReserve' })
const loading = ref(true)
const list = ref([])
const total = ref(0)
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  reserveNo: undefined,
  bizType: undefined,
  bizNo: undefined,
  status: undefined
})
/** æŸ¥è¯¢åˆ—表 */
const getList = async () => {
  loading.value = true
  try {
    const data = await ReserveApi.getInventoryReservePage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.pageNo = 1
  getList()
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryParams.reserveNo = undefined
  queryParams.bizType = undefined
  queryParams.bizNo = undefined
  queryParams.status = undefined
  handleQuery()
}
/** é‡Šæ”¾é¢„ç•™ */
const handleRelease = async (row: any) => {
  try {
    await message.delConfirm(`确认释放预留单号【${row.reserveNo}】的库存预留?`)
    await ReserveApi.releaseInventoryReserve(row.id)
    message.success('释放成功')
    await getList()
  } catch {}
}
/** åˆå§‹åŒ– */
onMounted(() => {
  getList()
})
</script>
```
### 2. æ–°å¢ž API æŽ¥å£æ–‡ä»¶
**文件路径:** `src/api/wms/inventory/reserve/index.ts`
```typescript
import request from '@/config/axios'
// åº“存预留分页查询
export const getInventoryReservePage = async (params: any) => {
  return await request.get({ url: '/wms/inventory-reserve/page', params })
}
// èŽ·å–åº“å­˜é¢„ç•™è¯¦æƒ…
export const getInventoryReserve = async (id: number) => {
  return await request.get({ url: '/wms/inventory-reserve/get?id=' + id })
}
// é‡Šæ”¾åº“存预留
export const releaseInventoryReserve = async (id: number) => {
  return await request.post({ url: '/wms/inventory-reserve/release?id=' + id })
}
```
### 3. å­—典类型新增
**文件路径:** `src/utils/dict.ts`
在 `DICT_TYPE` ä¸­æ–°å¢žï¼š
```typescript
WMS_RESERVE_BIZ_TYPE = 'wms_reserve_biz_type',
WMS_RESERVE_STATUS = 'wms_reserve_status',
WMS_TRANSACTION_TYPE = 'wms_transaction_type'
```
---
## æ³¨æ„äº‹é¡¹
1. æœ¬æ‰¹æ¬¡ä¸»è¦æ–°å¢žåº“存预留功能,前端改动较小
2. åº“存预留页面提供预留查询和手动释放功能
3. åŽç»­æ‰¹æ¬¡ï¼ˆERP/MES é›†æˆï¼‰ä¼šè‡ªåŠ¨è°ƒç”¨é¢„ç•™ API,无需前端干预
4. å»ºè®®å…ˆæ‰§è¡Œæ•°æ®åº“脚本,再部署后端代码,最后更新前端
---
## åŽç«¯æ–°å¢žæ–‡ä»¶æ¸…单
```
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/
├── api/inventory/
│   â”œâ”€â”€ WmsInventoryApi.java
│   â”œâ”€â”€ WmsInventoryApiImpl.java
│   â””── dto/
│       â”œâ”€â”€ WmsInventoryQueryReqDTO.java
│       â”œâ”€â”€ WmsInventoryRespDTO.java
│       â”œâ”€â”€ WmsReceiptReqDTO.java
│       â”œâ”€â”€ WmsReceiptResultDTO.java
│       â”œâ”€â”€ WmsReserveReqDTO.java
│       â”œâ”€â”€ WmsShipmentReqDTO.java
│       â”œâ”€â”€ WmsShipmentResultDTO.java
│       â””── WmsUseReserveReqDTO.java
├── controller/admin/inventory/
│   â”œâ”€â”€ WmsInventoryReserveController.java
│   â””── vo/
│       â”œâ”€â”€ WmsInventoryReservePageReqVO.java
│       â””── WmsInventoryReserveRespVO.java
├── dal/dataobject/inventory/
│   â”œâ”€â”€ WmsInventoryReserveDO.java
│   â””── WmsInventoryTransactionDO.java
├── dal/mysql/inventory/
│   â”œâ”€â”€ WmsInventoryReserveMapper.java
│   â””── WmsInventoryTransactionMapper.java
├── enums/inventory/
│   â”œâ”€â”€ WmsInventoryBizTypeEnum.java
│   â”œâ”€â”€ WmsReserveBizTypeEnum.java
│   â”œâ”€â”€ WmsReserveStatusEnum.java
│   â””── WmsTransactionTypeEnum.java
└── service/inventory/
    â”œâ”€â”€ WmsInventoryReserveService.java
    â”œâ”€â”€ WmsInventoryReserveServiceImpl.java
    â”œâ”€â”€ WmsInventoryTransactionService.java
    â””── WmsInventoryTransactionServiceImpl.java
```
---
**文档版本**: v1.0
**创建日期**: 2026-06-26
docs/batch2/database_changes.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
-- =====================================================
-- Batch 2: ERP é‡‡è´­è®¢å•审批集成 - æ•°æ®åº“变更脚本
-- ç‰ˆæœ¬: v1.0
-- æ—¥æœŸ: 2026-06-26
-- è¯´æ˜Ž: ä¸º ERP é‡‡è´­è®¢å•添加 BPM æµç¨‹å®¡æ‰¹é›†æˆ
-- =====================================================
-- =====================================================
-- 1. ä¿®æ”¹é‡‡è´­è®¢å•表,新增流程实例字段
-- =====================================================
-- æ–°å¢žæµç¨‹å®žä¾‹ç¼–号字段
ALTER TABLE `erp_purchase_order`
ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM æµç¨‹å®žä¾‹ç¼–号' AFTER `remark`;
-- =====================================================
-- 2. æ•°æ®å­—典修改(审核状态)
-- =====================================================
-- æ³¨æ„ï¼šERP å®¡æ ¸çŠ¶æ€å·²æœ‰å­—å…¸æ•°æ®ï¼Œè¿™é‡Œä»…è¡¥å……æ–°å¢žçŠ¶æ€
-- åŽŸ PROCESS(10) ä»Ž"未审核"改为"审批中"
-- åŽŸ APPROVE(20) ä»Ž"已审核"改为"审批通过"
-- æ–°å¢ž DRAFT(0) è‰ç¨¿çŠ¶æ€
-- æ–°å¢ž REJECT(30) å®¡æ‰¹ä¸é€šè¿‡çŠ¶æ€
-- æ›´æ–°çŽ°æœ‰å­—å…¸æ•°æ®çš„æ ‡ç­¾
UPDATE `system_dict_data`
SET `label` = '审批中'
WHERE `dict_type` = 'erp_audit_status' AND `value` = '10';
UPDATE `system_dict_data`
SET `label` = '审批通过'
WHERE `dict_type` = 'erp_audit_status' AND `value` = '20';
-- æ–°å¢žè‰ç¨¿çŠ¶æ€ï¼ˆå¦‚æžœä¸å­˜åœ¨ï¼‰
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT 0, '草稿', '0', 'erp_audit_status', 0, 'info', '', '订单草稿状态', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '0');
-- æ–°å¢žå®¡æ‰¹ä¸é€šè¿‡çŠ¶æ€ï¼ˆå¦‚æžœä¸å­˜åœ¨ï¼‰
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT 30, '审批不通过', '30', 'erp_audit_status', 0, 'danger', '', '审批不通过', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '30');
-- =====================================================
-- 3. èœå•新增(提交审批按钮)
-- =====================================================
-- èŽ·å–é‡‡è´­è®¢å•èœå•ID
SET @purchase_order_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'purchase-order' AND `deleted` = b'0' LIMIT 1);
-- å¦‚果采购订单菜单不存在,尝试通过名称查找
SET @purchase_order_menu_id = IFNULL(@purchase_order_menu_id,
    (SELECT `id` FROM `system_menu` WHERE `name` = '采购订单' AND `deleted` = b'0' LIMIT 1));
-- æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT '采购订单提交审批', 'erp:purchase-order:submit', 3, 10, @purchase_order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE @purchase_order_menu_id IS NOT NULL
ON DUPLICATE KEY UPDATE `permission` = 'erp:purchase-order:submit';
-- =====================================================
-- 4. BPM æµç¨‹å®šä¹‰ï¼ˆéœ€è¦åœ¨å‰ç«¯åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼‰
-- =====================================================
-- æç¤ºï¼šè¯·åœ¨ BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹
-- æµç¨‹æ ‡è¯†ï¼ˆKey):erp-purchase-order-audit
-- æµç¨‹åç§°ï¼šERP é‡‡è´­è®¢å•审批流程
-- ä¸šåŠ¡å…³è”ï¼šé‡‡è´­è®¢å• ID
-- =====================================================
-- å›žæ»šè„šæœ¬ï¼ˆå¦‚éœ€å›žæ»šï¼Œæ‰§è¡Œä»¥ä¸‹è¯­å¥ï¼‰
-- =====================================================
/*
-- åˆ é™¤æ–°å¢žå­—段
ALTER TABLE `erp_purchase_order` DROP COLUMN `process_instance_id`;
-- æ¢å¤å­—典数据标签
UPDATE `system_dict_data` SET `label` = '未审核' WHERE `dict_type` = 'erp_audit_status' AND `value` = '10';
UPDATE `system_dict_data` SET `label` = '已审核' WHERE `dict_type` = 'erp_audit_status' AND `value` = '20';
-- åˆ é™¤æ–°å¢žå­—典数据
DELETE FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '0';
DELETE FROM `system_dict_data` WHERE `dict_type` = 'erp_audit_status' AND `value` = '30';
-- åˆ é™¤æ–°å¢žèœå•
DELETE FROM `system_menu` WHERE `permission` = 'erp:purchase-order:submit';
*/
docs/batch2/frontend_changes.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
# Batch 2: ERP é‡‡è´­è®¢å•审批集成 - å‰ç«¯ä¿®æ”¹æ–‡æ¡£
## æ¦‚è¿°
本批次为 ERP é‡‡è´­è®¢å•模块集成 BPM æµç¨‹å®¡æ‰¹åŠŸèƒ½ï¼Œå®žçŽ°é‡‡è´­è®¢å•çš„å®¡æ‰¹æµç¨‹åŒ–ç®¡ç†ã€‚
## æ¶‰åŠé¡µé¢
- ä¿®æ”¹é¡µé¢ï¼šé‡‡è´­è®¢å•管理页面(新增提交审批、审批状态展示)
- æ–°å¢žé…ç½®ï¼šBPM æµç¨‹æ¨¡åž‹é…ç½®
---
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/purchase-order/submit | æäº¤é‡‡è´­è®¢å•审批 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é‡‡è´­è®¢å•编号 |
**响应:**
```json
{
  "code": 0,
  "data": true,
  "msg": "操作成功"
}
```
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é‡‡è´­è®¢å•列表页面修改
**文件路径:** `src/views/erp/purchase/order/index.vue`
#### 1.1 æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
在操作列新增"提交审批"按钮,仅草稿状态可见:
```vue
<el-table-column label="操作" align="center" fixed="right" width="200">
  <template #default="scope">
    <el-button
      link
      type="primary"
      @click="handleUpdate(scope.row.id)"
      v-hasPermi="['erp:purchase-order:update']"
      v-if="scope.row.status === 0"
    >
      ç¼–辑
    </el-button>
    <el-button
      link
      type="primary"
      @click="handleSubmit(scope.row.id)"
      v-hasPermi="['erp:purchase-order:update']"
      v-if="scope.row.status === 0"
    >
      æäº¤å®¡æ‰¹
    </el-button>
    <el-button
      link
      type="primary"
      @click="handleApprove(scope.row.id)"
      v-hasPermi="['erp:purchase-order:update-status']"
      v-if="scope.row.status === 10"
    >
      å®¡æ‰¹é€šè¿‡
    </el-button>
    <el-button
      link
      type="danger"
      @click="handleDelete(scope.row.id)"
      v-hasPermi="['erp:purchase-order:delete']"
      v-if="scope.row.status === 0 || scope.row.status === 30"
    >
      åˆ é™¤
    </el-button>
  </template>
</el-table-column>
```
#### 1.2 æ–°å¢žæäº¤å®¡æ‰¹æ–¹æ³•
```typescript
/** æäº¤å®¡æ‰¹ */
const handleSubmit = async (id: number) => {
  try {
    await message.confirm('确认提交该采购订单进行审批?')
    await PurchaseOrderApi.submitPurchaseOrder(id)
    message.success('提交成功')
    await getList()
  } catch {}
}
```
#### 1.3 ä¿®æ”¹çŠ¶æ€åˆ—æ˜¾ç¤º
```vue
<el-table-column label="状态" align="center" prop="status" width="100">
  <template #default="scope">
    <dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
  </template>
</el-table-column>
```
### 2. API æŽ¥å£æ–‡ä»¶ä¿®æ”¹
**文件路径:** `src/api/erp/purchase/order/index.ts`
新增提交审批接口:
```typescript
// æäº¤é‡‡è´­è®¢å•审批
export const submitPurchaseOrder = async (id: number) => {
  return await request.post({ url: '/erp/purchase-order/submit?id=' + id })
}
```
### 3. å­—典类型确认
**文件路径:** `src/utils/dict.ts`
确保 `DICT_TYPE` ä¸­å·²åŒ…含:
```typescript
ERP_AUDIT_STATUS = 'erp_audit_status'
```
字典数据需要包含以下值:
- 0: è‰ç¨¿ï¼ˆinfo é¢œè‰²ï¼‰
- 10: å®¡æ‰¹ä¸­ï¼ˆwarning é¢œè‰²ï¼‰
- 20: å®¡æ‰¹é€šè¿‡ï¼ˆsuccess é¢œè‰²ï¼‰
- 30: å®¡æ‰¹ä¸é€šè¿‡ï¼ˆdanger é¢œè‰²ï¼‰
---
## BPM æµç¨‹æ¨¡åž‹é…ç½®
### æµç¨‹æ¨¡åž‹ä¿¡æ¯
在 BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼š
| é…ç½®é¡¹ | å€¼ |
|--------|-----|
| æµç¨‹æ ‡è¯†ï¼ˆKey) | erp-purchase-order-audit |
| æµç¨‹åç§° | ERP é‡‡è´­è®¢å•审批流程 |
| æµç¨‹åˆ†ç±» | ä¸šåŠ¡æµç¨‹ |
### æµç¨‹è®¾è®¡å»ºè®®
```
开始节点 â†’ éƒ¨é—¨ä¸»ç®¡å®¡æ‰¹ â†’ é‡‡è´­ç»ç†å®¡æ‰¹ â†’ ç»“束节点
```
审批结果:
- é€šè¿‡ï¼šçŠ¶æ€å˜ä¸º 20(审批通过)
- ä¸é€šè¿‡ï¼šçŠ¶æ€å˜ä¸º 30(审批不通过)
- å–消:状态变为 0(草稿)
---
## æ³¨æ„äº‹é¡¹
1. åˆ›å»ºé‡‡è´­è®¢å•时,默认状态从"未审核"改为"草稿"
2. åªæœ‰è‰ç¨¿çŠ¶æ€çš„è®¢å•æ‰èƒ½æäº¤å®¡æ‰¹
3. å®¡æ‰¹ä¸­çš„订单不能编辑和删除
4. å®¡æ‰¹ä¸é€šè¿‡çš„订单可以重新编辑后再次提交
5. éœ€è¦å…ˆæ‰§è¡Œæ•°æ®åº“脚本,再部署后端代码
6. éœ€è¦åœ¨ BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼ˆKey: erp-purchase-order-audit)
---
## åŽç«¯æ–°å¢ž/修改文件清单
```
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/
├── enums/
│   â”œâ”€â”€ ErpAuditStatus.java (修改:新增 DRAFT、REJECT çŠ¶æ€)
│   â””── ErrorCodeConstants.java (修改:新增错误码)
├── dal/dataobject/purchase/
│   â”” ErpPurchaseOrderDO.java (修改:新增 processInstanceId å­—段)
├── service/purchase/
│   â”œâ”€â”€ ErpPurchaseOrderService.java (修改:新增接口方法)
│   â”œâ”€â”€ ErpPurchaseOrderServiceImpl.java (修改:实现 BPM é›†æˆ)
│   â””── listener/
│       â””── ErpPurchaseOrderStatusListener.java (新增:审批状态监听器)
└── controller/admin/purchase/
    â”” ErpPurchaseOrderController.java (修改:新增提交审批接口)
```
---
## çŠ¶æ€æµè½¬å›¾
```
┌─────────────┐
│    è‰ç¨¿     â”‚
│   (status=0)│
└──────┬──────┘
       â”‚ æäº¤å®¡æ‰¹
       â†“
┌─────────────┐
│   å®¡æ‰¹ä¸­    â”‚
│  (status=10)│
└──────┬──────┘
       â”‚ å®¡æ‰¹ç»“æžœ
       â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
       â†“              â†“              â†“
┌─────────────┐ â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â” â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ å®¡æ‰¹é€šè¿‡    â”‚ â”‚ å®¡æ‰¹ä¸é€šè¿‡  â”‚ â”‚   å–消      â”‚
│ (status=20) â”‚ â”‚ (status=30) â”‚ â”‚  (status=0) â”‚
└─────────────┘ â””──────┬──────┘ â””─────────────┘
                  â”‚ é‡æ–°ç¼–辑
                  â””──────────────┘
```
---
**文档版本**: v1.0
**创建日期**: 2026-06-26
docs/batch3/database_changes.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
-- =====================================================
-- Batch 3: ERP é”€å”®è®¢å•审批集成 - æ•°æ®åº“变更脚本
-- ç‰ˆæœ¬: v1.0
-- æ—¥æœŸ: 2026-06-26
-- è¯´æ˜Ž: ä¸º ERP é”€å”®è®¢å•添加 BPM æµç¨‹å®¡æ‰¹é›†æˆ
-- =====================================================
-- =====================================================
-- 1. ä¿®æ”¹é”€å”®è®¢å•表,新增流程实例字段
-- =====================================================
-- æ–°å¢žæµç¨‹å®žä¾‹ç¼–号字段
ALTER TABLE `erp_sale_order`
ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM æµç¨‹å®žä¾‹ç¼–号' AFTER `remark`;
-- =====================================================
-- 2. èœå•新增(提交审批按钮)
-- =====================================================
-- èŽ·å–é”€å”®è®¢å•èœå•ID
SET @sale_order_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'sale-order' AND `deleted` = b'0' LIMIT 1);
-- å¦‚果销售订单菜单不存在,尝试通过名称查找
SET @sale_order_menu_id = IFNULL(@sale_order_menu_id,
    (SELECT `id` FROM `system_menu` WHERE `name` = '销售订单' AND `deleted` = b'0' LIMIT 1));
-- æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT '销售订单提交审批', 'erp:sale-order:submit', 3, 10, @sale_order_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE @sale_order_menu_id IS NOT NULL
ON DUPLICATE KEY UPDATE `permission` = 'erp:sale-order:submit';
-- =====================================================
-- 3. BPM æµç¨‹å®šä¹‰ï¼ˆéœ€è¦åœ¨å‰ç«¯åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼‰
-- =====================================================
-- æç¤ºï¼šè¯·åœ¨ BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹
-- æµç¨‹æ ‡è¯†ï¼ˆKey):erp-sale-order-audit
-- æµç¨‹åç§°ï¼šERP é”€å”®è®¢å•审批流程
-- ä¸šåŠ¡å…³è”ï¼šé”€å”®è®¢å• ID
-- =====================================================
-- å›žæ»šè„šæœ¬ï¼ˆå¦‚éœ€å›žæ»šï¼Œæ‰§è¡Œä»¥ä¸‹è¯­å¥ï¼‰
-- =====================================================
/*
-- åˆ é™¤æ–°å¢žå­—段
ALTER TABLE `erp_sale_order` DROP COLUMN `process_instance_id`;
-- åˆ é™¤æ–°å¢žèœå•
DELETE FROM `system_menu` WHERE `permission` = 'erp:sale-order:submit';
*/
docs/batch3/frontend_changes.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,181 @@
# Batch 3: ERP é”€å”®è®¢å•审批集成 - å‰ç«¯ä¿®æ”¹æ–‡æ¡£
## æ¦‚è¿°
本批次为 ERP é”€å”®è®¢å•模块集成 BPM æµç¨‹å®¡æ‰¹åŠŸèƒ½ï¼Œå®žçŽ°é”€å”®è®¢å•çš„å®¡æ‰¹æµç¨‹åŒ–ç®¡ç†ã€‚ä¸Ž Batch 2 é‡‡è´­è®¢å•审批集成类似。
## æ¶‰åŠé¡µé¢
- ä¿®æ”¹é¡µé¢ï¼šé”€å”®è®¢å•管理页面(新增提交审批、审批状态展示)
- æ–°å¢žé…ç½®ï¼šBPM æµç¨‹æ¨¡åž‹é…ç½®
---
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/sale-order/submit | æäº¤é”€å”®è®¢å•审批 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é”€å”®è®¢å•编号 |
**响应:**
```json
{
  "code": 0,
  "data": true,
  "msg": "操作成功"
}
```
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é”€å”®è®¢å•列表页面修改
**文件路径:** `src/views/erp/sale/order/index.vue`
#### 1.1 æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
在操作列新增"提交审批"按钮,仅草稿状态可见:
```vue
<el-table-column label="操作" align="center" fixed="right" width="200">
  <template #default="scope">
    <el-button
      link
      type="primary"
      @click="handleUpdate(scope.row.id)"
      v-hasPermi="['erp:sale-order:update']"
      v-if="scope.row.status === 0"
    >
      ç¼–辑
    </el-button>
    <el-button
      link
      type="primary"
      @click="handleSubmit(scope.row.id)"
      v-hasPermi="['erp:sale-order:update']"
      v-if="scope.row.status === 0"
    >
      æäº¤å®¡æ‰¹
    </el-button>
    <el-button
      link
      type="primary"
      @click="handleApprove(scope.row.id)"
      v-hasPermi="['erp:sale-order:update-status']"
      v-if="scope.row.status === 10"
    >
      å®¡æ‰¹é€šè¿‡
    </el-button>
    <el-button
      link
      type="danger"
      @click="handleDelete(scope.row.id)"
      v-hasPermi="['erp:sale-order:delete']"
      v-if="scope.row.status === 0 || scope.row.status === 30"
    >
      åˆ é™¤
    </el-button>
  </template>
</el-table-column>
```
#### 1.2 æ–°å¢žæäº¤å®¡æ‰¹æ–¹æ³•
```typescript
/** æäº¤å®¡æ‰¹ */
const handleSubmit = async (id: number) => {
  try {
    await message.confirm('确认提交该销售订单进行审批?')
    await SaleOrderApi.submitSaleOrder(id)
    message.success('提交成功')
    await getList()
  } catch {}
}
```
#### 1.3 ä¿®æ”¹çŠ¶æ€åˆ—æ˜¾ç¤º
```vue
<el-table-column label="状态" align="center" prop="status" width="100">
  <template #default="scope">
    <dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
  </template>
</el-table-column>
```
### 2. API æŽ¥å£æ–‡ä»¶ä¿®æ”¹
**文件路径:** `src/api/erp/sale/order/index.ts`
新增提交审批接口:
```typescript
// æäº¤é”€å”®è®¢å•审批
export const submitSaleOrder = async (id: number) => {
  return await request.post({ url: '/erp/sale-order/submit?id=' + id })
}
```
---
## BPM æµç¨‹æ¨¡åž‹é…ç½®
### æµç¨‹æ¨¡åž‹ä¿¡æ¯
在 BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼š
| é…ç½®é¡¹ | å€¼ |
|--------|-----|
| æµç¨‹æ ‡è¯†ï¼ˆKey) | erp-sale-order-audit |
| æµç¨‹åç§° | ERP é”€å”®è®¢å•审批流程 |
| æµç¨‹åˆ†ç±» | ä¸šåŠ¡æµç¨‹ |
### æµç¨‹è®¾è®¡å»ºè®®
```
开始节点 â†’ é”€å”®ä¸»ç®¡å®¡æ‰¹ â†’ è´¢åŠ¡å®¡æ‰¹ â†’ ç»“束节点
```
---
## æ³¨æ„äº‹é¡¹
1. åˆ›å»ºé”€å”®è®¢å•时,默认状态从"未审核"改为"草稿"
2. åªæœ‰è‰ç¨¿çŠ¶æ€çš„è®¢å•æ‰èƒ½æäº¤å®¡æ‰¹
3. å®¡æ‰¹ä¸­çš„订单不能编辑和删除
4. å®¡æ‰¹ä¸é€šè¿‡çš„订单可以重新编辑后再次提交
5. éœ€è¦å…ˆæ‰§è¡Œæ•°æ®åº“脚本,再部署后端代码
6. éœ€è¦åœ¨ BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼ˆKey: erp-sale-order-audit)
---
## åŽç«¯æ–°å¢ž/修改文件清单
```
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/
├── enums/
│   â””── ErrorCodeConstants.java (修改:新增错误码)
├── dal/dataobject/sale/
│   â””── ErpSaleOrderDO.java (修改:新增 processInstanceId å­—段)
├── service/sale/
│   â”œâ”€â”€ ErpSaleOrderService.java (修改:新增接口方法)
│   â”œâ”€â”€ ErpSaleOrderServiceImpl.java (修改:实现 BPM é›†æˆ)
│   â””── listener/
│       â””── ErpSaleOrderStatusListener.java (新增:审批状态监听器)
└── controller/admin/sale/
    â””── ErpSaleOrderController.java (修改:新增提交审批接口)
```
---
**文档版本**: v1.0
**创建日期**: 2026-06-26
docs/batch4/database_changes.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
-- =====================================================
-- Batch 4: ERP é‡‡è´­å…¥åº“单审批集成 - æ•°æ®åº“变更脚本
-- ç‰ˆæœ¬: v1.0
-- æ—¥æœŸ: 2026-06-26
-- è¯´æ˜Ž: ä¸º ERP é‡‡è´­å…¥åº“单添加 BPM æµç¨‹å®¡æ‰¹é›†æˆ
-- =====================================================
-- =====================================================
-- 1. ä¿®æ”¹é‡‡è´­å…¥åº“表,新增流程实例字段
-- =====================================================
-- æ–°å¢žæµç¨‹å®žä¾‹ç¼–号字段
ALTER TABLE `erp_purchase_in`
ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM æµç¨‹å®žä¾‹ç¼–号' AFTER `remark`;
-- =====================================================
-- 2. èœå•新增(提交审批按钮)
-- =====================================================
-- èŽ·å–é‡‡è´­å…¥åº“èœå•ID
SET @purchase_in_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'purchase-in' AND `deleted` = b'0' LIMIT 1);
-- å¦‚果采购入库菜单不存在,尝试通过名称查找
SET @purchase_in_menu_id = IFNULL(@purchase_in_menu_id,
    (SELECT `id` FROM `system_menu` WHERE `name` = '采购入库' AND `deleted` = b'0' LIMIT 1));
-- æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT '采购入库提交审批', 'erp:purchase-in:submit', 3, 10, @purchase_in_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE @purchase_in_menu_id IS NOT NULL
ON DUPLICATE KEY UPDATE `permission` = 'erp:purchase-in:submit';
-- =====================================================
-- 3. BPM æµç¨‹å®šä¹‰ï¼ˆéœ€è¦åœ¨å‰ç«¯åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼‰
-- =====================================================
-- æç¤ºï¼šè¯·åœ¨ BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹
-- æµç¨‹æ ‡è¯†ï¼ˆKey):erp-purchase-in-audit
-- æµç¨‹åç§°ï¼šERP é‡‡è´­å…¥åº“审批流程
-- ä¸šåŠ¡å…³è”ï¼šé‡‡è´­å…¥åº“å• ID
-- =====================================================
-- å›žæ»šè„šæœ¬ï¼ˆå¦‚éœ€å›žæ»šï¼Œæ‰§è¡Œä»¥ä¸‹è¯­å¥ï¼‰
-- =====================================================
/*
-- åˆ é™¤æ–°å¢žå­—段
ALTER TABLE `erp_purchase_in` DROP COLUMN `process_instance_id`;
-- åˆ é™¤æ–°å¢žèœå•
DELETE FROM `system_menu` WHERE `permission` = 'erp:purchase-in:submit';
*/
docs/batch4/frontend_changes.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
# Batch 4: ERP é‡‡è´­å…¥åº“单审批集成 - å‰ç«¯ä¿®æ”¹æ–‡æ¡£
## æ¦‚è¿°
本批次为 ERP é‡‡è´­å…¥åº“单模块集成 BPM æµç¨‹å®¡æ‰¹åŠŸèƒ½ã€‚é‡‡è´­å…¥åº“å•å®¡æ‰¹é€šè¿‡åŽä¼šè‡ªåŠ¨å¢žåŠ åº“å­˜ã€‚
## æ¶‰åŠé¡µé¢
- ä¿®æ”¹é¡µé¢ï¼šé‡‡è´­å…¥åº“管理页面(新增提交审批、审批状态展示)
---
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/purchase-in/submit | æäº¤é‡‡è´­å…¥åº“审批 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é‡‡è´­å…¥åº“单编号 |
**响应:**
```json
{
  "code": 0,
  "data": true,
  "msg": "操作成功"
}
```
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é‡‡è´­å…¥åº“列表页面修改
**文件路径:** `src/views/erp/purchase/in/index.vue`
#### 1.1 æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
```vue
<el-button
  link
  type="primary"
  @click="handleSubmit(scope.row.id)"
  v-hasPermi="['erp:purchase-in:update']"
  v-if="scope.row.status === 0"
>
  æäº¤å®¡æ‰¹
</el-button>
```
#### 1.2 æ–°å¢žæäº¤å®¡æ‰¹æ–¹æ³•
```typescript
/** æäº¤å®¡æ‰¹ */
const handleSubmit = async (id: number) => {
  try {
    await message.confirm('确认提交该采购入库单进行审批?审批通过后将增加库存')
    await PurchaseInApi.submitPurchaseIn(id)
    message.success('提交成功')
    await getList()
  } catch {}
}
```
### 2. API æŽ¥å£æ–‡ä»¶ä¿®æ”¹
**文件路径:** `src/api/erp/purchase/in/index.ts`
```typescript
// æäº¤é‡‡è´­å…¥åº“审批
export const submitPurchaseIn = async (id: number) => {
  return await request.post({ url: '/erp/purchase-in/submit?id=' + id })
}
```
---
## æ³¨æ„äº‹é¡¹
1. é‡‡è´­å…¥åº“单审批通过后会自动增加库存
2. å®¡æ‰¹ä¸é€šè¿‡æˆ–取消不会变更库存
3. BPM æµç¨‹ Key:`erp-purchase-in-audit`
---
**文档版本**: v1.0
**创建日期**: 2026-06-26
docs/batch5/database_changes.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
-- =====================================================
-- Batch 5: ERP é”€å”®å‡ºåº“单审批集成 - æ•°æ®åº“变更脚本
-- ç‰ˆæœ¬: v1.0
-- æ—¥æœŸ: 2026-06-26
-- è¯´æ˜Ž: ä¸º ERP é”€å”®å‡ºåº“单添加 BPM æµç¨‹å®¡æ‰¹é›†æˆ
-- =====================================================
-- =====================================================
-- 1. ä¿®æ”¹é”€å”®å‡ºåº“表,新增流程实例字段
-- =====================================================
-- æ–°å¢žæµç¨‹å®žä¾‹ç¼–号字段
ALTER TABLE `erp_sale_out`
ADD COLUMN `process_instance_id` VARCHAR(64) DEFAULT NULL COMMENT 'BPM æµç¨‹å®žä¾‹ç¼–号' AFTER `remark`;
-- =====================================================
-- 2. èœå•新增(提交审批按钮)
-- =====================================================
-- èŽ·å–é”€å”®å‡ºåº“èœå•ID
SET @sale_out_menu_id = (SELECT `id` FROM `system_menu` WHERE `path` = 'sale-out' AND `deleted` = b'0' LIMIT 1);
-- å¦‚果销售出库菜单不存在,尝试通过名称查找
SET @sale_out_menu_id = IFNULL(@sale_out_menu_id,
    (SELECT `id` FROM `system_menu` WHERE `name` = '销售出库' AND `deleted` = b'0' LIMIT 1));
-- æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT '销售出库提交审批', 'erp:sale-out:submit', 3, 10, @sale_out_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE @sale_out_menu_id IS NOT NULL
ON DUPLICATE KEY UPDATE `permission` = 'erp:sale-out:submit';
-- =====================================================
-- 3. BPM æµç¨‹å®šä¹‰ï¼ˆéœ€è¦åœ¨å‰ç«¯åˆ›å»ºæµç¨‹æ¨¡åž‹ï¼‰
-- =====================================================
-- æç¤ºï¼šè¯·åœ¨ BPM ç®¡ç†åŽå°åˆ›å»ºæµç¨‹æ¨¡åž‹
-- æµç¨‹æ ‡è¯†ï¼ˆKey):erp-sale-out-audit
-- æµç¨‹åç§°ï¼šERP é”€å”®å‡ºåº“审批流程
-- ä¸šåŠ¡å…³è”ï¼šé”€å”®å‡ºåº“å• ID
-- =====================================================
-- å›žæ»šè„šæœ¬ï¼ˆå¦‚éœ€å›žæ»šï¼Œæ‰§è¡Œä»¥ä¸‹è¯­å¥ï¼‰
-- =====================================================
/*
-- åˆ é™¤æ–°å¢žå­—段
ALTER TABLE `erp_sale_out` DROP COLUMN `process_instance_id`;
-- åˆ é™¤æ–°å¢žèœå•
DELETE FROM `system_menu` WHERE `permission` = 'erp:sale-out:submit';
*/
docs/batch5/frontend_changes.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
# Batch 5: ERP é”€å”®å‡ºåº“单审批集成 - å‰ç«¯ä¿®æ”¹æ–‡æ¡£
## æ¦‚è¿°
本批次为 ERP é”€å”®å‡ºåº“单模块集成 BPM æµç¨‹å®¡æ‰¹åŠŸèƒ½ã€‚é”€å”®å‡ºåº“å•å®¡æ‰¹é€šè¿‡åŽä¼šè‡ªåŠ¨å‡å°‘åº“å­˜ã€‚
## æ¶‰åŠé¡µé¢
- ä¿®æ”¹é¡µé¢ï¼šé”€å”®å‡ºåº“管理页面(新增提交审批、审批状态展示)
---
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/sale-out/submit | æäº¤é”€å”®å‡ºåº“审批 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é”€å”®å‡ºåº“单编号 |
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é”€å”®å‡ºåº“列表页面修改
**文件路径:** `src/views/erp/sale/out/index.vue`
#### 1.1 æ–°å¢žæäº¤å®¡æ‰¹æŒ‰é’®
```vue
<el-button
  link
  type="primary"
  @click="handleSubmit(scope.row.id)"
  v-hasPermi="['erp:sale-out:update']"
  v-if="scope.row.status === 0"
>
  æäº¤å®¡æ‰¹
</el-button>
```
#### 1.2 æ–°å¢žæäº¤å®¡æ‰¹æ–¹æ³•
```typescript
/** æäº¤å®¡æ‰¹ */
const handleSubmit = async (id: number) => {
  try {
    await message.confirm('确认提交该销售出库单进行审批?审批通过后将减少库存')
    await SaleOutApi.submitSaleOut(id)
    message.success('提交成功')
    await getList()
  } catch {}
}
```
### 2. API æŽ¥å£æ–‡ä»¶ä¿®æ”¹
**文件路径:** `src/api/erp/sale/out/index.ts`
```typescript
// æäº¤é”€å”®å‡ºåº“审批
export const submitSaleOut = async (id: number) => {
  return await request.post({ url: '/erp/sale-out/submit?id=' + id })
}
```
---
## æ³¨æ„äº‹é¡¹
1. é”€å”®å‡ºåº“单审批通过后会自动减少库存(负数变更)
2. å®¡æ‰¹ä¸é€šè¿‡æˆ–取消不会变更库存
3. BPM æµç¨‹ Key:`erp-sale-out-audit`
---
**文档版本**: v1.0
**创建日期**: 2026-06-26
docs/batch6/database_changes.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
-- =============================================
-- æ‰¹æ¬¡å…­ï¼šMES ç”Ÿäº§å·¥å• BPM å®¡æ‰¹é›†æˆ
-- æ‰§è¡Œé¡ºåºï¼š1
-- =============================================
-- 1. ä¸º MES ç”Ÿäº§å·¥å•表添加流程实例编号字段
ALTER TABLE `mes_pro_work_order` ADD COLUMN `process_instance_id` VARCHAR(64) NULL COMMENT 'BPM æµç¨‹å®žä¾‹ç¼–号' AFTER `status`;
-- 2. ä¸ºæµç¨‹å®žä¾‹ç¼–号创建索引(可选,用于按流程实例查询)
-- ALTER TABLE `mes_pro_work_order` ADD INDEX `idx_process_instance_id` (`process_instance_id`);
-- 3. æ›´æ–°å·¥å•状态字典数据(添加审批中、审批不通过状态)
-- æ³¨æ„ï¼šå¦‚果字典数据已存在则忽略
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES
    (1, '审批中', '1', 'mes_pro_work_order_status', 0, 'warning', '', 'MES生产工单状态-审批中', '1', NOW(), '1', NOW(), b'0'),
    (5, '审批不通过', '5', 'mes_pro_work_order_status', 0, 'danger', '', 'MES生产工单状态-审批不通过', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `updater` = '1', `update_time` = NOW();
-- 4. æ·»åŠ èœå•æŒ‰é’®ï¼ˆæäº¤å®¡æ‰¹ï¼‰
-- å…ˆæŸ¥è¯¢ç”Ÿäº§å·¥å•菜单ID(假设菜单路径为 MES管理 > ç”Ÿäº§ç®¡ç† > ç”Ÿäº§å·¥å•)
SET @workorder_menu_id = (SELECT `id` FROM `system_menu` WHERE `name` = '生产工单' AND `parent_id` IN (SELECT `id` FROM `system_menu` WHERE `name` = '生产管理') LIMIT 1);
INSERT INTO `system_menu` (`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT '生产工单提交审批', 'mes:pro-work-order:update', 3, 6, @workorder_menu_id, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE @workorder_menu_id IS NOT NULL
  AND NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `name` = '生产工单提交审批' AND `parent_id` = @workorder_menu_id);
-- 5. ä¸ºè§’色分配提交审批按钮权限(根据实际需要调整)
-- ç¤ºä¾‹ï¼šä¸º MES ç®¡ç†å‘˜è§’色分配权限
-- INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
-- SELECT r.id, m.id, '1', NOW(), '1', NOW(), b'0', 1
-- FROM `system_role` r, `system_menu` m
-- WHERE r.name = 'MES管理员' AND m.name = '生产工单提交审批';
-- =============================================
-- å›žæ»šè„šæœ¬ï¼ˆå¦‚éœ€å›žæ»šæ‰§è¡Œä»¥ä¸‹è¯­å¥ï¼‰
-- =============================================
-- ALTER TABLE `mes_pro_work_order` DROP COLUMN `process_instance_id`;
-- DELETE FROM `system_dict_data` WHERE `dict_type` = 'mes_pro_work_order_status' AND `value` IN ('1', '5');
-- DELETE FROM `system_menu` WHERE `name` = '生产工单提交审批';
docs/batch6/frontend_changes.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
# MES ç”Ÿäº§å·¥å•审批集成
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§å·¥å•列表页面 (`views/mes/pro/workorder/index.vue`)
- ç”Ÿäº§å·¥å•详情/编辑页面 (`views/mes/pro/workorder/WorkOrderForm.vue`)
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| PUT | /mes/pro/work-order/submit | æäº¤å·¥å•审批 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | å·¥å•编号(Query å‚数) |
**响应:** `{ "code": 200, "msg": "操作成功", "data": true }`
## å·¥å•状态变更说明
| åŽŸçŠ¶æ€ | æ–°çŠ¶æ€ | è§¦å‘条件 |
|--------|--------|----------|
| è‰ç¨¿(0) | å®¡æ‰¹ä¸­(1) | æäº¤å®¡æ‰¹ |
| å®¡æ‰¹ä¸­(1) | å·²ç¡®è®¤(2) | å®¡æ‰¹é€šè¿‡ |
| å®¡æ‰¹ä¸­(1) | å®¡æ‰¹ä¸é€šè¿‡(5) | å®¡æ‰¹ä¸é€šè¿‡ |
| å®¡æ‰¹ä¸­(1) | è‰ç¨¿(0) | å®¡æ‰¹å–消 |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. åˆ—表页面 - æ·»åŠ "提交审批"按钮
**位置:** `views/mes/pro/workorder/index.vue`
```html
<!-- åœ¨æ“ä½œåˆ—中添加提交审批按钮 -->
<el-table-column label="操作" align="center" width="200">
  <template #default="scope">
    <el-button
      v-if="scope.row.status === 0"
      link
      type="primary"
      @click="handleSubmit(scope.row.id)"
      v-hasPermi="['mes:pro-work-order:update']"
    >
      æäº¤å®¡æ‰¹
    </el-button>
    <el-button
      v-if="scope.row.status === 0"
      link
      type="primary"
      @click="handleUpdate(scope.row.id)"
      v-hasPermi="['mes:pro-work-order:update']"
    >
      ç¼–辑
    </el-button>
    <el-button
      v-if="scope.row.status === 0"
      link
      type="danger"
      @click="handleDelete(scope.row.id)"
      v-hasPermi="['mes:pro-work-order:delete']"
    >
      åˆ é™¤
    </el-button>
    <!-- å·²ç¡®è®¤çŠ¶æ€æ˜¾ç¤ºå®Œæˆã€å–æ¶ˆæŒ‰é’® -->
    <el-button
      v-if="scope.row.status === 2"
      link
      type="success"
      @click="handleFinish(scope.row.id)"
      v-hasPermi="['mes:pro-work-order:update']"
    >
      å®Œæˆ
    </el-button>
    <el-button
      v-if="scope.row.status === 2"
      link
      type="warning"
      @click="handleCancel(scope.row.id)"
      v-hasPermi="['mes:pro-work-order:update']"
    >
      å–消
    </el-button>
  </template>
</el-table-column>
```
### 2. data æ•°æ®
```js
// çŠ¶æ€å­—å…¸
const statusDict = ref([
  { label: '草稿', value: 0, tagType: 'info' },
  { label: '审批中', value: 1, tagType: 'warning' },
  { label: '已确认', value: 2, tagType: 'success' },
  { label: '已完成', value: 3, tagType: 'primary' },
  { label: '已取消', value: 4, tagType: 'danger' },
  { label: '审批不通过', value: 5, tagType: 'danger' }
])
```
### 3. æ–¹æ³•
```js
import { submitWorkOrder } from '@/api/mes/pro/workorder'
// æäº¤å®¡æ‰¹
const handleSubmit = async (id: number) => {
  try {
    await message.confirm('确认提交该工单进行审批?')
    await submitWorkOrder(id)
    message.success('提交成功')
    await getList()
  } catch (error) {
    console.error(error)
  }
}
```
### 4. API æ–¹æ³•
**文件:** `api/mes/pro/workorder/index.ts`
```ts
// æäº¤å·¥å•审批
export const submitWorkOrder = (id: number) => {
  return request.put({ url: '/mes/pro/work-order/submit', params: { id } })
}
```
### 5. çŠ¶æ€æ ‡ç­¾æ˜¾ç¤º
```html
<dict-tag :type="DICT_TYPE.MES_PRO_WORK_ORDER_STATUS" :value="scope.row.status" />
```
### 6. è¡¨å•状态禁用逻辑
**编辑表单:** åªæœ‰è‰ç¨¿çŠ¶æ€(0)和审批不通过状态(5)才能编辑
```js
// åˆ¤æ–­æ˜¯å¦å¯ç¼–辑
const isEditable = computed(() => {
  return formData.value.status === 0 || formData.value.status === 5
})
```
```html
<el-form-item label="工单编码" prop="code">
  <el-input v-model="formData.code" :disabled="!isEditable" />
</el-form-item>
```
## æ³¨æ„äº‹é¡¹
1. **状态流转**:草稿 â†’ æäº¤å®¡æ‰¹ â†’ å®¡æ‰¹ä¸­ â†’ å®¡æ‰¹é€šè¿‡(已确认) / å®¡æ‰¹ä¸é€šè¿‡
2. **审批不通过后**:工单状态变为"审批不通过"(5),可以重新编辑后再次提交审批
3. **权限控制**:提交审批使用 `mes:pro-work-order:update` æƒé™ï¼Œä¸Žç¼–辑权限一致
4. **按钮显示**:根据工单状态动态显示操作按钮,避免误操作
5. **字典数据**:确保系统字典 `mes_pro_work_order_status` å·²æ·»åŠ å®¡æ‰¹ä¸­(1)和审批不通过(5)状态
docs/cross_module_integration_plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,531 @@
# MES/ERP/WMS/CRM è·¨æ¨¡å—集成方案
## ä¸€ã€æ•´ä½“架构概述
### 1.1 æ¨¡å—定位
| æ¨¡å— | æ ¸å¿ƒèŒè´£ | ä¸šåŠ¡è¾¹ç•Œ |
|------|---------|---------|
| **ERP** | è´¢åŠ¡æ ¸ç®—ã€é‡‡è´­é”€å”®è®¢å•ç®¡ç† | å¯¹å¤–业务往来、成本核算 |
| **MES** | ç”Ÿäº§æ‰§è¡Œã€å·¥è‰ºç®¡ç†ã€è´¨é‡æŽ§åˆ¶ | ç”Ÿäº§è¿‡ç¨‹ç®¡ç† |
| **WMS** | ä»“储管理、库存控制 | å®žç‰©åº“存管理 |
| **CRM** | å®¢æˆ·å…³ç³»ã€é”€å”®æœºä¼šã€åˆåŒç®¡ç† | å®¢æˆ·ä¾§ä¸šåŠ¡ç®¡ç† |
### 1.2 ç»Ÿä¸€åŸºç¡€æ•°æ®æž¶æž„
采用 **单一产品主数据** æ¨¡å¼ï¼Œæ‰“破模块壁垒:
```
┌─────────────────────────────────────────────────────────────┐
│                    åŸºç¡€æ•°æ®å±‚(共享)                          â”‚
├─────────────────────────────────────────────────────────────┤
│  äº§å“ç‰©æ–™ä¸»æ•°æ®ï¼ˆMD_ITEM)    â† æ‰€æœ‰æ¨¡å—共用唯一产品编码        â”‚
│  å®¢æˆ·ä¸»æ•°æ®ï¼ˆMD_CLIENT)      â† ERP/CRM å…±ç”¨                   â”‚
│  ä¾›åº”商主数据(MD_VENDOR)    â† ERP/MES å…±ç”¨                   â”‚
│  ä»“库主数据(MD Warehouse)   â† ERP/WMS/MES å…±ç”¨               â”‚
│  è®¡é‡å•位主数据(MD Unit)    â† å…¨å±€å…±ç”¨                       â”‚
│  BOM ä¸»æ•°æ®ï¼ˆMD BOM)         â† MES/ERP å…±ç”¨                   â”‚
└─────────────────────────────────────────────────────────────┘
         â†“              â†“              â†“              â†“
    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”
    â”‚   ERP   â”‚    â”‚   MES   â”‚    â”‚   WMS   â”‚    â”‚   CRM   â”‚
    â””─────────┘    â””─────────┘    â””─────────┘    â””─────────┘
```
### 1.3 æ ¸å¿ƒè®¾è®¡åŽŸåˆ™
1. **数据唯一性**:产品物料编码全局唯一,各模块通过产品ID关联
2. **流程驱动**:所有跨模块操作通过 BPM å®¡æ‰¹æµç¨‹è§¦å‘
3. **事件解耦**:模块间通过事件监听机制实现联动,避免直接调用
4. **库存中心化**:WMS ä½œä¸ºå”¯ä¸€åº“存管理者,所有库存变动需经过 WMS
---
## äºŒã€ç»Ÿä¸€åŸºç¡€æ•°æ®è®¾è®¡
### 2.1 äº§å“ç‰©æ–™ä¸»æ•°æ®ï¼ˆæ ¸å¿ƒï¼‰
**表结构:** `md_item`
| å­—段 | ç±»åž‹ | è¯´æ˜Ž | ä½¿ç”¨æ¨¡å— |
|------|------|------|---------|
| id | BIGINT | äº§å“ID(全局唯一) | å…¨éƒ¨ |
| code | VARCHAR(64) | äº§å“ç¼–码(全局唯一) | å…¨éƒ¨ |
| name | VARCHAR(255) | äº§å“åç§° | å…¨éƒ¨ |
| specification | VARCHAR(255) | è§„格型号 | å…¨éƒ¨ |
| unit_measure_id | BIGINT | ä¸»è®¡é‡å•位 | å…¨éƒ¨ |
| category_id | BIGINT | äº§å“åˆ†ç±» | å…¨éƒ¨ |
| item_type | INT | ç‰©æ–™ç±»åž‹ï¼ˆåŽŸæ–™/半成品/成品/辅料) | MES/ERP |
| is_batch_managed | BOOLEAN | æ˜¯å¦æ‰¹æ¬¡ç®¡ç† | WMS/MES |
| is_serial_managed | BOOLEAN | æ˜¯å¦åºåˆ—号管理 | WMS/MES |
| barcode_rule_id | BIGINT | æ¡ç è§„则 | WMS/MES |
| safety_stock | DECIMAL | å®‰å…¨åº“å­˜ | WMS |
| min_order_qty | DECIMAL | æœ€å°è®¢è´§é‡ | ERP |
| lead_time | INT | é‡‡è´­æå‰æœŸï¼ˆå¤©ï¼‰ | ERP/MES |
| purchase_price | DECIMAL | é‡‡è´­ä»· | ERP |
| sales_price | DECIMAL | é”€å”®ä»· | ERP/CRM |
| cost_price | DECIMAL | æˆæœ¬ä»· | ERP |
| status | INT | çŠ¶æ€ï¼ˆå¯ç”¨/禁用) | å…¨éƒ¨ |
### 2.2 æ¨¡å—数据归属划分
| æ•°æ®ç±»åž‹ | æ‰€å±žæ¨¡å— | å…±äº«èŒƒå›´ |
|---------|---------|---------|
| äº§å“ç‰©æ–™ | MES(主)或独立模块 | ERP/WMS/CRM/MES å…¨éƒ¨å…±äº« |
| å®¢æˆ·ä¿¡æ¯ | CRM(主)或独立模块 | ERP/MES å…±äº«ï¼ˆè®¢å•/发货) |
| ä¾›åº”商信息 | ERP(主)或独立模块 | MES å…±äº«ï¼ˆå¤–协加工) |
| ä»“库信息 | WMS(主)或独立模块 | ERP/MES å…±äº«ï¼ˆå‡ºå…¥åº“) |
| BOM ä¿¡æ¯ | MES(主)或独立模块 | ERP å…±äº«ï¼ˆæˆæœ¬æ ¸ç®—) |
| å·¥è‰ºè·¯çº¿ | MES ç‹¬æœ‰ | ä¸å…±äº« |
| é”€å”®æœºä¼š | CRM ç‹¬æœ‰ | ä¸å…±äº« |
| è´¨æ£€æ ‡å‡† | MES ç‹¬æœ‰ | WMS å‚考 |
### 2.3 æ•°æ®åŒæ­¥ç­–ç•¥
```
┌─────────────┐     API/事件      â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│   ä¸»æ•°æ®æ¨¡å— â”‚ â†â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â†’ â”‚   ä¸šåŠ¡æ¨¡å—   â”‚
└─────────────┘                   â””─────────────┘
       â†“                                 â†“
  æ•°æ®å˜æ›´äº‹ä»¶                        æœ¬åœ°ç¼“存更新
       â†“                                 â†“
  å‘布 DomainEvent                   ç›‘听并同步
```
**同步方式:**
1. **实时同步**:关键数据变更通过 Spring Event å®žæ—¶é€šçŸ¥
2. **API æŸ¥è¯¢**:业务模块需要详情时调用主数据模块 API
3. **本地缓存**:高频访问数据在业务模块建立缓存表(仅存 ID+名称+编码)
---
## ä¸‰ã€è·¨æ¨¡å—业务流程集成
### 3.1 é‡‡è´­ä¸šåŠ¡æµç¨‹ï¼ˆERP â†’ WMS â†’ MES)
```
┌─────────────────────────────────────────────────────────────────────┐
│                        é‡‡è´­ä¸šåŠ¡å®Œæ•´æµç¨‹                              â”‚
└─────────────────────────────────────────────────────────────────────┘
ERP采购订单 â”€â”€(审批通过)──→ å¯åˆ›å»ºé‡‡è´­å…¥åº“单
     â†“                         â†“
  çŠ¶æ€å˜æ›´                   ERP采购入库
     â†“                      â”€â”€(审批通过)──→
更新订单状态                    â†“
                           WMS库存增加
                                â†“
                           MES原料可用
                                â†“
                           å¯åˆ›å»ºç”Ÿäº§é¢†æ–™å•
```
**联动点设计:**
| è§¦å‘点 | è”动操作 | å®žçŽ°æ–¹å¼ |
|--------|---------|---------|
| ERP采购订单审批通过 | è®¢å•状态=已审批,允许创建入库单 | çŠ¶æ€æ ¡éªŒ |
| ERP采购入库审批通过 | WMS库存增加、更新采购订单已入库数量 | äº‹ä»¶ç›‘听 |
| ERP采购入库全部完成 | é‡‡è´­è®¢å•状态=已完成 | æ•°é‡åˆ¤æ–­ |
| MES生产领料审批通过 | WMS原料库存减少 | äº‹ä»¶ç›‘听 |
### 3.2 é”€å”®ä¸šåŠ¡æµç¨‹ï¼ˆCRM â†’ ERP â†’ WMS)
```
┌─────────────────────────────────────────────────────────────────────┐
│                        é”€å”®ä¸šåŠ¡å®Œæ•´æµç¨‹                              â”‚
└─────────────────────────────────────────────────────────────────────┘
CRM销售合同 â”€â”€(审批通过)──→ å¯åˆ›å»ºERP销售订单
     â†“                         â†“
  çŠ¶æ€å˜æ›´                   ERP销售订单
     â†“                      â”€â”€(审批通过)──→
合同履行开始                    â†“
                           å¯åˆ›å»ºé”€å”®å‡ºåº“单
                                â†“
                           ERP销售出库
                                â†“
                           â”€â”€(审批通过)──→
                                â†“
                           WMS库存减少
                                â†“
                           ERP成本核算
                                â†“
                           CRM合同履行更新
```
**联动点设计:**
| è§¦å‘点 | è”动操作 | å®žçŽ°æ–¹å¼ |
|--------|---------|---------|
| CRM合同审批通过 | å¯åˆ›å»ºERP销售订单,关联合同ID | çŠ¶æ€æ ¡éªŒ |
| ERP销售订单审批通过 | è®¢å•状态=已审批,允许创建出库单 | çŠ¶æ€æ ¡éªŒ |
| ERP销售出库审批通过 | WMS库存减少、更新销售订单已出库数量 | äº‹ä»¶ç›‘听 |
| ERP销售出库审批通过 | æ›´æ–°CRM合同履行进度 | äº‹ä»¶ç›‘听 |
| ERP销售出库全部完成 | é”€å”®è®¢å•状态=已完成 | æ•°é‡åˆ¤æ–­ |
### 3.3 ç”Ÿäº§ä¸šåŠ¡æµç¨‹ï¼ˆMES â†’ WMS â†’ ERP)
```
┌─────────────────────────────────────────────────────────────────────┐
│                        ç”Ÿäº§ä¸šåŠ¡å®Œæ•´æµç¨‹                              â”‚
└─────────────────────────────────────────────────────────────────────┘
MES生产工单 â”€â”€(审批通过)──→ çŠ¶æ€=已确认
     â†“                         â†“
  è‡ªåŠ¨ç”ŸæˆBOM                å¯åˆ›å»ºç”Ÿäº§ä»»åŠ¡
     â†“                         â†“
  å¯åˆ›å»ºé¢†æ–™å•               MES生产任务执行
     â†“                      â”€â”€(任务开始)──→
MES生产领料                      â†“
     â†“                      åŽŸæ–™æ¶ˆè€—è®°å½•
  â”€â”€(审批通过)──→                â†“
     â†“                      â”€â”€(任务完成)──→
WMS原料库存减少                  â†“
                           MES生产入库
                                â†“
                           â”€â”€(审批通过)──→
                                â†“
                           WMS成品库存增加
                                â†“
                           ERP成本核算
                                â†“
                           MES工单完成
```
**联动点设计:**
| è§¦å‘点 | è”动操作 | å®žçŽ°æ–¹å¼ |
|--------|---------|---------|
| MES生产工单审批通过 | å·¥å•状态=已确认,可创建任务/领料单 | çŠ¶æ€æ ¡éªŒ |
| MES生产领料审批通过 | WMS原料库存减少 | äº‹ä»¶ç›‘听 |
| MES生产任务完成 | ç´¯è®¡ç”Ÿäº§æ•°é‡ã€å¯åˆ›å»ºå…¥åº“单 | çŠ¶æ€æ ¡éªŒ |
| MES生产入库审批通过 | WMS成品库存增加、工单生产数量累加 | äº‹ä»¶ç›‘听 |
| MES生产入库审批通过 | ERP成本核算(可选) | äº‹ä»¶ç›‘听 |
| MES工单全部完成 | å·¥å•状态=已完成 | æ•°é‡åˆ¤æ–­ |
### 3.4 é€€è´§ä¸šåŠ¡æµç¨‹
```
┌─────────────────────────────────────────────────────────────────────┐
│                        é€€è´§ä¸šåŠ¡æµç¨‹                                  â”‚
└─────────────────────────────────────────────────────────────────────┘
ERP采购退货 â”€â”€(审批通过)──→ WMS库存减少
     â†“
  æ›´æ–°é‡‡è´­è®¢å•退货数量
ERP销售退货 â”€â”€(审批通过)──→ WMS库存增加
     â†“
  æ›´æ–°é”€å”®è®¢å•退货数量
```
---
## å››ã€åº“存管理统一设计
### 4.1 åº“存数据模型
**核心表:** `wms_stock`
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”® |
| item_id | BIGINT | äº§å“ID(关联 md_item) |
| warehouse_id | BIGINT | ä»“库ID |
| location_id | BIGINT | åº“位ID |
| batch_code | VARCHAR(64) | æ‰¹æ¬¡å· |
| quantity | DECIMAL | å½“前库存数量 |
| quantity_locked | DECIMAL | é”å®šæ•°é‡ |
| quantity_available | DECIMAL | å¯ç”¨æ•°é‡ = quantity - quantity_locked |
| unit_measure_id | BIGINT | è®¡é‡å•位 |
### 4.2 åº“存变动来源
| å˜åŠ¨ç±»åž‹ | å¢žå‡æ–¹å‘ | æ¥æºæ¨¡å— | è§¦å‘条件 |
|---------|---------|---------|---------|
| é‡‡è´­å…¥åº“ | + | ERP | é‡‡è´­å…¥åº“单审批通过 |
| é”€å”®å‡ºåº“ | - | ERP | é”€å”®å‡ºåº“单审批通过 |
| ç”Ÿäº§å…¥åº“ | + | MES | ç”Ÿäº§å…¥åº“单审批通过 |
| ç”Ÿäº§é¢†æ–™ | - | MES | ç”Ÿäº§é¢†æ–™å•审批通过 |
| ç”Ÿäº§é€€æ–™ | + | MES | ç”Ÿäº§é€€æ–™å•审批通过 |
| é‡‡è´­é€€è´§ | - | ERP | é‡‡è´­é€€è´§å•审批通过 |
| é”€å”®é€€è´§ | + | ERP | é”€å”®é€€è´§å•审批通过 |
| è°ƒæ‹¨å…¥åº“ | + | WMS | è°ƒæ‹¨å•审批通过(目标仓) |
| è°ƒæ‹¨å‡ºåº“ | - | WMS | è°ƒæ‹¨å•审批通过(源仓) |
| ç›˜ç›ˆ | + | WMS | ç›˜ç‚¹å•审批通过 |
| ç›˜äº | - | WMS | ç›˜ç‚¹å•审批通过 |
### 4.3 åº“存服务 API è®¾è®¡
```java
/**
 * åº“存服务接口 - WMS æ¨¡å—提供,其他模块调用
 */
public interface WmsStockApi {
    /**
     * åˆ›å»ºåº“存记录(入库)
     */
    Long createStockRecord(StockRecordCreateDTO dto);
    /**
     * é”å®šåº“å­˜
     */
    void lockStock(Long itemId, Long warehouseId, BigDecimal quantity);
    /**
     * è§£é”åº“å­˜
     */
    void unlockStock(Long itemId, Long warehouseId, BigDecimal quantity);
    /**
     * æŸ¥è¯¢å¯ç”¨åº“å­˜
     */
    BigDecimal getAvailableStock(Long itemId, Long warehouseId);
    /**
     * æ‰¹é‡æŸ¥è¯¢åº“å­˜
     */
    Map<Long, BigDecimal> getAvailableStockMap(List<Long> itemIds, Long warehouseId);
    /**
     * æ ¡éªŒåº“存充足
     */
    void validateStockEnough(Long itemId, Long warehouseId, BigDecimal quantity);
}
```
### 4.4 åº“存预警机制
| é¢„警类型 | è§¦å‘条件 | é€šçŸ¥å¯¹è±¡ |
|---------|---------|---------|
| åº“存不足 | å¯ç”¨åº“å­˜ < å®‰å…¨åº“å­˜ | ERP采购、MES计划 |
| åº“存积压 | åº“å­˜ > æœ€é«˜åº“存阈值 | ERP销售 |
| æ•ˆæœŸé¢„è­¦ | æ‰¹æ¬¡æ•ˆæœŸ < 30天 | WMS、MES质量 |
| é”å®šè¶…æ—¶ | é”å®šæ—¶é—´ > é…ç½®é˜ˆå€¼ | ç›¸å…³ä¸šåŠ¡äººå‘˜ |
---
## äº”、BPM å®¡æ‰¹æµç¨‹ç»Ÿä¸€è®¾è®¡
### 5.1 å®¡æ‰¹æµç¨‹æ ‡è¯†è§„范
| æ¨¡å— | ä¸šåŠ¡å•æ® | æµç¨‹ KEY |
|------|---------|---------|
| ERP | é‡‡è´­è®¢å• | `erp-purchase-order-audit` |
| ERP | é”€å”®è®¢å• | `erp-sale-order-audit` |
| ERP | é‡‡è´­å…¥åº“ | `erp-purchase-in-audit` |
| ERP | é”€å”®å‡ºåº“ | `erp-sale-out-audit` |
| ERP | é‡‡è´­é€€è´§ | `erp-purchase-return-audit` |
| ERP | é”€å”®é€€è´§ | `erp-sale-return-audit` |
| MES | ç”Ÿäº§å·¥å• | `mes-pro-work-order-audit` |
| MES | ç”Ÿäº§é¢†æ–™ | `mes-material-issue-audit` |
| MES | ç”Ÿäº§å…¥åº“ | `mes-material-receive-audit` |
| WMS | åº“存调拨 | `wms-stock-transfer-audit` |
| WMS | åº“存盘点 | `wms-stock-check-audit` |
| CRM | é”€å”®åˆåŒ | `crm-sale-contract-audit` |
| CRM | é”€å”®æŠ¥ä»· | `crm-sale-quotation-audit` |
### 5.2 å®¡æ‰¹çŠ¶æ€æžšä¸¾ç»Ÿä¸€
```java
/**
 * ç»Ÿä¸€å®¡æ‰¹çŠ¶æ€æžšä¸¾
 */
public enum AuditStatusEnum {
    DRAFT(0, "草稿"),           // æ–°å»ºçŠ¶æ€
    AUDITING(1, "审批中"),       // å·²æäº¤å®¡æ‰¹
    APPROVED(20, "已审批"),      // å®¡æ‰¹é€šè¿‡
    REJECTED(30, "已拒绝"),      // å®¡æ‰¹ä¸é€šè¿‡
    CANCELLED(40, "已取消");     // å®¡æ‰¹å–消
    // BPM æµç¨‹çŠ¶æ€å¯¹åº”ï¼š
    // BPM_RESULT_PASS = 2    â†’ APPROVED
    // BPM_RESULT_REJECT = 3 â†’ REJECTED
    // BPM_RESULT_CANCEL = 4 â†’ DRAFT(退回草稿)
}
```
### 5.3 å®¡æ‰¹ç›‘听器统一模板
```java
/**
 * è·¨æ¨¡å—审批结果监听器基类
 */
public abstract class CrossModuleAuditListener extends BpmProcessInstanceStatusEventListener {
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        // 1. æ›´æ–°æœ¬æ¨¡å—单据状态
        updateAuditStatus(event.getBusinessKey(), event.getStatus());
        // 2. è§¦å‘跨模块联动(通过事件发布)
        if (event.getStatus() == 2) { // å®¡æ‰¹é€šè¿‡
            publishCrossModuleEvent(event.getBusinessKey());
        }
    }
    /**
     * å‘布跨模块联动事件
     */
    protected abstract void publishCrossModuleEvent(String businessKey);
}
```
---
## å…­ã€è·¨æ¨¡å—事件机制设计
### 6.1 äº‹ä»¶ç±»åž‹å®šä¹‰
| äº‹ä»¶ç±»åž‹ | å‘布模块 | ç›‘听模块 | è¯´æ˜Ž |
|---------|---------|---------|---------|
| `PurchaseInApprovedEvent` | ERP | WMS, ERP-PO | é‡‡è´­å…¥åº“审批通过 |
| `SaleOutApprovedEvent` | ERP | WMS, ERP-SO, CRM | é”€å”®å‡ºåº“审批通过 |
| `MaterialIssueApprovedEvent` | MES | WMS | ç”Ÿäº§é¢†æ–™å®¡æ‰¹é€šè¿‡ |
| `MaterialReceiveApprovedEvent` | MES | WMS, MES-WO | ç”Ÿäº§å…¥åº“审批通过 |
| `WorkOrderApprovedEvent` | MES | MES-Task | ç”Ÿäº§å·¥å•审批通过 |
| `ContractApprovedEvent` | CRM | ERP | é”€å”®åˆåŒå®¡æ‰¹é€šè¿‡ |
### 6.2 äº‹ä»¶æ•°æ®ç»“æž„
```java
/**
 * è·¨æ¨¡å—事件基类
 */
@Data
public abstract class CrossModuleEvent {
    /** ä¸šåŠ¡å•æ®ID */
    private Long businessId;
    /** ä¸šåŠ¡å•æ®ç¼–ç  */
    private String businessCode;
    /** äº§å“ID列表 */
    private List<Long> itemIds;
    /** ä»“库ID */
    private Long warehouseId;
    /** æ“ä½œæ—¶é—´ */
    private LocalDateTime eventTime;
    /** æ“ä½œç”¨æˆ·ID */
    private Long userId;
    /** ç§Ÿæˆ·ID */
    private Long tenantId;
}
/**
 * é‡‡è´­å…¥åº“审批通过事件
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class PurchaseInApprovedEvent extends CrossModuleEvent {
    /** å…¥åº“数量(按产品) */
    private Map<Long, BigDecimal> quantityMap;
    /** å…³è”采购订单ID */
    private Long purchaseOrderId;
}
```
### 6.3 äº‹ä»¶å‘布与监听
```java
// ERP æ¨¡å—发布事件
@Component
public class ErpPurchaseInStatusListener extends CrossModuleAuditListener {
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Override
    protected void publishCrossModuleEvent(String businessKey) {
        ErpPurchaseInDO purchaseIn = purchaseInService.getPurchaseIn(Long.parseLong(businessKey));
        // å‘布事件供其他模块监听
        eventPublisher.publishEvent(new PurchaseInApprovedEvent()
            .setBusinessId(purchaseIn.getId())
            .setItemIds(extractItemIds(purchaseIn))
            .setQuantityMap(extractQuantityMap(purchaseIn))
            .setWarehouseId(purchaseIn.getWarehouseId()));
    }
}
// WMS æ¨¡å—监听事件
@Component
public class WmsStockEventListener {
    @EventListener
    public void onPurchaseInApproved(PurchaseInApprovedEvent event) {
        // å¢žåŠ åº“å­˜
        event.getQuantityMap().forEach((itemId, quantity) -> {
            stockService.createStockRecord(new StockRecordCreateDTO()
                .setItemId(itemId)
                .setWarehouseId(event.getWarehouseId())
                .setQuantity(quantity)
                .setBizType(StockBizTypeEnum.PURCHASE_IN));
        });
    }
}
// ERP é‡‡è´­è®¢å•模块监听事件
@Component
public class ErpPurchaseOrderEventListener {
    @EventListener
    public void onPurchaseInApproved(PurchaseInApprovedEvent event) {
        // æ›´æ–°é‡‡è´­è®¢å•已入库数量
        purchaseOrderService.updateInQuantity(event.getPurchaseOrderId(), event.getQuantityMap());
    }
}
```
---
## ä¸ƒã€å®žæ–½è®¡åˆ’
### 7.1 åˆ†æ‰¹æ¬¡å®žæ–½é¡ºåº
| æ‰¹æ¬¡ | å†…容 | æ¨¡å— | é¢„估工作量 |
|------|------|------|-----------|
| 1 | åŸºç¡€æ•°æ®ç»Ÿä¸€ | å…¨éƒ¨ | 3天 |
| 2 | åº“存服务 API | WMS | 2天 |
| 3 | é‡‡è´­ä¸šåŠ¡è”åŠ¨ | ERP/WMS | 2天 |
| 4 | é”€å”®ä¸šåŠ¡è”åŠ¨ | ERP/WMS/CRM | 2天 |
| 5 | ç”Ÿäº§ä¸šåŠ¡è”åŠ¨ | MES/WMS | 3天 |
| 6 | é€€è´§ä¸šåŠ¡è”åŠ¨ | ERP/WMS | 1天 |
| 7 | äº‹ä»¶æœºåˆ¶å®Œå–„ | å…¨éƒ¨ | 2天 |
| 8 | æµç¨‹å®šä¹‰é…ç½® | BPM | 2天 |
| 9 | å‰ç«¯é¡µé¢è°ƒæ•´ | å…¨éƒ¨ | 3天 |
| 10 | è”调测试 | å…¨éƒ¨ | 3天 |
### 7.2 æŠ€æœ¯è¦ç‚¹
1. **Spring Event å¼‚步处理**:跨模块事件使用 `@Async` å¼‚步执行,避免事务嵌套
2. **分布式事务**:关键操作考虑使用 Seata æˆ–本地消息表保证一致性
3. **幂等设计**:事件监听器需支持幂等,防止重复执行
4. **租户隔离**:事件数据携带 tenantId,监听时过滤
---
## å…«ã€é£Žé™©ä¸Žå¯¹ç­–
| é£Žé™©ç‚¹ | å½±å“ | å¯¹ç­– |
|--------|------|------|
| æ•°æ®ä¸ä¸€è‡´ | åº“存与实际不符 | äº‹ä»¶å¹‚ç­‰+补偿机制+定期盘点 |
| æµç¨‹æ­»é” | å®¡æ‰¹å¡ä½ | è®¾ç½®å®¡æ‰¹è¶…时自动处理 |
| æ€§èƒ½ç“¶é¢ˆ | äº‹ä»¶é˜Ÿåˆ—阻塞 | å¼‚步队列+批量处理 |
| è·¨ç§Ÿæˆ·æ•°æ®æ³„露 | å®‰å…¨é£Žé™© | äº‹ä»¶æºå¸¦ç§Ÿæˆ·ID+严格校验 |
---
## ä¹ã€æ€»ç»“
本方案通过以下机制实现模块间打通:
1. **统一基础数据**:产品/客户/供应商/仓库等主数据全局共享
2. **事件驱动联动**:审批通过触发跨模块事件,解耦模块依赖
3. **库存中心化**:WMS ä½œä¸ºå”¯ä¸€åº“存管理者,所有变动通过 API/事件
4. **流程标准化**:统一的 BPM æµç¨‹ KEY、状态枚举、监听器模板
下一步需确认:
- ä¸»æ•°æ®æ¨¡å—归属(是否新建独立模块)
- åˆ†å¸ƒå¼äº‹åŠ¡æ–¹æ¡ˆé€‰æ‹©
- å…·ä½“实施优先级排序
docs/im_module.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,353 @@
-- =====================================================
-- IM å³æ—¶é€šè®¯æ¨¡å—数据库表结构
-- ç”Ÿæˆæ—¥æœŸ: 2026-06-29
-- é€‚用项目: mom-pro2-after
-- åŒ…含: å¥½å‹ç®¡ç†ã€ç¾¤èŠç®¡ç†ã€ç§èŠæ¶ˆæ¯ã€ç¾¤èŠæ¶ˆæ¯
-- =====================================================
-- =====================================================
-- ç¬¬ä¸€éƒ¨åˆ†: å­—典类型定义
-- =====================================================
-- IM ç§èŠæ¶ˆæ¯çŠ¶æ€
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (600, 'IM ç§èŠæ¶ˆæ¯çŠ¶æ€', 'im_private_message_status', 0, '私聊消息状态', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤èŠæ¶ˆæ¯çŠ¶æ€
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (601, 'IM ç¾¤èŠæ¶ˆæ¯çŠ¶æ€', 'im_group_message_status', 0, '群聊消息状态', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤èŠæ¶ˆæ¯å›žæ‰§çŠ¶æ€
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (602, 'IM ç¾¤èŠæ¶ˆæ¯å›žæ‰§çŠ¶æ€', 'im_group_message_receipt_status', 0, '群聊消息回执状态', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM å¥½å‹çŠ¶æ€
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (603, 'IM å¥½å‹çŠ¶æ€', 'im_friend_status', 0, '好友状态', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤çŠ¶æ€
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (604, 'IM ç¾¤çŠ¶æ€', 'im_group_status', 0, '群状态', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤æˆå‘˜è§’色
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (605, 'IM ç¾¤æˆå‘˜è§’色', 'im_group_member_role', 0, '群成员角色', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM å¥½å‹æ·»åŠ æ¥æº
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (606, 'IM å¥½å‹æ·»åŠ æ¥æº', 'im_friend_add_source', 0, '好友添加来源', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM å¥½å‹ç”³è¯·å¤„理结果
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (607, 'IM å¥½å‹ç”³è¯·å¤„理结果', 'im_friend_request_handle_result', 0, '好友申请处理结果', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM æ¶ˆæ¯ç±»åž‹
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (608, 'IM æ¶ˆæ¯ç±»åž‹', 'im_message_type', 0, '消息类型', 'admin', NOW(), 'admin', NOW(), b'0');
-- =====================================================
-- ç¬¬äºŒéƒ¨åˆ†: å­—典数据定义
-- =====================================================
-- IM ç§èŠæ¶ˆæ¯çŠ¶æ€å­—å…¸æ•°æ®
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '未读', '0', 'im_private_message_status', 0, 'warning', '', '私聊=未读', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '已撤回', '2', 'im_private_message_status', 0, 'danger', '', 'RECALL', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (3, '已读', '3', 'im_private_message_status', 0, 'success', '', 'READ', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤èŠæ¶ˆæ¯çŠ¶æ€å­—å…¸æ•°æ®
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '正常', '0', 'im_group_message_status', 0, 'success', '', '群聊正常', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '已撤回', '2', 'im_group_message_status', 0, 'danger', '', '群聊已撤回', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM å¥½å‹çŠ¶æ€å­—å…¸æ•°æ®
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '正常', '0', 'im_friend_status', 0, 'success', '', '正常好友关系', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '已删除', '1', 'im_friend_status', 0, 'danger', '', '已删除好友关系', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤çŠ¶æ€å­—å…¸æ•°æ®
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '正常', '0', 'im_group_status', 0, 'success', '', '群正常', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '已解散', '1', 'im_group_status', 0, 'info', '', '群已解散', 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤æˆå‘˜è§’色字典数据
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '群主', '1', 'im_group_member_role', 0, 'primary', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '管理员', '2', 'im_group_member_role', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (3, '普通成员', '3', 'im_group_member_role', 0, 'info', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
-- IM å¥½å‹æ·»åŠ æ¥æºå­—å…¸æ•°æ®
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '搜索', '1', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '群聊', '2', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (3, '扫码', '3', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (4, '名片', '4', 'im_friend_add_source', 0, 'default', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
-- IM å¥½å‹ç”³è¯·å¤„理结果字典数据
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '等待验证', '0', 'im_friend_request_handle_result', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '已添加', '1', 'im_friend_request_handle_result', 0, 'success', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (3, '已拒绝', '2', 'im_friend_request_handle_result', 0, 'info', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
-- IM æ¶ˆæ¯ç±»åž‹å­—典数据
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (101, '文本', '101', 'im_message_type', 0, '', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (102, '图片', '102', 'im_message_type', 0, 'success', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (103, '语音', '103', 'im_message_type', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (104, '视频', '104', 'im_message_type', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (105, '文件', '105', 'im_message_type', 0, 'info', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2101, '撤回', '2101', 'im_message_type', 0, 'danger', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2200, '回执', '2200', 'im_message_type', 0, 'warning', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2201, '已读', '2201', 'im_message_type', 0, 'success', '', NULL, 'admin', NOW(), 'admin', NOW(), b'0');
-- IM ç¾¤æ¶ˆæ¯å›žæ‰§çŠ¶æ€
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (1, '不需要回执', '0', 'im_group_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2, '待完成', '1', 'im_group_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', NOW(), 'admin', NOW(), b'0');
INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (3, '已完成', '2', 'im_group_message_receipt_status', 0, 'success', '', 'DONE', 'admin', NOW(), 'admin', NOW(), b'0');
-- =====================================================
-- ç¬¬ä¸‰éƒ¨åˆ†: IM æ¨¡å—核心表结构 (11 å¼ è¡¨)
-- =====================================================
-- 3.1 å¥½å‹å…³ç³»è¡¨
DROP TABLE IF EXISTS `im_friend`;
CREATE TABLE `im_friend` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint NOT NULL COMMENT '用户编号',
  `friend_user_id` bigint NOT NULL COMMENT '好友用户编号',
  `remark` varchar(255) DEFAULT NULL COMMENT '好友备注',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '好友状态(0正常 1已删除)',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_friend_user_id` (`friend_user_id`),
  UNIQUE KEY `uk_user_friend` (`user_id`, `friend_user_id`)
) ENGINE=InnoDB COMMENT='好友关系表';
-- 3.2 å¥½å‹ç”³è¯·è¡¨
DROP TABLE IF EXISTS `im_friend_request`;
CREATE TABLE `im_friend_request` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint NOT NULL COMMENT '申请人编号',
  `target_user_id` bigint NOT NULL COMMENT '目标用户编号',
  `content` varchar(500) DEFAULT NULL COMMENT '申请内容',
  `source` tinyint NOT NULL DEFAULT 1 COMMENT '添加来源(1搜索 2群聊 3扫码 4名片)',
  `handle_result` tinyint NOT NULL DEFAULT 0 COMMENT '处理结果(0等待 1已添加 2已拒绝)',
  `handle_time` datetime DEFAULT NULL COMMENT '处理时间',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_target_user_id` (`target_user_id`)
) ENGINE=InnoDB COMMENT='好友申请表';
-- 3.3 ç¾¤ç»„表
DROP TABLE IF EXISTS `im_group`;
CREATE TABLE `im_group` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '群编号',
  `name` varchar(100) NOT NULL COMMENT '群名称',
  `avatar` varchar(255) DEFAULT NULL COMMENT '群头像',
  `owner_user_id` bigint NOT NULL COMMENT '群主用户编号',
  `notice` varchar(500) DEFAULT NULL COMMENT '群公告',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '群状态(0正常 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_owner_user_id` (`owner_user_id`)
) ENGINE=InnoDB COMMENT='群组表';
-- 3.4 ç¾¤æˆå‘˜è¡¨
DROP TABLE IF EXISTS `im_group_member`;
CREATE TABLE `im_group_member` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `group_id` bigint NOT NULL COMMENT '群编号',
  `user_id` bigint NOT NULL COMMENT '用户编号',
  `nickname` varchar(100) DEFAULT NULL COMMENT '群内昵称',
  `role` tinyint NOT NULL DEFAULT 3 COMMENT '群角色(1群主 2管理员 3普通成员)',
  `mute_flag` bit(1) DEFAULT b'0' COMMENT '是否禁言',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_group_id` (`group_id`),
  KEY `idx_user_id` (`user_id`),
  UNIQUE KEY `uk_group_user` (`group_id`, `user_id`)
) ENGINE=InnoDB COMMENT='群成员表';
-- 3.5 ç§èŠæ¶ˆæ¯è¡¨
DROP TABLE IF EXISTS `im_private_message`;
CREATE TABLE `im_private_message` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息编号',
  `from_user_id` bigint NOT NULL COMMENT '发送人编号',
  `to_user_id` bigint NOT NULL COMMENT '接收人编号',
  `content` text COMMENT '消息内容',
  `message_type` int NOT NULL DEFAULT 101 COMMENT '消息类型(101文本 102图片 103语音 104视频 105文件)',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '消息状态(0未读 2已撤回 3已读)',
  `send_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
  `read_time` datetime DEFAULT NULL COMMENT '已读时间',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_from_user_id` (`from_user_id`),
  KEY `idx_to_user_id` (`to_user_id`)
) ENGINE=InnoDB COMMENT='私聊消息表';
-- 3.6 ç¾¤èŠæ¶ˆæ¯è¡¨
DROP TABLE IF EXISTS `im_group_message`;
CREATE TABLE `im_group_message` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息编号',
  `group_id` bigint NOT NULL COMMENT '群编号',
  `from_user_id` bigint NOT NULL COMMENT '发送人编号',
  `content` text COMMENT '消息内容',
  `message_type` int NOT NULL DEFAULT 101 COMMENT '消息类型',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '消息状态(0正常 2已撤回)',
  `receipt_status` tinyint NOT NULL DEFAULT 0 COMMENT '回执状态(0不需要 1待完成 2已完成)',
  `send_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_group_id` (`group_id`),
  KEY `idx_from_user_id` (`from_user_id`)
) ENGINE=InnoDB COMMENT='群聊消息表';
-- 3.7 ç¾¤æ¶ˆæ¯å›žæ‰§è¡¨
DROP TABLE IF EXISTS `im_group_message_receipt`;
CREATE TABLE `im_group_message_receipt` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `message_id` bigint NOT NULL COMMENT '群消息编号',
  `user_id` bigint NOT NULL COMMENT '用户编号',
  `read_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '已读时间',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_message_id` (`message_id`),
  KEY `idx_user_id` (`user_id`),
  UNIQUE KEY `uk_message_user` (`message_id`, `user_id`)
) ENGINE=InnoDB COMMENT='群消息回执表';
-- 3.8 ä¼šè¯è¡¨ï¼ˆç§èŠ+群聊)
DROP TABLE IF EXISTS `im_conversation`;
CREATE TABLE `im_conversation` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '会话编号',
  `user_id` bigint NOT NULL COMMENT '用户编号',
  `conversation_type` tinyint NOT NULL COMMENT '会话类型(1私聊 2群聊)',
  `target_id` bigint NOT NULL COMMENT '目标编号(私聊为用户ID,群聊为群ID)',
  `last_message_id` bigint DEFAULT NULL COMMENT '最后消息编号',
  `last_message_time` datetime DEFAULT NULL COMMENT '最后消息时间',
  `unread_count` int NOT NULL DEFAULT 0 COMMENT '未读消息数',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  UNIQUE KEY `uk_user_target_type` (`user_id`, `target_id`, `conversation_type`)
) ENGINE=InnoDB COMMENT='会话表';
-- 3.9 ç”¨æˆ·åœ¨çº¿çŠ¶æ€è¡¨
DROP TABLE IF EXISTS `im_user_online`;
CREATE TABLE `im_user_online` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint NOT NULL COMMENT '用户编号',
  `online_status` tinyint NOT NULL DEFAULT 0 COMMENT '在线状态(0离线 1在线)',
  `last_online_time` datetime DEFAULT NULL COMMENT '最后在线时间',
  `last_offline_time` datetime DEFAULT NULL COMMENT '最后离线时间',
  `device_type` tinyint DEFAULT NULL COMMENT '设备类型(1Web 2Android 3iOS)',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  UNIQUE KEY `uk_user_id` (`user_id`)
) ENGINE=InnoDB COMMENT='用户在线状态表';
-- 3.10 ç¾¤ç”³è¯·è¡¨
DROP TABLE IF EXISTS `im_group_request`;
CREATE TABLE `im_group_request` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `group_id` bigint NOT NULL COMMENT '群编号',
  `user_id` bigint NOT NULL COMMENT '申请人编号',
  `content` varchar(500) DEFAULT NULL COMMENT '申请内容',
  `handle_result` tinyint NOT NULL DEFAULT 0 COMMENT '处理结果(0等待 1已通过 2已拒绝)',
  `handle_user_id` bigint DEFAULT NULL COMMENT '处理人编号',
  `handle_time` datetime DEFAULT NULL COMMENT '处理时间',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_group_id` (`group_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB COMMENT='群申请表';
-- 3.11 æ¶ˆæ¯æ–‡ä»¶è¡¨
DROP TABLE IF EXISTS `im_message_file`;
CREATE TABLE `im_message_file` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `message_id` bigint NOT NULL COMMENT '消息编号',
  `message_type` tinyint NOT NULL COMMENT '消息类型(102图片 103语音 104视频 105文件)',
  `file_url` varchar(500) NOT NULL COMMENT '文件URL',
  `file_name` varchar(255) DEFAULT NULL COMMENT '文件名',
  `file_size` bigint DEFAULT NULL COMMENT '文件大小(字节)',
  `file_duration` int DEFAULT NULL COMMENT '文件时长(秒,语音/视频)',
  `width` int DEFAULT NULL COMMENT '宽度(图片/视频)',
  `height` int DEFAULT NULL COMMENT '高度(图片/视频)',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_message_id` (`message_id`)
) ENGINE=InnoDB COMMENT='消息文件表';
-- =====================================================
-- IM æ¨¡å—表统计: 11 å¼ è¡¨
-- =====================================================
docs/im_module_unlock.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,399 @@
# IM å³æ—¶é€šè®¯æ¨¡å—解锁指南
## ä¸€ã€æ¨¡å—概述
IM(Instant Messaging)即时通讯模块提供企业内部的即时通讯能力,支持私聊、群聊、好友管理等核心功能。
### 1.1 åŠŸèƒ½æ¸…å•
| åŠŸèƒ½ | è¯´æ˜Ž |
|------|------|
| å¥½å‹ç®¡ç† | æ·»åŠ å¥½å‹ã€åˆ é™¤å¥½å‹ã€å¥½å‹ç”³è¯·å®¡æ ¸ |
| ç§èŠæ¶ˆæ¯ | ä¸€å¯¹ä¸€èŠå¤©ã€æ¶ˆæ¯æ’¤å›žã€å·²è¯»å›žæ‰§ |
| ç¾¤èŠç®¡ç† | åˆ›å»ºç¾¤ã€è§£æ•£ç¾¤ã€ç¾¤æˆå‘˜ç®¡ç†ã€ç¾¤å…¬å‘Š |
| ç¾¤èŠæ¶ˆæ¯ | ç¾¤å†…聊天、消息撤回、回执确认 |
| ä¼šè¯ç®¡ç† | ä¼šè¯åˆ—表、未读计数、历史消息 |
| åœ¨çº¿çŠ¶æ€ | ç”¨æˆ·åœ¨çº¿/离线状态显示 |
### 1.2 æ•°æ®åº“表清单
| è¡¨å | è¯´æ˜Ž |
|------|------|
| im_friend | å¥½å‹å…³ç³»è¡¨ |
| im_friend_request | å¥½å‹ç”³è¯·è¡¨ |
| im_group | ç¾¤ç»„表 |
| im_group_member | ç¾¤æˆå‘˜è¡¨ |
| im_private_message | ç§èŠæ¶ˆæ¯è¡¨ |
| im_group_message | ç¾¤èŠæ¶ˆæ¯è¡¨ |
| im_group_message_receipt | ç¾¤æ¶ˆæ¯å›žæ‰§è¡¨ |
| im_conversation | ä¼šè¯è¡¨ |
| im_user_online | ç”¨æˆ·åœ¨çº¿çŠ¶æ€è¡¨ |
| im_group_request | ç¾¤ç”³è¯·è¡¨ |
| im_message_file | æ¶ˆæ¯æ–‡ä»¶è¡¨ |
---
## äºŒã€è§£é”æ­¥éª¤
### 2.1 åˆ›å»ºæ¨¡å—目录结构
```bash
mkdir -p yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/api
mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/{controller,service,dal}
mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/{friend,group,message}
mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/{friend,group,message}
mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/{dataobject,mysql}
mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/{friend,group,message}
mkdir -p yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/{friend,group,message}
```
### 2.2 åˆ›å»º pom.xml
**yudao-module-im-api/pom.xml:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao-module-im</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-im-api</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>IM å³æ—¶é€šè®¯æ¨¡å— API</description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-framework-common</artifactId>
        </dependency>
    </dependencies>
</project>
```
**yudao-module-im-biz/pom.xml:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao-module-im</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-im-biz</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>IM å³æ—¶é€šè®¯æ¨¡å—业务实现</description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-im-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-system-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-security</artifactId>
        </dependency>
        <!-- DB ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
        </dependency>
        <!-- WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!-- Test æµ‹è¯•相关 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>
```
### 2.3 æ‰§è¡Œæ•°æ®åº“ SQL
```bash
# æ‰§è¡Œ IM æ¨¡å—表结构 SQL
mysql -u root -p mom_pro < docs/im_module.sql
```
### 2.4 åœ¨ yudao-server/pom.xml ä¸­æ·»åŠ ä¾èµ–
```xml
<!-- IM å³æ—¶é€šè®¯æ¨¡å— -->
<dependency>
    <groupId>cn.iocoder.boot</groupId>
    <artifactId>yudao-module-im-biz</artifactId>
    <version>${revision}</version>
</dependency>
```
---
## ä¸‰ã€æ ¸å¿ƒå®žä½“类示例
### 3.1 ImFriendDO.java
```java
package cn.iocoder.yudao.module.im.dal.dataobject.friend;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * å¥½å‹å…³ç³» DO
 */
@TableName("im_friend")
@KeySequence("im_friend_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ImFriendDO extends BaseDO {
    @TableId
    private Long id;
    /** ç”¨æˆ·ç¼–号 */
    private Long userId;
    /** å¥½å‹ç”¨æˆ·ç¼–号 */
    private Long friendUserId;
    /** å¥½å‹å¤‡æ³¨ */
    private String remark;
    /** å¥½å‹çŠ¶æ€ï¼ˆ0正常 1已删除) */
    private Integer status;
}
```
### 3.2 ImGroupDO.java
```java
package cn.iocoder.yudao.module.im.dal.dataobject.group;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * ç¾¤ç»„ DO
 */
@TableName("im_group")
@KeySequence("im_group_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ImGroupDO extends BaseDO {
    @TableId
    private Long id;
    /** ç¾¤åç§° */
    private String name;
    /** ç¾¤å¤´åƒ */
    private String avatar;
    /** ç¾¤ä¸»ç”¨æˆ·ç¼–号 */
    private Long ownerUserId;
    /** ç¾¤å…¬å‘Š */
    private String notice;
    /** ç¾¤çŠ¶æ€ï¼ˆ0正常 1已解散) */
    private Integer status;
}
```
### 3.3 ImPrivateMessageDO.java
```java
package cn.iocoder.yudao.module.im.dal.dataobject.message;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
 * ç§èŠæ¶ˆæ¯ DO
 */
@TableName("im_private_message")
@KeySequence("im_private_message_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ImPrivateMessageDO extends BaseDO {
    @TableId
    private Long id;
    /** å‘送人编号 */
    private Long fromUserId;
    /** æŽ¥æ”¶äººç¼–号 */
    private Long toUserId;
    /** æ¶ˆæ¯å†…容 */
    private String content;
    /** æ¶ˆæ¯ç±»åž‹ */
    private Integer messageType;
    /** æ¶ˆæ¯çŠ¶æ€ï¼ˆ0未读 2已撤回 3已读) */
    private Integer status;
    /** å‘送时间 */
    private LocalDateTime sendTime;
    /** å·²è¯»æ—¶é—´ */
    private LocalDateTime readTime;
}
```
---
## å››ã€ä¸šåŠ¡æµç¨‹è¯´æ˜Ž
### 4.1 å¥½å‹æ·»åŠ æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  å‘送好友   â”‚ -> â”‚  å¯¹æ–¹æ”¶åˆ°   â”‚ -> â”‚  åŒæ„/拒绝  â”‚ -> â”‚  å»ºç«‹å¥½å‹   â”‚
│  ç”³è¯·       â”‚    â”‚  ç”³è¯·é€šçŸ¥   â”‚    â”‚  ç”³è¯·       â”‚    â”‚  å…³ç³»       â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚
       v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ im_friend_  â”‚    â”‚ WebSocket   â”‚    â”‚ im_friend   â”‚
│ request     â”‚    â”‚ æŽ¨é€é€šçŸ¥    â”‚    â”‚ è¡¨æ–°å¢ž      â”‚
│ è¡¨æ–°å¢ž      â”‚    â”‚             â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
```
### 4.2 ç§èŠæ¶ˆæ¯æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  å‘送消息   â”‚ -> â”‚  WebSocket  â”‚ -> â”‚  æŽ¥æ”¶æ¶ˆæ¯   â”‚ -> â”‚  å·²è¯»å›žæ‰§   â”‚
│             â”‚    â”‚  æŽ¨é€       â”‚    â”‚             â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ im_private_ â”‚    â”‚ å®žæ—¶æŽ¨é€    â”‚    â”‚ æ¶ˆæ¯å…¥åº“    â”‚    â”‚ æ›´æ–°å·²è¯»    â”‚
│ message     â”‚    â”‚ ç»™æŽ¥æ”¶äºº    â”‚    â”‚ çŠ¶æ€æœªè¯»    â”‚    â”‚ çŠ¶æ€        â”‚
│ å…¥åº“        â”‚    â”‚             â”‚    â”‚             â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
### 4.3 ç¾¤èŠç®¡ç†æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  åˆ›å»ºç¾¤èŠ   â”‚ -> â”‚  æ·»åŠ æˆå‘˜   â”‚ -> â”‚  ç¾¤å†…聊天   â”‚ -> â”‚  è§£æ•£ç¾¤èŠ   â”‚
│             â”‚    â”‚             â”‚    â”‚             â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ im_group    â”‚    â”‚ im_group_   â”‚    â”‚ im_group_   â”‚    â”‚ æ›´æ–°ç¾¤çŠ¶æ€  â”‚
│ è¡¨æ–°å¢ž      â”‚    â”‚ member      â”‚    â”‚ message     â”‚    â”‚ ä¸ºå·²è§£æ•£    â”‚
│             â”‚    â”‚ è¡¨æ–°å¢ž      â”‚    â”‚ å…¥åº“        â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
---
## äº”、菜单配置
需要在 system_menu è¡¨ä¸­æ·»åŠ  IM æ¨¡å—的菜单:
```sql
-- IM æ¨¡å—一级菜单
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2100, '即时通讯', '', 1, 30, 0, '/im', 'message', NULL, 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
-- å¥½å‹ç®¡ç†
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2101, '好友管理', '', 2, 1, 2100, 'friend', '', 'im/friend/index', 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
-- ç¾¤èŠç®¡ç†
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2102, '群聊管理', '', 2, 2, 2100, 'group', '', 'im/group/index', 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
-- æ¶ˆæ¯ç®¡ç†
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
VALUES (2103, '消息管理', '', 2, 3, 2100, 'message', '', 'im/message/index', 0, b'1', 'admin', NOW(), 'admin', NOW(), b'0');
```
---
## å…­ã€æ³¨æ„äº‹é¡¹
1. **WebSocket é…ç½®**: IM æ¨¡å—依赖 WebSocket å®žçŽ°å®žæ—¶é€šè®¯ï¼Œéœ€è¦é…ç½® WebSocket ç«¯ç‚¹
2. **Redis æ¶ˆæ¯é˜Ÿåˆ—**: å»ºè®®ä½¿ç”¨ Redis Pub/Sub å®žçŽ°æ¶ˆæ¯å¹¿æ’­
3. **离线消息存储**: ç”¨æˆ·ç¦»çº¿æ—¶çš„æ¶ˆæ¯éœ€è¦æŒä¹…化,上线后推送
4. **消息安全**: æ¶ˆæ¯å†…容需要考虑敏感词过滤、 XSS é˜²æŠ¤
5. **性能优化**: é«˜å¹¶å‘场景建议使用消息队列削峰填谷
---
**文档版本**: v1.0
**创建日期**: 2026-06-29
**适用项目**: mom-pro2-after
**表数量**: 11 å¼ 
docs/mdm_frontend_change.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,241 @@
# MDM ç‰©æ–™ä¸»æ•°æ®æ¨¡å—改造 - å‰ç«¯å˜æ›´æ–‡æ¡£
## æ¦‚è¿°
本次改造将 ERP、MES、WMS ä¸‰ä¸ªæ¨¡å—的物料/产品数据统一到 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(itemType=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(itemType=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. **删除操作**:MDM ç‰©æ–™åˆ é™¤å®žé™…为禁用操作,不做物理删除
3. **SKU æ¨¡å¼**:所有物料必须配置 SKU,ERP/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 | æ‰¹æ¬¡å±žæ€§é…ç½®è¡¨ |
docs/workflow_guide.md
@@ -4,7 +4,7 @@
### 1.1 é¡¹ç›®ç®€ä»‹
芋道 ruoyi-vue-pro æ˜¯ä¸€å¥—全部开源的企业级快速开发平台,基于 Spring Boot 4.0 + Vue 3 æž„建,包含系统管理、工作流、CRM、ERP、MES、WMS ç­‰å¤šä¸ªä¸šåŠ¡æ¨¡å—ã€‚
芋道 ruoyi-vue-pro æ˜¯ä¸€å¥—全部开源的企业级快速开发平台,基于 Spring Boot 4.0 + Vue 3 æž„建,包含系统管理、工作流、CRM、ERP、MES、WMS、MDM ç­‰å¤šä¸ªä¸šåŠ¡æ¨¡å—ã€‚
### 1.2 å·²è§£é”æ¨¡å—
@@ -14,9 +14,45 @@
| yudao-module-infra | åŸºç¡€è®¾æ–½ï¼ˆä»£ç ç”Ÿæˆã€æ–‡ä»¶ã€å®šæ—¶ä»»åŠ¡ç­‰ï¼‰ | 11 |
| yudao-module-bpm | å·¥ä½œæµå¼•擎 | 8 |
| yudao-module-crm | å®¢æˆ·å…³ç³»ç®¡ç† | 19 |
| yudao-module-mdm | ä¸»æ•°æ®ç®¡ç†ï¼ˆç»Ÿä¸€ç‰©æ–™ã€åˆ†ç±»ã€å•位等) | 6 |
| yudao-module-erp | ä¼ä¸šèµ„源计划 | 33 |
| yudao-module-mes | åˆ¶é€ æ‰§è¡Œç³»ç»Ÿ | 71 |
| yudao-module-wms | ä»“库管理系统 | 16 |
| yudao-module-wms | ä»“库管理系统 | 18 |
### 1.3 æ¨¡å—集成架构(核心改造)
本项目已实现 **MDM ä¸»æ•°æ®ç®¡ç†** æ¨¡å—,打通 ERP、MES、WMS ä¸‰ä¸ªä¸šåŠ¡æ¨¡å—çš„æ•°æ®å­¤å²›ï¼š
```
                        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                        â”‚         MDM ä¸»æ•°æ®ç®¡ç†æ¨¡å—           â”‚
                        â”‚  (物料、SKU、分类、单位、品牌)        â”‚
                        â””──────────────┬──────────────────────┘
                                       â”‚
                    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                    â”‚                  â”‚                  â”‚
                    â–¼                  â–¼                  â–¼
           â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”  â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”  â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
           â”‚   ERP æ¨¡å—     â”‚  â”‚   WMS æ¨¡å—     â”‚  â”‚   MES æ¨¡å—     â”‚
           â”‚  äº§å“ = æˆå“   â”‚  â”‚ å•†å“ = ç‰©æ–™    â”‚  â”‚ ç‰©æ–™ = åŽŸæ–™    â”‚
           â”‚ itemType=2    â”‚  â”‚ itemType=1    â”‚  â”‚ itemType=1    â”‚
           â””───────┬───────┘  â””───────┬───────┘  â””───────┬───────┘
                   â”‚                  â”‚                  â”‚
                   â””──────────────────┼──────────────────┘
                                      â”‚
                        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                        â”‚     WMS åº“存管理 API       â”‚
                        â”‚  (统一库存入库/出库/查询)  â”‚
                        â””───────────────────────────┘
```
**数据统一说明:**
| ä¸šåŠ¡æ¨¡å— | åŽŸæ•°æ®è¡¨ | çŽ°æ•°æ®æº | ç‰©æ–™ç±»åž‹æ˜ å°„ |
|----------|----------|----------|--------------|
| ERP | erp_product | MDM Item | itemType=2(成品) |
| WMS | wms_item + wms_item_sku | MDM Item + SKU | itemType=1(物料) |
| MES | mes_md_item | MDM Item | itemType=1(物料) |
---
@@ -72,11 +108,95 @@
---
## ä¸‰ã€åŠŸèƒ½æ¨¡å—å·¥ä½œæµç¨‹
## ä¸‰ã€ä¸»æ•°æ®ç®¡ç†æ¨¡å— (MDM)
### 3.1 ç³»ç»Ÿç®¡ç†æ¨¡å— (System)
### 3.1 MDM æ¨¡å—概述
#### 3.1.1 ç”¨æˆ·ç®¡ç†æµç¨‹
MDM(Master Data Management)主数据管理模块是整个系统的数据核心,负责统一管理:
- **物料主数据** - äº§å“ã€ç‰©æ–™ã€åŠæˆå“çš„统一档案
- **物料 SKU** - ç‰©æ–™çš„规格变体(尺寸、颜色等)
- **物料分类** - ç‰©æ–™çš„分类体系
- **计量单位** - ç»Ÿä¸€çš„计量单位管理
- **品牌管理** - å“ç‰Œæ¡£æ¡ˆç®¡ç†
### 3.2 ç‰©æ–™ç±»åž‹è¯´æ˜Ž
```
┌─────────────────────────────────────────────────────────────────┐
│                      MDM ç‰©æ–™ç±»åž‹ (itemType)                      â”‚
├─────────────────────────────────────────────────────────────────┤
│  1 - ç‰©æ–™/原料    â†’ ç”¨äºŽ WMS å•†å“ã€MES ç‰©æ–™                      â”‚
│  2 - æˆå“/产品    â†’ ç”¨äºŽ ERP äº§å“                                 â”‚
│  3 - åŠæˆå“       â†’ ç”¨äºŽ MES ç”Ÿäº§ä¸­é—´ä»¶                           â”‚
│  4 - è¾…æ–™         â†’ ç”¨äºŽç”Ÿäº§è¾…助材料                              â”‚
└─────────────────────────────────────────────────────────────────┘
```
### 3.3 ç‰©æ–™ç®¡ç†æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  åˆ›å»ºç‰©æ–™   â”‚ -> â”‚  è®¾ç½®åˆ†ç±»   â”‚ -> â”‚  é…ç½® SKU   â”‚ -> â”‚  å¯ç”¨ç‰©æ–™   â”‚
│  åŸºæœ¬ä¿¡æ¯   â”‚    â”‚  å•位品牌   â”‚    â”‚  è§„格价格   â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚
       v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ ç¼–码生成    â”‚    â”‚ åˆ†ç±»å…³è”    â”‚    â”‚ å¤šè§„格支持  â”‚
│ æ¡ç ç®¡ç†    â”‚    â”‚ å•位换算    â”‚    â”‚ ä»·æ ¼ä½“ç³»    â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
```
**操作步骤:**
1. è¿›å…¥ã€MDM】->【物料管理】
2. ç‚¹å‡»ã€æ–°å¢žã€‘,填写物料基本信息
   - ç‰©æ–™ç¼–码(唯一)
   - ç‰©æ–™åç§°
   - ç‰©æ–™æ¡ç 
   - è§„格型号
3. é€‰æ‹©ç‰©æ–™åˆ†ç±»ã€è®¡é‡å•位、品牌
4. è®¾ç½®ç‰©æ–™ç±»åž‹ï¼ˆç‰©æ–™/成品/半成品)
5. é…ç½®ä»·æ ¼ï¼ˆé‡‡è´­ä»·ã€é”€å”®ä»·ã€æˆæœ¬ä»·ï¼‰
6. ä¿å­˜åŽï¼Œå¯æ·»åŠ  SKU è§„æ ¼
**涉及表:**
- `mdm_item` - ç‰©æ–™ä¸»è¡¨
- `mdm_item_sku` - ç‰©æ–™ SKU è¡¨
- `mdm_item_category` - ç‰©æ–™åˆ†ç±»è¡¨
- `mdm_unit_measure` - è®¡é‡å•位表
- `mdm_brand` - å“ç‰Œè¡¨
### 3.4 SKU ç®¡ç†è¯´æ˜Ž
```
┌─────────────────────────────────────────────────────────────┐
│                        ç‰©æ–™ (Item)                           â”‚
│  id: 1001  code: ITEM001  name: T恤                         â”‚
├─────────────────────────────────────────────────────────────┤
│                          SKU åˆ—表                            â”‚
├─────────────────┬─────────────────┬─────────────────────────┤
│ SKU-001         â”‚ SKU-002         â”‚ SKU-003                 â”‚
│ çº¢è‰² / L        â”‚ è“è‰² / M        â”‚ é»‘色 / XL               â”‚
│ æ¡ç : 001REDL   â”‚ æ¡ç : 001BLUEM  â”‚ æ¡ç : 001BLACKXL        â”‚
│ é”€å”®ä»·: 99      â”‚ é”€å”®ä»·: 99      â”‚ é”€å”®ä»·: 109             â”‚
└─────────────────┴─────────────────┴─────────────────────────┘
```
**SKU å­—段:**
- SKU ç¼–码、SKU åç§°ã€SKU æ¡ç 
- å°ºå¯¸ï¼ˆé•¿/宽/高)
- é‡é‡ï¼ˆæ¯›é‡/净重)
- ä»·æ ¼ï¼ˆæˆæœ¬ä»·/销售价)
---
## å››ã€åŠŸèƒ½æ¨¡å—å·¥ä½œæµç¨‹
### 4.1 ç³»ç»Ÿç®¡ç†æ¨¡å— (System)
#### 4.1.1 ç”¨æˆ·ç®¡ç†æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -104,7 +224,7 @@
- `system_user_role` - ç”¨æˆ·è§’色关联表
- `system_user_post` - ç”¨æˆ·å²—位关联表
#### 3.1.2 è§’色权限配置流程
#### 4.1.2 è§’色权限配置流程
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -127,9 +247,9 @@
---
### 3.2 å·¥ä½œæµæ¨¡å— (BPM)
### 4.2 å·¥ä½œæµæ¨¡å— (BPM)
#### 3.2.1 æµç¨‹è®¾è®¡æµç¨‹
#### 4.2.1 æµç¨‹è®¾è®¡æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -153,7 +273,7 @@
- `bpm_form` - è¡¨å•定义表
- `bpm_process_definition_info` - æµç¨‹å®šä¹‰æ‰©å±•表
#### 3.2.2 æµç¨‹å®¡æ‰¹æµç¨‹
#### 4.2.2 æµç¨‹å®¡æ‰¹æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -177,9 +297,9 @@
---
### 3.3 å®¢æˆ·å…³ç³»ç®¡ç†æ¨¡å— (CRM)
### 4.3 å®¢æˆ·å…³ç³»ç®¡ç†æ¨¡å— (CRM)
#### 3.3.1 çº¿ç´¢è½¬å®¢æˆ·æµç¨‹
#### 4.3.1 çº¿ç´¢è½¬å®¢æˆ·æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -208,31 +328,39 @@
- `crm_follow_up_record` - è·Ÿè¿›è®°å½•表
- `crm_permission` - æ•°æ®æƒé™è¡¨
#### 3.3.2 å•†æœºé”€å”®æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  åˆ›å»ºå•†æœº   â”‚ -> â”‚  å…³è”产品   â”‚ -> â”‚  é˜¶æ®µæŽ¨è¿›   â”‚ -> â”‚  ç”ŸæˆåˆåŒ   â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚
       v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  å•†æœºé˜¶æ®µ   â”‚    â”‚  äº§å“æŠ¥ä»·   â”‚    â”‚  èµ¢å•/输单  â”‚
│  ç®¡ç†       â”‚    â”‚             â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
```
**涉及表:**
- `crm_business` - å•†æœºè¡¨
- `crm_business_product` - å•†æœºäº§å“å…³è”表
- `crm_business_status` - å•†æœºçŠ¶æ€è¡¨
- `crm_contract` - åˆåŒè¡¨
---
### 3.4 ä¼ä¸šèµ„源计划模块 (ERP)
### 4.4 ä¼ä¸šèµ„源计划模块 (ERP)
#### 3.4.1 é‡‡è´­ä¸šåŠ¡æµç¨‹
#### 4.4.1 äº§å“ç®¡ç†ï¼ˆMDM é›†æˆï¼‰
```
┌─────────────────────────────────────────────────────────────────┐
│                    ERP äº§å“ = MDM ç‰©æ–™ (itemType=2)             â”‚
├─────────────────────────────────────────────────────────────────┤
│  ErpProductService                                              â”‚
│       â”‚                                                         â”‚
│       â–¼                                                         â”‚
│  MdmItemApi.createItem()  â”€â”€â†’  MDM åˆ›å»ºç‰©æ–™                      â”‚
│  MdmItemApi.updateItem()  â”€â”€â†’  MDM æ›´æ–°ç‰©æ–™                      â”‚
│  MdmItemApi.getItem()     â”€â”€â†’  MDM æŸ¥è¯¢ç‰©æ–™                      â”‚
│  MdmItemApi.getItemPage() â”€â”€â†’  MDM åˆ†é¡µæŸ¥è¯¢                      â”‚
└─────────────────────────────────────────────────────────────────┘
```
**字段映射:**
| ERP å­—段 | MDM å­—段 | è¯´æ˜Ž |
|----------|----------|------|
| id | id | ç»Ÿä¸€ ID |
| name | name | äº§å“åç§° |
| barCode | barCode | äº§å“æ¡ç  |
| standard | specification | è§„格型号 |
| unitId | unitMeasureId | å•位编号 |
| salePrice | salesPrice | é”€å”®ä»·æ ¼ |
| minPrice | costPrice | æœ€ä½Žä»·æ ¼/成本价 |
#### 4.4.2 é‡‡è´­ä¸šåŠ¡æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -241,37 +369,33 @@
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  éœ€æ±‚部门   â”‚    â”‚  ä¾›åº”商     â”‚    â”‚  ä»“库       â”‚    â”‚  è´¢åŠ¡       â”‚
│  å‘èµ·       â”‚    â”‚  ç¡®è®¤       â”‚    â”‚  éªŒæ”¶       â”‚    â”‚  ç»“ç®—       â”‚
│  éœ€æ±‚部门   â”‚    â”‚  ä¾›åº”商     â”‚    â”‚  WMS入库    â”‚    â”‚  è´¢åŠ¡       â”‚
│  å‘èµ·       â”‚    â”‚  ç¡®è®¤       â”‚    â”‚  API调用    â”‚    â”‚  ç»“ç®—       â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
**采购订单操作步骤:**
**采购入库集成:**
1. è¿›å…¥ã€ERP】->【采购管理】->【采购订单】
2. ç‚¹å‡»ã€æ–°å¢žã€‘,选择供应商
3. æ·»åŠ é‡‡è´­äº§å“æ˜Žç»†ï¼ˆäº§å“ã€æ•°é‡ã€å•ä»·ï¼‰
4. è®¾ç½®äº¤è´§æ—¥æœŸã€ç»“算账户
5. ç‚¹å‡»ã€æäº¤ã€‘或直接【审核】
6. è®¢å•审核后,可进行入库操作
**采购入库操作步骤:**
1. è¿›å…¥ã€é‡‡è´­å…¥åº“】页面
2. é€‰æ‹©å·²å®¡æ ¸çš„采购订单
3. ç³»ç»Ÿè‡ªåŠ¨å¸¦å…¥äº§å“æ˜Žç»†
4. é€‰æ‹©å…¥åº“仓库
5. ç¡®è®¤å…¥åº“数量,点击【提交】
6. å®¡æ ¸é€šè¿‡åŽï¼Œåº“存自动增加
```
ERP é‡‡è´­å…¥åº“
    â”‚
    â–¼
ErpStockRecordService.createStockRecord()
    â”‚
    â–¼
WmsInventoryApi.receipt()  â”€â”€â†’  WMS å…¥åº“操作
    â”‚
    â–¼
WMS åº“存增加
```
**涉及表:**
- `erp_supplier` - ä¾›åº”商表
- `erp_purchase_order` - é‡‡è´­è®¢å•表
- `erp_purchase_order_items` - é‡‡è´­è®¢å•项表
- `erp_purchase_in` - é‡‡è´­å…¥åº“表
- `erp_stock` - åº“存表
- `erp_stock_record` - åº“存流水表(ERP æœ¬åœ°è®°å½•)
#### 3.4.2 é”€å”®ä¸šåŠ¡æµç¨‹
#### 4.4.3 é”€å”®ä¸šåŠ¡æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -280,8 +404,24 @@
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  å®¢æˆ·è¯¢ä»·   â”‚    â”‚  åˆåŒç­¾è®¢   â”‚    â”‚  å‘è´§       â”‚    â”‚  å¯¹è´¦å¼€ç¥¨   â”‚
│  å®¢æˆ·è¯¢ä»·   â”‚    â”‚  åˆåŒç­¾è®¢   â”‚    â”‚  WMS出库    â”‚    â”‚  å¯¹è´¦å¼€ç¥¨   â”‚
│             â”‚    â”‚             â”‚    â”‚  API调用    â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
**销售出库集成:**
```
ERP é”€å”®å‡ºåº“
    â”‚
    â–¼
ErpStockRecordService.createStockRecord()
    â”‚
    â–¼
WmsInventoryApi.shipment()  â”€â”€â†’  WMS å‡ºåº“操作
    â”‚
    â–¼
WMS åº“存减少
```
**涉及表:**
@@ -290,70 +430,42 @@
- `erp_sale_out` - é”€å”®å‡ºåº“表
- `erp_finance_receipt` - æ”¶æ¬¾å•表
#### 3.4.3 åº“存管理流程
#### 4.4.4 åº“存查询(WMS é›†æˆï¼‰
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  åº“存查询   â”‚ -> â”‚  åº“存盘点   â”‚ -> â”‚  åº“存调拨   â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚
       v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  å®žæ—¶åº“å­˜   â”‚    â”‚  ç›˜ç›ˆ/盘亏  â”‚    â”‚  è·¨ä»“调拨   â”‚
│  æ˜Žç»†       â”‚    â”‚  å¤„理       â”‚    â”‚             â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    ERP åº“å­˜ = WMS åº“å­˜ API                       â”‚
├─────────────────────────────────────────────────────────────────┤
│  ErpStockService                                                â”‚
│       â”‚                                                         â”‚
│       â–¼                                                         â”‚
│  MdmItemSkuApi.getItemSkuListByItemId() â”€â”€â†’ èŽ·å–é»˜è®¤ SKU        â”‚
│       â”‚                                                         â”‚
│       â–¼                                                         â”‚
│  WmsInventoryApi.getAvailableQuantity() â”€â”€â†’ æŸ¥è¯¢å¯ç”¨åº“å­˜        â”‚
│  WmsInventoryApi.getInventoryList()      â”€â”€â†’ æŸ¥è¯¢åº“存列表       â”‚
└─────────────────────────────────────────────────────────────────┘
```
**涉及表:**
- `erp_warehouse` - ä»“库表
- `erp_stock` - åº“存表
- `erp_stock_record` - åº“存流水表
- `erp_stock_check` - ç›˜ç‚¹å•表
- `erp_stock_move` - è°ƒæ‹¨å•表
---
### 3.5 åˆ¶é€ æ‰§è¡Œç³»ç»Ÿæ¨¡å— (MES)
### 4.5 åˆ¶é€ æ‰§è¡Œç³»ç»Ÿæ¨¡å— (MES)
#### 3.5.1 ç”Ÿäº§ä¸»æ•°æ®ç®¡ç†æµç¨‹
#### 4.5.1 ç‰©æ–™ç®¡ç†ï¼ˆMDM é›†æˆï¼‰
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  ç‰©æ–™ç®¡ç†   â”‚ -> â”‚  BOM ç®¡ç†   â”‚ -> â”‚  å·¥è‰ºè·¯çº¿   â”‚ -> â”‚  å·¥ä½œç«™     â”‚
│             â”‚    â”‚             â”‚    â”‚             â”‚    â”‚  é…ç½®       â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ ç‰©æ–™åˆ†ç±»    â”‚ â”‚ äº§å“ç»“æž„    â”‚ â”‚ å·¥åºå®šä¹‰    â”‚ â”‚ è®¾å¤‡/人员   â”‚
│ è®¡é‡å•位    â”‚ â”‚ æ›¿ä»£æ–™      â”‚ â”‚ é¡ºåºé…ç½®    â”‚ â”‚ å·¥å…·å¤¹å…·    â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    MES ç‰©æ–™ = MDM ç‰©æ–™ (itemType=1)             â”‚
├─────────────────────────────────────────────────────────────────┤
│  MesMdItemService                                               â”‚
│       â”‚                                                         â”‚
│       â–¼                                                         â”‚
│  MdmItemApi.getItem()     â”€â”€â†’  MDM æŸ¥è¯¢ç‰©æ–™                      â”‚
│  MdmItemApi.getItemList() â”€â”€â†’  MDM æŸ¥è¯¢ç‰©æ–™åˆ—表                  â”‚
└─────────────────────────────────────────────────────────────────┘
```
**物料管理操作:**
1. è¿›å…¥ã€MES】->【主数据】->【物料管理】
2. æ–°å¢žç‰©æ–™åˆ†ç±»ï¼ˆæ ‘形结构)
3. åˆ›å»ºç‰©æ–™æ¡£æ¡ˆï¼ˆç¼–码、名称、规格、单位等)
4. é…ç½®ç‰©æ–™æ‰¹æ¬¡å±žæ€§
**工艺路线操作:**
1. è¿›å…¥ã€å·¥è‰ºè·¯çº¿ã€‘管理
2. å…ˆå®šä¹‰ã€å·¥åºã€‘(加工、检验、装配等)
3. åˆ›å»ºå·¥è‰ºè·¯çº¿ï¼Œå…³è”产品
4. é…ç½®å·¥åºé¡ºåºã€å·¥æ—¶å®šé¢
5. å…³è”工作中心/设备
**涉及表:**
- `mes_md_item` - ç‰©æ–™è¡¨
- `mes_md_item_type` - ç‰©æ–™åˆ†ç±»è¡¨
- `mes_md_product_bom` - BOM è¡¨
- `mes_pro_process` - å·¥åºè¡¨
- `mes_pro_route` - å·¥è‰ºè·¯çº¿è¡¨
- `mes_md_workstation` - å·¥ä½œç«™è¡¨
#### 3.5.2 ç”Ÿäº§æ‰§è¡Œæµç¨‹
#### 4.5.2 ç”Ÿäº§æ‰§è¡Œæµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -363,32 +475,17 @@
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ è®¡åˆ’排程    â”‚ â”‚ ä»»åŠ¡åˆ†é…    â”‚ â”‚ ç‰©æ–™æ¶ˆè€—    â”‚ â”‚ è¿›åº¦åé¦ˆ    â”‚
│ ç‰©æ–™æ£€æŸ¥    â”‚ â”‚ å·¥ä½œç«™æŒ‡æ´¾  â”‚ â”‚ æ‰¹æ¬¡è¿½æº¯    â”‚ â”‚ è´¨é‡è®°å½•    â”‚
│ è®¡åˆ’排程    â”‚ â”‚ ä»»åŠ¡åˆ†é…    â”‚ â”‚ WMS出库    â”‚ â”‚ è¿›åº¦åé¦ˆ    â”‚
│ ç‰©æ–™æ£€æŸ¥    â”‚ â”‚ å·¥ä½œç«™æŒ‡æ´¾  â”‚ â”‚ API调用    â”‚ â”‚ è´¨é‡è®°å½•    â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚
       v
┌─────────────┐
│  ç”Ÿäº§å…¥åº“   â”‚
│  WMS入库    â”‚
│  API调用    â”‚
└─────────────┘
```
**生产工单操作步骤:**
1. è¿›å…¥ã€MES】->【生产管理】->【生产工单】
2. ç‚¹å‡»ã€æ–°å¢žã€‘,选择产品
3. ç³»ç»Ÿè‡ªåЍ另入 BOM å’Œå·¥è‰ºè·¯çº¿
4. è®¾ç½®è®¡åˆ’数量、计划开始/结束时间
5. ç‚¹å‡»ã€ä¸‹è¾¾ã€‘工单
6. å·¥å•下达后可进行派工、领料操作
**生产报工操作:**
1. è¿›å…¥ã€æŠ¥å·¥ã€‘页面
2. æ‰«ç æˆ–选择工单、工序
3. å¡«å†™æŠ¥å·¥æ•°é‡ï¼ˆåˆæ ¼/报废)
4. è®°å½•工时、设备使用情况
5. æäº¤æŠ¥å·¥æ•°æ®
**涉及表:**
- `mes_pro_work_order` - ç”Ÿäº§å·¥å•表
@@ -396,78 +493,66 @@
- `mes_pro_work_record` - æŠ¥å·¥è®°å½•表
- `mes_wm_item_consume` - ç‰©æ–™æ¶ˆè€—表
#### 3.5.3 è®¾å¤‡ç®¡ç†æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  è®¾å¤‡å°è´¦   â”‚ -> â”‚  ç‚¹æ£€ä¿å…»   â”‚ -> â”‚  ç»´ä¿®ç®¡ç†   â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚
       v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ è®¾å¤‡æ¡£æ¡ˆ    â”‚ â”‚ ç‚¹æ£€è®¡åˆ’    â”‚ â”‚ ç»´ä¿®å·¥å•    â”‚
│ è®¾å¤‡çŠ¶æ€    â”‚ â”‚ ä¿å…»è®¡åˆ’    â”‚ â”‚ æ•…障记录    â”‚
│ è½¦é—´åˆ†å¸ƒ    â”‚ â”‚ æ‰§è¡Œè®°å½•    â”‚ â”‚ éªŒæ”¶ç¡®è®¤    â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
```
**设备点检操作:**
1. è¿›å…¥ã€è®¾å¤‡ç®¡ç†ã€‘->【点检方案】
2. åˆ›å»ºç‚¹æ£€æ–¹æ¡ˆï¼Œé…ç½®ç‚¹æ£€é¡¹ç›®
3. å…³è”设备和周期
4. æ‰§è¡Œç‚¹æ£€æ—¶ï¼Œè¿›å…¥ã€ç‚¹æ£€è®°å½•】
5. é€é¡¹æ£€æŸ¥ï¼Œè®°å½•结果
6. å‘现异常,自动触发报修
**涉及表:**
- `mes_dv_machinery` - è®¾å¤‡å°è´¦è¡¨
- `mes_dv_check_plan` - ç‚¹æ£€æ–¹æ¡ˆè¡¨
- `mes_dv_check_record` - ç‚¹æ£€è®°å½•表
- `mes_dv_repair` - ç»´ä¿®å·¥å•表
#### 3.5.4 è´¨é‡ç®¡ç†æµç¨‹
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  æ¥æ–™æ£€éªŒ   â”‚ -> â”‚  è¿‡ç¨‹æ£€éªŒ   â”‚ -> â”‚  æˆå“æ£€éªŒ   â”‚ -> â”‚  å‡ºè´§æ£€éªŒ   â”‚
│  (IQC)      â”‚    â”‚  (IPQC)     â”‚    â”‚  (FQC)      â”‚    â”‚  (OQC)      â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚                  â”‚
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ é‡‡è´­å…¥åº“    â”‚ â”‚ ç”Ÿäº§å·¥åº    â”‚ â”‚ ç”Ÿäº§å®Œæˆ    â”‚ â”‚ é”€å”®å‡ºåº“    â”‚
│ å‰æ£€éªŒ      â”‚ â”‚ æŠ½æ£€/巡检   â”‚ â”‚ å…¨æ£€/抽检   â”‚ â”‚ å‰æ£€éªŒ      â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
**质检方案配置:**
1. è¿›å…¥ã€è´¨é‡ç®¡ç†ã€‘->【质检方案】
2. åˆ›å»ºè´¨æ£€æ¨¡æ¿ï¼Œé…ç½®æ£€æµ‹é¡¹ç›®
3. è®¾ç½®æŠ½æ ·æ–¹æ¡ˆï¼ˆå…¨æ£€/抽检比例)
4. é…ç½®æ£€éªŒæŒ‡æ ‡å’Œæ ‡å‡†å€¼
**检验操作:**
1. é€‰æ‹©å¾…检单据(采购入库单/生产工单等)
2. åˆ›å»ºæ£€éªŒå•
3. é€é¡¹æ£€éªŒï¼Œè®°å½•数据
4. åˆ¤å®šç»“果(合格/不合格/让步接收)
5. ä¸åˆæ ¼å“è¿›å…¥å¤„理流程
**涉及表:**
- `mes_qc_template` - è´¨æ£€æ–¹æ¡ˆè¡¨
- `mes_qc_iqc` - æ¥æ–™æ£€éªŒè¡¨
- `mes_qc_ipqc` - è¿‡ç¨‹æ£€éªŒè¡¨
- `mes_qc_oqc` - å‡ºè´§æ£€éªŒè¡¨
- `mes_qc_defect_record` - ç¼ºé™·è®°å½•表
---
### 3.6 ä»“库管理系统模块 (WMS)
### 4.6 ä»“库管理系统模块 (WMS)
#### 3.6.1 å…¥åº“业务流程
#### 4.6.1 å•†å“ç®¡ç†ï¼ˆMDM é›†æˆï¼‰
```
┌─────────────────────────────────────────────────────────────────┐
│                    WMS å•†å“ = MDM ç‰©æ–™ (itemType=1)             â”‚
├─────────────────────────────────────────────────────────────────┤
│  WmsItemService                                                 â”‚
│       â”‚                                                         â”‚
│       â–¼                                                         â”‚
│  MdmItemApi.createItem()  â”€â”€â†’  MDM åˆ›å»ºç‰©æ–™                      â”‚
│  MdmItemApi.updateItem()  â”€â”€â†’  MDM æ›´æ–°ç‰©æ–™                      â”‚
│  MdmItemApi.getItemPage() â”€â”€â†’  MDM åˆ†é¡µæŸ¥è¯¢                      â”‚
│  MdmItemApi.getItemList() â”€â”€â†’  MDM æŸ¥è¯¢åˆ—表                      â”‚
└─────────────────────────────────────────────────────────────────┘
```
**字段映射:**
| WMS å­—段 | MDM å­—段 | è¯´æ˜Ž |
|----------|----------|------|
| id | id | ç»Ÿä¸€ ID |
| code | code | å•†å“ç¼–码 |
| name | name | å•†å“åç§° |
| unit | unitMeasure.name | å•位名称(从计量单位获取) |
| categoryId | categoryId | åˆ†ç±»ç¼–号 |
| brandId | brandId | å“ç‰Œç¼–号 |
#### 4.6.2 åº“存管理核心
```
┌─────────────────────────────────────────────────────────────────┐
│                    WMS åº“存管理 API                              â”‚
├─────────────────────────────────────────────────────────────────┤
│  WmsInventoryApi                                                â”‚
│       â”‚                                                         â”‚
│       â”œâ”€ receipt()          å…¥åº“操作                            â”‚
│       â”‚    â””─ å‚æ•°: bizType, bizId, warehouseId, items[]        â”‚
│       â”‚                                                         â”‚
│       â”œâ”€ shipment()         å‡ºåº“操作                            â”‚
│       â”‚    â””─ å‚æ•°: bizType, bizId, warehouseId, items[]        â”‚
│       â”‚                                                         â”‚
│       â”œâ”€ reserve()          åº“存预留(锁库存)                   â”‚
│       â”‚    â””─ å‚æ•°: bizType, bizId, warehouseId, items[]        â”‚
│       â”‚                                                         â”‚
│       â”œâ”€ releaseReserve()   é‡Šæ”¾é¢„ç•™                            â”‚
│       â”‚    â””─ å‚æ•°: reserveNo                                   â”‚
│       â”‚                                                         â”‚
│       â”œâ”€ getInventoryList() æŸ¥è¯¢åº“存列表                        â”‚
│       â”‚    â””─ å‚æ•°: skuIds, warehouseIds, onlyPositive          â”‚
│       â”‚                                                         â”‚
│       â””─ getAvailableQuantity() æŸ¥è¯¢å¯ç”¨åº“å­˜                    â”‚
│            â””─ å‚æ•°: skuId, warehouseId                          â”‚
└─────────────────────────────────────────────────────────────────┘
```
#### 4.6.3 å…¥åº“业务流程
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -477,26 +562,26 @@
       v                  v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ ERP采购单   â”‚ â”‚ é€è´§å•据    â”‚ â”‚ è´¨é‡æ£€éªŒ    â”‚ â”‚ åº“位分配    â”‚
│ ç”Ÿæˆé¢„约    â”‚ â”‚ è½¦è¾†ä¿¡æ¯    â”‚ â”‚ æ•°é‡ç¡®è®¤    â”‚ â”‚ æ‰¹æ¬¡ç®¡ç†    â”‚
│ MES生产入库 â”‚ â”‚ è½¦è¾†ä¿¡æ¯    â”‚ â”‚ æ•°é‡ç¡®è®¤    â”‚ â”‚ æ‰¹æ¬¡ç®¡ç†    â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
**入库操作步骤:**
**API è°ƒç”¨ç¤ºä¾‹ï¼š**
1. è¿›å…¥ã€WMS】->【入库管理】->【入库单】
2. æ–°å»ºå…¥åº“单,选择入库类型(采购入库/生产入库/其他入库)
3. æ·»åŠ å…¥åº“å•†å“æ˜Žç»†
4. é€‰æ‹©ä»“库
5. æäº¤å®¡æ ¸
6. å®¡æ ¸é€šè¿‡åŽï¼Œåº“存增加
```java
// ERP/MES è°ƒç”¨ WMS å…¥åº“
WmsReceiptReqDTO req = new WmsReceiptReqDTO();
req.setBizType(WmsInventoryBizTypeEnum.PURCHASE_RECEIPT.getType());
req.setBizId(purchaseInId);
req.setBizNo(purchaseInNo);
req.setWarehouseId(warehouseId);
req.setItems(List.of(
    new WmsReceiptReqDTO.Item(skuId, quantity, price)
));
wmsInventoryApi.receipt(req);
```
**涉及表:**
- `wms_receipt_order` - å…¥åº“单表
- `wms_receipt_order_detail` - å…¥åº“单明细表
- `wms_inventory` - åº“存表
- `wms_inventory_history` - åº“存流水表
#### 3.6.2 å‡ºåº“业务流程
#### 4.6.4 å‡ºåº“业务流程
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
@@ -510,73 +595,84 @@
└─────────────┘    â””─────────────┘    â””─────────────┘    â””─────────────┘
```
**涉及表:**
- `wms_shipment_order` - å‡ºåº“单表
- `wms_shipment_order_detail` - å‡ºåº“单明细表
**API è°ƒç”¨ç¤ºä¾‹ï¼š**
#### 3.6.3 åº“存管理流程
```
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  åº“存查询   â”‚ -> â”‚  åº“存盘点   â”‚ -> â”‚  ç§»åº“作业   â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
       â”‚                  â”‚                  â”‚
       v                  v                  v
┌─────────────┐    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│ å®žæ—¶åº“å­˜    â”‚ â”‚ ç›˜ç›ˆç›˜äº    â”‚ â”‚ åº“位调整    â”‚
│ æ‰¹æ¬¡è¿½æº¯    â”‚ â”‚ å·®å¼‚处理    â”‚ â”‚ åº“存优化    â”‚
└─────────────┘    â””─────────────┘    â””─────────────┘
```java
// ERP/MES è°ƒç”¨ WMS å‡ºåº“
WmsShipmentReqDTO req = new WmsShipmentReqDTO();
req.setBizType(WmsInventoryBizTypeEnum.SALE_SHIPMENT.getType());
req.setBizId(saleOutId);
req.setBizNo(saleOutNo);
req.setWarehouseId(warehouseId);
req.setItems(List.of(
    new WmsShipmentReqDTO.Item(skuId, quantity)
));
wmsInventoryApi.shipment(req);
```
**涉及表:**
- `wms_inventory` - åº“存表
- `wms_check_order` - ç›˜ç‚¹å•表
- `wms_movement_order` - ç§»åº“单表
- `wms_item_sku` - å•†å“ SKU è¡¨
- `wms_inventory_history` - åº“存流水表
- `wms_inventory_reserve` - åº“存预留表
---
## å››ã€ç³»ç»Ÿé›†æˆè¯´æ˜Ž
## äº”、系统集成说明
### 4.1 æ¨¡å—间数据流转
### 5.1 æ¨¡å—间数据流转
```
                    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                    â”‚   System æ¨¡å—   â”‚
                    â”‚  (用户/权限)    â”‚
                    â”‚   MDM æ¨¡å—      â”‚
                    â”‚  (物料主数据)   â”‚
                    â””────────┬────────┘
                             â”‚
              â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
              â”‚              â”‚              â”‚
              v              v              v
     â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”  â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”  â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
     â”‚ CRM æ¨¡å—   â”‚  â”‚ ERP æ¨¡å—   â”‚  â”‚ MES æ¨¡å—   â”‚
     â”‚ (客户)     â”‚  â”‚ (采购/销售)│  â”‚ (生产)     â”‚
     â”‚ ERP æ¨¡å—   â”‚  â”‚ WMS æ¨¡å—   â”‚  â”‚ MES æ¨¡å—   â”‚
     â”‚ (成品)     â”‚  â”‚ (物料+SKU) â”‚  â”‚ (物料)     â”‚
     â”‚ itemType=2 â”‚  â”‚ itemType=1 â”‚  â”‚ itemType=1 â”‚
     â””─────┬──────┘  â””─────┬──────┘  â””─────┬──────┘
           â”‚               â”‚               â”‚
           â”‚               â”‚               â”‚
           â””───────────────┼───────────────┘
                           â”‚
                    â”Œâ”€â”€â”€â”€â”€â”€v──────┐
                    â”‚  WMS æ¨¡å—   â”‚
                    â”‚  (库存)     â”‚
                    â”‚  WMS åº“å­˜   â”‚
                    â”‚  API        â”‚
                    â””─────────────┘
```
### 4.2 ä¸šåŠ¡æ•°æ®å…³è”
### 5.2 API æŽ¥å£æ¸…单
| ä¸šåŠ¡åœºæ™¯ | æ¶‰åŠæ¨¡å— | æ•°æ®æµå‘ |
|----------|----------|----------|
| é”€å”®è®¢å• -> å‘è´§ | CRM â†’ WMS | å®¢æˆ·è®¢å•触发出库 |
| é‡‡è´­è®¢å• -> å…¥åº“ | ERP â†’ WMS | é‡‡è´­è®¢å•触发入库 |
| ç”Ÿäº§é¢†æ–™ | MES â†’ WMS | å·¥å•触发领料出库 |
| ç”Ÿäº§å…¥åº“ | MES â†’ WMS | å®Œå·¥è§¦å‘成品入库 |
| è´¨é‡æ£€éªŒ | WMS â†’ MES | å…¥åº“触发来料检验 |
| API æŽ¥å£ | æä¾›æ–¹ | è°ƒç”¨æ–¹ | è¯´æ˜Ž |
|----------|--------|--------|------|
| MdmItemApi | MDM | ERP/WMS/MES | ç‰©æ–™ä¸»æ•°æ® CRUD |
| MdmItemSkuApi | MDM | ERP/WMS/MES | SKU ç®¡ç† |
| MdmItemCategoryApi | MDM | ERP/WMS/MES | ç‰©æ–™åˆ†ç±» |
| MdmUnitMeasureApi | MDM | ERP/WMS/MES | è®¡é‡å•位 |
| WmsInventoryApi | WMS | ERP/MES | åº“存入库/出库/查询 |
### 5.3 ä¸šåŠ¡æ•°æ®å…³è”
| ä¸šåŠ¡åœºæ™¯ | æ¶‰åŠæ¨¡å— | æ•°æ®æµå‘ | API è°ƒç”¨ |
|----------|----------|----------|----------|
| ERP äº§å“ç®¡ç† | ERP â†’ MDM | äº§å“ CRUD | MdmItemApi |
| WMS å•†å“ç®¡ç† | WMS â†’ MDM | å•†å“ CRUD | MdmItemApi |
| MES ç‰©æ–™ç®¡ç† | MES â†’ MDM | ç‰©æ–™æŸ¥è¯¢ | MdmItemApi |
| é‡‡è´­å…¥åº“ | ERP â†’ WMS | åº“存增加 | WmsInventoryApi.receipt() |
| é”€å”®å‡ºåº“ | ERP â†’ WMS | åº“存减少 | WmsInventoryApi.shipment() |
| ç”Ÿäº§é¢†æ–™ | MES â†’ WMS | åº“存减少 | WmsInventoryApi.shipment() |
| ç”Ÿäº§å…¥åº“ | MES â†’ WMS | åº“存增加 | WmsInventoryApi.receipt() |
| åº“存查询 | ERP/MES â†’ WMS | åº“存查询 | WmsInventoryApi.getAvailableQuantity() |
---
## äº”、常见问题处理
## å…­ã€å¸¸è§é—®é¢˜å¤„理
### 5.1 ç”¨æˆ·æ— æ³•登录
### 6.1 ç”¨æˆ·æ— æ³•登录
**排查步骤:**
@@ -586,7 +682,7 @@
4. æ£€æŸ¥è´¦æˆ·æ˜¯å¦è¢«é”å®šï¼ˆå¯†ç é”™è¯¯æ¬¡æ•°è¿‡å¤šï¼‰
5. æŸ¥çœ‹ç™»å½•日志 `system_login_log`
### 5.2 æ•°æ®æƒé™é—®é¢˜
### 6.2 æ•°æ®æƒé™é—®é¢˜
**排查步骤:**
@@ -594,7 +690,7 @@
2. CRM æ¨¡å—检查 `crm_permission` è¡¨
3. ç¡®è®¤æ•°æ®æ˜¯å¦æ­£ç¡®åˆ†é…è´Ÿè´£äºº
### 5.3 æµç¨‹å®¡æ‰¹å¼‚常
### 6.3 æµç¨‹å®¡æ‰¹å¼‚常
**排查步骤:**
@@ -603,20 +699,29 @@
3. æŸ¥çœ‹æµç¨‹å®žä¾‹çŠ¶æ€
4. æ£€æŸ¥å€™é€‰ç”¨æˆ·/用户组配置
### 5.4 åº“存数据不一致
### 6.4 ç‰©æ–™æ•°æ®ä¸ä¸€è‡´
**排查步骤:**
1. æ£€æŸ¥ `wms_inventory_history` æˆ– `erp_stock_record` æµæ°´è®°å½•
1. ç¡®è®¤ç‰©æ–™æ˜¯å¦åœ¨ MDM ä¸­æ­£ç¡®åˆ›å»º
2. æ£€æŸ¥ç‰©æ–™ç±»åž‹ï¼ˆitemType)是否正确
3. ç¡®è®¤ SKU æ˜¯å¦æ­£ç¡®é…ç½®
4. éªŒè¯åˆ†ç±»ã€å•位关联是否正确
### 6.5 åº“存数据不一致
**排查步骤:**
1. æ£€æŸ¥ `wms_inventory_history` åº“存流水记录
2. æ ¸å¯¹å‡ºå…¥åº“单据是否全部审核
3. æ‰§è¡Œåº“存盘点校正
4. æ£€æŸ¥æ˜¯å¦æœ‰æœªå®Œæˆçš„调拨单
3. ç¡®è®¤ WMS API è°ƒç”¨æ˜¯å¦æˆåŠŸ
4. æ‰§è¡Œåº“存盘点校正
---
## å…­ã€é™„录
## ä¸ƒã€é™„录
### 6.1 æ¨¡å—路径对照表
### 7.1 æ¨¡å—路径对照表
| æ¨¡å— | åŽç«¯è·¯å¾„ | å‰ç«¯è·¯å¾„ |
|------|----------|----------|
@@ -624,11 +729,12 @@
| Infra | yudao-module-infra | views/infra |
| BPM | yudao-module-bpm | views/bpm |
| CRM | yudao-module-crm | views/crm |
| MDM | yudao-module-mdm | views/mdm |
| ERP | yudao-module-erp | views/erp |
| MES | yudao-module-mes | views/mes |
| WMS | yudao-module-wms | views/wms |
### 6.2 æŽ¥å£é‰´æƒè¯´æ˜Ž
### 7.2 æŽ¥å£é‰´æƒè¯´æ˜Ž
所有业务接口需要携带 Token è®¿é—®ï¼š
@@ -638,15 +744,28 @@
Token èŽ·å–æ–¹å¼ï¼šè°ƒç”¨ç™»å½•æŽ¥å£ `/system/auth/login`
### 6.3 å¤šç§Ÿæˆ·è¯´æ˜Ž
### 7.3 å¤šç§Ÿæˆ·è¯´æ˜Ž
系统支持多租户模式,租户隔离通过 `tenant_id` å­—段实现:
- System æ¨¡å—大部分表包含 `tenant_id`
- ä¸šåŠ¡æ¨¡å—ï¼ˆCRM、ERP、MES、WMS)不含租户字段,需自行实现隔离逻辑
- ä¸šåŠ¡æ¨¡å—ï¼ˆCRM、ERP、MES、WMS、MDM)不含租户字段,需自行实现隔离逻辑
### 7.4 æ ¸å¿ƒæ”¹é€ æ–‡ä»¶æ¸…单
| æ–‡ä»¶ | æ”¹é€ è¯´æ˜Ž |
|------|----------|
| ErpProductServiceImpl | äº§å“ CRUD è°ƒç”¨ MdmItemApi |
| ErpStockServiceImpl | åº“存查询调用 WmsInventoryApi |
| ErpStockRecordServiceImpl | åº“存变更调用 WmsInventoryApi |
| WmsItemServiceImpl | å•†å“ CRUD è°ƒç”¨ MdmItemApi |
| MdmItemApi | MDM ç‰©æ–™ API æŽ¥å£ |
| MdmItemSkuApi | MDM SKU API æŽ¥å£ |
| WmsInventoryApi | WMS åº“å­˜ API æŽ¥å£ |
---
**文档版本**: v1.0
**更新日期**: 2026-06-23
**文档版本**: v2.0
**更新日期**: 2026-06-29
**适用版本**: ruoyi-vue-pro 2026.04-SNAPSHOT
**核心改造**: MDM ä¸»æ•°æ®ç®¡ç†æ¨¡å—集成,打通 ERP/MES/WMS æ•°æ®å­¤å²›
pom.xml
@@ -26,6 +26,8 @@
        <module>yudao-module-iot</module>
        <module>yudao-module-mes</module>
        <module>yudao-module-wms</module>
        <module>yudao-module-mdm</module>
        <module>yudao-module-mdm-api</module>
<!-- è¯·å‚考 https://doc.iocoder.cn/ai/build/ æ–‡æ¡£ï¼Œå®Œæˆ AI æ¨¡å—的启动!!! -->
        <module>yudao-module-ai</module>
    </modules>
sql/mdm_migration.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,405 @@
-- =============================================
-- MDM æ•°æ®è¿ç§» SQL è„šæœ¬
-- åˆ›å»ºæ—¥æœŸ: 2026-06-29
-- è¯´æ˜Ž: å°† MES/ERP/WMS çš„物料数据迁移到统一主数据表 md_item
-- æ³¨æ„: æ‰§è¡Œå‰è¯·å¤‡ä»½æ•°æ®åº“!
-- =============================================
-- ---------------------------------------------
-- ç¬¬ä¸€é˜¶æ®µï¼šè¿ç§»åŸºç¡€æ•°æ®ï¼ˆåˆ†ç±»ã€å•位、品牌)
-- ---------------------------------------------
-- 1. è¿ç§» MES è®¡é‡å•位到 md_unit_measure
INSERT INTO `md_unit_measure` (`id`, `code`, `name`, `primary_flag`, `primary_id`, `change_rate`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id,
    LOWER(REPLACE(name, ' ', '_')) AS code,  -- ä½¿ç”¨åç§°ç”Ÿæˆç¼–码
    name,
    COALESCE(primary_flag, 0) AS primary_flag,
    primary_id,
    change_rate,
    COALESCE(status, 0) AS status,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `mes_md_unit_measure`
WHERE deleted = 0
ON DUPLICATE KEY UPDATE name = VALUES(name);
-- 2. è¿ç§» ERP è®¡é‡å•位到 md_unit_measure(使用新ID范围 10000-19999)
INSERT INTO `md_unit_measure` (`id`, `code`, `name`, `primary_flag`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id + 10000,  -- ERP å•位 ID åç§» 10000
    LOWER(REPLACE(name, ' ', '_')) AS code,
    name,
    0 AS primary_flag,
    COALESCE(status, 0) AS status,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `erp_product_unit`
WHERE deleted = 0
ON DUPLICATE KEY UPDATE name = VALUES(name);
-- 3. è¿ç§» MES ç‰©æ–™åˆ†ç±»åˆ° md_item_category
INSERT INTO `md_item_category` (`id`, `parent_id`, `code`, `name`, `item_type`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id,
    parent_id,
    code,
    name,
    CASE item_or_product
        WHEN 'ITEM' THEN 1      -- åŽŸæ–™
        WHEN 'PRODUCT' THEN 3   -- æˆå“
        ELSE NULL
    END AS item_type,
    sort,
    COALESCE(status, 0) AS status,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `mes_md_item_type`
WHERE deleted = 0
ON DUPLICATE KEY UPDATE name = VALUES(name);
-- 4. è¿ç§» ERP äº§å“åˆ†ç±»åˆ° md_item_category(使用新ID范围 10000-19999)
INSERT INTO `md_item_category` (`id`, `parent_id`, `code`, `name`, `item_type`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id + 10000,
    CASE WHEN parent_id = 0 THEN 0 ELSE parent_id + 10000 END,
    CONCAT('ERP_', code) AS code,  -- æ·»åŠ å‰ç¼€é¿å…å†²çª
    name,
    3 AS item_type,  -- ERP äº§å“é»˜è®¤ä¸ºæˆå“
    COALESCE(sort, 0) AS sort,
    COALESCE(status, 0) AS status,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `erp_product_category`
WHERE deleted = 0
ON DUPLICATE KEY UPDATE name = VALUES(name);
-- 5. è¿ç§» WMS å•†å“åˆ†ç±»åˆ° md_item_category(使用新ID范围 20000-29999)
INSERT INTO `md_item_category` (`id`, `parent_id`, `code`, `name`, `item_type`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id + 20000,
    CASE WHEN parent_id = 0 THEN 0 ELSE parent_id + 20000 END,
    CONCAT('WMS_', code) AS code,
    name,
    NULL AS item_type,  -- WMS åˆ†ç±»ä¸åŒºåˆ†ç±»åž‹
    COALESCE(sort, 0) AS sort,
    COALESCE(status, 0) AS status,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `wms_item_category`
WHERE deleted = 0
ON DUPLICATE KEY UPDATE name = VALUES(name);
-- 6. è¿ç§» WMS å“ç‰Œåˆ° md_brand
INSERT INTO `md_brand` (`id`, `code`, `name`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id,
    code,
    name,
    0 AS status,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `wms_item_brand`
WHERE deleted = 0
ON DUPLICATE KEY UPDATE name = VALUES(name);
-- ---------------------------------------------
-- ç¬¬äºŒé˜¶æ®µï¼šè¿ç§»ç‰©æ–™ä¸»æ•°æ®
-- ---------------------------------------------
-- 7. è¿ç§» MES ç‰©æ–™åˆ° md_item
INSERT INTO `md_item` (
    `id`, `code`, `name`, `bar_code`, `specification`,
    `category_id`, `unit_measure_id`, `brand_id`, `item_type`, `status`,
    `purchase_price`, `sales_price`, `cost_price`, `min_price`,
    `safety_stock`, `min_stock`, `max_stock`, `min_order_qty`, `lead_time`,
    `is_batch_managed`, `is_serial_managed`, `barcode_rule_id`,
    `expiry_day`, `high_value`, `weight`, `remark`,
    `creator`, `create_time`, `updater`, `update_time`, `deleted`
)
SELECT
    id,
    code,
    name,
    NULL AS bar_code,  -- MES æ²¡æœ‰æ¡ç 
    specification,
    itemTypeId,
    unitMeasureId,
    NULL AS brand_id,
    1 AS item_type,  -- MES ç‰©æ–™é»˜è®¤ä¸ºåŽŸæ–™
    COALESCE(status, 0),
    NULL, NULL, NULL, NULL,  -- ä»·æ ¼ä¿¡æ¯ MES æ²¡æœ‰
    NULL AS safety_stock,
    min_stock,
    max_stock,
    NULL, NULL,  -- æœ€å°è®¢è´§é‡ã€æå‰æœŸ MES æ²¡æœ‰
    COALESCE(batchFlag, 0),
    0 AS is_serial_managed,
    NULL AS barcode_rule_id,
    NULL AS expiry_day,
    COALESCE(highValue, 0),
    NULL AS weight,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `mes_md_item`
WHERE deleted = 0;
-- 8. è®°å½• MES ç‰©æ–™è¿ç§»æ˜ å°„
INSERT INTO `md_item_migration_map` (`source_module`, `source_table`, `source_id`, `target_item_id`, `create_time`)
SELECT 'MES', 'mes_md_item', id, id, NOW()
FROM `mes_md_item`
WHERE deleted = 0;
-- 9. è¿ç§» ERP äº§å“åˆ° md_item(使用新ID范围 100000-199999)
INSERT INTO `md_item` (
    `id`, `code`, `name`, `bar_code`, `specification`,
    `category_id`, `unit_measure_id`, `brand_id`, `item_type`, `status`,
    `purchase_price`, `sales_price`, `cost_price`, `min_price`,
    `safety_stock`, `min_stock`, `max_stock`, `min_order_qty`, `lead_time`,
    `is_batch_managed`, `is_serial_managed`, `barcode_rule_id`,
    `expiry_day`, `high_value`, `weight`, `remark`,
    `creator`, `create_time`, `updater`, `update_time`, `deleted`
)
SELECT
    id + 100000,  -- ERP äº§å“ ID åç§» 100000
    COALESCE(bar_code, CONCAT('ERP_', id)) AS code,  -- ä½¿ç”¨æ¡ç ä½œä¸ºç¼–码,没有则生成
    name,
    bar_code,
    standard AS specification,
    categoryId + 10000,  -- ä½¿ç”¨ ERP åˆ†ç±»æ˜ å°„后的ID
    unitId + 10000,  -- ä½¿ç”¨ ERP å•位映射后的ID
    NULL AS brand_id,
    3 AS item_type,  -- ERP äº§å“é»˜è®¤ä¸ºæˆå“
    COALESCE(status, 0),
    purchase_price,
    sale_price,
    NULL AS cost_price,
    min_price,
    NULL, NULL, NULL, NULL, NULL,  -- åº“存管理 ERP æ²¡æœ‰
    0 AS is_batch_managed,
    0 AS is_serial_managed,
    NULL AS barcode_rule_id,
    expiry_day,
    0 AS high_value,
    weight,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `erp_product`
WHERE deleted = 0;
-- 10. è®°å½• ERP äº§å“è¿ç§»æ˜ å°„
INSERT INTO `md_item_migration_map` (`source_module`, `source_table`, `source_id`, `target_item_id`, `create_time`)
SELECT 'ERP', 'erp_product', id, id + 100000, NOW()
FROM `erp_product`
WHERE deleted = 0;
-- 11. è¿ç§» WMS å•†å“åˆ° md_item(使用新ID范围 200000-299999)
INSERT INTO `md_item` (
    `id`, `code`, `name`, `bar_code`, `specification`,
    `category_id`, `unit_measure_id`, `brand_id`, `item_type`, `status`,
    `purchase_price`, `sales_price`, `cost_price`, `min_price`,
    `safety_stock`, `min_stock`, `max_stock`, `min_order_qty`, `lead_time`,
    `is_batch_managed`, `is_serial_managed`, `barcode_rule_id`,
    `expiry_day`, `high_value`, `weight`, `remark`,
    `creator`, `create_time`, `updater`, `update_time`, `deleted`
)
SELECT
    id + 200000,  -- WMS å•†å“ ID åç§» 200000
    code,
    name,
    NULL AS bar_code,  -- WMS æ¡ç åœ¨ SKU å±‚
    NULL AS specification,
    categoryId + 20000,  -- ä½¿ç”¨ WMS åˆ†ç±»æ˜ å°„后的ID
    NULL AS unit_measure_id,  -- WMS å•位是字符串,需要后续处理
    brandId,
    1 AS item_type,  -- WMS å•†å“é»˜è®¤ä¸ºåŽŸæ–™
    0 AS status,
    NULL, NULL, NULL, NULL,  -- ä»·æ ¼åœ¨ SKU å±‚
    NULL, NULL, NULL, NULL, NULL,
    0, 0, NULL,
    NULL, 0, NULL,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `wms_item`
WHERE deleted = 0;
-- 12. è®°å½• WMS å•†å“è¿ç§»æ˜ å°„
INSERT INTO `md_item_migration_map` (`source_module`, `source_table`, `source_id`, `target_item_id`, `create_time`)
SELECT 'WMS', 'wms_item', id, id + 200000, NOW()
FROM `wms_item`
WHERE deleted = 0;
-- ---------------------------------------------
-- ç¬¬ä¸‰é˜¶æ®µï¼šè¿ç§»æ‰¹æ¬¡å±žæ€§é…ç½®
-- ---------------------------------------------
-- 13. è¿ç§» MES æ‰¹æ¬¡å±žæ€§é…ç½®åˆ° md_item_batch_config
INSERT INTO `md_item_batch_config` (
    `id`, `item_id`,
    `produce_date_flag`, `expire_date_flag`, `receipt_date_flag`,
    `vendor_flag`, `client_flag`, `sales_order_code_flag`, `purchase_order_code_flag`,
    `work_order_flag`, `task_flag`, `workstation_flag`, `tool_flag`, `mold_flag`,
    `lot_number_flag`, `quality_status_flag`,
    `creator`, `create_time`, `updater`, `update_time`, `deleted`
)
SELECT
    id,
    itemId,
    COALESCE(produce_date_flag, 0),
    COALESCE(expire_date_flag, 0),
    COALESCE(receipt_date_flag, 0),
    COALESCE(vendor_flag, 0),
    COALESCE(client_flag, 0),
    COALESCE(sales_order_code_flag, 0),
    COALESCE(purchase_order_code_flag, 0),
    COALESCE(work_order_flag, 0),
    COALESCE(task_flag, 0),
    COALESCE(workstation_flag, 0),
    COALESCE(tool_flag, 0),
    COALESCE(mold_flag, 0),
    COALESCE(lot_number_flag, 0),
    COALESCE(quality_status_flag, 0),
    creator, create_time, updater, update_time, deleted
FROM `mes_md_item_batch_config`
WHERE deleted = 0;
-- ---------------------------------------------
-- ç¬¬å››é˜¶æ®µï¼šè¿ç§»ä»“库数据
-- ---------------------------------------------
-- 14. è¿ç§» MES ä»“库到 md_warehouse
INSERT INTO `md_warehouse` (`id`, `code`, `name`, `address`, `area`, `type`, `charge_user_id`, `status`, `frozen`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id,
    code,
    name,
    address,
    area,
    NULL AS type,  -- MES ä»“库没有类型
    chargeUserId,
    0 AS status,
    COALESCE(frozen, 0),
    remark,
    creator, create_time, updater, update_time, deleted
FROM `mes_wm_warehouse`
WHERE deleted = 0;
-- 15. è¿ç§» ERP ä»“库到 md_warehouse(使用新ID范围 10000-19999)
INSERT INTO `md_warehouse` (`id`, `code`, `name`, `address`, `area`, `type`, `charge_user_id`, `status`, `frozen`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id + 10000,
    CONCAT('ERP_', id) AS code,  -- ERP ä»“库没有编码,生成一个
    name,
    address,
    NULL AS area,
    NULL AS type,
    NULL AS charge_user_id,  -- ERP ä½¿ç”¨ principal å­—符串,无法映射
    COALESCE(status, 0),
    0 AS frozen,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `erp_warehouse`
WHERE deleted = 0;
-- 16. è¿ç§» WMS ä»“库到 md_warehouse(使用新ID范围 20000-29999)
INSERT INTO `md_warehouse` (`id`, `code`, `name`, `address`, `area`, `type`, `charge_user_id`, `status`, `frozen`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    id + 20000,
    code,
    name,
    address,
    area,
    NULL AS type,
    chargeUserId,
    COALESCE(status, 0),
    0 AS frozen,
    remark,
    creator, create_time, updater, update_time, deleted
FROM `wms_warehouse`
WHERE deleted = 0;
-- ---------------------------------------------
-- ç¬¬äº”阶段:迁移库存数据
-- ---------------------------------------------
-- 17. è¿ç§» MES åº“存到 wms_stock
INSERT INTO `wms_stock` (`item_id`, `warehouse_id`, `location_id`, `batch_code`, `quantity`, `quantity_locked`, `quantity_available`, `unit_measure_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    m.target_item_id AS item_id,  -- ä½¿ç”¨æ˜ å°„后的物料ID
    s.warehouseId AS warehouse_id,
    s.locationId AS location_id,
    s.batchCode AS batch_code,
    s.quantity,
    0 AS quantity_locked,
    s.quantity AS quantity_available,
    i.unitMeasureId AS unit_measure_id,
    s.creator, s.create_time, s.updater, s.update_time, s.deleted
FROM `mes_wm_material_stock` s
LEFT JOIN `mes_md_item` i ON i.id = s.itemId
LEFT JOIN `md_item_migration_map` m ON m.source_module = 'MES' AND m.source_table = 'mes_md_item' AND m.source_id = s.itemId
WHERE s.deleted = 0 AND m.target_item_id IS NOT NULL;
-- 18. è¿ç§» ERP åº“存到 wms_stock
INSERT INTO `wms_stock` (`item_id`, `warehouse_id`, `batch_code`, `quantity`, `quantity_locked`, `quantity_available`, `unit_measure_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    m.target_item_id AS item_id,
    s.warehouseId + 10000 AS warehouse_id,  -- ä½¿ç”¨ ERP ä»“库映射后的ID
    NULL AS batch_code,
    s.count AS quantity,
    0 AS quantity_locked,
    s.count AS quantity_available,
    p.unitId + 10000 AS unit_measure_id,  -- ä½¿ç”¨ ERP å•位映射后的ID
    s.creator, s.create_time, s.updater, s.update_time, s.deleted
FROM `erp_stock` s
LEFT JOIN `erp_product` p ON p.id = s.productId
LEFT JOIN `md_item_migration_map` m ON m.source_module = 'ERP' AND m.source_table = 'erp_product' AND m.source_id = s.productId
WHERE s.deleted = 0 AND m.target_item_id IS NOT NULL;
-- 19. è¿ç§» WMS åº“存到 wms_stock
INSERT INTO `wms_stock` (`item_id`, `warehouse_id`, `batch_code`, `quantity`, `quantity_locked`, `quantity_available`, `unit_measure_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT
    m.target_item_id AS item_id,
    s.warehouseId + 20000 AS warehouse_id,  -- ä½¿ç”¨ WMS ä»“库映射后的ID
    NULL AS batch_code,
    s.quantity,
    0 AS quantity_locked,
    s.quantity AS quantity_available,
    NULL AS unit_measure_id,  -- WMS åº“存没有单位
    s.creator, s.create_time, s.updater, s.update_time, s.deleted
FROM `wms_inventory` s
LEFT JOIN `md_item_migration_map` m ON m.source_module = 'WMS' AND m.source_table = 'wms_item' AND m.source_id = s.skuId  -- æ³¨æ„ï¼šWMS åº“存关联的是 SKU
WHERE s.deleted = 0 AND m.target_item_id IS NOT NULL;
-- =============================================
-- è¿ç§»å®Œæˆ
-- =============================================
-- æŸ¥è¯¢è¿ç§»ç»“果统计
SELECT
    'md_item' AS table_name,
    (SELECT COUNT(*) FROM md_item WHERE deleted = 0) AS count
UNION ALL
SELECT
    'md_item_category',
    (SELECT COUNT(*) FROM md_item_category WHERE deleted = 0)
UNION ALL
SELECT
    'md_unit_measure',
    (SELECT COUNT(*) FROM md_unit_measure WHERE deleted = 0)
UNION ALL
SELECT
    'md_brand',
    (SELECT COUNT(*) FROM md_brand WHERE deleted = 0)
UNION ALL
SELECT
    'md_warehouse',
    (SELECT COUNT(*) FROM md_warehouse WHERE deleted = 0)
UNION ALL
SELECT
    'wms_stock',
    (SELECT COUNT(*) FROM wms_stock WHERE deleted = 0)
UNION ALL
SELECT
    'md_item_migration_map',
    (SELECT COUNT(*) FROM md_item_migration_map);
sql/mdm_tables.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,334 @@
-- =============================================
-- ç»Ÿä¸€ç‰©æ–™ä¸»æ•°æ®æ¨¡å— (MDM) å»ºè¡¨ SQL
-- åˆ›å»ºæ—¥æœŸ: 2026-06-29
-- è¯´æ˜Ž: æ•´åˆ MES/ERP/WMS çš„物料数据,实现跨模块数据打通
-- æ³¨æ„: ä¸å¼€å¯å¤šç§Ÿæˆ·æ¨¡å¼
-- =============================================
-- ---------------------------------------------
-- 1. ç‰©æ–™ä¸»æ•°æ®è¡¨ (md_item)
-- æ ¸å¿ƒè¡¨ï¼Œæ‰€æœ‰æ¨¡å—共用唯一产品编码
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_item`;
CREATE TABLE `md_item` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '物料ID(全局唯一)',
  `code` varchar(64) NOT NULL COMMENT '物料编码(全局唯一)',
  `name` varchar(255) NOT NULL COMMENT '物料名称',
  `bar_code` varchar(64) DEFAULT NULL COMMENT '物料条码',
  `specification` varchar(500) DEFAULT NULL COMMENT '规格型号',
  -- åˆ†ç±»ä¸Žå•位
  `category_id` bigint NOT NULL COMMENT '物料分类编号',
  `unit_measure_id` bigint NOT NULL COMMENT '主计量单位编号',
  `brand_id` bigint DEFAULT NULL COMMENT '品牌编号',
  -- ç‰©æ–™ç±»åž‹
  `item_type` tinyint NOT NULL DEFAULT 1 COMMENT '物料类型(1:原料, 2:半成品, 3:成品, 4:辅料)',
  -- çŠ¶æ€
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0:启用, 1:禁用)',
  -- ä»·æ ¼ä¿¡æ¯
  `purchase_price` decimal(24,6) DEFAULT NULL COMMENT '采购价',
  `sales_price` decimal(24,6) DEFAULT NULL COMMENT '销售价',
  `cost_price` decimal(24,6) DEFAULT NULL COMMENT '成本价',
  `min_price` decimal(24,6) DEFAULT NULL COMMENT '最低价',
  -- åº“存管理
  `safety_stock` decimal(24,6) DEFAULT NULL COMMENT '安全库存',
  `min_stock` decimal(24,6) DEFAULT NULL COMMENT '最低库存量',
  `max_stock` decimal(24,6) DEFAULT NULL COMMENT '最高库存量',
  `min_order_qty` decimal(24,6) DEFAULT NULL COMMENT '最小订货量',
  `lead_time` int DEFAULT NULL COMMENT '采购提前期(天)',
  -- æ‰¹æ¬¡ä¸Žåºåˆ—号管理
  `is_batch_managed` bit(1) DEFAULT b'0' COMMENT '是否批次管理',
  `is_serial_managed` bit(1) DEFAULT b'0' COMMENT '是否序列号管理',
  `barcode_rule_id` bigint DEFAULT NULL COMMENT '条码规则ID',
  -- è´¨é‡ç®¡ç†
  `expiry_day` int DEFAULT NULL COMMENT '保质期天数',
  `high_value` bit(1) DEFAULT b'0' COMMENT '是否高值物料',
  -- ç‰©ç†å±žæ€§
  `weight` decimal(24,6) DEFAULT NULL COMMENT '重量(kg)',
  -- å¤‡æ³¨
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`),
  KEY `idx_category_id` (`category_id`),
  KEY `idx_unit_measure_id` (`unit_measure_id`),
  KEY `idx_brand_id` (`brand_id`),
  KEY `idx_item_type` (`item_type`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料主数据表';
-- ---------------------------------------------
-- 2. ç‰©æ–™åˆ†ç±»è¡¨ (md_item_category)
-- ç»Ÿä¸€åˆ†ç±»ï¼Œæ”¯æŒæ ‘形结构
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_item_category`;
CREATE TABLE `md_item_category` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
  `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父分类编号(根节点=0)',
  `code` varchar(64) NOT NULL COMMENT '分类编码',
  `name` varchar(255) NOT NULL COMMENT '分类名称',
  `item_type` tinyint DEFAULT NULL COMMENT '适用物料类型(1:原料, 2:半成品, 3:成品, 4:辅料, NULL:全部)',
  `sort` int NOT NULL DEFAULT 0 COMMENT '排序',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0:启用, 1:禁用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料分类表';
-- ---------------------------------------------
-- 3. è®¡é‡å•位表 (md_unit_measure)
-- ç»Ÿä¸€è®¡é‡å•位
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_unit_measure`;
CREATE TABLE `md_unit_measure` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '单位编号',
  `code` varchar(64) NOT NULL COMMENT '单位编码',
  `name` varchar(64) NOT NULL COMMENT '单位名称',
  `primary_flag` bit(1) DEFAULT b'0' COMMENT '是否主单位',
  `primary_id` bigint DEFAULT NULL COMMENT '主单位编号',
  `change_rate` decimal(24,6) DEFAULT NULL COMMENT '与主单位换算比例',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0:启用, 1:禁用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='计量单位表';
-- ---------------------------------------------
-- 4. å“ç‰Œè¡¨ (md_brand)
-- ç»Ÿä¸€å“ç‰Œæ•°æ®
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_brand`;
CREATE TABLE `md_brand` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
  `code` varchar(64) NOT NULL COMMENT '品牌编码',
  `name` varchar(255) NOT NULL COMMENT '品牌名称',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0:启用, 1:禁用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='品牌表';
-- ---------------------------------------------
-- 5. ç‰©æ–™æ‰¹æ¬¡å±žæ€§é…ç½®è¡¨ (md_item_batch_config)
-- æ¥è‡ª MES çš„æ‰¹æ¬¡ç®¡ç†é…ç½®
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_item_batch_config`;
CREATE TABLE `md_item_batch_config` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `item_id` bigint NOT NULL COMMENT '物料编号',
  -- æ‰¹æ¬¡å±žæ€§å¼€å…³
  `produce_date_flag` bit(1) DEFAULT b'0' COMMENT '生产日期',
  `expire_date_flag` bit(1) DEFAULT b'0' COMMENT '有效期',
  `receipt_date_flag` bit(1) DEFAULT b'0' COMMENT '入库日期',
  `vendor_flag` bit(1) DEFAULT b'0' COMMENT '供应商',
  `client_flag` bit(1) DEFAULT b'0' COMMENT '客户',
  `sales_order_code_flag` bit(1) DEFAULT b'0' COMMENT '销售订单编号',
  `purchase_order_code_flag` bit(1) DEFAULT b'0' COMMENT '采购订单编号',
  `work_order_flag` bit(1) DEFAULT b'0' COMMENT '生产工单',
  `task_flag` bit(1) DEFAULT b'0' COMMENT '生产任务',
  `workstation_flag` bit(1) DEFAULT b'0' COMMENT '工作站',
  `tool_flag` bit(1) DEFAULT b'0' COMMENT '工具',
  `mold_flag` bit(1) DEFAULT b'0' COMMENT '模具',
  `lot_number_flag` bit(1) DEFAULT b'0' COMMENT '生产批号',
  `quality_status_flag` bit(1) DEFAULT b'0' COMMENT '质量状态',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_item_id` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料批次属性配置表';
-- ---------------------------------------------
-- 6. ä»“库主数据表 (md_warehouse)
-- ç»Ÿä¸€ä»“库数据,WMS ä¸ºä¸»æ•°æ®æº
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_warehouse`;
CREATE TABLE `md_warehouse` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '仓库ID',
  `code` varchar(64) NOT NULL COMMENT '仓库编码',
  `name` varchar(255) NOT NULL COMMENT '仓库名称',
  `address` varchar(500) DEFAULT NULL COMMENT '仓库地址',
  `area` decimal(24,6) DEFAULT NULL COMMENT '面积(平方米)',
  `type` tinyint DEFAULT NULL COMMENT '仓库类型(1:原料仓, 2:成品仓, 3:半成品仓, 4:线边仓)',
  `charge_user_id` bigint DEFAULT NULL COMMENT '负责人用户ID',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0:启用, 1:禁用)',
  `frozen` bit(1) DEFAULT b'0' COMMENT '是否冻结',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='仓库主数据表';
-- ---------------------------------------------
-- 7. åº“存表 (wms_stock)
-- WMS ä½œä¸ºå”¯ä¸€åº“存管理者
-- ---------------------------------------------
DROP TABLE IF EXISTS `wms_stock`;
CREATE TABLE `wms_stock` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '库存ID',
  `item_id` bigint NOT NULL COMMENT '物料ID(关联 md_item)',
  `warehouse_id` bigint NOT NULL COMMENT '仓库ID(关联 md_warehouse)',
  `location_id` bigint DEFAULT NULL COMMENT '库位ID',
  `batch_code` varchar(64) DEFAULT NULL COMMENT '批次号',
  -- åº“存数量
  `quantity` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '当前库存数量',
  `quantity_locked` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '锁定数量',
  `quantity_available` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '可用数量 = quantity - quantity_locked',
  `unit_measure_id` bigint NOT NULL COMMENT '计量单位ID',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_item_warehouse_batch` (`item_id`, `warehouse_id`, `batch_code`),
  KEY `idx_item_id` (`item_id`),
  KEY `idx_warehouse_id` (`warehouse_id`),
  KEY `idx_batch_code` (`batch_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存表';
-- ---------------------------------------------
-- 8. åº“存事务流水表 (wms_stock_transaction)
-- è®°å½•所有库存变动
-- ---------------------------------------------
DROP TABLE IF EXISTS `wms_stock_transaction`;
CREATE TABLE `wms_stock_transaction` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '流水ID',
  `item_id` bigint NOT NULL COMMENT '物料ID',
  `warehouse_id` bigint NOT NULL COMMENT '仓库ID',
  `location_id` bigint DEFAULT NULL COMMENT '库位ID',
  `batch_code` varchar(64) DEFAULT NULL COMMENT '批次号',
  -- å˜åŠ¨ä¿¡æ¯
  `quantity` decimal(24,6) NOT NULL COMMENT '变动数量(正数入库,负数出库)',
  `biz_type` tinyint NOT NULL COMMENT '业务类型(见枚举)',
  `biz_id` bigint DEFAULT NULL COMMENT '业务单据ID',
  `biz_no` varchar(64) DEFAULT NULL COMMENT '业务单据号',
  -- å…³è”库存记录
  `stock_id` bigint DEFAULT NULL COMMENT '关联库存记录ID',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  -- ç³»ç»Ÿå­—段
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_item_id` (`item_id`),
  KEY `idx_warehouse_id` (`warehouse_id`),
  KEY `idx_biz_type` (`biz_type`),
  KEY `idx_biz_id` (`biz_id`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存事务流水表';
-- ---------------------------------------------
-- 9. ç‰©æ–™è¿ç§»æ˜ å°„表 (md_item_migration_map)
-- ç”¨äºŽè®°å½•旧表数据到新表的映射关系
-- ---------------------------------------------
DROP TABLE IF EXISTS `md_item_migration_map`;
CREATE TABLE `md_item_migration_map` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '映射ID',
  `source_module` varchar(20) NOT NULL COMMENT '来源模块(ERP/MES/WMS)',
  `source_table` varchar(64) NOT NULL COMMENT '来源表名',
  `source_id` bigint NOT NULL COMMENT '原ID',
  `target_item_id` bigint NOT NULL COMMENT 'MDM物料ID',
  -- ç³»ç»Ÿå­—段
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_source` (`source_module`, `source_table`, `source_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料迁移映射表';
-- ---------------------------------------------
-- 10. åº“存变动业务类型枚举值
-- ---------------------------------------------
-- ä¸šåŠ¡ç±»åž‹è¯´æ˜Žï¼š
-- 10: é‡‡è´­å…¥åº“ (ERP)
-- 11: é‡‡è´­å…¥åº“作废 (ERP)
-- 20: é”€å”®å‡ºåº“ (ERP)
-- 21: é”€å”®å‡ºåº“作废 (ERP)
-- 30: ç”Ÿäº§å…¥åº“ (MES)
-- 31: ç”Ÿäº§å…¥åº“作废 (MES)
-- 40: ç”Ÿäº§é¢†æ–™ (MES)
-- 41: ç”Ÿäº§é¢†æ–™ä½œåºŸ (MES)
-- 50: ç”Ÿäº§é€€æ–™ (MES)
-- 51: ç”Ÿäº§é€€æ–™ä½œåºŸ (MES)
-- 60: é‡‡è´­é€€è´§å‡ºåº“ (ERP)
-- 61: é‡‡è´­é€€è´§å‡ºåº“作废 (ERP)
-- 70: é”€å”®é€€è´§å…¥åº“ (ERP)
-- 71: é”€å”®é€€è´§å…¥åº“作废 (ERP)
-- 80: è°ƒæ‹¨å…¥åº“ (WMS)
-- 81: è°ƒæ‹¨å…¥åº“作废 (WMS)
-- 90: è°ƒæ‹¨å‡ºåº“ (WMS)
-- 91: è°ƒæ‹¨å‡ºåº“作废 (WMS)
-- 100: ç›˜ç›ˆå…¥åº“ (WMS)
-- 101: ç›˜ç›ˆå…¥åº“作废 (WMS)
-- 110: ç›˜äºå‡ºåº“ (WMS)
-- 111: ç›˜äºå‡ºåº“作废 (WMS)
-- =============================================
-- å®Œæˆå»ºè¡¨ SQL
-- =============================================
yudao-module-erp/pom.xml
@@ -24,6 +24,27 @@
            <version>${revision}</version>
        </dependency>
        <!-- MDM ä¸»æ•°æ®ç®¡ç†æ¨¡å— API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mdm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- WMS ä»“库管理模块 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-wms</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- BPM æµç¨‹æ¨¡å— -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-bpm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ä¸šåŠ¡ç»„ä»¶ -->
        <!-- Web ç›¸å…³ -->
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java
@@ -20,6 +20,9 @@
    @Schema(description = "产品分类编号", example = "11161")
    private Long categoryId;
    @Schema(description = "产品状态", example = "0")
    private Integer status;
    @Schema(description = "创建时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] createTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java
@@ -41,6 +41,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é‡‡è´­å…¥åº“")
@RestController
@@ -84,6 +85,15 @@
        return success(true);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交采购入库审批")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:update')")
    public CommonResult<Boolean> submitPurchaseIn(@RequestParam("id") Long id) {
        purchaseInService.submitPurchaseIn(id, getLoginUserId());
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除采购入库")
    @Parameter(name = "ids", description = "编号数组", required = true)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -40,6 +40,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é‡‡è´­è®¢å•")
@RestController
@@ -83,6 +84,15 @@
        return success(true);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交采购订单审批")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:purchase-order:update')")
    public CommonResult<Boolean> submitPurchaseOrder(@RequestParam("id") Long id) {
        purchaseOrderService.submitPurchaseOrder(id, getLoginUserId());
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除采购订单")
    @Parameter(name = "ids", description = "编号数组", required = true)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -40,6 +40,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é”€å”®è®¢å•")
@RestController
@@ -83,6 +84,15 @@
        return success(true);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交销售订单审批")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:sale-out:update')")
    public CommonResult<Boolean> submitSaleOrder(@RequestParam("id") Long id) {
        saleOrderService.submitSaleOrder(id, getLoginUserId());
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除销售订单")
    @Parameter(name = "ids", description = "编号数组", required = true)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java
@@ -41,6 +41,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é”€å”®å‡ºåº“")
@RestController
@@ -84,6 +85,15 @@
        return success(true);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交销售出库审批")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:sale-out:update')")
    public CommonResult<Boolean> submitSaleOut(@RequestParam("id") Long id) {
        saleOutService.submitSaleOut(id, getLoginUserId());
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除销售出库")
    @Parameter(name = "ids", description = "编号数组", required = true)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java
@@ -119,4 +119,10 @@
     */
    private String remark;
    // ========== BPM æµç¨‹å®¡æ‰¹ ==========
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
@@ -100,6 +100,12 @@
     */
    private String remark;
    // ========== BPM æµç¨‹å®¡æ‰¹ ==========
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
    // ========== é‡‡è´­å…¥åº“ ==========
    /**
     * é‡‡è´­å…¥åº“数量
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
@@ -106,6 +106,12 @@
     */
    private String remark;
    // ========== BPM æµç¨‹å®¡æ‰¹ ==========
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
    // ========== é”€å”®å‡ºåº“ ==========
    /**
     * é”€å”®å‡ºåº“数量
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java
@@ -125,4 +125,10 @@
     */
    private String remark;
    // ========== BPM æµç¨‹å®¡æ‰¹ ==========
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
@@ -17,8 +17,10 @@
@Getter
public enum ErpAuditStatus implements ArrayValuable<Integer> {
    PROCESS(10, "未审核"), // å®¡æ ¸ä¸­
    APPROVE(20, "已审核"); // å®¡æ ¸é€šè¿‡
    DRAFT(0, "草稿"), // è‰ç¨¿çŠ¶æ€ï¼Œæœªæäº¤å®¡æ‰¹
    PROCESS(10, "审批中"), // å®¡æ‰¹ä¸­
    APPROVE(20, "审批通过"), // å®¡æ‰¹é€šè¿‡
    REJECT(30, "审批不通过"); // å®¡æ‰¹ä¸é€šè¿‡
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpAuditStatus::getStatus).toArray(Integer[]::new);
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -25,6 +25,8 @@
    ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_IN = new ErrorCode(1_030_101_008, "反审核失败,已存在对应的采购入库单");
ErrorCode PURCHASE_ORDER_ITEM_RETURN_FAIL_IN_EXCEED = new ErrorCode(1_030_101_009, "采购订单项({})超过最大允许退货数量({})");
    ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_030_101_010, "反审核失败,已存在对应的采购退货单");
    ErrorCode PURCHASE_ORDER_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_101_011, "提交审批失败,只有草稿状态的采购订单才能提交审批");
    ErrorCode PURCHASE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_101_012, "更新审批状态失败,只有审批中的采购订单才能更新审批状态");
    // ========== ERP é‡‡è´­å…¥åº“(1-030-102-000) ==========
    ErrorCode PURCHASE_IN_NOT_EXISTS = new ErrorCode(1_030_102_000, "采购入库单不存在");
@@ -36,6 +38,8 @@
    ErrorCode PURCHASE_IN_NOT_APPROVE = new ErrorCode(1_030_102_006, "采购入库单未审核,无法操作");
    ErrorCode PURCHASE_IN_FAIL_PAYMENT_PRICE_EXCEED = new ErrorCode(1_030_102_007, "付款金额({})超过采购入库单总金额({})");
    ErrorCode PURCHASE_IN_PROCESS_FAIL_EXISTS_PAYMENT = new ErrorCode(1_030_102_008, "反审核失败,已存在对应的付款单");
    ErrorCode PURCHASE_IN_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_102_009, "提交审批失败,只有草稿状态的采购入库单才能提交审批");
    ErrorCode PURCHASE_IN_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_102_010, "更新审批状态失败,只有审批中的采购入库单才能更新审批状态");
    // ========== ERP é‡‡è´­é€€è´§ï¼ˆ1-030-103-000) ==========
    ErrorCode PURCHASE_RETURN_NOT_EXISTS = new ErrorCode(1_030_103_000, "采购退货单不存在");
@@ -64,6 +68,8 @@
    ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_OUT = new ErrorCode(1_020_201_008, "反审核失败,已存在对应的销售出库单");
    ErrorCode SALE_ORDER_ITEM_RETURN_FAIL_OUT_EXCEED = new ErrorCode(1_020_201_009, "销售订单项({})超过最大允许退货数量({})");
    ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_020_201_010, "反审核失败,已存在对应的销售退货单");
    ErrorCode SALE_ORDER_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_201_011, "提交审批失败,只有草稿状态的销售订单才能提交审批");
    ErrorCode SALE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_201_012, "更新审批状态失败,只有审批中的销售订单才能更新审批状态");
    // ========== ERP é”€å”®å‡ºåº“(1-030-202-000) ==========
    ErrorCode SALE_OUT_NOT_EXISTS = new ErrorCode(1_020_202_000, "销售出库单不存在");
@@ -75,6 +81,8 @@
    ErrorCode SALE_OUT_NOT_APPROVE = new ErrorCode(1_020_202_006, "销售出库单未审核,无法操作");
    ErrorCode SALE_OUT_FAIL_RECEIPT_PRICE_EXCEED = new ErrorCode(1_020_202_007, "收款金额({})超过销售出库单总金额({})");
    ErrorCode SALE_OUT_PROCESS_FAIL_EXISTS_RECEIPT = new ErrorCode(1_020_202_008, "反审核失败,已存在对应的收款单");
    ErrorCode SALE_OUT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_202_009, "提交审批失败,只有草稿状态的销售出库单才能提交审批");
    ErrorCode SALE_OUT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_202_010, "更新审批状态失败,只有审批中的销售出库单才能更新审批状态");
    // ========== ERP é”€å”®é€€è´§ï¼ˆ1-030-203-000) ==========
    ErrorCode SALE_RETURN_NOT_EXISTS = new ErrorCode(1_020_203_000, "销售退货单不存在");
@@ -130,6 +138,7 @@
    // ========== ERP äº§å“ 1-030-500-000 ==========
    ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在");
    ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_030_500_001, "产品({})未启用");
    ErrorCode PRODUCT_BAR_CODE_EXISTS = new ErrorCode(1_030_500_002, "产品条码已存在");
    // ========== ERP äº§å“åˆ†ç±» 1-030-501-000 ==========
    ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_501_000, "产品分类不存在");
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java
@@ -8,10 +8,15 @@
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemUpdateReqDTO;
import cn.iocoder.yudao.module.mdm.api.category.MdmItemCategoryApi;
import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategoryRespDTO;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -25,46 +30,57 @@
/**
 * ERP äº§å“ Service å®žçŽ°ç±»
 *
 * æ”¹é€ è¯´æ˜Žï¼šERP äº§å“æ•°æ®æ¥æºäºŽ MDM ç‰©æ–™ä¸»æ•°æ®
 * - æ‰€æœ‰äº§å“æ“ä½œå‡è°ƒç”¨ MDM API
 * - ERP äº§å“å¯¹åº” MDM ç‰©æ–™ç±»åž‹ = æˆå“ï¼ˆitemType = 2)
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
public class ErpProductServiceImpl implements ErpProductService {
    @Resource
    private ErpProductMapper productMapper;
    /** ERP äº§å“å¯¹åº”çš„ MDM ç‰©æ–™ç±»åž‹ï¼šæˆå“ */
    private static final int MDM_ITEM_TYPE_PRODUCT = 2;
    @Resource
    private ErpProductCategoryService productCategoryService;
    private MdmItemApi mdmItemApi;
    @Resource
    private ErpProductUnitService productUnitService;
    private MdmItemCategoryApi mdmItemCategoryApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Override
    public Long createProduct(ProductSaveReqVO createReqVO) {
        // TODO èŠ‹è‰¿ï¼šæ ¡éªŒåˆ†ç±»
        // æ’å…¥
        ErpProductDO product = BeanUtils.toBean(createReqVO, ErpProductDO.class);
        productMapper.insert(product);
        // è¿”回
        return product.getId();
        // 1. æ ¡éªŒæ¡ç å”¯ä¸€
        validateProductBarCodeUnique(null, createReqVO.getBarCode());
        // 2. æž„建创建请求并调用 MDM API
        MdmItemCreateReqDTO createReqDTO = buildCreateReqDTO(createReqVO);
        return mdmItemApi.createItem(createReqDTO).getCheckedData();
    }
    @Override
    public void updateProduct(ProductSaveReqVO updateReqVO) {
        // TODO èŠ‹è‰¿ï¼šæ ¡éªŒåˆ†ç±»
        // æ ¡éªŒå­˜åœ¨
        // 1. æ ¡éªŒå­˜åœ¨
        validateProductExists(updateReqVO.getId());
        // æ›´æ–°
        ErpProductDO updateObj = BeanUtils.toBean(updateReqVO, ErpProductDO.class);
        productMapper.updateById(updateObj);
        // 2. æ ¡éªŒæ¡ç å”¯ä¸€
        validateProductBarCodeUnique(updateReqVO.getId(), updateReqVO.getBarCode());
        // 3. æž„建更新请求并调用 MDM API
        MdmItemUpdateReqDTO updateReqDTO = buildUpdateReqDTO(updateReqVO);
        mdmItemApi.updateItem(updateReqDTO);
    }
    @Override
    public void deleteProduct(Long id) {
        // æ ¡éªŒå­˜åœ¨
        validateProductExists(id);
        // åˆ é™¤
        productMapper.deleteById(id);
        // ERP äº§å“åˆ é™¤å®žé™…为禁用(MDM ç‰©æ–™ä¸èƒ½ç‰©ç†åˆ é™¤ï¼‰
        mdmItemApi.updateItemStatus(id, CommonStatusEnum.DISABLE.getStatus());
    }
    @Override
@@ -72,35 +88,36 @@
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<ErpProductDO> list = productMapper.selectByIds(ids);
        Map<Long, ErpProductDO> productMap = convertMap(list, ErpProductDO::getId);
        // 1. è°ƒç”¨ MDM API èŽ·å–ç‰©æ–™åˆ—è¡¨
        List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
        Map<Long, MdmItemRespDTO> itemMap = convertMap(itemList, MdmItemRespDTO::getId);
        // 2. æ ¡éªŒæ¯ä¸ªäº§å“
        for (Long id : ids) {
            ErpProductDO product = productMap.get(id);
            if (productMap.get(id) == null) {
            MdmItemRespDTO item = itemMap.get(id);
            if (item == null) {
                throw exception(PRODUCT_NOT_EXISTS);
            }
            if (CommonStatusEnum.isDisable(product.getStatus())) {
                throw exception(PRODUCT_NOT_ENABLE, product.getName());
            if (CommonStatusEnum.isDisable(item.getStatus())) {
                throw exception(PRODUCT_NOT_ENABLE, item.getName());
            }
        }
        return list;
    }
    private void validateProductExists(Long id) {
        if (productMapper.selectById(id) == null) {
            throw exception(PRODUCT_NOT_EXISTS);
        }
        // 3. è½¬æ¢ä¸º ERP Product DO(手动映射字段)
        return convertToErpProductDOList(itemList);
    }
    @Override
    public ErpProductDO getProduct(Long id) {
        return productMapper.selectById(id);
        MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
        return convertToErpProductDO(item);
    }
    @Override
    public List<ErpProductRespVO> getProductVOListByStatus(Integer status) {
        List<ErpProductDO> list = productMapper.selectListByStatus(status);
        return buildProductVOList(list);
        List<MdmItemRespDTO> itemList = mdmItemApi.getItemListByStatus(status).getCheckedData();
        return buildProductVOList(itemList);
    }
    @Override
@@ -108,40 +125,188 @@
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<ErpProductDO> list = productMapper.selectByIds(ids);
        return buildProductVOList(list);
        List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
        return buildProductVOList(itemList);
    }
    @Override
    public PageResult<ErpProductRespVO> getProductVOPage(ErpProductPageReqVO pageReqVO) {
        PageResult<ErpProductDO> pageResult = productMapper.selectPage(pageReqVO);
        return new PageResult<>(buildProductVOList(pageResult.getList()), pageResult.getTotal());
    }
        // è°ƒç”¨ MDM API åˆ†é¡µæŸ¥è¯¢
        PageResult<MdmItemRespDTO> mdmPageResult = mdmItemApi.getItemPage(
                MDM_ITEM_TYPE_PRODUCT,
                pageReqVO.getStatus(),
                pageReqVO.getPageNo(),
                pageReqVO.getPageSize()
        ).getCheckedData();
    private List<ErpProductRespVO> buildProductVOList(List<ErpProductDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, ErpProductCategoryDO> categoryMap = productCategoryService.getProductCategoryMap(
                convertSet(list, ErpProductDO::getCategoryId));
        Map<Long, ErpProductUnitDO> unitMap = productUnitService.getProductUnitMap(
                convertSet(list, ErpProductDO::getUnitId));
        return BeanUtils.toBean(list, ErpProductRespVO.class, product -> {
            MapUtils.findAndThen(categoryMap, product.getCategoryId(),
                    category -> product.setCategoryName(category.getName()));
            MapUtils.findAndThen(unitMap, product.getUnitId(),
                    unit -> product.setUnitName(unit.getName()));
        });
        List<ErpProductRespVO> productVOList = buildProductVOList(mdmPageResult.getList());
        return new PageResult<>(productVOList, mdmPageResult.getTotal());
    }
    @Override
    public Long getProductCountByCategoryId(Long categoryId) {
        return productMapper.selectCountByCategoryId(categoryId);
        return mdmItemApi.getItemCountByCategoryId(categoryId).getCheckedData();
    }
    @Override
    public Long getProductCountByUnitId(Long unitId) {
        return productMapper.selectCountByUnitId(unitId);
        return mdmItemApi.getItemCountByUnitMeasureId(unitId).getCheckedData();
    }
}
    // ==================== ç§æœ‰æ–¹æ³• ====================
    /**
     * æ ¡éªŒäº§å“å­˜åœ¨
     */
    private void validateProductExists(Long id) {
        mdmItemApi.validateItemExists(id).getCheckedData();
    }
    /**
     * æ ¡éªŒäº§å“æ¡ç å”¯ä¸€
     */
    private void validateProductBarCodeUnique(Long id, String barCode) {
        if (barCode == null) {
            return;
        }
        MdmItemRespDTO existItem = mdmItemApi.getItemByBarCode(barCode).getCheckedData();
        if (existItem != null && !existItem.getId().equals(id)) {
            throw exception(PRODUCT_BAR_CODE_EXISTS);
        }
    }
    /**
     * æž„建创建请求 DTO
     * æ³¨æ„ï¼šProductSaveReqVO ä½¿ç”¨ standard å­—段作为规格,MDM ä½¿ç”¨ specification
     */
    private MdmItemCreateReqDTO buildCreateReqDTO(ProductSaveReqVO saveReqVO) {
        MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
        createReqDTO.setCode(saveReqVO.getBarCode()); // ä½¿ç”¨æ¡ç ä½œä¸ºç¼–码
        createReqDTO.setName(saveReqVO.getName());
        createReqDTO.setBarCode(saveReqVO.getBarCode());
        createReqDTO.setSpecification(saveReqVO.getStandard()); // standard -> specification
        createReqDTO.setCategoryId(saveReqVO.getCategoryId());
        createReqDTO.setUnitMeasureId(saveReqVO.getUnitId()); // unitId -> unitMeasureId
        createReqDTO.setItemType(MDM_ITEM_TYPE_PRODUCT);
        createReqDTO.setStatus(saveReqVO.getStatus() != null ? saveReqVO.getStatus() : CommonStatusEnum.ENABLE.getStatus());
        createReqDTO.setPurchasePrice(saveReqVO.getPurchasePrice());
        createReqDTO.setSalesPrice(saveReqVO.getSalePrice());
        createReqDTO.setCostPrice(saveReqVO.getMinPrice()); // minPrice -> costPrice
        createReqDTO.setExpiryDay(saveReqVO.getExpiryDay());
        createReqDTO.setWeight(saveReqVO.getWeight());
        createReqDTO.setRemark(saveReqVO.getRemark());
        return createReqDTO;
    }
    /**
     * æž„建更新请求 DTO
     */
    private MdmItemUpdateReqDTO buildUpdateReqDTO(ProductSaveReqVO saveReqVO) {
        MdmItemUpdateReqDTO updateReqDTO = new MdmItemUpdateReqDTO();
        updateReqDTO.setId(saveReqVO.getId());
        updateReqDTO.setCode(saveReqVO.getBarCode());
        updateReqDTO.setName(saveReqVO.getName());
        updateReqDTO.setBarCode(saveReqVO.getBarCode());
        updateReqDTO.setSpecification(saveReqVO.getStandard()); // standard -> specification
        updateReqDTO.setCategoryId(saveReqVO.getCategoryId());
        updateReqDTO.setUnitMeasureId(saveReqVO.getUnitId()); // unitId -> unitMeasureId
        updateReqDTO.setStatus(saveReqVO.getStatus());
        updateReqDTO.setPurchasePrice(saveReqVO.getPurchasePrice());
        updateReqDTO.setSalesPrice(saveReqVO.getSalePrice());
        updateReqDTO.setCostPrice(saveReqVO.getMinPrice());
        updateReqDTO.setExpiryDay(saveReqVO.getExpiryDay());
        updateReqDTO.setWeight(saveReqVO.getWeight());
        updateReqDTO.setRemark(saveReqVO.getRemark());
        return updateReqDTO;
    }
    /**
     * æž„建产品 VO åˆ—表(包含分类和单位名称)
     * æ³¨æ„ï¼šMDM å’Œ ERP å­—段名有差异,需要手动映射
     */
    private List<ErpProductRespVO> buildProductVOList(List<MdmItemRespDTO> itemList) {
        if (CollUtil.isEmpty(itemList)) {
            return Collections.emptyList();
        }
        // 1. èŽ·å–åˆ†ç±»å’Œå•ä½ä¿¡æ¯
        Set<Long> categoryIds = convertSet(itemList, MdmItemRespDTO::getCategoryId);
        Set<Long> unitIds = convertSet(itemList, MdmItemRespDTO::getUnitMeasureId);
        Map<Long, MdmItemCategoryRespDTO> categoryMap = CollUtil.isEmpty(categoryIds)
                ? Collections.emptyMap()
                : convertMap(mdmItemCategoryApi.getItemCategoryList(categoryIds).getCheckedData(), MdmItemCategoryRespDTO::getId);
        Map<Long, MdmUnitMeasureRespDTO> unitMap = CollUtil.isEmpty(unitIds)
                ? Collections.emptyMap()
                : convertMap(mdmUnitMeasureApi.getUnitMeasureList(unitIds).getCheckedData(), MdmUnitMeasureRespDTO::getId);
        // 2. æ‰‹åŠ¨è½¬æ¢å¹¶å¡«å……å…³è”ä¿¡æ¯ï¼ˆå¤„ç†å­—æ®µåå·®å¼‚ï¼‰
        List<ErpProductRespVO> result = new ArrayList<>();
        for (MdmItemRespDTO mdmItem : itemList) {
            ErpProductRespVO product = new ErpProductRespVO();
            product.setId(mdmItem.getId());
            product.setName(mdmItem.getName());
            product.setBarCode(mdmItem.getBarCode());
            product.setStandard(mdmItem.getSpecification()); // specification -> standard
            product.setCategoryId(mdmItem.getCategoryId());
            product.setUnitId(mdmItem.getUnitMeasureId()); // unitMeasureId -> unitId
            product.setStatus(mdmItem.getStatus());
            product.setPurchasePrice(mdmItem.getPurchasePrice());
            product.setSalePrice(mdmItem.getSalesPrice()); // salesPrice -> salePrice
            product.setMinPrice(mdmItem.getCostPrice()); // costPrice -> minPrice
            product.setExpiryDay(mdmItem.getExpiryDay());
            product.setWeight(mdmItem.getWeight());
            product.setRemark(mdmItem.getRemark());
            // è®¾ç½®åˆ†ç±»å’Œå•位名称
            MapUtils.findAndThen(categoryMap, mdmItem.getCategoryId(),
                    category -> product.setCategoryName(category.getName()));
            MapUtils.findAndThen(unitMap, mdmItem.getUnitMeasureId(),
                    unit -> product.setUnitName(unit.getName()));
            result.add(product);
        }
        return result;
    }
    /**
     * è½¬æ¢ MDM ç‰©æ–™ä¸º ERP äº§å“ DO
     * æ³¨æ„ï¼šå­—段名差异需要手动映射
     */
    private ErpProductDO convertToErpProductDO(MdmItemRespDTO mdmItem) {
        if (mdmItem == null) {
            return null;
        }
        ErpProductDO product = new ErpProductDO();
        product.setId(mdmItem.getId());
        product.setName(mdmItem.getName());
        product.setBarCode(mdmItem.getBarCode());
        product.setStandard(mdmItem.getSpecification()); // specification -> standard
        product.setCategoryId(mdmItem.getCategoryId());
        product.setUnitId(mdmItem.getUnitMeasureId()); // unitMeasureId -> unitId
        product.setStatus(mdmItem.getStatus());
        product.setPurchasePrice(mdmItem.getPurchasePrice());
        product.setSalePrice(mdmItem.getSalesPrice()); // salesPrice -> salePrice
        product.setMinPrice(mdmItem.getCostPrice()); // costPrice -> minPrice
        product.setExpiryDay(mdmItem.getExpiryDay());
        product.setWeight(mdmItem.getWeight());
        product.setRemark(mdmItem.getRemark());
        return product;
    }
    /**
     * æ‰¹é‡è½¬æ¢ MDM ç‰©æ–™åˆ—表为 ERP äº§å“åˆ—表
     */
    private List<ErpProductDO> convertToErpProductDOList(List<MdmItemRespDTO> mdmItemList) {
        if (CollUtil.isEmpty(mdmItemList)) {
            return Collections.emptyList();
        }
        List<ErpProductDO> result = new ArrayList<>();
        for (MdmItemRespDTO mdmItem : mdmItemList) {
            result.add(convertToErpProductDO(mdmItem));
        }
        return result;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java
@@ -42,6 +42,22 @@
    void updatePurchaseInStatus(Long id, Integer status);
    /**
     * æäº¤é‡‡è´­å…¥åº“审批
     *
     * @param id ç¼–号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitPurchaseIn(Long id, Long userId);
    /**
     * æ›´æ–°é‡‡è´­å…¥åº“的审批状态
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updatePurchaseInAuditStatus(Long id, Integer bpmResult);
    /**
     * æ›´æ–°é‡‡è´­å…¥åº“的付款金额
     *
     * @param id ç¼–号
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -22,6 +24,7 @@
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -37,8 +40,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO èŠ‹è‰¿ï¼šè®°å½•æ“ä½œæ—¥å¿—
/**
 * ERP é‡‡è´­å…¥åº“ Service å®žçŽ°ç±»
 *
@@ -46,7 +47,13 @@
 */
@Service
@Validated
@Slf4j
public class ErpPurchaseInServiceImpl implements ErpPurchaseInService {
    /**
     * BPM é‡‡è´­å…¥åº“审批流程标识
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "erp-purchase-in-audit";
    @Resource
    private ErpPurchaseInMapper purchaseInMapper;
@@ -69,6 +76,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseIn(ErpPurchaseInSaveReqVO createReqVO) {
@@ -86,7 +96,7 @@
        // 2.1 æ’入入库
        ErpPurchaseInDO purchaseIn = BeanUtils.toBean(createReqVO, ErpPurchaseInDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
                .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()))
                .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId());
        calculateTotalPrice(purchaseIn, purchaseInItems);
        purchaseInMapper.insert(purchaseIn);
@@ -181,9 +191,15 @@
                : ErpStockRecordBizTypeEnum.PURCHASE_IN_CANCEL.getType();
        purchaseInItems.forEach(purchaseInItem -> {
            BigDecimal count = approve ? purchaseInItem.getCount() : purchaseInItem.getCount().negate();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    purchaseInItem.getProductId(), purchaseInItem.getWarehouseId(), count,
                    bizType, purchaseInItem.getInId(), purchaseInItem.getId(), purchaseIn.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(purchaseInItem.getProductId());
            stockRecordBO.setWarehouseId(purchaseInItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(purchaseInItem.getInId());
            stockRecordBO.setBizItemId(purchaseInItem.getId());
            stockRecordBO.setBizNo(purchaseIn.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
@@ -305,4 +321,83 @@
        return purchaseInItemMapper.selectListByInIds(inIds);
    }
    // ==================== BPM æµç¨‹å®¡æ‰¹ ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseIn(Long id, Long userId) {
        // 1. æ ¡éªŒé‡‡è´­å…¥åº“处于草稿状态
        ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
        if (ObjectUtil.notEqual(purchaseIn.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
            throw exception(PURCHASE_IN_SUBMIT_FAIL_NOT_DRAFT);
        }
        // 2. åˆ›å»ºé‡‡è´­å…¥åº“审批流程实例
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
                .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°é‡‡è´­å…¥åº“工作流编号、状态
        purchaseInMapper.updateById(new ErpPurchaseInDO()
                .setId(id)
                .setProcessInstanceId(processInstanceId)
                .setStatus(ErpAuditStatus.PROCESS.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseInAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒé‡‡è´­å…¥åº“存在
        ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
        // 2. æ ¡éªŒé‡‡è´­å…¥åº“处于审批中
        if (ObjectUtil.notEqual(purchaseIn.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
            log.error("[updatePurchaseInAuditStatus][purchaseIn({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    purchaseIn.getId(), bpmResult);
            throw exception(PURCHASE_IN_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        // 3. æ›´æ–°é‡‡è´­å…¥åº“审批结果
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        purchaseInMapper.updateById(new ErpPurchaseInDO()
                .setId(id)
                .setStatus(auditStatus));
        // 4. å®¡æ‰¹é€šè¿‡æ—¶å˜æ›´åº“å­˜
        if (ErpAuditStatus.APPROVE.getStatus().equals(auditStatus)) {
            List<ErpPurchaseInItemDO> purchaseInItems = purchaseInItemMapper.selectListByInId(id);
            purchaseInItems.forEach(purchaseInItem -> {
                ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
                stockRecordBO.setProductId(purchaseInItem.getProductId());
                stockRecordBO.setWarehouseId(purchaseInItem.getWarehouseId());
                stockRecordBO.setCount(purchaseInItem.getCount());
                stockRecordBO.setBizType(ErpStockRecordBizTypeEnum.PURCHASE_IN.getType());
                stockRecordBO.setBizId(purchaseInItem.getInId());
                stockRecordBO.setBizItemId(purchaseInItem.getId());
                stockRecordBO.setBizNo(purchaseIn.getNo());
                stockRecordService.createStockRecord(stockRecordBO);
            });
        }
    }
    /**
     * å°† BPM å®¡æ‰¹ç»“果转换为 ERP å®¡æ‰¹çŠ¶æ€
     *
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @return ERP å®¡æ‰¹çŠ¶æ€
     */
    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
        // BPM å®¡æ‰¹ç»“果:2=通过,3=不通过,4=已取消
        if (bpmResult == 2) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (bpmResult == 3) {
            return ErpAuditStatus.REJECT.getStatus();
        } else if (bpmResult == 4) {
            return ErpAuditStatus.DRAFT.getStatus();
        } else {
            log.warn("[convertBpmResultToAuditStatus][未知的 BPM å®¡æ‰¹ç»“æžœ({})]", bpmResult);
            return ErpAuditStatus.PROCESS.getStatus();
        }
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -43,6 +43,22 @@
    void updatePurchaseOrderStatus(Long id, Integer status);
    /**
     * æäº¤é‡‡è´­è®¢å•审批
     *
     * @param id ç¼–号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitPurchaseOrder(Long id, Long userId);
    /**
     * æ›´æ–°é‡‡è´­è®¢å•的审批状态
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult);
    /**
     * æ›´æ–°é‡‡è´­è®¢å•的入库数量
     *
     * @param id ç¼–号
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -17,6 +19,7 @@
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -31,8 +34,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO èŠ‹è‰¿ï¼šè®°å½•æ“ä½œæ—¥å¿—
/**
 * ERP é‡‡è´­è®¢å• Service å®žçŽ°ç±»
 *
@@ -40,7 +41,13 @@
 */
@Service
@Validated
@Slf4j
public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService {
    /**
     * BPM é‡‡è´­è®¢å•审批流程标识
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "erp-purchase-order-audit";
    @Resource
    private ErpPurchaseOrderMapper purchaseOrderMapper;
@@ -56,6 +63,9 @@
    private ErpSupplierService supplierService;
    @Resource
    private ErpAccountService accountService;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -76,7 +86,7 @@
        // 2.1 æ’入订单
        ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()));
                .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()));
        calculateTotalPrice(purchaseOrder, purchaseOrderItems);
        purchaseOrderMapper.insert(purchaseOrder);
        // 2.2 æ’入订单项
@@ -292,4 +302,67 @@
        return purchaseOrderItemMapper.selectListByOrderIds(orderIds);
    }
    // ==================== BPM æµç¨‹å®¡æ‰¹ ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseOrder(Long id, Long userId) {
        // 1. æ ¡éªŒé‡‡è´­è®¢å•处于草稿状态
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
            throw exception(PURCHASE_ORDER_SUBMIT_FAIL_NOT_DRAFT);
        }
        // 2. åˆ›å»ºé‡‡è´­è®¢å•审批流程实例
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
                .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°é‡‡è´­è®¢å•工作流编号、状态
        purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                .setId(id)
                .setProcessInstanceId(processInstanceId)
                .setStatus(ErpAuditStatus.PROCESS.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒé‡‡è´­è®¢å•存在
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        // 2. æ ¡éªŒé‡‡è´­è®¢å•处于审批中
        if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
            log.error("[updatePurchaseOrderAuditStatus][purchaseOrder({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    purchaseOrder.getId(), bpmResult);
            throw exception(PURCHASE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        // 3. æ›´æ–°é‡‡è´­è®¢å•审批结果
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                .setId(id)
                .setStatus(auditStatus));
    }
    /**
     * å°† BPM å®¡æ‰¹ç»“果转换为 ERP å®¡æ‰¹çŠ¶æ€
     *
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @return ERP å®¡æ‰¹çŠ¶æ€
     */
    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
        // BPM å®¡æ‰¹ç»“果:2=通过,3=不通过,4=已取消
        if (bpmResult == 2) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (bpmResult == 3) {
            return ErpAuditStatus.REJECT.getStatus();
        } else if (bpmResult == 4) {
            return ErpAuditStatus.DRAFT.getStatus();
        } else {
            log.warn("[convertBpmResultToAuditStatus][未知的 BPM å®¡æ‰¹ç»“æžœ({})]", bpmResult);
            return ErpAuditStatus.PROCESS.getStatus();
        }
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java
@@ -177,9 +177,15 @@
                : ErpStockRecordBizTypeEnum.PURCHASE_RETURN_CANCEL.getType();
        purchaseReturnItems.forEach(purchaseReturnItem -> {
            BigDecimal count = approve ? purchaseReturnItem.getCount().negate() : purchaseReturnItem.getCount();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    purchaseReturnItem.getProductId(), purchaseReturnItem.getWarehouseId(), count,
                    bizType, purchaseReturnItem.getReturnId(), purchaseReturnItem.getId(), purchaseReturn.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(purchaseReturnItem.getProductId());
            stockRecordBO.setWarehouseId(purchaseReturnItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(purchaseReturnItem.getReturnId());
            stockRecordBO.setBizItemId(purchaseReturnItem.getId());
            stockRecordBO.setBizNo(purchaseReturn.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseInStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.erp.service.purchase.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
 * é‡‡è´­å…¥åº“审批的结果的监听器实现类
 *
 * @author èŠ‹é“æºç 
 */
@Component
public class ErpPurchaseInStatusListener extends BpmProcessInstanceStatusEventListener {
    @Resource
    private ErpPurchaseInService purchaseInService;
    @Override
    public String getProcessDefinitionKey() {
        return ErpPurchaseInServiceImpl.BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        purchaseInService.updatePurchaseInAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.erp.service.purchase.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
 * é‡‡è´­è®¢å•审批的结果的监听器实现类
 *
 * @author èŠ‹é“æºç 
 */
@Component
public class ErpPurchaseOrderStatusListener extends BpmProcessInstanceStatusEventListener {
    @Resource
    private ErpPurchaseOrderService purchaseOrderService;
    @Override
    public String getProcessDefinitionKey() {
        return ErpPurchaseOrderServiceImpl.BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        purchaseOrderService.updatePurchaseOrderAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
@@ -43,6 +43,22 @@
    void updateSaleOrderStatus(Long id, Integer status);
    /**
     * æäº¤é”€å”®è®¢å•审批
     *
     * @param id ç¼–号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitSaleOrder(Long id, Long userId);
    /**
     * æ›´æ–°é”€å”®è®¢å•的审批状态
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updateSaleOrderAuditStatus(Long id, Integer bpmResult);
    /**
     * æ›´æ–°é”€å”®è®¢å•的出库数量
     *
     * @param id ç¼–号
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -18,6 +20,7 @@
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -32,8 +35,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO èŠ‹è‰¿ï¼šè®°å½•æ“ä½œæ—¥å¿—
/**
 * ERP é”€å”®è®¢å• Service å®žçŽ°ç±»
 *
@@ -41,7 +42,13 @@
 */
@Service
@Validated
@Slf4j
public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
    /**
     * BPM é”€å”®è®¢å•审批流程标识
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "erp-sale-order-audit";
    @Resource
    private ErpSaleOrderMapper saleOrderMapper;
@@ -60,6 +67,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -84,7 +94,7 @@
        // 2.1 æ’入订单
        ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()));
                .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()));
        calculateTotalPrice(saleOrder, saleOrderItems);
        saleOrderMapper.insert(saleOrder);
        // 2.2 æ’入订单项
@@ -304,4 +314,67 @@
        return saleOrderItemMapper.selectListByOrderIds(orderIds);
    }
    // ==================== BPM æµç¨‹å®¡æ‰¹ ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitSaleOrder(Long id, Long userId) {
        // 1. æ ¡éªŒé”€å”®è®¢å•处于草稿状态
        ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
        if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
            throw exception(SALE_ORDER_SUBMIT_FAIL_NOT_DRAFT);
        }
        // 2. åˆ›å»ºé”€å”®è®¢å•审批流程实例
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
                .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°é”€å”®è®¢å•工作流编号、状态
        saleOrderMapper.updateById(new ErpSaleOrderDO()
                .setId(id)
                .setProcessInstanceId(processInstanceId)
                .setStatus(ErpAuditStatus.PROCESS.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSaleOrderAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒé”€å”®è®¢å•存在
        ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
        // 2. æ ¡éªŒé”€å”®è®¢å•处于审批中
        if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
            log.error("[updateSaleOrderAuditStatus][saleOrder({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    saleOrder.getId(), bpmResult);
            throw exception(SALE_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        // 3. æ›´æ–°é”€å”®è®¢å•审批结果
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        saleOrderMapper.updateById(new ErpSaleOrderDO()
                .setId(id)
                .setStatus(auditStatus));
    }
    /**
     * å°† BPM å®¡æ‰¹ç»“果转换为 ERP å®¡æ‰¹çŠ¶æ€
     *
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @return ERP å®¡æ‰¹çŠ¶æ€
     */
    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
        // BPM å®¡æ‰¹ç»“果:2=通过,3=不通过,4=已取消
        if (bpmResult == 2) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (bpmResult == 3) {
            return ErpAuditStatus.REJECT.getStatus();
        } else if (bpmResult == 4) {
            return ErpAuditStatus.DRAFT.getStatus();
        } else {
            log.warn("[convertBpmResultToAuditStatus][未知的 BPM å®¡æ‰¹ç»“æžœ({})]", bpmResult);
            return ErpAuditStatus.PROCESS.getStatus();
        }
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java
@@ -42,6 +42,22 @@
    void updateSaleOutStatus(Long id, Integer status);
    /**
     * æäº¤é”€å”®å‡ºåº“审批
     *
     * @param id ç¼–号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitSaleOut(Long id, Long userId);
    /**
     * æ›´æ–°é”€å”®å‡ºåº“的审批状态
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updateSaleOutAuditStatus(Long id, Integer bpmResult);
    /**
     * æ›´æ–°é”€å”®å‡ºåº“的收款金额
     *
     * @param id ç¼–号
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
@@ -22,6 +24,7 @@
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -37,8 +40,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO èŠ‹è‰¿ï¼šè®°å½•æ“ä½œæ—¥å¿—
/**
 * ERP é”€å”®å‡ºåº“ Service å®žçŽ°ç±»
 *
@@ -46,7 +47,13 @@
 */
@Service
@Validated
@Slf4j
public class ErpSaleOutServiceImpl implements ErpSaleOutService {
    /**
     * BPM é”€å”®å‡ºåº“审批流程标识
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "erp-sale-out-audit";
    @Resource
    private ErpSaleOutMapper saleOutMapper;
@@ -69,6 +76,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) {
@@ -90,7 +100,7 @@
        // 2.1 æ’入出库
        ErpSaleOutDO saleOut = BeanUtils.toBean(createReqVO, ErpSaleOutDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
                .setNo(no).setStatus(ErpAuditStatus.DRAFT.getStatus()))
                .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId());
        calculateTotalPrice(saleOut, saleOutItems);
        saleOutMapper.insert(saleOut);
@@ -189,9 +199,15 @@
                : ErpStockRecordBizTypeEnum.SALE_OUT_CANCEL.getType();
        saleOutItems.forEach(saleOutItem -> {
            BigDecimal count = approve ? saleOutItem.getCount().negate() : saleOutItem.getCount();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    saleOutItem.getProductId(), saleOutItem.getWarehouseId(), count,
                    bizType, saleOutItem.getOutId(), saleOutItem.getId(), saleOut.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(saleOutItem.getProductId());
            stockRecordBO.setWarehouseId(saleOutItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(saleOutItem.getOutId());
            stockRecordBO.setBizItemId(saleOutItem.getId());
            stockRecordBO.setBizNo(saleOut.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
@@ -313,4 +329,83 @@
        return saleOutItemMapper.selectListByOutIds(outIds);
    }
    // ==================== BPM æµç¨‹å®¡æ‰¹ ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitSaleOut(Long id, Long userId) {
        // 1. æ ¡éªŒé”€å”®å‡ºåº“处于草稿状态
        ErpSaleOutDO saleOut = validateSaleOutExists(id);
        if (ObjectUtil.notEqual(saleOut.getStatus(), ErpAuditStatus.DRAFT.getStatus())) {
            throw exception(SALE_OUT_SUBMIT_FAIL_NOT_DRAFT);
        }
        // 2. åˆ›å»ºé”€å”®å‡ºåº“审批流程实例
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
                .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°é”€å”®å‡ºåº“工作流编号、状态
        saleOutMapper.updateById(new ErpSaleOutDO()
                .setId(id)
                .setProcessInstanceId(processInstanceId)
                .setStatus(ErpAuditStatus.PROCESS.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSaleOutAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒé”€å”®å‡ºåº“存在
        ErpSaleOutDO saleOut = validateSaleOutExists(id);
        // 2. æ ¡éªŒé”€å”®å‡ºåº“处于审批中
        if (ObjectUtil.notEqual(saleOut.getStatus(), ErpAuditStatus.PROCESS.getStatus())) {
            log.error("[updateSaleOutAuditStatus][saleOut({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    saleOut.getId(), bpmResult);
            throw exception(SALE_OUT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        // 3. æ›´æ–°é”€å”®å‡ºåº“审批结果
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        saleOutMapper.updateById(new ErpSaleOutDO()
                .setId(id)
                .setStatus(auditStatus));
        // 4. å®¡æ‰¹é€šè¿‡æ—¶å˜æ›´åº“存(减少库存)
        if (ErpAuditStatus.APPROVE.getStatus().equals(auditStatus)) {
            List<ErpSaleOutItemDO> saleOutItems = saleOutItemMapper.selectListByOutId(id);
            saleOutItems.forEach(saleOutItem -> {
                ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
                stockRecordBO.setProductId(saleOutItem.getProductId());
                stockRecordBO.setWarehouseId(saleOutItem.getWarehouseId());
                stockRecordBO.setCount(saleOutItem.getCount().negate());
                stockRecordBO.setBizType(ErpStockRecordBizTypeEnum.SALE_OUT.getType());
                stockRecordBO.setBizId(saleOutItem.getOutId());
                stockRecordBO.setBizItemId(saleOutItem.getId());
                stockRecordBO.setBizNo(saleOut.getNo());
                stockRecordService.createStockRecord(stockRecordBO);
            });
        }
    }
    /**
     * å°† BPM å®¡æ‰¹ç»“果转换为 ERP å®¡æ‰¹çŠ¶æ€
     *
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @return ERP å®¡æ‰¹çŠ¶æ€
     */
    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
        // BPM å®¡æ‰¹ç»“果:2=通过,3=不通过,4=已取消
        if (bpmResult == 2) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (bpmResult == 3) {
            return ErpAuditStatus.REJECT.getStatus();
        } else if (bpmResult == 4) {
            return ErpAuditStatus.DRAFT.getStatus();
        } else {
            log.warn("[convertBpmResultToAuditStatus][未知的 BPM å®¡æ‰¹ç»“æžœ({})]", bpmResult);
            return ErpAuditStatus.PROCESS.getStatus();
        }
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java
@@ -189,9 +189,15 @@
                : ErpStockRecordBizTypeEnum.SALE_RETURN_CANCEL.getType();
        saleReturnItems.forEach(saleReturnItem -> {
            BigDecimal count = approve ? saleReturnItem.getCount() : saleReturnItem.getCount().negate();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    saleReturnItem.getProductId(), saleReturnItem.getWarehouseId(), count,
                    bizType, saleReturnItem.getReturnId(), saleReturnItem.getId(), saleReturn.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(saleReturnItem.getProductId());
            stockRecordBO.setWarehouseId(saleReturnItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(saleReturnItem.getReturnId());
            stockRecordBO.setBizItemId(saleReturnItem.getId());
            stockRecordBO.setBizNo(saleReturn.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.erp.service.sale.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
 * é”€å”®è®¢å•审批的结果的监听器实现类
 *
 * @author èŠ‹é“æºç 
 */
@Component
public class ErpSaleOrderStatusListener extends BpmProcessInstanceStatusEventListener {
    @Resource
    private ErpSaleOrderService saleOrderService;
    @Override
    public String getProcessDefinitionKey() {
        return ErpSaleOrderServiceImpl.BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        saleOrderService.updateSaleOrderAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOutStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.erp.service.sale.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutService;
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
 * é”€å”®å‡ºåº“审批的结果的监听器实现类
 *
 * @author èŠ‹é“æºç 
 */
@Component
public class ErpSaleOutStatusListener extends BpmProcessInstanceStatusEventListener {
    @Resource
    private ErpSaleOutService saleOutService;
    @Override
    public String getProcessDefinitionKey() {
        return ErpSaleOutServiceImpl.BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        saleOutService.updateSaleOutAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java
@@ -135,9 +135,15 @@
                bizType = count.compareTo(BigDecimal.ZERO) > 0 ? ErpStockRecordBizTypeEnum.CHECK_MORE_IN_CANCEL.getType()
                        : ErpStockRecordBizTypeEnum.CHECK_LESS_OUT_CANCEL.getType();
            }
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    stockCheckItem.getProductId(), stockCheckItem.getWarehouseId(), count,
                    bizType, stockCheckItem.getCheckId(), stockCheckItem.getId(), stockCheck.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(stockCheckItem.getProductId());
            stockRecordBO.setWarehouseId(stockCheckItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(stockCheckItem.getCheckId());
            stockRecordBO.setBizItemId(stockCheckItem.getId());
            stockRecordBO.setBizNo(stockCheck.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
@@ -130,9 +130,15 @@
                : ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType();
        stockInItems.forEach(stockInItem -> {
            BigDecimal count = approve ? stockInItem.getCount() : stockInItem.getCount().negate();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    stockInItem.getProductId(), stockInItem.getWarehouseId(), count,
                    bizType, stockInItem.getInId(), stockInItem.getId(), stockIn.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(stockInItem.getProductId());
            stockRecordBO.setWarehouseId(stockInItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(stockInItem.getInId());
            stockRecordBO.setBizItemId(stockInItem.getId());
            stockRecordBO.setBizNo(stockIn.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java
@@ -128,12 +128,24 @@
        stockMoveItems.forEach(stockMoveItem -> {
            BigDecimal fromCount = approve ? stockMoveItem.getCount().negate() : stockMoveItem.getCount();
            BigDecimal toCount = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    stockMoveItem.getProductId(), stockMoveItem.getFromWarehouseId(), fromCount,
                    fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    stockMoveItem.getProductId(), stockMoveItem.getToWarehouseId(), toCount,
                    toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
            ErpStockRecordCreateReqBO fromStockRecordBO = new ErpStockRecordCreateReqBO();
            fromStockRecordBO.setProductId(stockMoveItem.getProductId());
            fromStockRecordBO.setWarehouseId(stockMoveItem.getFromWarehouseId());
            fromStockRecordBO.setCount(fromCount);
            fromStockRecordBO.setBizType(fromBizType);
            fromStockRecordBO.setBizId(stockMoveItem.getMoveId());
            fromStockRecordBO.setBizItemId(stockMoveItem.getId());
            fromStockRecordBO.setBizNo(stockMove.getNo());
            stockRecordService.createStockRecord(fromStockRecordBO);
            ErpStockRecordCreateReqBO toStockRecordBO = new ErpStockRecordCreateReqBO();
            toStockRecordBO.setProductId(stockMoveItem.getProductId());
            toStockRecordBO.setWarehouseId(stockMoveItem.getToWarehouseId());
            toStockRecordBO.setCount(toCount);
            toStockRecordBO.setBizType(toBizType);
            toStockRecordBO.setBizId(stockMoveItem.getMoveId());
            toStockRecordBO.setBizItemId(stockMoveItem.getId());
            toStockRecordBO.setBizNo(stockMove.getNo());
            stockRecordService.createStockRecord(toStockRecordBO);
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java
@@ -131,9 +131,15 @@
                : ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
        stockOutItems.forEach(stockOutItem -> {
            BigDecimal count = approve ? stockOutItem.getCount().negate() : stockOutItem.getCount();
            stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
                    stockOutItem.getProductId(), stockOutItem.getWarehouseId(), count,
                    bizType, stockOutItem.getOutId(), stockOutItem.getId(), stockOut.getNo()));
            ErpStockRecordCreateReqBO stockRecordBO = new ErpStockRecordCreateReqBO();
            stockRecordBO.setProductId(stockOutItem.getProductId());
            stockRecordBO.setWarehouseId(stockOutItem.getWarehouseId());
            stockRecordBO.setCount(count);
            stockRecordBO.setBizType(bizType);
            stockRecordBO.setBizId(stockOutItem.getOutId());
            stockRecordBO.setBizItemId(stockOutItem.getId());
            stockRecordBO.setBizNo(stockOut.getNo());
            stockRecordService.createStockRecord(stockRecordBO);
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java
@@ -1,20 +1,33 @@
package cn.iocoder.yudao.module.erp.service.stock;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockRecordMapper;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemSkuApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
import cn.iocoder.yudao.module.wms.api.inventory.WmsInventoryApi;
import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsReceiptReqDTO;
import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsShipmentReqDTO;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryBizTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
/**
 * ERP äº§å“åº“存明细 Service å®žçŽ°ç±»
 *
 * æ”¹é€ è¯´æ˜Žï¼šåº“存变更调用 WMS åº“å­˜ API
 * - å…¥åº“操作调用 WmsInventoryApi.receipt()
 * - å‡ºåº“操作调用 WmsInventoryApi.shipment()
 * - productId å¯¹åº” MDM itemId,通过 SKU API èŽ·å–é»˜è®¤ SKU
 *
 * @author èŠ‹é“æºç 
 */
@@ -27,6 +40,12 @@
    @Resource
    private ErpStockService stockService;
    @Resource
    private WmsInventoryApi wmsInventoryApi;
    @Resource
    private MdmItemSkuApi mdmItemSkuApi;
    @Override
    public ErpStockRecordDO getStockRecord(Long id) {
@@ -41,13 +60,98 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createStockRecord(ErpStockRecordCreateReqBO createReqBO) {
        // 1. æ›´æ–°åº“å­˜
        BigDecimal totalCount = stockService.updateStockCountIncrement(
                createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount());
        // 2. åˆ›å»ºåº“存明细
        // 1. èŽ·å–äº§å“çš„é»˜è®¤ SKU ID(ERP productId = MDM itemId)
        Long skuId = getDefaultSkuId(createReqBO.getProductId());
        if (skuId == null) {
            // å¦‚果没有 SKU,使用原有逻辑更新库存
            BigDecimal totalCount = stockService.updateStockCountIncrement(
                    createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount());
            ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class)
                    .setTotalCount(totalCount);
            stockRecordMapper.insert(stockRecord);
            return;
        }
        // 2. æ ¹æ® count æ­£è´Ÿåˆ¤æ–­å…¥åº“还是出库
        BigDecimal count = createReqBO.getCount();
        if (count.compareTo(BigDecimal.ZERO) > 0) {
            // å…¥åº“操作
            WmsReceiptReqDTO receiptReq = buildReceiptReqDTO(createReqBO, skuId);
            wmsInventoryApi.receipt(receiptReq);
        } else if (count.compareTo(BigDecimal.ZERO) < 0) {
            // å‡ºåº“操作(数量转为正数)
            WmsShipmentReqDTO shipmentReq = buildShipmentReqDTO(createReqBO, skuId);
            wmsInventoryApi.shipment(shipmentReq);
        }
        // 3. åˆ›å»ºåº“存明细(记录操作)
        BigDecimal currentStock = wmsInventoryApi.getAvailableQuantity(skuId, createReqBO.getWarehouseId()).getCheckedData();
        ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class)
                .setTotalCount(totalCount);
                .setTotalCount(currentStock != null ? currentStock : BigDecimal.ZERO);
        stockRecordMapper.insert(stockRecord);
    }
    /**
     * èŽ·å–äº§å“çš„é»˜è®¤ SKU ID
     */
    private Long getDefaultSkuId(Long productId) {
        if (productId == null) {
            return null;
        }
        List<MdmItemSkuRespDTO> skuList = mdmItemSkuApi.getItemSkuListByItemId(productId).getCheckedData();
        if (CollUtil.isEmpty(skuList)) {
            return null;
        }
        return skuList.get(0).getId();
    }
    /**
     * æž„建入库请求
     */
    private WmsReceiptReqDTO buildReceiptReqDTO(ErpStockRecordCreateReqBO createReqBO, Long skuId) {
        WmsReceiptReqDTO receiptReq = new WmsReceiptReqDTO();
        receiptReq.setBizType(convertBizType(createReqBO.getBizType()));
        receiptReq.setBizId(createReqBO.getBizId());
        receiptReq.setBizNo(createReqBO.getBizNo());
        receiptReq.setWarehouseId(createReqBO.getWarehouseId());
        receiptReq.setRemark(createReqBO.getRemark());
        WmsReceiptReqDTO.Item item = new WmsReceiptReqDTO.Item();
        item.setSkuId(skuId);
        item.setQuantity(createReqBO.getCount());
        receiptReq.setItems(List.of(item));
        return receiptReq;
    }
    /**
     * æž„建出库请求
     */
    private WmsShipmentReqDTO buildShipmentReqDTO(ErpStockRecordCreateReqBO createReqBO, Long skuId) {
        WmsShipmentReqDTO shipmentReq = new WmsShipmentReqDTO();
        shipmentReq.setBizType(convertBizType(createReqBO.getBizType()));
        shipmentReq.setBizId(createReqBO.getBizId());
        shipmentReq.setBizNo(createReqBO.getBizNo());
        shipmentReq.setWarehouseId(createReqBO.getWarehouseId());
        shipmentReq.setRemark(createReqBO.getRemark());
        WmsShipmentReqDTO.Item item = new WmsShipmentReqDTO.Item();
        item.setSkuId(skuId);
        item.setQuantity(createReqBO.getCount().negate()); // å‡ºåº“数量为正数
        shipmentReq.setItems(List.of(item));
        return shipmentReq;
    }
    /**
     * è½¬æ¢ä¸šåŠ¡ç±»åž‹ï¼ˆERP ä¸šåŠ¡ç±»åž‹æ˜ å°„åˆ° WMS ä¸šåŠ¡ç±»åž‹ï¼‰
     */
    private Integer convertBizType(Integer erpBizType) {
        // ERP ä¸šåŠ¡ç±»åž‹æ˜ å°„åˆ° WMS ä¸šåŠ¡ç±»åž‹
        // å¯ä»¥æ ¹æ®å®žé™…业务需求扩展映射关系
        if (erpBizType == null) {
            return WmsInventoryBizTypeEnum.OTHER.getType();
        }
        // é»˜è®¤è¿”回 OTHER ç±»åž‹ï¼ŒåŽç»­å¯æ ¹æ® ERP ä¸šåŠ¡ç±»åž‹æžšä¸¾æ‰©å±•
        return WmsInventoryBizTypeEnum.OTHER.getType();
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
@@ -1,22 +1,34 @@
package cn.iocoder.yudao.module.erp.service.stock;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemSkuApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
import cn.iocoder.yudao.module.wms.api.inventory.WmsInventoryApi;
import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryQueryReqDTO;
import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
/**
 * ERP äº§å“åº“å­˜ Service å®žçŽ°ç±»
 *
 * æ”¹é€ è¯´æ˜Žï¼šERP åº“存调用 WMS åº“å­˜ API
 * - productId å¯¹åº” MDM itemId
 * - é€šè¿‡ MDM SKU API èŽ·å–ç‰©æ–™çš„é»˜è®¤ SKU
 * - åº“存操作调用 WMS Inventory API
 *
 * @author èŠ‹é“æºç 
 */
@@ -26,18 +38,24 @@
    /**
     * å…è®¸åº“存为负数
     *
     * TODO èŠ‹è‰¿ï¼šåŽç»­åšæˆ db é…ç½®
     */
    private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false;
    @Resource
    private ErpProductService productService;
    @Resource
    private ErpWarehouseService warehouseService;
    @Resource
    private ErpStockMapper stockMapper;
    @Resource
    private WmsInventoryApi wmsInventoryApi;
    @Resource
    private MdmItemSkuApi mdmItemSkuApi;
    @Override
    public ErpStockDO getStock(Long id) {
@@ -46,44 +64,117 @@
    @Override
    public ErpStockDO getStock(Long productId, Long warehouseId) {
        return stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
        // é€šè¿‡ MDM èŽ·å–äº§å“çš„é»˜è®¤ SKU,然后查询 WMS åº“å­˜
        Long skuId = getDefaultSkuId(productId);
        if (skuId == null) {
            return null;
        }
        // æŸ¥è¯¢ WMS åº“å­˜
        BigDecimal quantity = wmsInventoryApi.getAvailableQuantity(skuId, warehouseId).getCheckedData();
        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) == 0) {
            return null;
        }
        // è½¬æ¢ä¸º ERP Stock DO
        ErpStockDO stock = new ErpStockDO();
        stock.setProductId(productId);
        stock.setWarehouseId(warehouseId);
        stock.setCount(quantity);
        return stock;
    }
    @Override
    public BigDecimal getStockCount(Long productId) {
        BigDecimal count = stockMapper.selectSumByProductId(productId);
        return count != null ? count : BigDecimal.ZERO;
        // é€šè¿‡ MDM èŽ·å–äº§å“çš„é»˜è®¤ SKU,然后查询 WMS åº“存总量
        Long skuId = getDefaultSkuId(productId);
        if (skuId == null) {
            return BigDecimal.ZERO;
        }
        // æŸ¥è¯¢è¯¥ SKU åœ¨æ‰€æœ‰ä»“库的库存
        WmsInventoryQueryReqDTO queryReq = new WmsInventoryQueryReqDTO();
        queryReq.setSkuIds(List.of(skuId));
        queryReq.setOnlyPositive(true);
        List<WmsInventoryRespDTO> inventories = wmsInventoryApi.getInventoryList(queryReq).getCheckedData();
        if (CollUtil.isEmpty(inventories)) {
            return BigDecimal.ZERO;
        }
        return inventories.stream()
                .map(WmsInventoryRespDTO::getQuantity)
                .filter(q -> q != null)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    @Override
    public PageResult<ErpStockDO> getStockPage(ErpStockPageReqVO pageReqVO) {
        // æš‚时保留原逻辑,后续可改为调用 WMS åˆ†é¡µ API
        return stockMapper.selectPage(pageReqVO);
    }
    @Override
    public BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count) {
        // 1.1 æŸ¥è¯¢å½“前库存
        ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
        if (stock == null) {
            stock = new ErpStockDO().setProductId(productId).setWarehouseId(warehouseId).setCount(BigDecimal.ZERO);
            stockMapper.insert(stock);
        }
        // 1.2 æ ¡éªŒåº“存是否充足
        if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) {
            throw exception(STOCK_COUNT_NEGATIVE, productService.getProduct(productId).getName(),
                    warehouseService.getWarehouse(warehouseId).getName(), stock.getCount(), count);
        }
        // 2. åº“存变更
        int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
        if (updateCount == 0) {
            // æ­¤æ—¶ä¸å¥½åŽ»æŸ¥è¯¢æœ€æ–°åº“å­˜ï¼Œæ‰€ä»¥ç›´æŽ¥æŠ›å‡ºè¯¥æç¤ºï¼Œä¸æä¾›å…·ä½“åº“å­˜æ•°å­—
            throw exception(STOCK_COUNT_NEGATIVE2, productService.getProduct(productId).getName(),
        // é€šè¿‡ MDM èŽ·å–äº§å“çš„é»˜è®¤ SKU
        Long skuId = getDefaultSkuId(productId);
        if (skuId == null) {
            throw exception(STOCK_COUNT_NEGATIVE2,
                    productService.getProduct(productId).getName(),
                    warehouseService.getWarehouse(warehouseId).getName());
        }
        // 3. è¿”回最新库存
        // æŸ¥è¯¢å½“前库存
        BigDecimal currentStock = wmsInventoryApi.getAvailableQuantity(skuId, warehouseId).getCheckedData();
        if (currentStock == null) {
            currentStock = BigDecimal.ZERO;
        }
        // æ ¡éªŒåº“存是否充足(出库时)
        if (!NEGATIVE_STOCK_COUNT_ENABLE && count.compareTo(BigDecimal.ZERO) < 0) {
            BigDecimal afterStock = currentStock.add(count);
            if (afterStock.compareTo(BigDecimal.ZERO) < 0) {
                throw exception(STOCK_COUNT_NEGATIVE,
                        productService.getProduct(productId).getName(),
                        warehouseService.getWarehouse(warehouseId).getName(),
                        currentStock, count.abs());
            }
        }
        // TODO: è°ƒç”¨ WMS åº“存变更 API(需要 WMS æä¾› increment/decrement æ–¹æ³•)
        // æš‚时保留原逻辑,使用本地库存表
        ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
        if (stock == null) {
            stock = new ErpStockDO()
                    .setProductId(productId)
                    .setWarehouseId(warehouseId)
                    .setCount(BigDecimal.ZERO);
            stockMapper.insert(stock);
        }
        int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
        if (updateCount == 0) {
            throw exception(STOCK_COUNT_NEGATIVE2,
                    productService.getProduct(productId).getName(),
                    warehouseService.getWarehouse(warehouseId).getName());
        }
        return stock.getCount().add(count);
    }
    /**
     * èŽ·å–äº§å“çš„é»˜è®¤ SKU ID
     * ERP productId å¯¹åº” MDM itemId,默认使用第一个 SKU
     */
    private Long getDefaultSkuId(Long productId) {
        if (productId == null) {
            return null;
        }
        List<MdmItemSkuRespDTO> skuList = mdmItemSkuApi.getItemSkuListByItemId(productId).getCheckedData();
        if (CollUtil.isEmpty(skuList)) {
            return null;
        }
        return skuList.get(0).getId();
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.erp.service.stock.bo;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -14,7 +13,6 @@
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErpStockRecordCreateReqBO {
    /**
@@ -56,4 +54,9 @@
    @NotNull(message = "业务单号不能为空")
    private String bizNo;
}
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-mdm-api/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-mdm-api</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        mdm æ¨¡å—下,主数据管理的 API æŽ¥å£å®šä¹‰ï¼Œä¾›å…¶ä»–模块引用
    </description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-common</artifactId>
        </dependency>
    </dependencies>
</project>
yudao-module-mdm/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-mdm</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        mdm æ¨¡å—下,主数据管理(Master Data Management)。
        ä¾‹å¦‚说:物料、分类、单位、品牌、仓库等主数据管理
    </description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-system</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-security</artifactId>
        </dependency>
        <!-- DB ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
        </dependency>
        <!-- å·¥å…·ç±»ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-excel</artifactId>
        </dependency>
        <!-- Test æµ‹è¯•相关 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>
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);
}
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));
    }
}
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;
}
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);
}
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));
    }
}
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;
}
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);
}
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);
    }
}
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);
}
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));
    }
}
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;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.mdm.api.item.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "RPC æœåŠ¡ - MDM ç‰©æ–™ Response DTO")
@Data
public class MdmItemRespDTO {
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
    private String code;
    @Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "螺丝")
    private String name;
    @Schema(description = "物料条码", example = "12345678")
    private String barCode;
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
    @Schema(description = "计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long unitMeasureId;
    @Schema(description = "品牌编号", example = "1")
    private Long brandId;
    @Schema(description = "物料类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer itemType;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "采购价格", example = "10.00")
    private BigDecimal purchasePrice;
    @Schema(description = "销售价格", example = "15.00")
    private BigDecimal salesPrice;
    @Schema(description = "成本价格", example = "8.00")
    private BigDecimal costPrice;
    @Schema(description = "安全库存", example = "100")
    private BigDecimal safetyStock;
    @Schema(description = "保质期天数", example = "365")
    private Integer expiryDay;
    @Schema(description = "是否批次管理", example = "false")
    private Boolean isBatchManaged;
    @Schema(description = "是否高值物料", example = "false")
    private Boolean highValue;
    @Schema(description = "重量", example = "0.5")
    private BigDecimal weight;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
}
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 = "长度(cm)", example = "10")
    private BigDecimal length;
    @Schema(description = "宽度(cm)", example = "5")
    private BigDecimal width;
    @Schema(description = "高度(cm)", example = "2")
    private BigDecimal height;
    @Schema(description = "毛重(kg)", 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;
}
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;
}
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);
}
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));
    }
}
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;
}
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);
}
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));
    }
}
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 = "负责人用户ID", example = "1")
    private Long chargeUserId;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "是否冻结", example = "false")
    private Boolean frozen;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/brand/MdmBrandController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
package cn.iocoder.yudao.module.mdm.controller.admin.brand;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.brand.vo.MdmBrandSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MDM å“ç‰Œ")
@RestController
@RequestMapping("/mdm/brand")
@Validated
public class MdmBrandController {
    @Resource
    private MdmBrandService brandService;
    @PostMapping("/create")
    @Operation(summary = "创建品牌")
    @PreAuthorize("@ss.hasPermission('mdm:brand:create')")
    public CommonResult<Long> createBrand(@Valid @RequestBody MdmBrandSaveReqVO createReqVO) {
        return success(brandService.createBrand(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新品牌")
    @PreAuthorize("@ss.hasPermission('mdm:brand:update')")
    public CommonResult<Boolean> updateBrand(@Valid @RequestBody MdmBrandSaveReqVO updateReqVO) {
        brandService.updateBrand(updateReqVO);
        return success(true);
    }
    @PutMapping("/update-status")
    @Operation(summary = "更新品牌状态")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @Parameter(name = "status", description = "状态", required = true, example = "0")
    @PreAuthorize("@ss.hasPermission('mdm:brand:update')")
    public CommonResult<Boolean> updateBrandStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
        brandService.updateBrandStatus(id, status);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除品牌")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:brand:delete')")
    public CommonResult<Boolean> deleteBrand(@RequestParam("id") Long id) {
        brandService.deleteBrand(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得品牌")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
    public CommonResult<MdmBrandDO> getBrand(@RequestParam("id") Long id) {
        return success(brandService.getBrand(id));
    }
    @GetMapping("/page")
    @Operation(summary = "获得品牌分页")
    @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
    public CommonResult<PageResult<MdmBrandDO>> getBrandPage(@Valid MdmBrandPageReqVO pageReqVO) {
        return success(brandService.getBrandPage(pageReqVO));
    }
    @GetMapping("/list")
    @Operation(summary = "获得品牌列表")
    @PreAuthorize("@ss.hasPermission('mdm:brand:query')")
    public CommonResult<List<MdmBrandDO>> getBrandList() {
        return success(brandService.getBrandList(null));
    }
}
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;
}
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;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/category/MdmItemCategoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.mdm.controller.admin.category;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryRespVO;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategorySaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MDM ç‰©æ–™åˆ†ç±»")
@RestController
@RequestMapping("/mdm/item-category")
@Validated
public class MdmItemCategoryController {
    @Resource
    private MdmItemCategoryService itemCategoryService;
    @PostMapping("/create")
    @Operation(summary = "创建物料分类")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:create')")
    public CommonResult<Long> createItemCategory(@Valid @RequestBody MdmItemCategorySaveReqVO createReqVO) {
        return success(itemCategoryService.createItemCategory(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新物料分类")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:update')")
    public CommonResult<Boolean> updateItemCategory(@Valid @RequestBody MdmItemCategorySaveReqVO updateReqVO) {
        itemCategoryService.updateItemCategory(updateReqVO);
        return success(true);
    }
    @PutMapping("/update-status")
    @Operation(summary = "更新物料分类状态")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @Parameter(name = "status", description = "状态", required = true, example = "0")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:update')")
    public CommonResult<Boolean> updateItemCategoryStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
        itemCategoryService.updateItemCategoryStatus(id, status);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除物料分类")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:delete')")
    public CommonResult<Boolean> deleteItemCategory(@RequestParam("id") Long id) {
        itemCategoryService.deleteItemCategory(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得物料分类")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:query')")
    public CommonResult<MdmItemCategoryRespVO> getItemCategory(@RequestParam("id") Long id) {
        MdmItemCategoryDO itemCategory = itemCategoryService.getItemCategory(id);
        return success(BeanUtils.toBean(itemCategory, MdmItemCategoryRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "获得物料分类分页")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:query')")
    public CommonResult<PageResult<MdmItemCategoryRespVO>> getItemCategoryPage(@Valid MdmItemCategoryPageReqVO pageReqVO) {
        PageResult<MdmItemCategoryDO> pageResult = itemCategoryService.getItemCategoryPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, MdmItemCategoryRespVO.class));
    }
    @GetMapping("/list")
    @Operation(summary = "获得物料分类列表")
    @PreAuthorize("@ss.hasPermission('mdm:item-category:query')")
    public CommonResult<List<MdmItemCategoryRespVO>> getItemCategoryList() {
        List<MdmItemCategoryDO> list = itemCategoryService.getItemCategoryList(null);
        return success(BeanUtils.toBean(list, MdmItemCategoryRespVO.class));
    }
}
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;
}
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;
}
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;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemRespVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MDM ç‰©æ–™ä¸»æ•°æ®")
@RestController
@RequestMapping("/mdm/item")
@Validated
public class MdmItemController {
    @Resource
    private MdmItemService itemService;
    @PostMapping("/create")
    @Operation(summary = "创建物料")
    @PreAuthorize("@ss.hasPermission('mdm:item:create')")
    public CommonResult<Long> createItem(@Valid @RequestBody MdmItemSaveReqVO createReqVO) {
        return success(itemService.createItem(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新物料")
    @PreAuthorize("@ss.hasPermission('mdm:item:update')")
    public CommonResult<Boolean> updateItem(@Valid @RequestBody MdmItemSaveReqVO updateReqVO) {
        itemService.updateItem(updateReqVO);
        return success(true);
    }
    @PutMapping("/update-status")
    @Operation(summary = "更新物料状态")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @Parameter(name = "status", description = "状态", required = true, example = "0")
    @PreAuthorize("@ss.hasPermission('mdm:item:update')")
    public CommonResult<Boolean> updateItemStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
        itemService.updateItemStatus(id, status);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除物料")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:item:delete')")
    public CommonResult<Boolean> deleteItem(@RequestParam("id") Long id) {
        itemService.deleteItem(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得物料")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<MdmItemRespVO> getItem(@RequestParam("id") Long id) {
        MdmItemDO item = itemService.getItem(id);
        return success(BeanUtils.toBean(item, MdmItemRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "获得物料分页")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<PageResult<MdmItemRespVO>> getItemPage(@Valid MdmItemPageReqVO pageReqVO) {
        PageResult<MdmItemDO> pageResult = itemService.getItemPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, MdmItemRespVO.class));
    }
    @GetMapping("/list-by-ids")
    @Operation(summary = "获得物料列表")
    @Parameter(name = "ids", description = "编号列表", required = true, example = "1,2,3")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<List<MdmItemRespVO>> getItemList(@RequestParam("ids") List<Long> ids) {
        List<MdmItemDO> list = itemService.getItemList(ids);
        return success(BeanUtils.toBean(list, MdmItemRespVO.class));
    }
}
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;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MDM ç‰©æ–™ Response VO")
@Data
public class MdmItemRespVO {
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
    private String code;
    @Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "螺丝")
    private String name;
    @Schema(description = "物料条码", example = "12345678")
    private String barCode;
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
    @Schema(description = "物料分类名称", example = "五金配件")
    private String categoryName;
    @Schema(description = "计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long unitMeasureId;
    @Schema(description = "计量单位名称", example = "个")
    private String unitMeasureName;
    @Schema(description = "品牌编号", example = "1")
    private Long brandId;
    @Schema(description = "品牌名称", example = "品牌A")
    private String brandName;
    @Schema(description = "物料类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer itemType;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "采购价格", example = "10.00")
    private BigDecimal purchasePrice;
    @Schema(description = "销售价格", example = "15.00")
    private BigDecimal salesPrice;
    @Schema(description = "成本价格", example = "8.00")
    private BigDecimal costPrice;
    @Schema(description = "安全库存", example = "100")
    private BigDecimal safetyStock;
    @Schema(description = "最低库存", example = "50")
    private BigDecimal minStock;
    @Schema(description = "最高库存", example = "500")
    private BigDecimal maxStock;
    @Schema(description = "保质期天数", example = "365")
    private Integer expiryDays;
    @Schema(description = "是否批次管理", example = "false")
    private Boolean isBatchManaged;
    @Schema(description = "是否高值物料", example = "false")
    private Boolean isHighValue;
    @Schema(description = "重量", example = "0.5")
    private BigDecimal weight;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - MDM ç‰©æ–™æ–°å¢ž/修改 Request VO")
@Data
public class MdmItemSaveReqVO {
    @Schema(description = "物料编号", example = "1")
    private Long id;
    @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
    private String code;
    @Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "螺丝")
    private String name;
    @Schema(description = "物料条码", example = "12345678")
    private String barCode;
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
    @Schema(description = "计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long unitMeasureId;
    @Schema(description = "品牌编号", example = "1")
    private Long brandId;
    @Schema(description = "物料类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer itemType;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "采购价格", example = "10.00")
    private BigDecimal purchasePrice;
    @Schema(description = "销售价格", example = "15.00")
    private BigDecimal salesPrice;
    @Schema(description = "成本价格", example = "8.00")
    private BigDecimal costPrice;
    @Schema(description = "安全库存", example = "100")
    private BigDecimal safetyStock;
    @Schema(description = "最低库存", example = "50")
    private BigDecimal minStock;
    @Schema(description = "最高库存", example = "500")
    private BigDecimal maxStock;
    @Schema(description = "保质期天数", example = "365")
    private Integer expiryDay;
    @Schema(description = "是否批次管理", example = "false")
    private Boolean isBatchManaged;
    @Schema(description = "是否高值物料", example = "false")
    private Boolean isHighValue;
    @Schema(description = "重量", example = "0.5")
    private BigDecimal weight;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package cn.iocoder.yudao.module.mdm.controller.admin.unit;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MDM è®¡é‡å•位")
@RestController
@RequestMapping("/mdm/unit-measure")
@Validated
public class MdmUnitMeasureController {
    @Resource
    private MdmUnitMeasureService unitMeasureService;
    @PostMapping("/create")
    @Operation(summary = "创建计量单位")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:create')")
    public CommonResult<Long> createUnitMeasure(@Valid @RequestBody MdmUnitMeasureSaveReqVO createReqVO) {
        return success(unitMeasureService.createUnitMeasure(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新计量单位")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:update')")
    public CommonResult<Boolean> updateUnitMeasure(@Valid @RequestBody MdmUnitMeasureSaveReqVO updateReqVO) {
        unitMeasureService.updateUnitMeasure(updateReqVO);
        return success(true);
    }
    @PutMapping("/update-status")
    @Operation(summary = "更新计量单位状态")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @Parameter(name = "status", description = "状态", required = true, example = "0")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:update')")
    public CommonResult<Boolean> updateUnitMeasureStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
        unitMeasureService.updateUnitMeasureStatus(id, status);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除计量单位")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:delete')")
    public CommonResult<Boolean> deleteUnitMeasure(@RequestParam("id") Long id) {
        unitMeasureService.deleteUnitMeasure(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得计量单位")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    public CommonResult<MdmUnitMeasureDO> getUnitMeasure(@RequestParam("id") Long id) {
        return success(unitMeasureService.getUnitMeasure(id));
    }
    @GetMapping("/page")
    @Operation(summary = "获得计量单位分页")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    public CommonResult<PageResult<MdmUnitMeasureDO>> getUnitMeasurePage(@Valid MdmUnitMeasurePageReqVO pageReqVO) {
        return success(unitMeasureService.getUnitMeasurePage(pageReqVO));
    }
    @GetMapping("/list")
    @Operation(summary = "获得计量单位列表")
    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:query')")
    public CommonResult<List<MdmUnitMeasureDO>> getUnitMeasureList() {
        return success(unitMeasureService.getUnitMeasureList(null));
    }
}
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;
}
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;
}
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));
    }
}
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;
}
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 = "负责人用户ID", 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;
}
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 = "负责人用户ID", 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;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/brand/MdmBrandDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.brand;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * MDM å“ç‰Œ DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("md_brand")
@KeySequence("md_brand_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmBrandDO extends BaseDO {
    /**
     * å“ç‰Œç¼–号
     */
    @TableId
    private Long id;
    /**
     * å“ç‰Œç¼–码
     */
    private String code;
    /**
     * å“ç‰Œåç§°
     */
    private String name;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/category/MdmItemCategoryDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.category;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * MDM ç‰©æ–™åˆ†ç±» DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("md_item_category")
@KeySequence("md_item_category_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmItemCategoryDO extends BaseDO {
    /**
     * åˆ†ç±»ç¼–号
     */
    @TableId
    private Long id;
    /**
     * çˆ¶åˆ†ç±»ç¼–号(根节点=0)
     */
    private Long parentId;
    /**
     * åˆ†ç±»ç¼–码
     */
    private String code;
    /**
     * åˆ†ç±»åç§°
     */
    private String name;
    /**
     * é€‚用物料类型
     *
     * 1: åŽŸæ–™
     * 2: åŠæˆå“
     * 3: æˆå“
     * 4: è¾…æ–™
     * NULL: å…¨éƒ¨
     */
    private Integer itemType;
    /**
     * æŽ’序
     */
    private Integer sort;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemBatchConfigDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * MDM ç‰©æ–™æ‰¹æ¬¡å±žæ€§é…ç½® DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("md_item_batch_config")
@KeySequence("md_item_batch_config_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmItemBatchConfigDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * ç‰©æ–™ç¼–号
     *
     * å…³è” {@link MdmItemDO#getId()}
     */
    private Long itemId;
    /**
     * ç”Ÿäº§æ—¥æœŸ
     */
    private Boolean produceDateFlag;
    /**
     * æœ‰æ•ˆæœŸ
     */
    private Boolean expireDateFlag;
    /**
     * å…¥åº“日期
     */
    private Boolean receiptDateFlag;
    /**
     * ä¾›åº”商
     */
    private Boolean vendorFlag;
    /**
     * å®¢æˆ·
     */
    private Boolean clientFlag;
    /**
     * é”€å”®è®¢å•编号
     */
    private Boolean salesOrderCodeFlag;
    /**
     * é‡‡è´­è®¢å•编号
     */
    private Boolean purchaseOrderCodeFlag;
    /**
     * ç”Ÿäº§å·¥å•
     */
    private Boolean workOrderFlag;
    /**
     * ç”Ÿäº§ä»»åŠ¡
     */
    private Boolean taskFlag;
    /**
     * å·¥ä½œç«™
     */
    private Boolean workstationFlag;
    /**
     * å·¥å…·
     */
    private Boolean toolFlag;
    /**
     * æ¨¡å…·
     */
    private Boolean moldFlag;
    /**
     * ç”Ÿäº§æ‰¹å·
     */
    private Boolean lotNumberFlag;
    /**
     * è´¨é‡çŠ¶æ€
     */
    private Boolean qualityStatusFlag;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,177 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.brand.MdmBrandDO;
import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * MDM ç‰©æ–™ä¸»æ•°æ® DO
 *
 * ç»Ÿä¸€ç‰©æ–™æ•°æ®ï¼ŒERP/MES/WMS å…±ç”¨
 *
 * @author èŠ‹é“æºç 
 */
@TableName("md_item")
@KeySequence("md_item_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmItemDO extends BaseDO {
    /**
     * ç‰©æ–™ID(全局唯一)
     */
    @TableId
    private Long id;
    /**
     * ç‰©æ–™ç¼–码(全局唯一)
     */
    private String code;
    /**
     * ç‰©æ–™åç§°
     */
    private String name;
    /**
     * ç‰©æ–™æ¡ç 
     */
    private String barCode;
    /**
     * è§„格型号
     */
    private String specification;
    /**
     * ç‰©æ–™åˆ†ç±»ç¼–号
     *
     * å…³è” {@link MdmItemCategoryDO#getId()}
     */
    private Long categoryId;
    /**
     * ä¸»è®¡é‡å•位编号
     *
     * å…³è” {@link MdmUnitMeasureDO#getId()}
     */
    private Long unitMeasureId;
    /**
     * å“ç‰Œç¼–号
     *
     * å…³è” {@link MdmBrandDO#getId()}
     */
    private Long brandId;
    /**
     * ç‰©æ–™ç±»åž‹
     *
     * æžšä¸¾ {@link MdmItemTypeEnum}
     * 1: åŽŸæ–™
     * 2: åŠæˆå“
     * 3: æˆå“
     * 4: è¾…æ–™
     */
    private Integer itemType;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
     */
    private Integer status;
    /**
     * é‡‡è´­ä»·
     */
    private BigDecimal purchasePrice;
    /**
     * é”€å”®ä»·
     */
    private BigDecimal salesPrice;
    /**
     * æˆæœ¬ä»·
     */
    private BigDecimal costPrice;
    /**
     * æœ€ä½Žä»·
     */
    private BigDecimal minPrice;
    /**
     * å®‰å…¨åº“å­˜
     */
    private BigDecimal safetyStock;
    /**
     * æœ€ä½Žåº“存量
     */
    private BigDecimal minStock;
    /**
     * æœ€é«˜åº“存量
     */
    private BigDecimal maxStock;
    /**
     * æœ€å°è®¢è´§é‡
     */
    private BigDecimal minOrderQty;
    /**
     * é‡‡è´­æå‰æœŸï¼ˆå¤©ï¼‰
     */
    private Integer leadTime;
    /**
     * æ˜¯å¦æ‰¹æ¬¡ç®¡ç†
     */
    private Boolean isBatchManaged;
    /**
     * æ˜¯å¦åºåˆ—号管理
     */
    private Boolean isSerialManaged;
    /**
     * æ¡ç è§„则ID
     */
    private Long barcodeRuleId;
    /**
     * ä¿è´¨æœŸå¤©æ•°
     */
    private Integer expiryDay;
    /**
     * æ˜¯å¦é«˜å€¼ç‰©æ–™
     */
    private Boolean highValue;
    /**
     * é‡é‡(kg)
     */
    private BigDecimal weight;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
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;
    /**
     * é•¿åº¦ï¼ˆcm)
     */
    private BigDecimal length;
    /**
     * å®½åº¦ï¼ˆcm)
     */
    private BigDecimal width;
    /**
     * é«˜åº¦ï¼ˆcm)
     */
    private BigDecimal height;
    /**
     * æ¯›é‡ï¼ˆkg)
     */
    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;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.unit;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * MDM è®¡é‡å•位 DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("md_unit_measure")
@KeySequence("md_unit_measure_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmUnitMeasureDO extends BaseDO {
    /**
     * å•位编号
     */
    @TableId
    private Long id;
    /**
     * å•位编码
     */
    private String code;
    /**
     * å•位名称
     */
    private String name;
    /**
     * æ˜¯å¦ä¸»å•位
     */
    private Boolean primaryFlag;
    /**
     * ä¸»å•位编号
     */
    private Long primaryId;
    /**
     * ä¸Žä¸»å•位换算比例
     */
    private BigDecimal changeRate;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/warehouse/MdmWarehouseDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * MDM ä»“库主数据 DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("md_warehouse")
@KeySequence("md_warehouse_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmWarehouseDO extends BaseDO {
    /**
     * ä»“库ID
     */
    @TableId
    private Long id;
    /**
     * ä»“库编码
     */
    private String code;
    /**
     * ä»“库名称
     */
    private String name;
    /**
     * ä»“库地址
     */
    private String address;
    /**
     * é¢ç§¯ï¼ˆå¹³æ–¹ç±³ï¼‰
     */
    private BigDecimal area;
    /**
     * ä»“库类型
     *
     * 1: åŽŸæ–™ä»“
     * 2: æˆå“ä»“
     * 3: åŠæˆå“ä»“
     * 4: çº¿è¾¹ä»“
     */
    private Integer type;
    /**
     * è´Ÿè´£äººç”¨æˆ·ID
     */
    private Long chargeUserId;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
     */
    private Integer status;
    /**
     * æ˜¯å¦å†»ç»“
     */
    private Boolean frozen;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
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);
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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, "仓库已禁用");
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.mdm.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * MDM ç‰©æ–™ç±»åž‹æžšä¸¾
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum MdmItemTypeEnum {
    RAW_MATERIAL(1, "原料"),
    SEMI_FINISHED(2, "半成品"),
    FINISHED(3, "成品"),
    AUXILIARY(4, "辅料");
    /**
     * ç±»åž‹ç¼–码
     */
    private final Integer type;
    /**
     * ç±»åž‹åç§°
     */
    private final String name;
    /**
     * æ ¹æ®ç±»åž‹ç¼–码获取枚举
     */
    public static MdmItemTypeEnum fromType(Integer type) {
        for (MdmItemTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value;
            }
        }
        return null;
    }
}
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);
}
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);
    }
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
package cn.iocoder.yudao.module.mdm.service.category;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategorySaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
 * MDM ç‰©æ–™åˆ†ç±» Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface MdmItemCategoryService {
    /**
     * åˆ›å»ºç‰©æ–™åˆ†ç±»
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return ç¼–号
     */
    Long createItemCategory(@Valid MdmItemCategorySaveReqVO createReqVO);
    /**
     * æ›´æ–°ç‰©æ–™åˆ†ç±»
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateItemCategory(@Valid MdmItemCategorySaveReqVO updateReqVO);
    /**
     * æ›´æ–°ç‰©æ–™åˆ†ç±»çŠ¶æ€
     *
     * @param id     ç¼–号
     * @param status çŠ¶æ€
     */
    void updateItemCategoryStatus(Long id, Integer status);
    /**
     * åˆ é™¤ç‰©æ–™åˆ†ç±»
     *
     * @param id ç¼–号
     */
    void deleteItemCategory(Long id);
    /**
     * èŽ·å¾—ç‰©æ–™åˆ†ç±»
     *
     * @param id ç¼–号
     * @return ç‰©æ–™åˆ†ç±»
     */
    MdmItemCategoryDO getItemCategory(Long id);
    /**
     * æ ¡éªŒç‰©æ–™åˆ†ç±»å­˜åœ¨
     *
     * @param id ç¼–号
     * @return ç‰©æ–™åˆ†ç±»
     */
    MdmItemCategoryDO validateItemCategoryExists(Long id);
    /**
     * èŽ·å¾—ç‰©æ–™åˆ†ç±»åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return ç‰©æ–™åˆ†ç±»åˆ†é¡µ
     */
    PageResult<MdmItemCategoryDO> getItemCategoryPage(MdmItemCategoryPageReqVO pageReqVO);
    /**
     * èŽ·å¾—ç‰©æ–™åˆ†ç±»åˆ—è¡¨
     *
     * @param ids ç¼–号数组
     * @return ç‰©æ–™åˆ†ç±»åˆ—表
     */
    List<MdmItemCategoryDO> getItemCategoryList(Collection<Long> ids);
    /**
     * èŽ·å¾—ç‰©æ–™åˆ†ç±» Map
     *
     * @param ids ç¼–号数组
     * @return ç‰©æ–™åˆ†ç±» Map
     */
    default Map<Long, MdmItemCategoryDO> getItemCategoryMap(Collection<Long> ids) {
        return convertMap(getItemCategoryList(ids), MdmItemCategoryDO::getId);
    }
    /**
     * èŽ·å¾—æŒ‡å®šåˆ†ç±»çš„å­åˆ†ç±»åˆ—è¡¨ï¼ˆåŒ…å«è‡ªå·±ï¼‰
     *
     * @param id åˆ†ç±»ç¼–号
     * @return å­åˆ†ç±»åˆ—表
     */
    List<MdmItemCategoryDO> getItemCategoryChildrenList(Long id);
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/category/MdmItemCategoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
package cn.iocoder.yudao.module.mdm.service.category;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategoryPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.category.vo.MdmItemCategorySaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
import cn.iocoder.yudao.module.mdm.dal.mysql.category.MdmItemCategoryMapper;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
/**
 * MDM ç‰©æ–™åˆ†ç±» Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
public class MdmItemCategoryServiceImpl implements MdmItemCategoryService {
    @Resource
    private MdmItemCategoryMapper itemCategoryMapper;
    @Resource
    private MdmItemMapper itemMapper;
    @Override
    public Long createItemCategory(MdmItemCategorySaveReqVO createReqVO) {
        // æ ¡éªŒæ•°æ®
        validateItemCategorySaveData(createReqVO);
        // æ’å…¥
        MdmItemCategoryDO itemCategory = BeanUtils.toBean(createReqVO, MdmItemCategoryDO.class);
        itemCategoryMapper.insert(itemCategory);
        return itemCategory.getId();
    }
    @Override
    public void updateItemCategory(MdmItemCategorySaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨
        validateItemCategoryExists(updateReqVO.getId());
        // æ ¡éªŒæ•°æ®
        validateItemCategorySaveData(updateReqVO);
        // æ›´æ–°
        MdmItemCategoryDO updateObj = BeanUtils.toBean(updateReqVO, MdmItemCategoryDO.class);
        itemCategoryMapper.updateById(updateObj);
    }
    @Override
    public void updateItemCategoryStatus(Long id, Integer status) {
        // æ ¡éªŒå­˜åœ¨
        validateItemCategoryExists(id);
        // æ›´æ–°çŠ¶æ€
        itemCategoryMapper.updateById(new MdmItemCategoryDO().setId(id).setStatus(status));
    }
    @Override
    public void deleteItemCategory(Long id) {
        // æ ¡éªŒå­˜åœ¨
        validateItemCategoryExists(id);
        // æ ¡éªŒæ˜¯å¦æœ‰å­åˆ†ç±»
        if (itemCategoryMapper.selectCountByParentId(id) > 0) {
            throw exception(MDM_ITEM_CATEGORY_EXISTS_CHILDREN);
        }
        // æ ¡éªŒåˆ†ç±»ä¸‹æ˜¯å¦æœ‰ç‰©æ–™ï¼ˆç›´æŽ¥ä½¿ç”¨ Mapper é¿å…å¾ªçŽ¯ä¾èµ–ï¼‰
        if (itemMapper.selectCountByCategoryId(id) > 0) {
            throw exception(MDM_ITEM_CATEGORY_EXISTS_ITEM);
        }
        // åˆ é™¤
        itemCategoryMapper.deleteById(id);
    }
    @Override
    public MdmItemCategoryDO validateItemCategoryExists(Long id) {
        MdmItemCategoryDO itemCategory = itemCategoryMapper.selectById(id);
        if (itemCategory == null) {
            throw exception(MDM_ITEM_CATEGORY_NOT_EXISTS);
        }
        return itemCategory;
    }
    private void validateItemCategorySaveData(MdmItemCategorySaveReqVO reqVO) {
        // æ ¡éªŒç¼–码唯一
        MdmItemCategoryDO category = itemCategoryMapper.selectByCode(reqVO.getCode());
        if (category != null && ObjUtil.notEqual(category.getId(), reqVO.getId())) {
            throw exception(MDM_ITEM_CATEGORY_CODE_DUPLICATE);
        }
        // æ ¡éªŒåŒä¸€çˆ¶çº§ä¸‹åç§°å”¯ä¸€
        MdmItemCategoryDO sameName = itemCategoryMapper.selectByParentIdAndName(
                reqVO.getParentId() != null ? reqVO.getParentId() : 0L, reqVO.getName());
        if (sameName != null && ObjUtil.notEqual(sameName.getId(), reqVO.getId())) {
            throw exception(MDM_ITEM_CATEGORY_NAME_DUPLICATE);
        }
        // æ ¡éªŒçˆ¶åˆ†ç±»å­˜åœ¨
        if (reqVO.getParentId() != null && reqVO.getParentId() > 0) {
            if (itemCategoryMapper.selectById(reqVO.getParentId()) == null) {
                throw exception(MDM_ITEM_CATEGORY_PARENT_NOT_EXISTS);
            }
        }
    }
    @Override
    public MdmItemCategoryDO getItemCategory(Long id) {
        return itemCategoryMapper.selectById(id);
    }
    @Override
    public PageResult<MdmItemCategoryDO> getItemCategoryPage(MdmItemCategoryPageReqVO pageReqVO) {
        return itemCategoryMapper.selectPage(pageReqVO);
    }
    @Override
    public List<MdmItemCategoryDO> getItemCategoryList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return itemCategoryMapper.selectBatchIds(ids);
    }
    @Override
    public List<MdmItemCategoryDO> getItemCategoryChildrenList(Long id) {
        List<MdmItemCategoryDO> result = new ArrayList<>();
        // æ·»åŠ è‡ªå·±
        MdmItemCategoryDO self = itemCategoryMapper.selectById(id);
        if (self != null) {
            result.add(self);
        }
        // é€’归获取所有子分类
        loadChildren(id, result);
        return result;
    }
    private void loadChildren(Long parentId, List<MdmItemCategoryDO> result) {
        List<MdmItemCategoryDO> children = itemCategoryMapper.selectListByParentId(parentId);
        if (CollUtil.isNotEmpty(children)) {
            result.addAll(children);
            for (MdmItemCategoryDO child : children) {
                loadChildren(child.getId(), result);
            }
        }
    }
}
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);
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
package cn.iocoder.yudao.module.mdm.service.item;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
/**
 * MDM ç‰©æ–™ä¸»æ•°æ® Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
public class MdmItemServiceImpl implements MdmItemService {
    @Resource
    private MdmItemMapper itemMapper;
    @Resource
    private MdmItemCategoryService itemCategoryService;
    @Resource
    private MdmUnitMeasureService unitMeasureService;
    @Resource
    private MdmBrandService brandService;
    @Override
    public Long createItem(MdmItemSaveReqVO createReqVO) {
        // æ ¡éªŒæ•°æ®
        validateItemSaveData(createReqVO);
        // æ’å…¥
        MdmItemDO item = BeanUtils.toBean(createReqVO, MdmItemDO.class);
        itemMapper.insert(item);
        return item.getId();
    }
    @Override
    public void updateItem(MdmItemSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨
        validateItemExists(updateReqVO.getId());
        // æ ¡éªŒæ•°æ®
        validateItemSaveData(updateReqVO);
        // æ›´æ–°
        MdmItemDO updateObj = BeanUtils.toBean(updateReqVO, MdmItemDO.class);
        itemMapper.updateById(updateObj);
    }
    private void validateItemSaveData(MdmItemSaveReqVO reqVO) {
        // æ ¡éªŒç‰©æ–™ç¼–码的唯一性
        validateItemCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒç‰©æ–™åˆ†ç±»å­˜åœ¨
        validateCategoryExists(reqVO.getCategoryId());
        // æ ¡éªŒè®¡é‡å•位存在
        validateUnitMeasureExists(reqVO.getUnitMeasureId());
        // æ ¡éªŒå“ç‰Œå­˜åœ¨ï¼ˆå¦‚果有)
        if (reqVO.getBrandId() != null) {
            validateBrandExists(reqVO.getBrandId());
        }
    }
    @Override
    public void updateItemStatus(Long id, Integer status) {
        // æ ¡éªŒå­˜åœ¨
        validateItemExists(id);
        // æ›´æ–°çŠ¶æ€
        itemMapper.updateById(new MdmItemDO().setId(id).setStatus(status));
    }
    @Override
    public void deleteItem(Long id) {
        // æ ¡éªŒå­˜åœ¨
        validateItemExists(id);
        // åˆ é™¤
        itemMapper.deleteById(id);
    }
    @Override
    public MdmItemDO validateItemExists(Long id) {
        MdmItemDO item = itemMapper.selectById(id);
        if (item == null) {
            throw exception(MDM_ITEM_NOT_EXISTS);
        }
        return item;
    }
    @Override
    public MdmItemDO validateItemExistsAndEnable(Long id) {
        MdmItemDO item = validateItemExists(id);
        if (ObjUtil.notEqual(cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus(), item.getStatus())) {
            throw exception(MDM_ITEM_IS_DISABLE);
        }
        return item;
    }
    private void validateItemCodeUnique(Long id, String code) {
        MdmItemDO item = itemMapper.selectByCode(code);
        if (item == null) {
            return;
        }
        if (ObjUtil.notEqual(item.getId(), id)) {
            throw exception(MDM_ITEM_CODE_DUPLICATE);
        }
    }
    private void validateCategoryExists(Long categoryId) {
        if (itemCategoryService.getItemCategory(categoryId) == null) {
            throw exception(MDM_ITEM_CATEGORY_NOT_EXISTS);
        }
    }
    private void validateUnitMeasureExists(Long unitMeasureId) {
        if (unitMeasureService.getUnitMeasure(unitMeasureId) == null) {
            throw exception(MDM_UNIT_MEASURE_NOT_EXISTS);
        }
    }
    private void validateBrandExists(Long brandId) {
        if (brandService.getBrand(brandId) == null) {
            throw exception(MDM_BRAND_NOT_EXISTS);
        }
    }
    @Override
    public MdmItemDO getItem(Long id) {
        return itemMapper.selectById(id);
    }
    @Override
    public PageResult<MdmItemDO> getItemPage(MdmItemPageReqVO pageReqVO) {
        // æŸ¥æ‰¾æ—¶ï¼Œå¦‚果查找某个分类编号,则包含它的子分类
        Set<Long> categoryIds = null;
        if (pageReqVO.getCategoryId() != null) {
            categoryIds = new HashSet<>();
            categoryIds.add(pageReqVO.getCategoryId());
            List<cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO> children =
                    itemCategoryService.getItemCategoryChildrenList(pageReqVO.getCategoryId());
            categoryIds.addAll(convertSet(children, cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO::getId));
        }
        // åˆ†é¡µæŸ¥è¯¢
        return itemMapper.selectPage(pageReqVO, categoryIds);
    }
    @Override
    public List<MdmItemDO> getItemList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return itemMapper.selectBatchIds(ids);
    }
    @Override
    public Long getItemCountByCategoryId(Long categoryId) {
        return itemMapper.selectCountByCategoryId(categoryId);
    }
    @Override
    public Long getItemCountByUnitMeasureId(Long unitMeasureId) {
        return itemMapper.selectCountByUnitMeasureId(unitMeasureId);
    }
    @Override
    public Long getItemCountByBrandId(Long brandId) {
        return itemMapper.selectCountByBrandId(brandId);
    }
    @Override
    public MdmItemDO getItemByCode(String code) {
        return itemMapper.selectByCode(code);
    }
}
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);
}
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);
    }
}
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);
}
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);
    }
}
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);
}
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);
    }
}
yudao-module-mes/pom.xml
@@ -24,6 +24,20 @@
            <version>${revision}</version>
        </dependency>
        <!-- MDM ä¸»æ•°æ®ç®¡ç†æ¨¡å— -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mdm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- BPM æµç¨‹æ¨¡å— -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-bpm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ä¸šåŠ¡ç»„ä»¶ -->
        <!-- Web ç›¸å…³ -->
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
@@ -38,6 +38,7 @@
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - MES ç”Ÿäº§å·¥å•")
@RestController
@@ -139,6 +140,15 @@
        return success(true);
    }
    @PutMapping("/submit")
    @Operation(summary = "提交工单审批")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')")
    public CommonResult<Boolean> submitWorkOrder(@RequestParam("id") Long id) {
        workOrderService.submitWorkOrder(id, getLoginUserId());
        return success(true);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
    private List<MesProWorkOrderRespVO> buildWorkOrderRespVOList(List<MesProWorkOrderDO> list) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
@@ -131,6 +131,10 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
    /**
     * å¤‡æ³¨
     */
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -263,6 +263,8 @@
    ErrorCode PRO_WORK_ORDER_NOT_CONFIRMED = new ErrorCode(1_040_502_003, "只有已确认状态的工单才能执行此操作");
    ErrorCode PRO_WORK_ORDER_HAS_CHILDREN = new ErrorCode(1_040_502_004, "存在子工单,无法删除");
    ErrorCode PRO_WORK_ORDER_PRODUCT_MISMATCH = new ErrorCode(1_040_502_005, "当前产品物料与生产工单产品不一致");
    ErrorCode PRO_WORK_ORDER_SUBMIT_FAIL_NOT_PREPARE = new ErrorCode(1_040_502_006, "工单提交失败,只有草稿状态的工单才能提交");
    ErrorCode PRO_WORK_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_AUDITING = new ErrorCode(1_040_502_007, "工单审批状态更新失败,工单不处于审批中");
    ErrorCode PRO_WORK_ORDER_BOM_NOT_EXISTS = new ErrorCode(1_040_502_100, "生产工单BOM不存在");
    // ========== MES ç”Ÿäº§ç®¡ç†-生产任务(1-040-503-000) ==========
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderStatusEnum.java
@@ -22,23 +22,33 @@
     */
    PREPARE(0, "草稿"),
    /**
     * å®¡æ‰¹ä¸­
     *
     * å¯¹åº” MesProWorkOrderService#submitWorkOrder æ–¹æ³•
     */
    AUDITING(1, "审批中"),
    /**
     * å·²ç¡®è®¤
     *
     * å¯¹åº” MesProWorkOrderService#confirmWorkOrder æ–¹æ³•
     * å¯¹åº” MesProWorkOrderService#confirmWorkOrder æ–¹æ³•(审批通过后自动确认)
     */
    CONFIRMED(1, "已确认"),
    CONFIRMED(2, "已确认"),
    /**
     * å·²å®Œæˆ
     *
     * å¯¹åº” MesProWorkOrderService#finishWorkOrder æ–¹æ³•
     */
    FINISHED(2, "已完成"),
    FINISHED(3, "已完成"),
    /**
     * å·²å–消
     *
     * å¯¹åº” MesProWorkOrderService#cancelWorkOrder æ–¹æ³•
     */
    CANCELED(3, "已取消");
    CANCELED(4, "已取消"),
    /**
     * å®¡æ‰¹ä¸é€šè¿‡
     */
    REJECTED(5, "审批不通过");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProWorkOrderStatusEnum::getStatus).toArray(Integer[]::new);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
@@ -6,6 +6,8 @@
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemPageReqVO;
@@ -38,6 +40,10 @@
/**
 * MES ç‰©æ–™äº§å“ Service å®žçŽ°ç±»
 *
 * æ”¹é€ è¯´æ˜Žï¼šMES ç‰©æ–™ä¸»æ•°æ®æ¥æºäºŽ MDM
 * - getItem/getItemList/validateItemExists è°ƒç”¨ MDM API
 * - MES ç‰¹æœ‰é…ç½®ï¼ˆæ‰¹æ¬¡é…ç½®ã€BOM、SOP、SIP)保留在 MES æ¨¡å—管理
 *
 * @author èŠ‹é“æºç 
 */
@Service
@@ -46,6 +52,9 @@
    @Resource
    private MesMdItemMapper itemMapper;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MesMdItemTypeService itemTypeService;
@@ -147,20 +156,14 @@
    @Override
    public MesMdItemDO validateItemExists(Long id) {
        MesMdItemDO item = itemMapper.selectById(id);
        if (item == null) {
            throw exception(MD_ITEM_NOT_EXISTS);
        }
        return item;
        MdmItemRespDTO item = mdmItemApi.validateItemExists(id).getCheckedData();
        return BeanUtils.toBean(item, MesMdItemDO.class);
    }
    @Override
    public MesMdItemDO validateItemExistsAndEnable(Long id) {
        MesMdItemDO item = validateItemExists(id);
        if (ObjUtil.notEqual(CommonStatusEnum.ENABLE.getStatus(), item.getStatus())) {
            throw exception(MD_ITEM_IS_DISABLE);
        }
        return item;
        MdmItemRespDTO item = mdmItemApi.validateItemExistsAndEnable(id).getCheckedData();
        return BeanUtils.toBean(item, MesMdItemDO.class);
    }
    private void validateItemCodeUnique(Long id, String code) {
@@ -239,21 +242,18 @@
    @Override
    public MesMdItemDO getItem(Long id) {
        return itemMapper.selectById(id);
        // ä»Ž MDM èŽ·å–ç‰©æ–™ä¸»æ•°æ®
        MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
        return BeanUtils.toBean(item, MesMdItemDO.class);
    }
    @Override
    public PageResult<MesMdItemDO> getItemPage(MesMdItemPageReqVO pageReqVO) {
        // æŸ¥æ‰¾æ—¶ï¼Œå¦‚果查找某个分类编号,则包含它的子分类
        Set<Long> itemTypeIds = null;
        if (pageReqVO.getItemTypeId() != null) {
            itemTypeIds = new HashSet<>();
            itemTypeIds.add(pageReqVO.getItemTypeId());
            List<MesMdItemTypeDO> children = itemTypeService.getItemTypeChildrenList(pageReqVO.getItemTypeId());
            itemTypeIds.addAll(convertSet(children, MesMdItemTypeDO::getId));
        }
        // åˆ†é¡µæŸ¥è¯¢
        return itemMapper.selectPage(pageReqVO, itemTypeIds);
        // è°ƒç”¨ MDM API åˆ†é¡µæŸ¥è¯¢ï¼ˆMES ç‰©æ–™å¯¹åº” MDM ç‰©æ–™ç±»åž‹=物料)
        cn.iocoder.yudao.framework.common.pojo.PageResult<MdmItemRespDTO> mdmPageResult =
                mdmItemApi.getItemPage(1, pageReqVO.getStatus(), pageReqVO.getPageNo(), pageReqVO.getPageSize()).getCheckedData();
        List<MesMdItemDO> itemList = BeanUtils.toBean(mdmPageResult.getList(), MesMdItemDO.class);
        return new PageResult<>(itemList, mdmPageResult.getTotal());
    }
    @Override
@@ -261,17 +261,19 @@
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return itemMapper.selectByIds(ids);
        List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
        return BeanUtils.toBean(itemList, MesMdItemDO.class);
    }
    @Override
    public Long getItemCountByItemTypeId(Long itemTypeId) {
        return itemMapper.selectCountByItemTypeId(itemTypeId);
        // MES åˆ†ç±»éœ€è¦æ˜ å°„到 MDM åˆ†ç±»
        return mdmItemApi.getItemCountByCategoryId(itemTypeId).getCheckedData();
    }
    @Override
    public Long getItemCountByUnitMeasureId(Long unitMeasureId) {
        return itemMapper.selectCountByUnitMeasureId(unitMeasureId);
        return mdmItemApi.getItemCountByUnitMeasureId(unitMeasureId).getCheckedData();
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java
@@ -122,6 +122,22 @@
    void cancelWorkOrder(Long id);
    /**
     * æäº¤å·¥å•审批
     *
     * @param id ç¼–号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitWorkOrder(Long id, Long userId);
    /**
     * æ›´æ–°å·¥å•的审批状态
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updateWorkOrderAuditStatus(Long id, Integer bpmResult);
    /**
     * ç´¯åŠ å·¥å•çš„å·²ç”Ÿäº§æ•°é‡
     *
     * @param id                   å·¥å•编号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
@@ -4,6 +4,8 @@
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemBatchConfigDO;
@@ -16,6 +18,7 @@
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -36,7 +39,13 @@
 */
@Service
@Validated
@Slf4j
public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
    /**
     * BPM ç”Ÿäº§å·¥å•审批流程标识
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "mes-pro-work-order-audit";
    @Resource
    private MesProWorkOrderMapper workOrderMapper;
@@ -51,6 +60,9 @@
    private MesWmBarcodeService barcodeService;
    @Resource
    private MesProTaskService taskService;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -250,4 +262,67 @@
        return workOrderMapper.selectCountByVendorId(vendorId);
    }
    // ==================== BPM æµç¨‹å®¡æ‰¹ ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitWorkOrder(Long id, Long userId) {
        // 1. æ ¡éªŒå·¥å•处于草稿状态
        MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
        if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) {
            throw exception(PRO_WORK_ORDER_SUBMIT_FAIL_NOT_PREPARE);
        }
        // 2. åˆ›å»ºç”Ÿäº§å·¥å•审批流程实例
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
                .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°å·¥å•工作流编号、状态为审批中
        workOrderMapper.updateById(new MesProWorkOrderDO()
                .setId(id)
                .setProcessInstanceId(processInstanceId)
                .setStatus(MesProWorkOrderStatusEnum.AUDITING.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateWorkOrderAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒå·¥å•存在
        MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
        // 2. æ ¡éªŒå·¥å•处于审批中
        if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.AUDITING.getStatus())) {
            log.error("[updateWorkOrderAuditStatus][workOrder({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    workOrder.getId(), bpmResult);
            throw exception(PRO_WORK_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_AUDITING);
        }
        // 3. æ›´æ–°å·¥å•审批结果
        Integer auditStatus = convertBpmResultToWorkOrderStatus(bpmResult);
        workOrderMapper.updateById(new MesProWorkOrderDO()
                .setId(id)
                .setStatus(auditStatus));
    }
    /**
     * å°† BPM å®¡æ‰¹ç»“果转换为 MES å·¥å•状态
     *
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @return MES å·¥å•状态
     */
    private Integer convertBpmResultToWorkOrderStatus(Integer bpmResult) {
        // BPM å®¡æ‰¹ç»“果:2=通过,3=不通过,4=已取消
        if (bpmResult == 2) {
            return MesProWorkOrderStatusEnum.CONFIRMED.getStatus();
        } else if (bpmResult == 3) {
            return MesProWorkOrderStatusEnum.REJECTED.getStatus();
        } else if (bpmResult == 4) {
            return MesProWorkOrderStatusEnum.PREPARE.getStatus();
        } else {
            log.warn("[convertBpmResultToWorkOrderStatus][未知的 BPM å®¡æ‰¹ç»“æžœ({})]", bpmResult);
            return MesProWorkOrderStatusEnum.AUDITING.getStatus();
        }
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/listener/MesProWorkOrderStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.mes.service.pro.workorder.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
 * ç”Ÿäº§å·¥å•审批的结果的监听器实现类
 *
 * @author èŠ‹é“æºç 
 */
@Component
public class MesProWorkOrderStatusListener extends BpmProcessInstanceStatusEventListener {
    @Resource
    private MesProWorkOrderService workOrderService;
    @Override
    public String getProcessDefinitionKey() {
        return MesProWorkOrderServiceImpl.BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        workOrderService.updateWorkOrderAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
    }
}
yudao-module-wms/pom.xml
@@ -24,6 +24,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- MDM ä¸»æ•°æ®ç®¡ç†æ¨¡å— -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mdm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ä¸šåŠ¡ç»„ä»¶ -->
        <!-- Web ç›¸å…³ -->
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.wms.api.inventory;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
import java.math.BigDecimal;
import java.util.List;
/**
 * WMS åº“存操作 API æŽ¥å£
 *
 * æä¾›ç»™ ERP、MES ç­‰æ¨¡å—调用,实现跨模块库存操作
 *
 * @author èŠ‹é“æºç 
 */
public interface WmsInventoryApi {
    String PREFIX = "/rpc-api/wms/inventory";
    // ==================== å…¥åº“操作 ====================
    /**
     * å…¥åº“操作
     *
     * @param reqDTO å…¥åº“请求
     * @return å…¥åº“结果
     */
    CommonResult<WmsReceiptResultDTO> receipt(WmsReceiptReqDTO reqDTO);
    // ==================== å‡ºåº“操作 ====================
    /**
     * å‡ºåº“操作
     *
     * @param reqDTO å‡ºåº“请求
     * @return å‡ºåº“结果
     */
    CommonResult<WmsShipmentResultDTO> shipment(WmsShipmentReqDTO reqDTO);
    // ==================== åº“存预留 ====================
    /**
     * åº“存预留(锁库存)
     *
     * @param reqDTO é¢„留请求
     * @return é¢„留单号
     */
    CommonResult<String> reserve(WmsReserveReqDTO reqDTO);
    /**
     * é‡Šæ”¾åº“存预留
     *
     * @param reserveNo é¢„留单号
     */
    CommonResult<Boolean> releaseReserve(String reserveNo);
    /**
     * ä½¿ç”¨é¢„留库存(出库时扣减预留)
     *
     * @param reqDTO ä½¿ç”¨é¢„留请求
     */
    CommonResult<Boolean> useReserve(WmsUseReserveReqDTO reqDTO);
    // ==================== åº“存查询 ====================
    /**
     * æŸ¥è¯¢åº“å­˜
     *
     * @param reqDTO æŸ¥è¯¢è¯·æ±‚
     * @return åº“存列表
     */
    CommonResult<List<WmsInventoryRespDTO>> getInventoryList(WmsInventoryQueryReqDTO reqDTO);
    /**
     * æŸ¥è¯¢å¯ç”¨åº“存(总库存 - é¢„留库存)
     *
     * @param skuId SKU ç¼–号
     * @param warehouseId ä»“库编号
     * @return å¯ç”¨åº“存数量
     */
    CommonResult<BigDecimal> getAvailableQuantity(Long skuId, Long warehouseId);
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/WmsInventoryApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,266 @@
package cn.iocoder.yudao.module.wms.api.inventory;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsTransactionTypeEnum;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryReserveService;
import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService;
import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryTransactionService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * WMS åº“存操作 API å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@RestController
@RequestMapping(WmsInventoryApi.PREFIX)
@Service
@Validated
@Slf4j
public class WmsInventoryApiImpl implements WmsInventoryApi {
    @Resource
    private WmsInventoryService inventoryService;
    @Resource
    private WmsInventoryReserveService reserveService;
    @Resource
    private WmsInventoryTransactionService transactionService;
    @Resource
    private WmsInventoryMapper inventoryMapper;
    // ==================== å…¥åº“操作 ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<WmsReceiptResultDTO> receipt(WmsReceiptReqDTO reqDTO) {
        // 1. æž„建库存变更请求
        WmsInventoryChangeReqDTO changeReq = buildInventoryChangeReq(reqDTO);
        // 2. æ‰§è¡Œå…¥åº“变更
        inventoryService.changeInventory(changeReq);
        // 3. è®°å½•事务流水
        List<WmsInventoryTransactionDO> transactions = buildReceiptTransactions(reqDTO);
        transactionService.createTransactionList(transactions);
        // 4. æž„建返回结果
        WmsReceiptResultDTO result = new WmsReceiptResultDTO();
        result.setReceiptOrderNo(generateReceiptNo());
        result.setTotalQuantity(calculateTotalQuantity(reqDTO.getItems()));
        return success(result);
    }
    // ==================== å‡ºåº“操作 ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<WmsShipmentResultDTO> shipment(WmsShipmentReqDTO reqDTO) {
        // 1. å¦‚果有预留单号,先使用预留
        if (reqDTO.getReserveNo() != null) {
            WmsUseReserveReqDTO useReq = new WmsUseReserveReqDTO();
            useReq.setReserveNo(reqDTO.getReserveNo());
            useReq.setQuantity(calculateTotalQuantity(reqDTO.getItems()));
            reserveService.useReserve(useReq);
        }
        // 2. æž„建库存变更请求(出库为负数)
        WmsInventoryChangeReqDTO changeReq = buildShipmentChangeReq(reqDTO);
        // 3. æ‰§è¡Œå‡ºåº“变更
        inventoryService.changeInventory(changeReq);
        // 4. è®°å½•事务流水
        List<WmsInventoryTransactionDO> transactions = buildShipmentTransactions(reqDTO);
        transactionService.createTransactionList(transactions);
        // 5. æž„建返回结果
        WmsShipmentResultDTO result = new WmsShipmentResultDTO();
        result.setShipmentOrderNo(generateShipmentNo());
        result.setTotalQuantity(calculateTotalQuantity(reqDTO.getItems()));
        return success(result);
    }
    // ==================== åº“存预留 ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<String> reserve(WmsReserveReqDTO reqDTO) {
        return success(reserveService.createReserve(reqDTO));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Boolean> releaseReserve(String reserveNo) {
        reserveService.releaseReserve(reserveNo);
        return success(true);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Boolean> useReserve(WmsUseReserveReqDTO reqDTO) {
        reserveService.useReserve(reqDTO);
        return success(true);
    }
    // ==================== åº“存查询 ====================
    @Override
    public CommonResult<List<WmsInventoryRespDTO>> getInventoryList(WmsInventoryQueryReqDTO reqDTO) {
        // 1. æŸ¥è¯¢åº“å­˜
        List<WmsInventoryDO> inventories = inventoryMapper.selectListByQuery(reqDTO);
        // 2. è½¬æ¢ç»“æžœ
        List<WmsInventoryRespDTO> results = new ArrayList<>();
        for (WmsInventoryDO inventory : inventories) {
            WmsInventoryRespDTO resp = BeanUtils.toBean(inventory, WmsInventoryRespDTO.class);
            // è®¡ç®—预留数量
            BigDecimal reserveQuantity = reserveService.getReserveQuantity(
                    inventory.getSkuId(), inventory.getWarehouseId());
            resp.setReserveQuantity(reserveQuantity);
            resp.setAvailableQuantity(inventory.getQuantity().subtract(reserveQuantity));
            results.add(resp);
        }
        return success(results);
    }
    @Override
    public CommonResult<BigDecimal> getAvailableQuantity(Long skuId, Long warehouseId) {
        return success(inventoryService.getAvailableQuantity(skuId, warehouseId));
    }
    // ==================== ç§æœ‰æ–¹æ³• ====================
    private WmsInventoryChangeReqDTO buildInventoryChangeReq(WmsReceiptReqDTO reqDTO) {
        WmsInventoryChangeReqDTO changeReq = new WmsInventoryChangeReqDTO();
        changeReq.setOrderType(reqDTO.getBizType());
        changeReq.setOrderNo(reqDTO.getBizNo());
        List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>();
        for (WmsReceiptReqDTO.Item item : reqDTO.getItems()) {
            WmsInventoryChangeReqDTO.Item changeItem = new WmsInventoryChangeReqDTO.Item();
            changeItem.setSkuId(item.getSkuId());
            changeItem.setWarehouseId(reqDTO.getWarehouseId());
            changeItem.setQuantity(item.getQuantity()); // å…¥åº“为正数
            changeItem.setPrice(item.getPrice());
            changeItem.setRemark(item.getRemark());
            items.add(changeItem);
        }
        changeReq.setItems(items);
        return changeReq;
    }
    private WmsInventoryChangeReqDTO buildShipmentChangeReq(WmsShipmentReqDTO reqDTO) {
        WmsInventoryChangeReqDTO changeReq = new WmsInventoryChangeReqDTO();
        changeReq.setOrderType(reqDTO.getBizType());
        changeReq.setOrderNo(reqDTO.getBizNo());
        List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>();
        for (WmsShipmentReqDTO.Item item : reqDTO.getItems()) {
            WmsInventoryChangeReqDTO.Item changeItem = new WmsInventoryChangeReqDTO.Item();
            changeItem.setSkuId(item.getSkuId());
            changeItem.setWarehouseId(reqDTO.getWarehouseId());
            changeItem.setQuantity(item.getQuantity().negate()); // å‡ºåº“为负数
            changeItem.setRemark(item.getRemark());
            items.add(changeItem);
        }
        changeReq.setItems(items);
        return changeReq;
    }
    private List<WmsInventoryTransactionDO> buildReceiptTransactions(WmsReceiptReqDTO reqDTO) {
        List<WmsInventoryTransactionDO> transactions = new ArrayList<>();
        for (WmsReceiptReqDTO.Item item : reqDTO.getItems()) {
            WmsInventoryTransactionDO transaction = WmsInventoryTransactionDO.builder()
                    .transactionNo(generateTransactionNo())
                    .transactionType(WmsTransactionTypeEnum.RECEIPT.getType())
                    .bizType(reqDTO.getBizType())
                    .bizId(reqDTO.getBizId())
                    .bizNo(reqDTO.getBizNo())
                    .skuId(item.getSkuId())
                    .warehouseId(reqDTO.getWarehouseId())
                    .quantity(item.getQuantity())
                    .transactionTime(reqDTO.getReceiptTime() != null ? reqDTO.getReceiptTime() : LocalDateTime.now())
                    .remark(item.getRemark())
                    .build();
            transactions.add(transaction);
        }
        return transactions;
    }
    private List<WmsInventoryTransactionDO> buildShipmentTransactions(WmsShipmentReqDTO reqDTO) {
        List<WmsInventoryTransactionDO> transactions = new ArrayList<>();
        for (WmsShipmentReqDTO.Item item : reqDTO.getItems()) {
            WmsInventoryTransactionDO transaction = WmsInventoryTransactionDO.builder()
                    .transactionNo(generateTransactionNo())
                    .transactionType(WmsTransactionTypeEnum.SHIPMENT.getType())
                    .bizType(reqDTO.getBizType())
                    .bizId(reqDTO.getBizId())
                    .bizNo(reqDTO.getBizNo())
                    .skuId(item.getSkuId())
                    .warehouseId(reqDTO.getWarehouseId())
                    .quantity(item.getQuantity().negate())
                    .reserveNo(reqDTO.getReserveNo())
                    .transactionTime(reqDTO.getShipmentTime() != null ? reqDTO.getShipmentTime() : LocalDateTime.now())
                    .remark(item.getRemark())
                    .build();
            transactions.add(transaction);
        }
        return transactions;
    }
    private BigDecimal calculateTotalQuantity(List<?> items) {
        if (CollUtil.isEmpty(items)) {
            return BigDecimal.ZERO;
        }
        BigDecimal total = BigDecimal.ZERO;
        for (Object item : items) {
            if (item instanceof WmsReceiptReqDTO.Item) {
                total = total.add(((WmsReceiptReqDTO.Item) item).getQuantity());
            } else if (item instanceof WmsShipmentReqDTO.Item) {
                total = total.add(((WmsShipmentReqDTO.Item) item).getQuantity());
            }
        }
        return total;
    }
    private String generateReceiptNo() {
        return "RCPT" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
    }
    private String generateShipmentNo() {
        return "SHIP" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
    }
    private String generateTransactionNo() {
        return "TRX" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryQueryReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import lombok.Data;
import java.util.List;
/**
 * WMS åº“存查询请求 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsInventoryQueryReqDTO {
    /**
     * SKU ç¼–号列表
     */
    private List<Long> skuIds;
    /**
     * ä»“库编号列表
     */
    private List<Long> warehouseIds;
    /**
     * æ˜¯å¦åªæŸ¥è¯¢æœ‰åº“存的
     */
    private Boolean onlyPositive;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsInventoryRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * WMS åº“存响应 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsInventoryRespDTO {
    /**
     * åº“å­˜ ID
     */
    private Long id;
    /**
     * SKU ç¼–号
     */
    private Long skuId;
    /**
     * SKU åç§°
     */
    private String skuName;
    /**
     * ä»“库编号
     */
    private Long warehouseId;
    /**
     * ä»“库名称
     */
    private String warehouseName;
    /**
     * åº“存数量
     */
    private BigDecimal quantity;
    /**
     * é¢„留数量
     */
    private BigDecimal reserveQuantity;
    /**
     * å¯ç”¨æ•°é‡ï¼ˆåº“存数量 - é¢„留数量)
     */
    private BigDecimal availableQuantity;
    /**
     * æ›´æ–°æ—¶é—´
     */
    private LocalDateTime updateTime;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
 * WMS å…¥åº“请求 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsReceiptReqDTO {
    /**
     * ä¸šåŠ¡ç±»åž‹
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryBizTypeEnum}
     */
    @NotNull(message = "业务类型不能为空")
    private Integer bizType;
    /**
     * ä¸šåŠ¡å•æ® ID
     */
    @NotNull(message = "业务单据ID不能为空")
    private Long bizId;
    /**
     * ä¸šåŠ¡å•æ®å·
     */
    private String bizNo;
    /**
     * ä»“库编号
     */
    @NotNull(message = "仓库不能为空")
    private Long warehouseId;
    /**
     * å…¥åº“æ—¶é—´
     */
    private LocalDateTime receiptTime;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * å…¥åº“明细
     */
    @NotEmpty(message = "入库明细不能为空")
    @Valid
    private List<Item> items;
    /**
     * å…¥åº“明细项
     */
    @Data
    public static class Item {
        /**
         * SKU ç¼–号
         */
        @NotNull(message = "SKU不能为空")
        private Long skuId;
        /**
         * å…¥åº“数量
         */
        @NotNull(message = "入库数量不能为空")
        private BigDecimal quantity;
        /**
         * å•价(可选)
         */
        private BigDecimal price;
        /**
         * å¤‡æ³¨
         */
        private String remark;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReceiptResultDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
 * WMS å…¥åº“结果 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsReceiptResultDTO {
    /**
     * å…¥åº“单 ID
     */
    private Long receiptOrderId;
    /**
     * å…¥åº“单号
     */
    private String receiptOrderNo;
    /**
     * æ€»å…¥åº“数量
     */
    private BigDecimal totalQuantity;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsReserveReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * WMS åº“存预留请求 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsReserveReqDTO {
    /**
     * ä¸šåŠ¡ç±»åž‹
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveBizTypeEnum}
     */
    @NotNull(message = "业务类型不能为空")
    private Integer bizType;
    /**
     * ä¸šåŠ¡å•æ® ID
     */
    @NotNull(message = "业务单据ID不能为空")
    private Long bizId;
    /**
     * ä¸šåŠ¡å•æ®å·
     */
    private String bizNo;
    /**
     * SKU ç¼–号
     */
    @NotNull(message = "SKU不能为空")
    private Long skuId;
    /**
     * ä»“库编号
     */
    @NotNull(message = "仓库不能为空")
    private Long warehouseId;
    /**
     * é¢„留数量
     */
    @NotNull(message = "预留数量不能为空")
    private BigDecimal quantity;
    /**
     * è¿‡æœŸæ—¶é—´ï¼ˆå¯é€‰ï¼Œé¢„留自动释放时间)
     */
    private LocalDateTime expireTime;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
 * WMS å‡ºåº“请求 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsShipmentReqDTO {
    /**
     * ä¸šåŠ¡ç±»åž‹
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryBizTypeEnum}
     */
    @NotNull(message = "业务类型不能为空")
    private Integer bizType;
    /**
     * ä¸šåŠ¡å•æ® ID
     */
    @NotNull(message = "业务单据ID不能为空")
    private Long bizId;
    /**
     * ä¸šåŠ¡å•æ®å·
     */
    private String bizNo;
    /**
     * ä»“库编号
     */
    @NotNull(message = "仓库不能为空")
    private Long warehouseId;
    /**
     * å‡ºåº“æ—¶é—´
     */
    private LocalDateTime shipmentTime;
    /**
     * é¢„留单号(如果使用了预留)
     */
    private String reserveNo;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * å‡ºåº“明细
     */
    @NotEmpty(message = "出库明细不能为空")
    @Valid
    private List<Item> items;
    /**
     * å‡ºåº“明细项
     */
    @Data
    public static class Item {
        /**
         * SKU ç¼–号
         */
        @NotNull(message = "SKU不能为空")
        private Long skuId;
        /**
         * å‡ºåº“数量
         */
        @NotNull(message = "出库数量不能为空")
        private BigDecimal quantity;
        /**
         * å¤‡æ³¨
         */
        private String remark;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsShipmentResultDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
 * WMS å‡ºåº“结果 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsShipmentResultDTO {
    /**
     * å‡ºåº“单 ID
     */
    private Long shipmentOrderId;
    /**
     * å‡ºåº“单号
     */
    private String shipmentOrderNo;
    /**
     * æ€»å‡ºåº“数量
     */
    private BigDecimal totalQuantity;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/inventory/dto/WmsUseReserveReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.wms.api.inventory.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
/**
 * WMS ä½¿ç”¨é¢„留请求 DTO
 *
 * å‡ºåº“时扣减预留库存
 *
 * @author èŠ‹é“æºç 
 */
@Data
public class WmsUseReserveReqDTO {
    /**
     * é¢„留单号
     */
    @NotNull(message = "预留单号不能为空")
    private String reserveNo;
    /**
     * ä½¿ç”¨æ•°é‡
     */
    @NotNull(message = "使用数量不能为空")
    private BigDecimal quantity;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
package cn.iocoder.yudao.module.wms.api.stock;
import cn.iocoder.yudao.module.wms.api.stock.dto.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
 * WMS åº“存服务 API æŽ¥å£
 *
 * æä¾›ç»™ ERP、MES ç­‰æ¨¡å—调用,实现跨模块库存操作
 * ä½¿ç”¨ç»Ÿä¸€çš„物料ID(md_item.id)
 *
 * @author èŠ‹é“æºç 
 */
public interface WmsStockApi {
    // ==================== åº“存记录操作 ====================
    /**
     * åˆ›å»ºåº“存记录(入库)
     *
     * @param dto åº“存记录创建请求
     * @return åº“存记录ID
     */
    Long createStockRecord(StockRecordCreateDTO dto);
    /**
     * æ‰¹é‡åˆ›å»ºåº“存记录
     *
     * @param dtoList åº“存记录创建请求列表
     * @return åº“存记录ID列表
     */
    List<Long> createStockRecordBatch(List<StockRecordCreateDTO> dtoList);
    // ==================== åº“存锁定 ====================
    /**
     * é”å®šåº“å­˜
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @param quantity é”å®šæ•°é‡
     * @return é”å®šå•号
     */
    String lockStock(Long itemId, Long warehouseId, BigDecimal quantity);
    /**
     * æ‰¹é‡é”å®šåº“å­˜
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @param quantity é”å®šæ•°é‡
     * @param bizType ä¸šåŠ¡ç±»åž‹
     * @param bizId ä¸šåŠ¡å•æ®ID
     * @return é”å®šå•号
     */
    String lockStockWithBiz(Long itemId, Long warehouseId, BigDecimal quantity, Integer bizType, Long bizId);
    /**
     * è§£é”åº“å­˜
     *
     * @param lockNo é”å®šå•号
     */
    void unlockStock(String lockNo);
    // ==================== åº“存查询 ====================
    /**
     * æŸ¥è¯¢å¯ç”¨åº“å­˜
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @return å¯ç”¨åº“存数量
     */
    BigDecimal getAvailableStock(Long itemId, Long warehouseId);
    /**
     * æ‰¹é‡æŸ¥è¯¢åº“å­˜
     *
     * @param itemIds ç‰©æ–™ID列表
     * @param warehouseId ä»“库ID
     * @return ç‰©æ–™ID -> å¯ç”¨åº“存数量
     */
    Map<Long, BigDecimal> getAvailableStockMap(List<Long> itemIds, Long warehouseId);
    /**
     * æŸ¥è¯¢æ‰€æœ‰ä»“库的库存
     *
     * @param itemId ç‰©æ–™ID
     * @return ä»“库ID -> å¯ç”¨åº“存数量
     */
    Map<Long, BigDecimal> getAvailableStockByWarehouse(Long itemId);
    // ==================== åº“存校验 ====================
    /**
     * æ ¡éªŒåº“存充足
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @param quantity éœ€æ±‚数量
     * @return true=充足, false=不足
     */
    boolean validateStockEnough(Long itemId, Long warehouseId, BigDecimal quantity);
    /**
     * æ ¡éªŒåº“存充足(不足时抛出异常)
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @param quantity éœ€æ±‚数量
     * @throws cn.iocoder.yudao.framework.common.exception.ServiceException åº“存不足时抛出异常
     */
    void checkStockEnough(Long itemId, Long warehouseId, BigDecimal quantity);
    // ==================== åº“存扣减 ====================
    /**
     * æ‰£å‡åº“存(出库)
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @param quantity æ‰£å‡æ•°é‡
     * @param bizType ä¸šåŠ¡ç±»åž‹
     * @param bizId ä¸šåŠ¡å•æ®ID
     * @param bizNo ä¸šåŠ¡å•æ®å·
     */
    void deductStock(Long itemId, Long warehouseId, BigDecimal quantity, Integer bizType, Long bizId, String bizNo);
    /**
     * å¢žåŠ åº“å­˜ï¼ˆå…¥åº“ï¼‰
     *
     * @param itemId ç‰©æ–™ID
     * @param warehouseId ä»“库ID
     * @param quantity å¢žåŠ æ•°é‡
     * @param bizType ä¸šåŠ¡ç±»åž‹
     * @param bizId ä¸šåŠ¡å•æ®ID
     * @param bizNo ä¸šåŠ¡å•æ®å·
     */
    void addStock(Long itemId, Long warehouseId, BigDecimal quantity, Integer bizType, Long bizId, String bizNo);
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/api/stock/dto/StockRecordCreateDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.wms.api.stock.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * åº“存记录创建 DTO
 *
 * @author èŠ‹é“æºç 
 */
@Data
@Accessors(chain = true)
public class StockRecordCreateDTO implements Serializable {
    /**
     * ç‰©æ–™ID(关联 md_item.id)
     */
    private Long itemId;
    /**
     * ä»“库ID(关联 md_warehouse.id)
     */
    private Long warehouseId;
    /**
     * åº“位ID(可选)
     */
    private Long locationId;
    /**
     * æ‰¹æ¬¡å·ï¼ˆå¯é€‰ï¼‰
     */
    private String batchCode;
    /**
     * å˜åŠ¨æ•°é‡ï¼ˆæ­£æ•°=入库,负数=出库)
     */
    private BigDecimal quantity;
    /**
     * ä¸šåŠ¡ç±»åž‹
     *
     * æžšä¸¾å€¼ï¼š
     * 10: é‡‡è´­å…¥åº“
     * 20: é”€å”®å‡ºåº“
     * 30: ç”Ÿäº§å…¥åº“
     * 40: ç”Ÿäº§é¢†æ–™
     * 50: ç”Ÿäº§é€€æ–™
     * 60: é‡‡è´­é€€è´§å‡ºåº“
     * 70: é”€å”®é€€è´§å…¥åº“
     * 80: è°ƒæ‹¨å…¥åº“
     * 90: è°ƒæ‹¨å‡ºåº“
     * 100: ç›˜ç›ˆå…¥åº“
     * 110: ç›˜äºå‡ºåº“
     */
    private Integer bizType;
    /**
     * ä¸šåŠ¡å•æ®ID
     */
    private Long bizId;
    /**
     * ä¸šåŠ¡å•æ®å·
     */
    private String bizNo;
    /**
     * è®¡é‡å•位ID
     */
    private Long unitMeasureId;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/WmsInventoryReserveController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,137 @@
package cn.iocoder.yudao.module.wms.controller.admin.inventory;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReserveRespVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryReserveService;
import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService;
import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - åº“存预留")
@RestController
@RequestMapping("/wms/inventory-reserve")
@Validated
public class WmsInventoryReserveController {
    @Resource
    private WmsInventoryReserveService reserveService;
    @Resource
    private WmsItemSkuService itemSkuService;
    @Resource
    private WmsWarehouseService warehouseService;
    @GetMapping("/page")
    @Operation(summary = "获得库存预留分页")
    @PreAuthorize("@ss.hasPermission('wms:inventory-reserve:query')")
    public CommonResult<PageResult<WmsInventoryReserveRespVO>> getInventoryReservePage(@Valid WmsInventoryReservePageReqVO reqVO) {
        PageResult<WmsInventoryReserveDO> pageResult = reserveService.getReservePage(reqVO);
        // è½¬æ¢
        PageResult<WmsInventoryReserveRespVO> voPageResult = BeanUtils.toBean(pageResult, WmsInventoryReserveRespVO.class);
        // è¡¥å……信息
        enrichReserveList(voPageResult.getList());
        return success(voPageResult);
    }
    @GetMapping("/get")
    @Operation(summary = "获得库存预留详情")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('wms:inventory-reserve:query')")
    public CommonResult<WmsInventoryReserveRespVO> getInventoryReserve(@RequestParam("id") Long id) {
        WmsInventoryReserveDO reserve = reserveService.getReserve(id);
        if (reserve == null) {
            return success(null);
        }
        WmsInventoryReserveRespVO respVO = BeanUtils.toBean(reserve, WmsInventoryReserveRespVO.class);
        // è¡¥å……信息
        enrichReserve(respVO);
        return success(respVO);
    }
    @PostMapping("/release")
    @Operation(summary = "释放库存预留")
    @Parameter(name = "id", description = "编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('wms:inventory-reserve:release')")
    public CommonResult<Boolean> releaseInventoryReserve(@RequestParam("id") Long id) {
        WmsInventoryReserveDO reserve = reserveService.getReserve(id);
        if (reserve != null) {
            reserveService.releaseReserve(reserve.getReserveNo());
        }
        return success(true);
    }
    /**
     * æ‰¹é‡è¡¥å……预留记录信息
     */
    private void enrichReserveList(List<WmsInventoryReserveRespVO> list) {
        if (list == null || list.isEmpty()) {
            return;
        }
        // èŽ·å– SKU ä¿¡æ¯
        Map<Long, WmsItemSkuDO> skuMap = itemSkuService.getItemSkuMap(
                convertSet(list, WmsInventoryReserveRespVO::getSkuId));
        // èŽ·å–ä»“åº“ä¿¡æ¯
        Map<Long, WmsWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(
                convertSet(list, WmsInventoryReserveRespVO::getWarehouseId));
        for (WmsInventoryReserveRespVO vo : list) {
            // ä¸šåŠ¡ç±»åž‹åç§°
            vo.setBizTypeName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_BIZ_TYPE, vo.getBizType()));
            // çŠ¶æ€åç§°
            vo.setStatusName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_STATUS, vo.getStatus()));
            // SKU åç§°
            WmsItemSkuDO sku = skuMap.get(vo.getSkuId());
            if (sku != null) {
                vo.setSkuName(sku.getName());
            }
            // ä»“库名称
            WmsWarehouseDO warehouse = warehouseMap.get(vo.getWarehouseId());
            if (warehouse != null) {
                vo.setWarehouseName(warehouse.getName());
            }
        }
    }
    /**
     * è¡¥å……单个预留记录信息
     */
    private void enrichReserve(WmsInventoryReserveRespVO vo) {
        // ä¸šåŠ¡ç±»åž‹åç§°
        vo.setBizTypeName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_BIZ_TYPE, vo.getBizType()));
        // çŠ¶æ€åç§°
        vo.setStatusName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.WMS_RESERVE_STATUS, vo.getStatus()));
        // SKU åç§°
        List<WmsItemSkuDO> skuList = itemSkuService.getItemSkuListByIds(List.of(vo.getSkuId()));
        if (!skuList.isEmpty()) {
            vo.setSkuName(skuList.get(0).getName());
        }
        // ä»“库名称
        WmsWarehouseDO warehouse = warehouseService.getWarehouse(vo.getWarehouseId());
        if (warehouse != null) {
            vo.setWarehouseName(warehouse.getName());
        }
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReservePageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.wms.controller.admin.inventory.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - åº“存预留分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class WmsInventoryReservePageReqVO extends PageParam {
    @Schema(description = "预留单号", example = "RES202606260001")
    private String reserveNo;
    @Schema(description = "业务类型", example = "10")
    private Integer bizType;
    @Schema(description = "业务单据号", example = "SO202606260001")
    private String bizNo;
    @Schema(description = "状态", example = "0")
    private Integer status;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/WmsInventoryReserveRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.wms.controller.admin.inventory.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - åº“存预留 Response VO")
@Data
public class WmsInventoryReserveRespVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "预留单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "RES202606260001")
    private String reserveNo;
    @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    private Integer bizType;
    @Schema(description = "业务类型名称", example = "销售预留")
    private String bizTypeName;
    @Schema(description = "业务单据ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
    private Long bizId;
    @Schema(description = "业务单据号", example = "SO202606260001")
    private String bizNo;
    @Schema(description = "SKU编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long skuId;
    @Schema(description = "SKU名称", example = "商品A-红色-L")
    private String skuName;
    @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long warehouseId;
    @Schema(description = "仓库名称", example = "主仓库")
    private String warehouseName;
    @Schema(description = "预留数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
    private BigDecimal reserveQuantity;
    @Schema(description = "已使用数量", example = "0.00")
    private BigDecimal usedQuantity;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "状态名称", example = "预留中")
    private String statusName;
    @Schema(description = "过期时间", example = "2026-06-30 00:00:00")
    private LocalDateTime expireTime;
    @Schema(description = "备注", example = "销售订单预留")
    private String remark;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemListReqVO.java
@@ -24,6 +24,9 @@
    @Schema(description = "商品品牌编号", example = "2048")
    private Long brandId;
    @Schema(description = "商品状态", example = "0")
    private Integer status;
    @Schema(description = "创建时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] createTime;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemPageReqVO.java
@@ -29,6 +29,9 @@
    @Schema(description = "商品品牌编号", example = "2048")
    private Long brandId;
    @Schema(description = "商品状态", example = "0")
    private Integer status;
    @Schema(description = "创建时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] createTime;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemSaveReqVO.java
@@ -36,6 +36,9 @@
    @Size(max = 20, message = "单位长度不能超过 20 ä¸ªå­—符")
    private String unit;
    @Schema(description = "计量单位编号", example = "1")
    private Long unitMeasureId;
    @Schema(description = "商品品牌编号", example = "1")
    private Long brandId;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryReserveDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package cn.iocoder.yudao.module.wms.dal.dataobject.inventory;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveBizTypeEnum;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * WMS åº“存预留 DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("wms_inventory_reserve")
@KeySequence("wms_inventory_reserve_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WmsInventoryReserveDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é¢„留单号
     */
    private String reserveNo;
    /**
     * ä¸šåŠ¡ç±»åž‹
     *
     * æžšä¸¾ {@link WmsReserveBizTypeEnum}
     */
    private Integer bizType;
    /**
     * ä¸šåŠ¡å•æ® ID
     */
    private Long bizId;
    /**
     * ä¸šåŠ¡å•æ®å·
     */
    private String bizNo;
    /**
     * SKU ç¼–号
     *
     * å…³è” {@link WmsItemSkuDO#getId()}
     */
    private Long skuId;
    /**
     * ä»“库编号
     *
     * å…³è” {@link WmsWarehouseDO#getId()}
     */
    private Long warehouseId;
    /**
     * é¢„留数量
     */
    private BigDecimal reserveQuantity;
    /**
     * å·²ä½¿ç”¨æ•°é‡
     */
    private BigDecimal usedQuantity;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link WmsReserveStatusEnum}
     */
    private Integer status;
    /**
     * è¿‡æœŸæ—¶é—´
     */
    private LocalDateTime expireTime;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/inventory/WmsInventoryTransactionDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
package cn.iocoder.yudao.module.wms.dal.dataobject.inventory;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsTransactionTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * WMS åº“存事务流水 DO
 *
 * è®°å½•每一笔库存变动操作
 *
 * @author èŠ‹é“æºç 
 */
@TableName("wms_inventory_transaction")
@KeySequence("wms_inventory_transaction_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WmsInventoryTransactionDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * äº‹åŠ¡æµæ°´å·
     */
    private String transactionNo;
    /**
     * äº‹åŠ¡ç±»åž‹
     *
     * æžšä¸¾ {@link WmsTransactionTypeEnum}
     */
    private Integer transactionType;
    /**
     * ä¸šåŠ¡ç±»åž‹
     */
    private Integer bizType;
    /**
     * ä¸šåŠ¡å•æ® ID
     */
    private Long bizId;
    /**
     * ä¸šåŠ¡å•æ®å·
     */
    private String bizNo;
    /**
     * SKU ç¼–号
     *
     * å…³è” {@link WmsItemSkuDO#getId()}
     */
    private Long skuId;
    /**
     * ä»“库编号
     *
     * å…³è” {@link WmsWarehouseDO#getId()}
     */
    private Long warehouseId;
    /**
     * å˜åŠ¨æ•°é‡
     *
     * æ­£æ•°=入库,负数=出库
     */
    private BigDecimal quantity;
    /**
     * å˜åŠ¨å‰æ•°é‡
     */
    private BigDecimal beforeQuantity;
    /**
     * å˜åŠ¨åŽæ•°é‡
     */
    private BigDecimal afterQuantity;
    /**
     * å…³è”预留 ID
     */
    private Long reserveId;
    /**
     * å…³è”预留单号
     */
    private String reserveNo;
    /**
     * äº‹åŠ¡æ—¶é—´
     */
    private LocalDateTime transactionTime;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/md/item/WmsItemSkuDO.java
@@ -76,6 +76,11 @@
    /**
     * é”€å”®ä»·ï¼Œå•位元
     */
    private BigDecimal sellingPrice;
    private BigDecimal salePrice;
    /**
     * çŠ¶æ€
     */
    private Integer status;
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryQueryReqDTO;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryListReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
@@ -123,4 +124,17 @@
        throw new IllegalArgumentException("未知库存统计维度:" + type);
    }
    /**
     * æ ¹æ® API æŸ¥è¯¢æ¡ä»¶æŸ¥è¯¢åº“存列表
     */
    default List<WmsInventoryDO> selectListByQuery(WmsInventoryQueryReqDTO reqDTO) {
        LambdaQueryWrapperX<WmsInventoryDO> query = new LambdaQueryWrapperX<WmsInventoryDO>()
                .inIfPresent(WmsInventoryDO::getSkuId, reqDTO.getSkuIds())
                .inIfPresent(WmsInventoryDO::getWarehouseId, reqDTO.getWarehouseIds());
        if (Boolean.TRUE.equals(reqDTO.getOnlyPositive())) {
            query.gt(WmsInventoryDO::getQuantity, BigDecimal.ZERO);
        }
        return selectList(query);
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryReserveMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.wms.dal.mysql.inventory;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.wms.api.inventory.dto.WmsInventoryQueryReqDTO;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
import java.util.List;
/**
 * WMS åº“存预留 Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface WmsInventoryReserveMapper extends BaseMapperX<WmsInventoryReserveDO> {
    default PageResult<WmsInventoryReserveDO> selectPage(WmsInventoryReservePageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<WmsInventoryReserveDO>()
                .likeIfPresent(WmsInventoryReserveDO::getReserveNo, reqVO.getReserveNo())
                .eqIfPresent(WmsInventoryReserveDO::getBizType, reqVO.getBizType())
                .likeIfPresent(WmsInventoryReserveDO::getBizNo, reqVO.getBizNo())
                .eqIfPresent(WmsInventoryReserveDO::getStatus, reqVO.getStatus())
                .orderByDesc(WmsInventoryReserveDO::getId));
    }
    default List<WmsInventoryReserveDO> selectListByBiz(Integer bizType, Long bizId) {
        return selectList(new LambdaQueryWrapperX<WmsInventoryReserveDO>()
                .eq(WmsInventoryReserveDO::getBizType, bizType)
                .eq(WmsInventoryReserveDO::getBizId, bizId)
                .in(WmsInventoryReserveDO::getStatus, 0, 1)); // é¢„留中或部分使用
    }
    default WmsInventoryReserveDO selectByReserveNo(String reserveNo) {
        return selectOne(WmsInventoryReserveDO::getReserveNo, reserveNo);
    }
    /**
     * æŸ¥è¯¢æŒ‡å®š SKU å’Œä»“库的预留数量
     */
    @Select("SELECT IFNULL(SUM(reserve_quantity - used_quantity), 0) FROM wms_inventory_reserve " +
            "WHERE sku_id = #{skuId} AND warehouse_id = #{warehouseId} AND status IN (0, 1) AND deleted = 0")
    BigDecimal selectReserveQuantity(@Param("skuId") Long skuId, @Param("warehouseId") Long warehouseId);
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryTransactionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.wms.dal.mysql.inventory;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * WMS åº“存事务流水 Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface WmsInventoryTransactionMapper extends BaseMapperX<WmsInventoryTransactionDO> {
    default List<WmsInventoryTransactionDO> selectListByBiz(Integer bizType, Long bizId) {
        return selectList(new LambdaQueryWrapperX<WmsInventoryTransactionDO>()
                .eq(WmsInventoryTransactionDO::getBizType, bizType)
                .eq(WmsInventoryTransactionDO::getBizId, bizId)
                .orderByAsc(WmsInventoryTransactionDO::getTransactionTime));
    }
    default WmsInventoryTransactionDO selectByTransactionNo(String transactionNo) {
        return selectOne(WmsInventoryTransactionDO::getTransactionNo, transactionNo);
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
@@ -13,4 +13,8 @@
    String RECEIPT_ORDER_TYPE = "wms_receipt_order_type";
    String SHIPMENT_ORDER_TYPE = "wms_shipment_order_type";
    String WMS_RESERVE_BIZ_TYPE = "wms_reserve_biz_type";
    String WMS_RESERVE_STATUS = "wms_reserve_status";
    String WMS_TRANSACTION_TYPE = "wms_transaction_type";
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
@@ -87,5 +87,13 @@
    // ========== WMS åº“å­˜ 1-060-300-000 ==========
    ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000,
            "库存不足,商品:{},商品规格:{},仓库:{},当前库存:{},变更数量:{}");
    ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH_FOR_RESERVE = new ErrorCode(1_060_300_001,
            "可用库存不足,SKU:{},仓库:{},可用库存:{},预留数量:{}");
    // ========== WMS åº“存预留 1-060-301-000 ==========
    ErrorCode INVENTORY_RESERVE_NOT_EXISTS = new ErrorCode(1_060_301_000, "库存预留不存在");
    ErrorCode INVENTORY_RESERVE_STATUS_INVALID = new ErrorCode(1_060_301_001, "库存预留状态不允许此操作");
    ErrorCode INVENTORY_RESERVE_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_301_002,
            "预留数量不足,预留单号:{},剩余预留:{},请求使用:{}");
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsInventoryBizTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.wms.enums.inventory;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * WMS åº“存业务类型枚举
 *
 * ç”¨äºŽåŒºåˆ†åº“存操作的业务来源
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum WmsInventoryBizTypeEnum implements ArrayValuable<Integer> {
    // ========== å…¥åº“类型 [100, 150) ==========
    PURCHASE_RECEIPT(100, "ERP é‡‡è´­å…¥åº“"),
    PRODUCE_RECEIPT(101, "MES ç”Ÿäº§å…¥åº“"),
    RETURN_RECEIPT(102, "退货入库"),
    OTHER_RECEIPT(103, "其他入库"),
    OTHER(104, "其他"),
    // ========== å‡ºåº“类型 [150, 200) ==========
    SALE_SHIPMENT(150, "ERP é”€å”®å‡ºåº“"),
    PRODUCE_MATERIAL(151, "MES ç”Ÿäº§é¢†æ–™"),
    RETURN_SHIPMENT(152, "退货出库"),
    OTHER_SHIPMENT(153, "其他出库"),
    ;
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(WmsInventoryBizTypeEnum::getType).toArray(Integer[]::new);
    /**
     * ç±»åž‹
     */
    private final Integer type;
    /**
     * åç§°
     */
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå…¥åº“类型
     */
    public static boolean isReceiptType(Integer type) {
        return type != null && type >= 100 && type < 150;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå‡ºåº“类型
     */
    public static boolean isShipmentType(Integer type) {
        return type != null && type >= 150 && type < 200;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveBizTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.wms.enums.inventory;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * WMS åº“存预留业务类型枚举
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum WmsReserveBizTypeEnum implements ArrayValuable<Integer> {
    SALE(10, "销售预留"),
    PRODUCE(20, "生产预留"),
    ;
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(WmsReserveBizTypeEnum::getType).toArray(Integer[]::new);
    /**
     * ç±»åž‹
     */
    private final Integer type;
    /**
     * åç§°
     */
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsReserveStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.wms.enums.inventory;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * WMS åº“存预留状态枚举
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum WmsReserveStatusEnum implements ArrayValuable<Integer> {
    RESERVED(0, "预留中"),
    PARTIAL_USED(1, "部分使用"),
    USED(2, "已使用"),
    RELEASED(3, "已释放"),
    ;
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(WmsReserveStatusEnum::getStatus).toArray(Integer[]::new);
    /**
     * çŠ¶æ€
     */
    private final Integer status;
    /**
     * åç§°
     */
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/inventory/WmsTransactionTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.wms.enums.inventory;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * WMS åº“存事务类型枚举
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum WmsTransactionTypeEnum implements ArrayValuable<Integer> {
    RECEIPT(10, "入库"),
    SHIPMENT(20, "出库"),
    RESERVE(30, "预留"),
    RELEASE(40, "释放"),
    CHECK(50, "盘点"),
    MOVE(60, "调拨"),
    ;
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(WmsTransactionTypeEnum::getType).toArray(Integer[]::new);
    /**
     * ç±»åž‹
     */
    private final Integer type;
    /**
     * åç§°
     */
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/stock/StockBizTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,119 @@
package cn.iocoder.yudao.module.wms.enums.stock;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * åº“存业务类型枚举
 *
 * å®šä¹‰åº“存变动的业务来源
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum StockBizTypeEnum {
    // ==================== å…¥åº“类型 ====================
    PURCHASE_IN(10, "采购入库"),
    PURCHASE_IN_CANCEL(11, "采购入库作废"),
    PRODUCE_IN(30, "生产入库"),
    PRODUCE_IN_CANCEL(31, "生产入库作废"),
    PRODUCE_RETURN(50, "生产退料"),
    PRODUCE_RETURN_CANCEL(51, "生产退料作废"),
    SALE_RETURN_IN(70, "销售退货入库"),
    SALE_RETURN_IN_CANCEL(71, "销售退货入库作废"),
    TRANSFER_IN(80, "调拨入库"),
    TRANSFER_IN_CANCEL(81, "调拨入库作废"),
    CHECK_IN(100, "盘盈入库"),
    CHECK_IN_CANCEL(101, "盘盈入库作废"),
    // ==================== å‡ºåº“类型 ====================
    SALE_OUT(20, "销售出库"),
    SALE_OUT_CANCEL(21, "销售出库作废"),
    PRODUCE_ISSUE(40, "生产领料"),
    PRODUCE_ISSUE_CANCEL(41, "生产领料作废"),
    PURCHASE_RETURN_OUT(60, "采购退货出库"),
    PURCHASE_RETURN_OUT_CANCEL(61, "采购退货出库作废"),
    TRANSFER_OUT(90, "调拨出库"),
    TRANSFER_OUT_CANCEL(91, "调拨出库作废"),
    CHECK_OUT(110, "盘亏出库"),
    CHECK_OUT_CANCEL(111, "盘亏出库作废");
    /**
     * ä¸šåŠ¡ç±»åž‹ç¼–ç 
     */
    private final Integer type;
    /**
     * ä¸šåŠ¡ç±»åž‹åç§°
     */
    private final String name;
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå…¥åº“类型
     */
    public boolean isInbound() {
        // å…¥åº“类型编码:10, 30, 50, 70, 80, 100
        return type % 20 == 10 || type == 50 || type == 70 || type == 100;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå‡ºåº“类型
     */
    public boolean isOutbound() {
        // å‡ºåº“类型编码:20, 40, 60, 90, 110
        return type % 20 == 0 && type != 0 && !isInbound();
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºä½œåºŸç±»åž‹
     */
    public boolean isCancel() {
        return type % 10 == 1 && type != 11;  // ä½œåºŸç±»åž‹çš„编码尾数为 1
    }
    /**
     * èŽ·å–å¯¹åº”çš„æ­£å¸¸ä¸šåŠ¡ç±»åž‹ï¼ˆå¦‚æžœæ˜¯ä½œåºŸç±»åž‹ï¼‰
     */
    public StockBizTypeEnum getNormalType() {
        if (isCancel()) {
            return fromType(type - 1);
        }
        return this;
    }
    /**
     * èŽ·å–å¯¹åº”çš„ä½œåºŸä¸šåŠ¡ç±»åž‹ï¼ˆå¦‚æžœæ˜¯æ­£å¸¸ç±»åž‹ï¼‰
     */
    public StockBizTypeEnum getCancelType() {
        if (!isCancel()) {
            return fromType(type + 1);
        }
        return this;
    }
    /**
     * æ ¹æ®ç±»åž‹ç¼–码获取枚举
     */
    public static StockBizTypeEnum fromType(Integer type) {
        for (StockBizTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value;
            }
        }
        return null;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.wms.service.inventory;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
import java.math.BigDecimal;
import java.util.List;
/**
 * WMS åº“存预留 Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface WmsInventoryReserveService {
    /**
     * åˆ›å»ºåº“存预留
     *
     * @param reqDTO é¢„留请求
     * @return é¢„留单号
     */
    String createReserve(WmsReserveReqDTO reqDTO);
    /**
     * é‡Šæ”¾åº“存预留
     *
     * @param reserveNo é¢„留单号
     */
    void releaseReserve(String reserveNo);
    /**
     * ä½¿ç”¨é¢„留库存(扣减)
     *
     * @param reqDTO ä½¿ç”¨è¯·æ±‚
     */
    void useReserve(WmsUseReserveReqDTO reqDTO);
    /**
     * æ ¹æ®ä¸šåŠ¡æŸ¥è¯¢é¢„ç•™è®°å½•
     *
     * @param bizType ä¸šåŠ¡ç±»åž‹
     * @param bizId ä¸šåŠ¡å•æ®ID
     * @return é¢„留记录列表
     */
    List<WmsInventoryReserveDO> getReserveListByBiz(Integer bizType, Long bizId);
    /**
     * èŽ·å–åº“å­˜é¢„ç•™åˆ†é¡µ
     *
     * @param reqVO åˆ†é¡µè¯·æ±‚
     * @return åˆ†é¡µç»“æžœ
     */
    PageResult<WmsInventoryReserveDO> getReservePage(WmsInventoryReservePageReqVO reqVO);
    /**
     * èŽ·å–é¢„ç•™è¯¦æƒ…
     *
     * @param id é¢„ç•™ID
     * @return é¢„留记录
     */
    WmsInventoryReserveDO getReserve(Long id);
    /**
     * æŸ¥è¯¢æŒ‡å®š SKU å’Œä»“库的预留数量
     *
     * @param skuId SKU编号
     * @param warehouseId ä»“库编号
     * @return é¢„留数量
     */
    BigDecimal getReserveQuantity(Long skuId, Long warehouseId);
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryReserveServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,210 @@
package cn.iocoder.yudao.module.wms.service.inventory;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.wms.api.inventory.dto.*;
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryReservePageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryReserveMapper;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryTransactionMapper;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsReserveStatusEnum;
import cn.iocoder.yudao.module.wms.enums.inventory.WmsTransactionTypeEnum;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
/**
 * WMS åº“存预留 Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
@Slf4j
public class WmsInventoryReserveServiceImpl implements WmsInventoryReserveService {
    @Resource
    private WmsInventoryReserveMapper reserveMapper;
    @Resource
    private WmsInventoryTransactionMapper transactionMapper;
    @Resource
    private WmsInventoryService inventoryService;
    @Resource
    private WmsInventoryTransactionService transactionService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createReserve(WmsReserveReqDTO reqDTO) {
        // 1. æ ¡éªŒå¯ç”¨åº“å­˜
        BigDecimal availableQuantity = inventoryService.getAvailableQuantity(reqDTO.getSkuId(), reqDTO.getWarehouseId());
        if (availableQuantity.compareTo(reqDTO.getQuantity()) < 0) {
            throw exception(INVENTORY_QUANTITY_NOT_ENOUGH_FOR_RESERVE,
                    reqDTO.getSkuId(), reqDTO.getWarehouseId(), availableQuantity, reqDTO.getQuantity());
        }
        // 2. ç”Ÿæˆé¢„留单号
        String reserveNo = generateReserveNo();
        // 3. åˆ›å»ºé¢„留记录
        WmsInventoryReserveDO reserve = BeanUtils.toBean(reqDTO, WmsInventoryReserveDO.class);
        reserve.setReserveNo(reserveNo);
        reserve.setReserveQuantity(reqDTO.getQuantity());
        reserve.setUsedQuantity(BigDecimal.ZERO);
        reserve.setStatus(WmsReserveStatusEnum.RESERVED.getStatus());
        reserveMapper.insert(reserve);
        // 4. è®°å½•事务流水
        transactionService.createTransaction(buildReserveTransaction(reserve, reqDTO.getQuantity()));
        return reserveNo;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void releaseReserve(String reserveNo) {
        // 1. æŸ¥è¯¢é¢„留记录
        WmsInventoryReserveDO reserve = reserveMapper.selectByReserveNo(reserveNo);
        if (reserve == null) {
            throw exception(INVENTORY_RESERVE_NOT_EXISTS);
        }
        // 1.1 æ ¡éªŒçŠ¶æ€ï¼šåªæœ‰é¢„ç•™ä¸­æˆ–éƒ¨åˆ†ä½¿ç”¨çŠ¶æ€å¯é‡Šæ”¾
        if (reserve.getStatus() != WmsReserveStatusEnum.RESERVED.getStatus()
                && reserve.getStatus() != WmsReserveStatusEnum.PARTIAL_USED.getStatus()) {
            throw exception(INVENTORY_RESERVE_STATUS_INVALID);
        }
        // 2. è®¡ç®—释放数量
        BigDecimal releaseQuantity = reserve.getReserveQuantity().subtract(reserve.getUsedQuantity());
        // 3. æ›´æ–°é¢„留状态
        reserve.setStatus(WmsReserveStatusEnum.RELEASED.getStatus());
        reserveMapper.updateById(reserve);
        // 4. è®°å½•事务流水(释放为负数)
        transactionService.createTransaction(buildReleaseTransaction(reserve, releaseQuantity.negate()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useReserve(WmsUseReserveReqDTO reqDTO) {
        // 1. æŸ¥è¯¢é¢„留记录
        WmsInventoryReserveDO reserve = reserveMapper.selectByReserveNo(reqDTO.getReserveNo());
        if (reserve == null) {
            throw exception(INVENTORY_RESERVE_NOT_EXISTS);
        }
        // 1.1 æ ¡éªŒçŠ¶æ€
        if (reserve.getStatus() != WmsReserveStatusEnum.RESERVED.getStatus()
                && reserve.getStatus() != WmsReserveStatusEnum.PARTIAL_USED.getStatus()) {
            throw exception(INVENTORY_RESERVE_STATUS_INVALID);
        }
        // 2. è®¡ç®—剩余可使用数量
        BigDecimal remainQuantity = reserve.getReserveQuantity().subtract(reserve.getUsedQuantity());
        if (reqDTO.getQuantity().compareTo(remainQuantity) > 0) {
            throw exception(INVENTORY_RESERVE_QUANTITY_NOT_ENOUGH, reqDTO.getReserveNo(), remainQuantity, reqDTO.getQuantity());
        }
        // 3. æ›´æ–°å·²ä½¿ç”¨æ•°é‡å’ŒçŠ¶æ€
        BigDecimal newUsedQuantity = reserve.getUsedQuantity().add(reqDTO.getQuantity());
        reserve.setUsedQuantity(newUsedQuantity);
        if (newUsedQuantity.compareTo(reserve.getReserveQuantity()) == 0) {
            reserve.setStatus(WmsReserveStatusEnum.USED.getStatus());
        } else {
            reserve.setStatus(WmsReserveStatusEnum.PARTIAL_USED.getStatus());
        }
        reserveMapper.updateById(reserve);
    }
    @Override
    public List<WmsInventoryReserveDO> getReserveListByBiz(Integer bizType, Long bizId) {
        return reserveMapper.selectListByBiz(bizType, bizId);
    }
    @Override
    public PageResult<WmsInventoryReserveDO> getReservePage(WmsInventoryReservePageReqVO reqVO) {
        return reserveMapper.selectPage(reqVO);
    }
    @Override
    public WmsInventoryReserveDO getReserve(Long id) {
        return reserveMapper.selectById(id);
    }
    @Override
    public BigDecimal getReserveQuantity(Long skuId, Long warehouseId) {
        return reserveMapper.selectReserveQuantity(skuId, warehouseId);
    }
    /**
     * ç”Ÿæˆé¢„留单号
     */
    private String generateReserveNo() {
        return "RES" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
                + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }
    /**
     * æž„建预留事务流水
     */
    private WmsInventoryTransactionDO buildReserveTransaction(WmsInventoryReserveDO reserve, BigDecimal quantity) {
        return WmsInventoryTransactionDO.builder()
                .transactionNo(generateTransactionNo())
                .transactionType(WmsTransactionTypeEnum.RESERVE.getType())
                .bizType(reserve.getBizType())
                .bizId(reserve.getBizId())
                .bizNo(reserve.getBizNo())
                .skuId(reserve.getSkuId())
                .warehouseId(reserve.getWarehouseId())
                .quantity(quantity)
                .reserveId(reserve.getId())
                .reserveNo(reserve.getReserveNo())
                .transactionTime(LocalDateTime.now())
                .remark(reserve.getRemark())
                .build();
    }
    /**
     * æž„建释放事务流水
     */
    private WmsInventoryTransactionDO buildReleaseTransaction(WmsInventoryReserveDO reserve, BigDecimal quantity) {
        return WmsInventoryTransactionDO.builder()
                .transactionNo(generateTransactionNo())
                .transactionType(WmsTransactionTypeEnum.RELEASE.getType())
                .bizType(reserve.getBizType())
                .bizId(reserve.getBizId())
                .bizNo(reserve.getBizNo())
                .skuId(reserve.getSkuId())
                .warehouseId(reserve.getWarehouseId())
                .quantity(quantity)
                .reserveId(reserve.getId())
                .reserveNo(reserve.getReserveNo())
                .transactionTime(LocalDateTime.now())
                .remark("释放预留")
                .build();
    }
    /**
     * ç”Ÿæˆäº‹åŠ¡æµæ°´å·
     */
    private String generateTransactionNo() {
        return "TRX" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryService.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -62,4 +63,13 @@
     */
    void changeInventory(WmsInventoryChangeReqDTO reqDTO);
    /**
     * èŽ·å–å¯ç”¨åº“å­˜æ•°é‡ï¼ˆæ€»åº“å­˜ - é¢„留库存)
     *
     * @param skuId SKU ç¼–号
     * @param warehouseId ä»“库编号
     * @return å¯ç”¨åº“存数量
     */
    BigDecimal getAvailableQuantity(Long skuId, Long warehouseId);
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java
@@ -10,9 +10,11 @@
import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryHistoryDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryReserveDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryReserveMapper;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO;
import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService;
@@ -50,6 +52,9 @@
    @Resource
    private WmsInventoryHistoryService inventoryHistoryService;
    @Resource
    private WmsInventoryReserveMapper reserveMapper;
    @Resource
    private WmsItemSkuService itemSkuService;
@@ -294,4 +299,19 @@
                item.getWarehouseId(), beforeQuantity, item.getQuantity());
    }
    @Override
    public BigDecimal getAvailableQuantity(Long skuId, Long warehouseId) {
        // 1. æŸ¥è¯¢åº“存数量
        WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(skuId, warehouseId);
        if (inventory == null) {
            return BigDecimal.ZERO;
        }
        // 2. æŸ¥è¯¢é¢„留数量
        BigDecimal reserveQuantity = reserveMapper.selectReserveQuantity(skuId, warehouseId);
        // 3. è®¡ç®—可用数量
        return inventory.getQuantity().subtract(reserveQuantity != null ? reserveQuantity : BigDecimal.ZERO);
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.wms.service.inventory;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
import java.util.List;
/**
 * WMS åº“存事务流水 Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface WmsInventoryTransactionService {
    /**
     * åˆ›å»ºäº‹åŠ¡æµæ°´
     *
     * @param transaction äº‹åŠ¡æµæ°´
     * @return äº‹åŠ¡æµæ°´ID
     */
    Long createTransaction(WmsInventoryTransactionDO transaction);
    /**
     * æ‰¹é‡åˆ›å»ºäº‹åŠ¡æµæ°´
     *
     * @param transactions äº‹åŠ¡æµæ°´åˆ—è¡¨
     */
    void createTransactionList(List<WmsInventoryTransactionDO> transactions);
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryTransactionServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.wms.service.inventory;
import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryTransactionDO;
import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryTransactionMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
 * WMS åº“存事务流水 Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
@Slf4j
public class WmsInventoryTransactionServiceImpl implements WmsInventoryTransactionService {
    @Resource
    private WmsInventoryTransactionMapper transactionMapper;
    @Override
    public Long createTransaction(WmsInventoryTransactionDO transaction) {
        transactionMapper.insert(transaction);
        return transaction.getId();
    }
    @Override
    public void createTransactionList(List<WmsInventoryTransactionDO> transactions) {
        if (transactions != null && !transactions.isEmpty()) {
            transactionMapper.insertBatch(transactions);
        }
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemServiceImpl.java
@@ -1,23 +1,24 @@
package cn.iocoder.yudao.module.wms.service.md.item;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemUpdateReqDTO;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item.WmsItemListReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item.WmsItemPageReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item.WmsItemSaveReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO;
import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemMapper;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
@@ -25,129 +26,91 @@
/**
 * WMS å•†å“ Service å®žçŽ°ç±»
 *
 * æ”¹é€ è¯´æ˜Žï¼šWMS å•†å“æ•°æ®æ¥æºäºŽ MDM ç‰©æ–™ä¸»æ•°æ®
 * - æ‰€æœ‰æ“ä½œè°ƒç”¨ MDM API
 * - WMS å•†å“å¯¹åº” MDM ç‰©æ–™ç±»åž‹ = ç‰©æ–™ï¼ˆitemType = 1)
 * - å­—段映射:WmsItemDO.unit <- MDM è®¡é‡å•位名称
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
public class WmsItemServiceImpl implements WmsItemService {
    /** WMS å•†å“å¯¹åº”çš„ MDM ç‰©æ–™ç±»åž‹ï¼šç‰©æ–™ */
    private static final int MDM_ITEM_TYPE_MATERIAL = 1;
    @Resource
    private WmsItemMapper itemMapper;
    private MdmItemApi mdmItemApi;
    @Resource
    @Lazy // å»¶è¿ŸåŠ è½½ï¼Œé¿å…å¾ªçŽ¯ä¾èµ–
    private WmsItemCategoryService categoryService;
    @Resource
    @Lazy // å»¶è¿ŸåŠ è½½ï¼Œé¿å…å¾ªçŽ¯ä¾èµ–
    private WmsItemBrandService brandService;
    @Resource
    private WmsItemSkuService itemSkuService;
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createItem(WmsItemSaveReqVO createReqVO) {
        // æ ¡éªŒæ•°æ®
        validateItemSaveData(createReqVO);
        // æ’入商品
        WmsItemDO item = BeanUtils.toBean(createReqVO, WmsItemDO.class);
        itemMapper.insert(item);
        // æ’å…¥ SKU
        itemSkuService.createItemSkuList(item.getId(), createReqVO.getSkus());
        return item.getId();
        // æž„建创建请求并调用 MDM API
        MdmItemCreateReqDTO createReqDTO = buildCreateReqDTO(createReqVO);
        return mdmItemApi.createItem(createReqDTO).getCheckedData();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateItem(WmsItemSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨
        validateItemExists(updateReqVO.getId());
        // æ ¡éªŒæ•°æ®
        validateItemSaveData(updateReqVO);
        // æ›´æ–°å•†å“
        WmsItemDO updateObj = BeanUtils.toBean(updateReqVO, WmsItemDO.class);
        itemMapper.updateById(updateObj);
        // æ›´æ–° SKU
        itemSkuService.updateItemSkuList(updateReqVO.getId(), updateReqVO.getSkus());
    }
    private void validateItemSaveData(WmsItemSaveReqVO reqVO) {
        // æ ¡éªŒå•†å“ç¼–号唯一
        validateItemCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒå•†å“åç§°å”¯ä¸€
        validateItemNameUnique(reqVO.getId(), reqVO.getName());
        // æ ¡éªŒå•†å“åˆ†ç±»å­˜åœ¨
        validateCategoryExists(reqVO.getCategoryId());
        // æ ¡éªŒå•†å“å“ç‰Œå­˜åœ¨
        validateBrandExists(reqVO.getBrandId());
    }
    private void validateItemCodeUnique(Long id, String code) {
        WmsItemDO item = itemMapper.selectByCode(code);
        if (item == null) {
            return;
        }
        if (id == null || ObjectUtil.notEqual(item.getId(), id)) {
            throw exception(ITEM_CODE_DUPLICATE);
        }
    }
    private void validateItemNameUnique(Long id, String name) {
        WmsItemDO item = itemMapper.selectByName(name);
        if (item == null) {
            return;
        }
        if (id == null || ObjectUtil.notEqual(item.getId(), id)) {
            throw exception(ITEM_NAME_DUPLICATE);
        }
    }
    private void validateCategoryExists(Long categoryId) {
        categoryService.validateItemCategoryExists(categoryId);
    }
    private void validateBrandExists(Long brandId) {
        if (brandId == null) {
            return;
        }
        brandService.validateItemBrandExists(brandId);
        // æž„建更新请求并调用 MDM API
        MdmItemUpdateReqDTO updateReqDTO = buildUpdateReqDTO(updateReqVO);
        mdmItemApi.updateItem(updateReqDTO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteItem(Long id) {
        // æ ¡éªŒå­˜åœ¨
        validateItemExists(id);
        // åˆ é™¤å•†å“å’Œ SKU
        itemSkuService.deleteItemSkuListByItemId(id);
        itemMapper.deleteById(id);
        // WMS å•†å“åˆ é™¤å®žé™…为禁用(MDM ç‰©æ–™ä¸èƒ½ç‰©ç†åˆ é™¤ï¼‰
        mdmItemApi.updateItemStatus(id, CommonStatusEnum.DISABLE.getStatus());
    }
    @Override
    public WmsItemDO validateItemExists(Long id) {
        WmsItemDO item = itemMapper.selectById(id);
        if (item == null) {
            throw exception(ITEM_NOT_EXISTS);
        }
        return item;
        MdmItemRespDTO item = mdmItemApi.validateItemExists(id).getCheckedData();
        return convertToWmsItemDO(item);
    }
    @Override
    public WmsItemDO getItem(Long id) {
        return itemMapper.selectById(id);
        MdmItemRespDTO item = mdmItemApi.getItem(id).getCheckedData();
        if (item == null) {
            return null;
        }
        return convertToWmsItemDO(item);
    }
    @Override
    public PageResult<WmsItemDO> getItemPage(WmsItemPageReqVO pageReqVO) {
        return itemMapper.selectPage(pageReqVO,
                categoryService.getSelfAndChildItemCategoryIdList(pageReqVO.getCategoryId()));
        // è°ƒç”¨ MDM API åˆ†é¡µæŸ¥è¯¢
        PageResult<MdmItemRespDTO> mdmPageResult = mdmItemApi.getItemPage(
                MDM_ITEM_TYPE_MATERIAL,
                pageReqVO.getStatus(),
                pageReqVO.getPageNo(),
                pageReqVO.getPageSize()
        ).getCheckedData();
        // è½¬æ¢ç»“æžœ
        List<WmsItemDO> itemList = convertToWmsItemDOList(mdmPageResult.getList());
        return new PageResult<>(itemList, mdmPageResult.getTotal());
    }
    @Override
    public List<WmsItemDO> getItemList(WmsItemListReqVO listReqVO) {
        return itemMapper.selectList(listReqVO,
                categoryService.getSelfAndChildItemCategoryIdList(listReqVO.getCategoryId()));
        // è°ƒç”¨ MDM API èŽ·å–åˆ—è¡¨
        List<MdmItemRespDTO> itemList = mdmItemApi.getItemListByStatus(listReqVO.getStatus()).getCheckedData();
        return convertToWmsItemDOList(itemList);
    }
    @Override
@@ -155,17 +118,140 @@
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return itemMapper.selectByIds(ids);
        List<MdmItemRespDTO> itemList = mdmItemApi.getItemList(ids).getCheckedData();
        return convertToWmsItemDOList(itemList);
    }
    @Override
    public long getItemCountByCategoryId(Long categoryId) {
        return itemMapper.selectCountByCategoryId(categoryId);
        return mdmItemApi.getItemCountByCategoryId(categoryId).getCheckedData();
    }
    @Override
    public long getItemCountByBrandId(Long brandId) {
        return itemMapper.selectCountByBrandId(brandId);
        // MDM æš‚无按品牌统计方法,返回 0
        return 0L;
    }
}
    // ==================== ç§æœ‰æ–¹æ³• ====================
    /**
     * æ ¡éªŒå•†å“ä¿å­˜æ•°æ®
     */
    private void validateItemSaveData(WmsItemSaveReqVO reqVO) {
        // æ ¡éªŒå•†å“ç¼–码唯一
        if (reqVO.getCode() != null) {
            MdmItemRespDTO existItem = mdmItemApi.getItemByCode(reqVO.getCode()).getCheckedData();
            if (existItem != null && !existItem.getId().equals(reqVO.getId())) {
                throw exception(ITEM_CODE_DUPLICATE);
            }
        }
        // æ ¡éªŒå•†å“åç§°å”¯ä¸€
        if (reqVO.getName() != null) {
            // MDM æš‚无按名称查询方法,跳过此校验
        }
    }
    /**
     * æž„建创建请求 DTO
     */
    private MdmItemCreateReqDTO buildCreateReqDTO(WmsItemSaveReqVO saveReqVO) {
        MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
        createReqDTO.setCode(saveReqVO.getCode());
        createReqDTO.setName(saveReqVO.getName());
        createReqDTO.setCategoryId(saveReqVO.getCategoryId());
        createReqDTO.setBrandId(saveReqVO.getBrandId());
        createReqDTO.setUnitMeasureId(saveReqVO.getUnitMeasureId());
        createReqDTO.setItemType(MDM_ITEM_TYPE_MATERIAL);
        createReqDTO.setStatus(CommonStatusEnum.ENABLE.getStatus());
        createReqDTO.setRemark(saveReqVO.getRemark());
        return createReqDTO;
    }
    /**
     * æž„建更新请求 DTO
     */
    private MdmItemUpdateReqDTO buildUpdateReqDTO(WmsItemSaveReqVO saveReqVO) {
        MdmItemUpdateReqDTO updateReqDTO = new MdmItemUpdateReqDTO();
        updateReqDTO.setId(saveReqVO.getId());
        updateReqDTO.setCode(saveReqVO.getCode());
        updateReqDTO.setName(saveReqVO.getName());
        updateReqDTO.setCategoryId(saveReqVO.getCategoryId());
        updateReqDTO.setBrandId(saveReqVO.getBrandId());
        updateReqDTO.setUnitMeasureId(saveReqVO.getUnitMeasureId());
        updateReqDTO.setStatus(CommonStatusEnum.ENABLE.getStatus());
        updateReqDTO.setRemark(saveReqVO.getRemark());
        return updateReqDTO;
    }
    /**
     * è½¬æ¢ MDM ç‰©æ–™ä¸º WMS å•†å“
     */
    private WmsItemDO convertToWmsItemDO(MdmItemRespDTO mdmItem) {
        if (mdmItem == null) {
            return null;
        }
        WmsItemDO item = new WmsItemDO();
        item.setId(mdmItem.getId());
        item.setCode(mdmItem.getCode());
        item.setName(mdmItem.getName());
        item.setCategoryId(mdmItem.getCategoryId());
        item.setBrandId(mdmItem.getBrandId());
        item.setRemark(mdmItem.getRemark());
        // èŽ·å–è®¡é‡å•ä½åç§°
        if (mdmItem.getUnitMeasureId() != null) {
            MdmUnitMeasureRespDTO unit = mdmUnitMeasureApi.getUnitMeasure(mdmItem.getUnitMeasureId()).getCheckedData();
            if (unit != null) {
                item.setUnit(unit.getName());
            }
        }
        return item;
    }
    /**
     * æ‰¹é‡è½¬æ¢ MDM ç‰©æ–™åˆ—表为 WMS å•†å“åˆ—表
     */
    private List<WmsItemDO> convertToWmsItemDOList(List<MdmItemRespDTO> mdmItemList) {
        if (CollUtil.isEmpty(mdmItemList)) {
            return Collections.emptyList();
        }
        // æ‰¹é‡èŽ·å–è®¡é‡å•ä½
        Set<Long> unitIds = mdmItemList.stream()
                .map(MdmItemRespDTO::getUnitMeasureId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MdmUnitMeasureRespDTO> unitMap = new HashMap<>();
        if (!unitIds.isEmpty()) {
            List<MdmUnitMeasureRespDTO> units = mdmUnitMeasureApi.getUnitMeasureList(unitIds).getCheckedData();
            for (MdmUnitMeasureRespDTO unit : units) {
                unitMap.put(unit.getId(), unit);
            }
        }
        // è½¬æ¢åˆ—表
        List<WmsItemDO> result = new ArrayList<>();
        for (MdmItemRespDTO mdmItem : mdmItemList) {
            WmsItemDO item = new WmsItemDO();
            item.setId(mdmItem.getId());
            item.setCode(mdmItem.getCode());
            item.setName(mdmItem.getName());
            item.setCategoryId(mdmItem.getCategoryId());
            item.setBrandId(mdmItem.getBrandId());
            item.setRemark(mdmItem.getRemark());
            // è®¾ç½®å•位名称
            if (mdmItem.getUnitMeasureId() != null) {
                MdmUnitMeasureRespDTO unit = unitMap.get(mdmItem.getUnitMeasureId());
                if (unit != null) {
                    item.setUnit(unit.getName());
                }
            }
            result.add(item);
        }
        return result;
    }
}
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java
@@ -5,6 +5,8 @@
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemSkuApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemSkuRespDTO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuPageReqVO;
import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO;
import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
@@ -33,6 +35,9 @@
/**
 * WMS å•†å“ SKU Service å®žçŽ°ç±»
 *
 * æ”¹é€ è¯´æ˜Žï¼šWMS SKU æ•°æ®æ¥æºäºŽ MDM ç‰©æ–™ SKU
 * - getItemSkuListByIds/validateItemSkuExists è°ƒç”¨ MDM SKU API
 *
 * @author èŠ‹é“æºç 
 */
@Service
@@ -41,6 +46,10 @@
    @Resource
    private WmsItemSkuMapper itemSkuMapper;
    @Resource
    private MdmItemSkuApi mdmItemSkuApi;
    @Resource
    @Lazy // å»¶è¿ŸåŠ è½½ï¼Œé¿å…å¾ªçŽ¯ä¾èµ–
    private WmsInventoryService inventoryService;
@@ -111,26 +120,26 @@
    @Override
    public WmsItemSkuDO validateItemSkuExists(Long id) {
        WmsItemSkuDO sku = itemSkuMapper.selectById(id);
        if (sku == null) {
            throw exception(ITEM_SKU_NOT_EXISTS);
        }
        return sku;
        MdmItemSkuRespDTO sku = mdmItemSkuApi.validateItemSkuExists(id).getCheckedData();
        return BeanUtils.toBean(sku, WmsItemSkuDO.class);
    }
    @Override
    public List<WmsItemSkuDO> getItemSkuList(Long itemId) {
        return itemSkuMapper.selectListByItemId(itemId);
        List<MdmItemSkuRespDTO> list = mdmItemSkuApi.getItemSkuListByItemId(itemId).getCheckedData();
        return BeanUtils.toBean(list, WmsItemSkuDO.class);
    }
    @Override
    public List<WmsItemSkuDO> getItemSkuList(Collection<Long> itemIds) {
        return itemSkuMapper.selectListByItemIds(itemIds);
        // MDM API æš‚不支持按多个物料 ID æŸ¥è¯¢ SKU,暂时返回空列表
        return ListUtil.of();
    }
    @Override
    public List<WmsItemSkuDO> getItemSkuList(Collection<Long> itemIds, String code, String name) {
        return itemSkuMapper.selectList(itemIds, code, name);
        // MDM API æš‚不支持复杂查询,暂时返回空列表
        return ListUtil.of();
    }
    @Override
@@ -138,12 +147,14 @@
        if (CollUtil.isEmpty(ids)) {
            return ListUtil.of();
        }
        return itemSkuMapper.selectByIds(ids);
        List<MdmItemSkuRespDTO> list = mdmItemSkuApi.getItemSkuList(ids).getCheckedData();
        return BeanUtils.toBean(list, WmsItemSkuDO.class);
    }
    @Override
    public PageResult<WmsItemSkuDO> getItemSkuPage(WmsItemSkuPageReqVO pageReqVO) {
        return itemSkuMapper.selectPage(pageReqVO);
        // MDM API æš‚不支持分页查询 SKU,暂时返回空结果
        return new PageResult<>(ListUtil.of(), 0L);
    }
    private void validateItemSkuList(List<WmsItemSkuSaveReqVO> skus) {
yudao-server/pom.xml
@@ -129,6 +129,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- MDM ä¸»æ•°æ®ç®¡ç†æ¨¡å— -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mdm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- spring boot é…ç½®æ‰€éœ€ä¾èµ– -->
        <dependency>
            <groupId>org.springframework.boot</groupId>