From abf1c0a35d85d38239ff5d2ed746a4e4b797c9d1 Mon Sep 17 00:00:00 2001
From: xiaoyi <908147524@qq.com>
Date: 星期四, 20 八月 2026 14:05:02 +0800
Subject: [PATCH] feat 功能修正

---
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingServiceImpl.java                                          |  109 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/CrmCancellationController.java                            |  114 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java                                    |   12 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingService.java                                              |   27 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiByCategoryRespVO.java                             |   19 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java                                        |   18 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdPriceStandardDO.java                                    |   64 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceServiceImpl.java                                    |   69 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/refund/CrmRefundMapper.java                                                   |   36 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyService.java                                     |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeService.java                                    |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeRespVO.java                         |   43 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanRespVO.java                            |  116 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/dv/MesDvTelemetryPullJob.java                                                       |   30 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanItemMapper.java                                       |   31 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java                                                             |   12 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java                                                           |   22 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiService.java                                                |   35 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPullRespVO.java                          |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdAuditStatusEnum.java                                                      |   45 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingPageReqVO.java                                |   22 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java                                              |   39 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/listener/CrmCancellationStatusListener.java                        |   54 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardSaveReqVO.java                        |   41 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandService.java                                         |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java                                   |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java                              |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/process/MesProProcessDO.java                                         |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductRespVO.java                                  |   76 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelSaveReqVO.java                                |   50 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanService.java                               |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/product/MesPdProductDO.java                                           |   84 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java                                                          |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcReqVO.java                                |   37 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessSaveReqVO.java                             |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java                               |   87 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandPageReqVO.java                   |   25 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessRespVO.java                                |    5 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/credit/CrmCreditRatingMapper.java                                             |   39 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchasePlanStatusConvert.java                                               |   48 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/ctc/ProductCtcDO.java                                                 |  103 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiServiceImpl.java                                            |  160 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmRespVO.java                        |   92 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdServicePackageMapper.java                                    |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java                                         |   14 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicySaveReqVO.java                       |   45 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPageReqVO.java                           |   39 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDetailRespVO.java                                 |   30 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/MesProMaintenancePlanController.java               |  175 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java                                                       |    2 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanMapper.java                                           |   41 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java                                       |    7 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRespVO.java                                     |   55 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionService.java                                              |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdPriceStandardMapper.java                                     |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestService.java                                 |   69 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiForecastDataDO.java                                             |   90 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java                                         |   26 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcSaveReqVO.java                                     |   68 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java                         |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcReqVO.java                                 |   33 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRecordDO.java                                              |   95 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchasePlanStatusListener.java                            |   53 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java                                               |   31 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationPageReqVO.java                          |   25 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/ProductCtcReelController.java                                  |  123 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdElectricityTypeDO.java                                  |   50 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/techconfirm/ProductTechConfirmMapper.java                                  |   23 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardService.java                                      |   28 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRuleDO.java                                                |   69 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelService.java                                       |   32 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java                            |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/ctc/ProductCtcMapper.java                                                  |   26 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanServiceImpl.java                                        |  431 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelServiceImpl.java                                          |  102 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java                                              |   17 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanDO.java                                          |  102 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingRespVO.java                                   |   72 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordService.java                                                |   27 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandSaveReqVO.java                   |   56 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportExcelVO.java |   33 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSafetyStockRespDTO.java                                       |   52 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java                                                            |   14 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java                           |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/MesProEmergencyPlanController.java                   |   87 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java                                        |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java                             |   13 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaSaveReqVO.java                  |    3 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/cancellation/CrmCancellationMapper.java                                       |   37 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypePageReqVO.java                      |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/ProductTechConfirmController.java                       |   97 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcServiceImpl.java                                               |  175 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java                                        |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcReqVO.java                                   |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmPageReqVO.java                     |   30 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java                                     |  169 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceRespVO.java                             |   52 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                                           |   30 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/CrmRefundController.java                                        |  114 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductSaveReqVO.java                               |   41 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/emergencyplan/MesProEmergencyPlanDO.java                             |   77 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmSaveReqVO.java                     |   66 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRulePageReqVO.java                              |   19 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanMapper.java                          |   39 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanDO.java                         |  106 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java                             |   66 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiOverviewRespVO.java                               |   36 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdVoltageLevelMapper.java                                      |   21 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java                         |    6 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java                                       |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java                                   |   52 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiForecastDataMapper.java                                              |   22 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java                                |  210 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductService.java                                             |   42 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java                                               |   30 
 sql/mysql/ruoyi-vue-pro.sql                                                                                                                        |   23 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcRespVO.java                               |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardServiceImpl.java                                  |   84 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyRespVO.java                          |   63 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java                                               |   10 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTelemetryProperties.java                                            |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductAuditReqVO.java                              |   22 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateRespVO.java                               |   52 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractCommandDO.java                                       |   50 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanSaveReqVO.java                 |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/outsourcetest/MesQcOutsourceTestDO.java                               |   99 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastReqVO.java                                   |   22 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractCommandMapper.java                                        |   34 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/techconfirm/ProductTechConfirmDO.java                                 |  105 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/md/item/MesMdItemDO.java                                                 |    7 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationSaveReqVO.java                          |   71 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApiImpl.java                                                |   50 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanService.java                                            |  118 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundServiceImpl.java                                                |  259 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/telemetry/MesDvTelemetryMapper.java                                        |   49 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java                                            |    3 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertPageReqVO.java                                  |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeServiceImpl.java                                |   76 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiKpiDefinitionDO.java                                            |   99 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/telemetry/MesDvTelemetryDO.java                                       |   90 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateService.java                                          |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyServiceImpl.java                                 |   84 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcService.java                                                   |   39 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java                                       |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderTypeEnum.java                                                  |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageService.java                                     |   32 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/credit/CrmCreditRatingDO.java                                            |   41 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdDiscountPolicyMapper.java                                    |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryRespVO.java                              |   60 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvOeeRespVO.java                                   |   65 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueSaveReqVO.java                     |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanRespVO.java                    |   52 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertRuleController.java                                |   64 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java                                     |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductPageReqVO.java                               |   27 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/CrmCreditRatingController.java                                  |   95 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchasePlanStatusEnum.java                                                    |   55 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java                              |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemRespVO.java                                        |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductIssueTypeEnum.java                                                 |   38 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcDetailRespVO.java                            |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanPageReqVO.java             |   36 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/AlertEvaluationTask.java                                                 |  139 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiController.java                                      |   55 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java                                    |    5 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java                               |   28 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationService.java                                        |   35 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java                                                         |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java                                                          |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java                                   |    6 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDefinitionPageReqVO.java                          |   22 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanItemDO.java                                      |   63 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProMaintenancePlanTypeEnum.java                                            |   40 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/cancellation/CrmCancellationDO.java                                      |   54 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelRespVO.java                            |   44 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateServiceImpl.java                                      |   83 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/MesPdPriceController.java                                     |   36 
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchasePlanCreateReqDTO.java                                   |   74 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundService.java                                                    |   35 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java                                                   |   19 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiKpiDefinitionMapper.java                                             |    9 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractCommandController.java                             |   94 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java                            |   23 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundPageReqVO.java                                      |   25 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeSaveReqVO.java                      |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java                                     |  109 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanRespVO.java                |  105 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanService.java                           |  103 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordServiceImpl.java                                            |   87 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java                                      |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java                               |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java                                                            |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductServiceImpl.java                                         |  151 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdTieredPriceMapper.java                                       |   38 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java                    |   58 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationAuditReqVO.java                         |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanPageReqVO.java                 |   27 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundAuditReqVO.java                                     |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackagePageReqVO.java                       |   21 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java                                         |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProductArchiveTypeEnum.java                                               |   44 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/refund/CrmRefundDO.java                                                  |   51 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvStatisticsRespVO.java                            |   59 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundRespVO.java                                         |   86 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdTieredPriceDO.java                                      |   59 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationServiceImpl.java                                    |  259 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java                            |    7 
 yudao-server/src/main/resources/application-local.yaml                                                                                             |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestSaveReqVO.java                   |   48 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateSaveReqVO.java                            |   45 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanItemMapper.java                      |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceService.java                                                 |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/emergencyplan/MesProEmergencyPlanMapper.java                              |   31 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRecordMapper.java                                               |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java                                     |   10 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPricePageReqVO.java                          |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportRespVO.java  |   26 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/certificate/HrmCertificateDO.java                                        |   81 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelPageReqVO.java                         |   21 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java                         |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java                              |  133 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java                                                           |   13 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceService.java                                        |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java                                             |   19 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSummaryRespVO.java                       |   66 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanSaveReqVO.java             |   81 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java                            |   24 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/listener/CrmRefundStatusListener.java                                    |   54 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelSaveReqVO.java                         |   34 
 yudao-module-crm/pom.xml                                                                                                                           |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaRespVO.java                     |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskLineController.java            |   26 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertController.java                                    |   59 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/MesPdBaseDataController.java                               |  386 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationRespVO.java                             |   95 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdServicePackageDO.java                                   |   61 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueRespVO.java                        |    3 
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApi.java                                                |   22 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundSaveReqVO.java                                      |   65 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryLatestReqVO.java                         |   16 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastRespVO.java                                  |   49 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionServiceImpl.java                                          |   81 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcRespVO.java                                        |  101 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java                                    |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdElectricityTypeMapper.java                                   |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcReqVO.java                           |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/MesPdProductController.java                                 |  104 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceServiceImpl.java                                             |  140 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanPageReqVO.java                         |   45 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryDeviceRespVO.java                        |   16 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRuleMapper.java                                                 |    9 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandRespVO.java                      |   74 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageServiceImpl.java                                 |   88 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcRespVO.java                                  |   31 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcRespVO.java                                |   18 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java                                               |   51 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java                                         |   39 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/reel/ProductCtcReelDO.java                                            |   79 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/outsourcetest/MesQcOutsourceTestMapper.java                                |   33 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleServiceImpl.java                                              |   76 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardPageReqVO.java                        |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/MesDvStatisticsController.java                           |  221 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueTypeEnum.java                                                    |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java                     |  128 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/job/purchase/ErpPurchasePlanStockAlertJob.java                                          |   37 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java                                 |   49 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java                            |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardRespVO.java                           |   59 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelServiceImpl.java                                   |   76 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyPageReqVO.java                       |   24 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanSaveReqVO.java                         |   76 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestPageReqVO.java                   |   39 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestServiceImpl.java                             |  158 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/ProductCtcController.java                                       |  142 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelService.java                                              |   31 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingSaveReqVO.java                                |   52 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanServiceImpl.java                           |  115 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmService.java                                   |   25 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelPageReqVO.java                                |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanItemDO.java                     |   54 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleService.java                                                  |   26 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/reel/ProductCtcReelMapper.java                                             |   30 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcPageReqVO.java                                     |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/product/MesPdProductMapper.java                                            |   26 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageSaveReqVO.java                       |   41 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcRespVO.java                          |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestRespVO.java                      |   81 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdVoltageLevelDO.java                                     |   52 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageRespVO.java                          |   52 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueDO.java                                 |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java                         |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineService.java                        |   14 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandServiceImpl.java                                     |  146 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java                           |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdDiscountPolicyDO.java                                   |   68 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiDefinitionController.java                            |   71 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelRespVO.java                                   |   73 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanServiceImpl.java                       |  288 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceSaveReqVO.java                          |   37 
 299 files changed, 15,657 insertions(+), 18 deletions(-)

diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql
index c83563a..2317cc5 100644
--- a/sql/mysql/ruoyi-vue-pro.sql
+++ b/sql/mysql/ruoyi-vue-pro.sql
@@ -1155,6 +1155,10 @@
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3106, 1, '鑷鐢熶骇', '1', 'mes_pro_work_order_type', 0, 'primary', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3107, 2, '浠e伐', '2', 'mes_pro_work_order_type', 0, 'warning', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3108, 3, '閲囪喘', '3', 'mes_pro_work_order_type', 0, 'info', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3109, 3, '杩愮淮璁″垝', '3', 'mes_pro_work_order_source_type', 0, 'primary', '', '', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3110, 4, '鐢靛姏杩愮淮', '4', 'mes_pro_work_order_type', 0, 'primary', '', '', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3111, 5, '鍙戠數', '5', 'mes_pro_work_order_type', 0, 'warning', '', '', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3112, 6, '妫�淇�', '6', 'mes_pro_work_order_type', 0, 'info', '', '', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3121, 1, 'IQC锛堟潵鏂欐楠岋級', '1', 'mes_qc_type', 0, 'primary', '', '鏉ユ枡璐ㄩ噺妫�楠�', '1', '2026-02-18 14:12:05', '1', '2026-02-18 14:12:05', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3122, 2, 'IPQC锛堣繃绋嬫楠岋級', '2', 'mes_qc_type', 0, 'warning', '', '鐢熶骇鍒剁▼璐ㄩ噺妫�楠�', '1', '2026-02-18 14:12:05', '1', '2026-03-24 15:21:34', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3123, 3, 'OQC锛堝嚭璐ф楠岋級', '3', 'mes_qc_type', 0, 'success', '', '鍑鸿揣璐ㄩ噺妫�楠�', '1', '2026-02-18 14:12:05', '1', '2026-02-18 14:12:05', b'0');
@@ -1234,6 +1238,8 @@
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3224, 3, '寰呮墽琛岄鍑�', '3', 'mes_wm_product_issue_status', 0, 'primary', '', '宸插鎵癸紝鎷h揣瀹屾垚', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3225, 4, '宸插畬鎴�', '4', 'mes_wm_product_issue_status', 0, 'success', '', '宸插畬鎴愬嚭搴�', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3226, 5, '宸插彇娑�', '5', 'mes_wm_product_issue_status', 0, 'success', '', '宸插畬鎴愬嚭搴�', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061137, 1, '姝e父棰嗘枡', '1', 'mes_wm_product_issue_type', 0, 'primary', '', '姝e父鐢熶骇棰嗘枡', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061138, 2, '绱ф�ヨˉ鏂�', '2', 'mes_wm_product_issue_type', 0, 'warning', '', '鐢熶骇鐜板満绱ф�ヨˉ鏂�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3232, 1, '鑽夌', '0', 'mes_wm_return_issue_status', 0, 'info', '', '鑽夌鐘舵�侊紝鍙紪杈�', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:24', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3233, 2, '寰呮楠�', '1', 'mes_wm_return_issue_status', 0, 'default', '', '宸茬‘璁わ紝绛夊緟璐ㄦ', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:28', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3234, 3, '寰呬笂鏋�', '2', 'mes_wm_return_issue_status', 0, 'warning', '', '妫�楠屽畬鎴愶紝绛夊緟浠撳簱涓婃灦', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:31', b'0');
@@ -1263,6 +1269,15 @@
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3277, 1, '搴撳瓨璋冩暣', '1', 'mes_wm_misc_receipt_type', 0, 'primary', '', '搴撳瓨璋冩暣鍏ュ簱', '1', '2026-03-03 07:34:33', '1', '2026-03-03 07:34:33', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3278, 1, '搴撳瓨璋冩暣', '1', 'mes_wm_misc_issue_type', 0, 'primary', '', '搴撳瓨璋冩暣鍑哄簱', '1', '2026-03-03 07:34:33', '1', '2026-03-03 07:34:33', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3279, 2, '鎶ュ簾鍑哄簱', '2', 'mes_wm_misc_issue_type', 0, 'danger', '', '鎶ュ簾鍑哄簱', '1', '2026-03-03 07:36:13', '1', '2026-03-03 07:36:13', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061144, 3, '搴熸棫鐗╄祫鍑哄簱', '3', 'mes_wm_misc_issue_type', 0, 'warning', '', '搴熸棫鐗╄祫鍑哄簱', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061145, 4, '璁惧鍑哄簱', '4', 'mes_wm_misc_issue_type', 0, 'primary', '', '鐢靛姏璁惧鍑哄簱', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061146, 2, '璁惧鍏ュ簱', '2', 'mes_wm_misc_receipt_type', 0, 'primary', '', '鐢靛姏璁惧鍏ュ簱', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061147, 3, '搴熸棫鐗╄祫鍏ュ簱', '3', 'mes_wm_misc_receipt_type', 0, 'warning', '', '搴熸棫鐗╄祫鍏ュ簱', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061148, 1, '鏅�氫粨搴�', '1', 'mes_wm_warehouse_type', 0, 'default', '', '閫氱敤鐗╄祫浠撳簱', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061149, 2, '璁惧浠�', '2', 'mes_wm_warehouse_type', 0, 'primary', '', '鐢靛姏璁惧浠撳偍', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061150, 3, '澶囦欢浠�', '3', 'mes_wm_warehouse_type', 0, 'warning', '', '澶囧搧澶囦欢浠撳偍', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061151, 4, '鑰楁潗浠�', '4', 'mes_wm_warehouse_type', 0, 'info', '', '鏃ュ父鑰楁潗浠撳偍', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061152, 5, '鍗卞寲鍝佷粨', '5', 'mes_wm_warehouse_type', 0, 'danger', '', '鍗遍櫓鍖栧鍝佷笓鐢ㄤ粨鍌�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3280, 1, '鑽夌', '0', 'mes_wm_outsource_receipt_status', 0, 'info', '', '鑽夌鐘舵��', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3281, 2, '寰呮楠�', '1', 'mes_wm_outsource_receipt_status', 0, 'warning', '', '宸茬‘璁わ紝绛夊緟璐ㄦ', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3282, 3, '寰呬笂鏋�', '2', 'mes_wm_outsource_receipt_status', 0, 'primary', '', '妫�楠屽畬鎴愶紝绛夊緟浠撳簱涓婃灦', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', b'0');
@@ -1334,6 +1349,11 @@
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3380, 6, '璐ㄩ噺鐘舵��', '900', 'mes_wm_stock_taking_plan_param_type', 0, 'default', '', '鎸夎川閲忕姸鎬佺洏鐐�', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3381, 1, '鐗╂枡', 'ITEM', 'mes_md_item_or_product', 0, 'info', '', '', '1', '2026-03-15 01:55:06', '1', '2026-03-15 01:55:06', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3382, 2, '浜у搧', 'PRODUCT', 'mes_md_item_or_product', 0, 'success', '', '', '1', '2026-03-15 01:55:06', '1', '2026-03-15 01:55:06', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061139, 1, '鐢靛姏璁惧', '1', 'mes_material_category', 0, 'primary', '', '鍙樺帇鍣ㄣ�佸紑鍏虫煖銆佽緭鐢电嚎璺�佸彂鐢垫満缁勩�佸偍鑳借澶囩瓑', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061140, 2, '澶囧搧澶囦欢', '2', 'mes_material_category', 0, 'warning', '', '鐢靛姏璁惧缁翠慨鏇存崲鐢ㄥ鍝佸浠�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061141, 3, '鑰楁潗', '3', 'mes_material_category', 0, 'info', '', '鏃ュ父杩愮淮娑堣�楁�ф潗鏂�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061142, 4, '宸ョ▼鏉愭枡', '4', 'mes_material_category', 0, 'success', '', '鐢靛姏宸ョ▼寤鸿鐢ㄦ潗鏂�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061143, 5, '瀹夐槻鐗╄祫', '5', 'mes_material_category', 0, 'danger', '', '瀹夊叏鐢熶骇闃叉姢鐗╄祫', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3383, 1, '鑽夌', '0', 'mes_wm_item_consume_status', 0, 'info', '', '鑽夌鐘舵��', '1', '2026-03-19 15:06:23', '1', '2026-03-19 15:06:23', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3384, 2, '宸插畬鎴�', '4', 'mes_wm_item_consume_status', 0, 'success', '', '宸插畬鎴�', '1', '2026-03-19 15:06:23', '1', '2026-03-19 15:06:23', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3385, 1, '鍒拌揣閫氱煡鍗�', '100', 'mes_qc_source_doc_type', 0, 'primary', '', 'IQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', b'0');
@@ -1675,6 +1695,7 @@
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2067, 'MES 閲囪喘鍏ュ簱鍗曠姸鎬�', 'mes_wm_item_receipt_status', 0, 'MES 閲囪喘鍏ュ簱鍗曠姸鎬�', '1', '2026-02-22 14:54:05', '1', '2026-02-22 14:54:05', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2068, 'MES 鍗曟嵁鐘舵��', 'mes_order_status', 0, '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:17:37', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2069, 'MES 棰嗘枡鍑哄簱鍗曠姸鎬�', 'mes_wm_product_issue_status', 0, 'MES 棰嗘枡鍑哄簱鍗曠姸鎬�', '1', '2026-02-26 16:39:44', '1', '2026-04-05 15:05:11', b'0', NULL);
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061099, 'MES 鐢熶骇棰嗘枡鍗曟嵁绫诲瀷', 'mes_wm_product_issue_type', 0, 'MES 鐢熶骇棰嗘枡鍗曟嵁绫诲瀷锛�1=姝e父棰嗘枡锛�2=绱ф�ヨˉ鏂欙級', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2073, 'MES 鐢熶骇閫�鏂欏崟鐘舵��', 'mes_wm_return_issue_status', 0, 'MES 鐢熶骇閫�鏂欏崟鐘舵��', '1', '2026-02-28 14:11:09', '1', '2026-04-05 15:05:14', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2074, 'MES 鐢熶骇閫�鏂欑被鍨�', 'mes_wm_return_issue_type', 0, 'MES 鐢熶骇閫�鏂欑被鍨�', '1', '2026-02-28 14:11:09', '1', '2026-04-05 15:05:16', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2075, 'MES 璐ㄩ噺鐘舵��', 'mes_wm_quality_status', 0, 'MES 璐ㄩ噺鐘舵�侊紙寰呮/鍚堟牸/涓嶅悎鏍硷級', '1', '2026-02-28 15:00:53', '1', '2026-02-28 15:00:53', b'0', NULL);
@@ -1683,6 +1704,7 @@
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2105, 'MES 鏉傞」鍏ュ簱绫诲瀷', 'mes_wm_misc_receipt_type', 0, '鏉傞」鍏ュ簱绫诲瀷', '1', '2026-03-03 07:18:12', '1', '2026-04-05 15:05:23', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2106, 'MES 鏉傞」鍏ュ簱鐘舵��', 'mes_wm_misc_receipt_status', 0, '鏉傞」鍏ュ簱鐘舵��', '1', '2026-03-03 07:18:12', '1', '2026-04-05 15:05:25', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2109, 'MES 鏉傞」鍑哄簱绫诲瀷', 'mes_wm_misc_issue_type', 0, 'MES 鏉傞」鍑哄簱绫诲瀷', '1', '2026-03-03 07:34:33', '1', '2026-03-03 07:34:33', b'0', NULL);
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061101, 'MES 浠撳簱/搴撳尯绫诲瀷', 'mes_wm_warehouse_type', 0, 'MES 浠撳簱/搴撳尯绫诲瀷锛�1=鏅�氫粨搴擄紝2=璁惧浠擄紝3=澶囦欢浠擄紝4=鑰楁潗浠擄紝5=鍗卞寲鍝佷粨锛�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2110, 'MES 澶栧崗鍏ュ簱鍗曠姸鎬�', 'mes_wm_outsource_receipt_status', 0, 'MES 澶栧崗鍏ュ簱鍗曠姸鎬�', '1', '2026-03-03 14:03:20', '1', '2026-03-03 14:03:20', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2112, 'MES 澶栧崗鍙戞枡鍗曠姸鎬�', 'mes_wm_outsource_issue_status', 0, 'MES 澶栧崗鍙戞枡鍗曠姸鎬�', '1', '2026-03-03 16:30:56', '1', '2026-04-05 15:05:47', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2113, 'MES 缂栫爜瑙勫垯鍒嗘绫诲瀷', 'mes_md_auto_code_part_type', 0, 'MES 缂栫爜瑙勫垯鍒嗘绫诲瀷', '1', '2026-03-04 14:45:46', '1', '2026-03-04 15:24:40', b'0', NULL);
@@ -1698,6 +1720,7 @@
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2126, 'MES 鐩樼偣浠诲姟鐘舵��', 'mes_wm_stock_taking_task_status', 0, 'MES 鐩樼偣浠诲姟鐘舵��', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2127, 'MES 鐩樼偣浠诲姟琛岀姸鎬�', 'mes_wm_stock_taking_task_line_status', 0, 'MES 鐩樼偣浠诲姟琛岀姸鎬�', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2129, 'MES 鐗╂枡浜у搧鏍囪瘑', 'mes_md_item_or_product', 0, '鐗╂枡鍒嗙被锛氱墿鏂�(ITEM) / 浜у搧(PRODUCT)', '1', '2026-03-15 01:55:06', '1', '2026-03-15 01:55:06', b'0', NULL);
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061100, 'MES 鐢靛姏鐗╄祫鍒嗙被', 'mes_material_category', 0, '鐢靛姏鐗╄祫鍒嗙被锛�1=鐢靛姏璁惧锛�2=澶囧搧澶囦欢锛�3=鑰楁潗锛�4=宸ョ▼鏉愭枡锛�5=瀹夐槻鐗╄祫锛�', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2130, 'MES 渚涘簲鍟嗛��璐у崟鐘舵��', 'mes_wm_return_vendor_status', 0, '閲囪喘閫�璐у崟鐘舵��', '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2131, 'MES 鍙戣揣閫氱煡鍗曠姸鎬�', 'mes_wm_sales_notice_status', 0, 'MES 鍙戣揣閫氱煡鍗曠姸鎬�', '1', '2026-03-30 08:54:30', '1', '2026-04-05 15:53:46', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2132, 'MES 鏉傞」鍑哄簱鍗曠姸鎬�', 'mes_wm_misc_issue_status', 0, '鏉傞」鍑哄簱鍗曠姸鎬�', '1', '2026-03-30 15:00:18', '1', '2026-04-05 15:05:41', b'0', NULL);
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java
index ff45d51..df5b105 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java
@@ -16,7 +16,11 @@
     UNCLASSIFIED(0, "鏈垽瀹�"),
     GENERAL(1, "涓�鑸棶棰�"),
     REPAIR(2, "缁翠慨闂"),
-    RETURN(3, "閫�璐ч棶棰�");
+    RETURN(3, "閫�璐ч棶棰�"),
+    POWER_OUTAGE_REPAIR(4, "鍋滅數鎶ヤ慨"),
+    POWER_FAULT(5, "鐢ㄧ數鏁呴殰"),
+    SERVICE_COMPLAINT(6, "鏈嶅姟鎶曡瘔"),
+    POWER_QUALITY_DISPUTE(7, "渚涚數璐ㄩ噺寮傝");
 
     public static final Integer[] ARRAYS = Arrays.stream(values())
             .map(AfterSaleIssueTypeEnum::getType).toArray(Integer[]::new);
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertController.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertController.java
new file mode 100644
index 0000000..70dbbba
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertController.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertPageReqVO;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertRespVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRecordDO;
+import cn.iocoder.yudao.module.bi.service.decision.BiAlertRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 棰勮涓績")
+@RestController
+@RequestMapping("/bi/decision/alert")
+@Validated
+public class DecisionAlertController {
+
+    @Resource
+    private BiAlertRecordService alertRecordService;
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰彇棰勮璁板綍鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert:query')")
+    public CommonResult<PageResult<BiAlertRecordDO>> getAlertPage(@Valid DecisionAlertPageReqVO reqVO) {
+        return success(alertRecordService.getRecordPage(reqVO));
+    }
+
+    @GetMapping("/unprocessed-count")
+    @Operation(summary = "鑾峰彇鏈鐞嗛璀︽暟閲�")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert:query')")
+    public CommonResult<Long> getUnprocessedCount() {
+        return success(alertRecordService.getUnprocessedCount());
+    }
+
+    @PostMapping("/confirm/{id}")
+    @Operation(summary = "纭棰勮")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert:confirm')")
+    public CommonResult<Boolean> confirmAlert(@PathVariable("id") Long id) {
+        alertRecordService.confirmRecord(id, 1L);
+        return success(true);
+    }
+
+    @PostMapping("/handle")
+    @Operation(summary = "澶勭悊棰勮")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert:handle')")
+    public CommonResult<Boolean> handleAlert(@RequestParam("id") Long id,
+                                              @RequestParam(value = "handleRemark", required = false) String handleRemark) {
+        alertRecordService.handleRecord(id, 1L, handleRemark);
+        return success(true);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertRuleController.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertRuleController.java
new file mode 100644
index 0000000..6fba522
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionAlertRuleController.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertRulePageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRuleDO;
+import cn.iocoder.yudao.module.bi.service.decision.BiAlertRuleService;
+import io.swagger.v3.oas.annotations.Operation;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 棰勮瑙勫垯")
+@RestController
+@RequestMapping("/bi/decision/alert-rule")
+@Validated
+public class DecisionAlertRuleController {
+
+    @Resource
+    private BiAlertRuleService alertRuleService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓棰勮瑙勫垯")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert-rule:create')")
+    public CommonResult<Long> createRule(@Valid @RequestBody BiAlertRuleDO rule) {
+        return success(alertRuleService.createRule(rule));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊棰勮瑙勫垯")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert-rule:update')")
+    public CommonResult<Boolean> updateRule(@Valid @RequestBody BiAlertRuleDO rule) {
+        alertRuleService.updateRule(rule);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎棰勮瑙勫垯")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert-rule:delete')")
+    public CommonResult<Boolean> deleteRule(@RequestParam("id") Long id) {
+        alertRuleService.deleteRule(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰彇棰勮瑙勫垯")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert-rule:query')")
+    public CommonResult<BiAlertRuleDO> getRule(@RequestParam("id") Long id) {
+        return success(alertRuleService.getRule(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰彇棰勮瑙勫垯鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('bi:decision:alert-rule:query')")
+    public CommonResult<PageResult<BiAlertRuleDO>> getRulePage(@Valid DecisionAlertRulePageReqVO pageReq) {
+        return success(alertRuleService.getRulePage(pageReq));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java
new file mode 100644
index 0000000..2106626
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastReqVO;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastRespVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
+import cn.iocoder.yudao.module.bi.service.decision.BiForecastService;
+import io.swagger.v3.oas.annotations.Operation;
+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.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 璐熻嵎棰勬祴")
+@RestController
+@RequestMapping("/bi/decision/forecast")
+@Validated
+public class DecisionForecastController {
+
+    @Resource
+    private BiForecastService forecastService;
+
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰彇棰勬祴鏁版嵁")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<List<BiForecastDataDO>> getForecastList(@Valid DecisionForecastReqVO reqVO) {
+        // 榛樿鏌ユ渶杩�30澶�
+        LocalDateTime endTime = LocalDateTime.now();
+        LocalDateTime beginTime = endTime.minusDays(30);
+        return success(forecastService.getForecastList(reqVO.getForecastCode(), beginTime, endTime));
+    }
+
+    @PostMapping("/generate")
+    @Operation(summary = "鐢熸垚棰勬祴锛堝崰浣嶏級")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<Boolean> generateForecast(@RequestParam("forecastCode") String forecastCode) {
+        forecastService.generateForecast(forecastCode, LocalDateTime.now());
+        return success(true);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiController.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiController.java
new file mode 100644
index 0000000..de14c43
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiController.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.*;
+import cn.iocoder.yudao.module.bi.service.decision.BiDecisionKpiService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 KPI")
+@RestController
+@RequestMapping("/bi/decision/kpi")
+@Validated
+public class DecisionKpiController {
+
+    @Resource
+    private BiDecisionKpiService decisionKpiService;
+
+    @GetMapping("/overview")
+    @Operation(summary = "鑾峰彇KPI姒傝")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<Map<String, Object>> getKpiOverview() {
+        return success(decisionKpiService.getKpiOverview());
+    }
+
+    @GetMapping("/detail/{kpiCode}")
+    @Operation(summary = "鑾峰彇KPI璇︽儏")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<Map<String, Object>> getKpiDetail(@PathVariable("kpiCode") String kpiCode) {
+        return success(decisionKpiService.getKpiDetail(kpiCode));
+    }
+
+    @GetMapping("/category/{category}")
+    @Operation(summary = "鎸夊垎绫昏幏鍙朘PI")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<Map<String, Object>> getKpiByCategory(@PathVariable("category") String category) {
+        return success(decisionKpiService.getKpiByCategory(category));
+    }
+
+    @PostMapping("/refresh/{kpiCode}")
+    @Operation(summary = "鍒锋柊KPI鍊�")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<BigDecimal> refreshKpi(@PathVariable("kpiCode") String kpiCode) {
+        return success(decisionKpiService.refreshKpi(kpiCode));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiDefinitionController.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiDefinitionController.java
new file mode 100644
index 0000000..f8d1963
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionKpiDefinitionController.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionKpiDefinitionPageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+import cn.iocoder.yudao.module.bi.service.decision.BiKpiDefinitionService;
+import io.swagger.v3.oas.annotations.Operation;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 KPI鎸囨爣瀹氫箟")
+@RestController
+@RequestMapping("/bi/decision/kpi-definition")
+@Validated
+public class DecisionKpiDefinitionController {
+
+    @Resource
+    private BiKpiDefinitionService kpiDefinitionService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓KPI鎸囨爣")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:create')")
+    public CommonResult<Long> createKpi(@Valid @RequestBody BiKpiDefinitionDO kpi) {
+        return success(kpiDefinitionService.createKpi(kpi));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊KPI鎸囨爣")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:update')")
+    public CommonResult<Boolean> updateKpi(@Valid @RequestBody BiKpiDefinitionDO kpi) {
+        kpiDefinitionService.updateKpi(kpi);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎KPI鎸囨爣")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:delete')")
+    public CommonResult<Boolean> deleteKpi(@RequestParam("id") Long id) {
+        kpiDefinitionService.deleteKpi(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰彇KPI鎸囨爣")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<BiKpiDefinitionDO> getKpi(@RequestParam("id") Long id) {
+        return success(kpiDefinitionService.getKpi(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰彇KPI鎸囨爣鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<PageResult<BiKpiDefinitionDO>> getKpiPage(@Valid DecisionKpiDefinitionPageReqVO pageReq) {
+        return success(kpiDefinitionService.getKpiPage(pageReq));
+    }
+
+    @GetMapping("/list-all")
+    @Operation(summary = "鑾峰彇鎵�鏈塊PI鎸囨爣")
+    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
+    public CommonResult<PageResult<BiKpiDefinitionDO>> getKpiList() {
+        return success(kpiDefinitionService.getKpiPage(new DecisionKpiDefinitionPageReqVO()));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertPageReqVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertPageReqVO.java
new file mode 100644
index 0000000..337460c
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertPageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - 棰勮璁板綍鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DecisionAlertPageReqVO extends PageParam {
+
+    @Schema(description = "鐘舵�侊細0-鏈鐞� 1-宸茬‘璁� 2-宸插鐞�", example = "0")
+    private Integer status;
+
+    @Schema(description = "涓ラ噸绾у埆锛�1-璀﹀憡 2-涓ラ噸 3-绱ф��", example = "1")
+    private Integer severity;
+
+    @Schema(description = "KPI缂栫爜", example = "power_supply_amount")
+    private String kpiCode;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRespVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRespVO.java
new file mode 100644
index 0000000..8bcbcac
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRespVO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.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 DecisionAlertRespVO {
+
+    @Schema(description = "璁板綍ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍏宠仈瑙勫垯ID", example = "1")
+    private Long ruleId;
+
+    @Schema(description = "KPI缂栫爜", example = "power_supply_amount")
+    private String kpiCode;
+
+    @Schema(description = "KPI鍚嶇О", example = "渚涚數閲�")
+    private String kpiName;
+
+    @Schema(description = "瀹為檯鍊�", example = "15000.00")
+    private BigDecimal actualValue;
+
+    @Schema(description = "闃堝��", example = "10000.00")
+    private BigDecimal thresholdValue;
+
+    @Schema(description = "涓ラ噸绾у埆锛�1-璀﹀憡 2-涓ラ噸 3-绱ф��", example = "1")
+    private Integer severity;
+
+    @Schema(description = "棰勮娑堟伅", example = "渚涚數閲忓綋鍓嶅��15000瓒呭嚭闃堝��10000")
+    private String alertMessage;
+
+    @Schema(description = "鐘舵�侊細0-鏈鐞� 1-宸茬‘璁� 2-宸插鐞�", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶勭悊浜篒D", example = "1")
+    private Long handlerId;
+
+    @Schema(description = "澶勭悊鏃堕棿", example = "2026-08-20 10:00:00")
+    private LocalDateTime handleTime;
+
+    @Schema(description = "澶勭悊澶囨敞", example = "宸茬‘璁�")
+    private String handleRemark;
+
+    @Schema(description = "瑙﹀彂鏃堕棿", example = "2026-08-20 09:55:00")
+    private LocalDateTime triggeredTime;
+
+    @Schema(description = "鍒涘缓鏃堕棿", example = "2026-08-20 09:55:00")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRulePageReqVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRulePageReqVO.java
new file mode 100644
index 0000000..a36f249
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionAlertRulePageReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - 棰勮瑙勫垯鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DecisionAlertRulePageReqVO extends PageParam {
+
+    @Schema(description = "鍏宠仈KPI鎸囨爣ID", example = "1")
+    private Long kpiId;
+
+    @Schema(description = "鏄惁鍚敤锛�0-绂佺敤 1-鍚敤", example = "1")
+    private Integer enabled;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastReqVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastReqVO.java
new file mode 100644
index 0000000..8f27cea
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - 璐熻嵎棰勬祴 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DecisionForecastReqVO extends PageParam {
+
+    @Schema(description = "棰勬祴缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "load_forecast_daily")
+    private String forecastCode;
+
+    @Schema(description = "缁村害", example = "region")
+    private String dimension;
+
+    @Schema(description = "缁村害鍊�", example = "鍗庝笢")
+    private String dimensionValue;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastRespVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastRespVO.java
new file mode 100644
index 0000000..5582840
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastRespVO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.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 DecisionForecastRespVO {
+
+    @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "棰勬祴缂栫爜", example = "load_forecast_daily")
+    private String forecastCode;
+
+    @Schema(description = "棰勬祴鍚嶇О", example = "鏃ヨ礋鑽烽娴�")
+    private String forecastName;
+
+    @Schema(description = "棰勬祴鏃堕棿鐐�", example = "2026-08-21 00:00:00")
+    private LocalDateTime pointTime;
+
+    @Schema(description = "棰勬祴鍊�", example = "5000.00")
+    private BigDecimal forecastValue;
+
+    @Schema(description = "瀹為檯鍊�", example = "4800.00")
+    private BigDecimal actualValue;
+
+    @Schema(description = "缃俊鍖洪棿涓嬮檺", example = "4500.00")
+    private BigDecimal lowerBound;
+
+    @Schema(description = "缃俊鍖洪棿涓婇檺", example = "5500.00")
+    private BigDecimal upperBound;
+
+    @Schema(description = "缃俊姘村钩(%)", example = "95.00")
+    private BigDecimal confidenceLevel;
+
+    @Schema(description = "妯″瀷鐗堟湰", example = "v1.0")
+    private String modelVersion;
+
+    @Schema(description = "缁村害", example = "region")
+    private String dimension;
+
+    @Schema(description = "缁村害鍊�", example = "鍗庝笢")
+    private String dimensionValue;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiByCategoryRespVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiByCategoryRespVO.java
new file mode 100644
index 0000000..c854d4b
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiByCategoryRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 KPI 鍒嗙被 Response VO")
+@Data
+public class DecisionKpiByCategoryRespVO {
+
+    @Schema(description = "鍒嗙被", requiredMode = Schema.RequiredMode.REQUIRED, example = "power_supply")
+    private String category;
+
+    @Schema(description = "KPI鍒楄〃")
+    private List<Map<String, Object>> kpis;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDefinitionPageReqVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDefinitionPageReqVO.java
new file mode 100644
index 0000000..aed6aa7
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDefinitionPageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - KPI鎸囨爣瀹氫箟鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DecisionKpiDefinitionPageReqVO extends PageParam {
+
+    @Schema(description = "KPI鍚嶇О", example = "渚涚數閲�")
+    private String name;
+
+    @Schema(description = "鍒嗙被", example = "power_supply")
+    private String category;
+
+    @Schema(description = "鐘舵�侊細0-绂佺敤 1-鍚敤", example = "1")
+    private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDetailRespVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDetailRespVO.java
new file mode 100644
index 0000000..c0b94be
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiDetailRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 KPI 璇︽儏 Response VO")
+@Data
+public class DecisionKpiDetailRespVO {
+
+    @Schema(description = "KPI缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "power_supply_amount")
+    private String code;
+
+    @Schema(description = "KPI鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "渚涚數閲�")
+    private String name;
+
+    @Schema(description = "鍗曚綅", example = "涓囧崈鐡︽椂")
+    private String unit;
+
+    @Schema(description = "鍒嗙被", example = "power_supply")
+    private String category;
+
+    @Schema(description = "褰撳墠鍊�", example = "12345.67")
+    private BigDecimal value;
+
+    @Schema(description = "鏃堕棿搴忓垪鏁版嵁(JSON)", example = "[{\"time\":\"2026-08\",\"value\":1000}]")
+    private String timeSeries;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiOverviewRespVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiOverviewRespVO.java
new file mode 100644
index 0000000..c5a104b
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionKpiOverviewRespVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍐崇瓥鏀寔 KPI 姒傝 Response VO")
+@Data
+public class DecisionKpiOverviewRespVO {
+
+    @Schema(description = "KPI缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "power_supply_amount")
+    private String code;
+
+    @Schema(description = "KPI鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "渚涚數閲�")
+    private String name;
+
+    @Schema(description = "鍒嗙被", example = "power_supply")
+    private String category;
+
+    @Schema(description = "鍗曚綅", example = "涓囧崈鐡︽椂")
+    private String unit;
+
+    @Schema(description = "褰撳墠鍊�", example = "12345.67")
+    private BigDecimal value;
+
+    @Schema(description = "棰勮鐘舵�侊細normal/warn/critical", example = "normal")
+    private String alertStatus;
+
+    @Schema(description = "榛樿鍥捐〃绫诲瀷", example = "number")
+    private String chartType;
+
+    @Schema(description = "鐘舵�侊細0-绂佺敤 1-鍚敤", example = "1")
+    private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRecordDO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRecordDO.java
new file mode 100644
index 0000000..a9ffd18
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRecordDO.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.bi.dal.dataobject.decision;
+
+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;
+import java.time.LocalDateTime;
+
+/**
+ * 棰勮璁板綍 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("bi_alert_record")
+@KeySequence("bi_alert_record_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BiAlertRecordDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /**
+     * 鍏宠仈瑙勫垯ID
+     */
+    private Long ruleId;
+
+    /**
+     * 鍏宠仈KPI鎸囨爣ID
+     */
+    private Long kpiId;
+
+    /**
+     * KPI缂栫爜(鍐椾綑)
+     */
+    private String kpiCode;
+
+    /**
+     * KPI鍚嶇О(鍐椾綑)
+     */
+    private String kpiName;
+
+    /**
+     * 瀹為檯鍊�
+     */
+    private BigDecimal actualValue;
+
+    /**
+     * 闃堝��(鍐椾綑)
+     */
+    private BigDecimal thresholdValue;
+
+    /**
+     * 涓ラ噸绾у埆
+     */
+    private Integer severity;
+
+    /**
+     * 棰勮娑堟伅
+     */
+    private String alertMessage;
+
+    /**
+     * 鐘舵�侊細0-鏈鐞� 1-宸茬‘璁� 2-宸插鐞�
+     */
+    private Integer status;
+
+    /**
+     * 澶勭悊浜篒D
+     */
+    private Long handlerId;
+
+    /**
+     * 澶勭悊鏃堕棿
+     */
+    private LocalDateTime handleTime;
+
+    /**
+     * 澶勭悊澶囨敞
+     */
+    private String handleRemark;
+
+    /**
+     * 瑙﹀彂鏃堕棿
+     */
+    private LocalDateTime triggeredTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRuleDO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRuleDO.java
new file mode 100644
index 0000000..3279325
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiAlertRuleDO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.bi.dal.dataobject.decision;
+
+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;
+
+/**
+ * 棰勮瑙勫垯 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("bi_alert_rule")
+@KeySequence("bi_alert_rule_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BiAlertRuleDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /**
+     * 鍏宠仈KPI鎸囨爣ID
+     */
+    private Long kpiId;
+
+    /**
+     * 瑙勫垯鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 涓ラ噸绾у埆锛�1-璀﹀憡 2-涓ラ噸 3-绱ф��
+     */
+    private Integer severity;
+
+    /**
+     * 姣旇緝杩愮畻绗�
+     */
+    private String comparisonOperator;
+
+    /**
+     * 闃堝��
+     */
+    private BigDecimal thresholdValue;
+
+    /**
+     * 閫氱煡鏂瑰紡锛歴ystem/message/email(閫楀彿鍒嗛殧)
+     */
+    private String notificationType;
+
+    /**
+     * 鏄惁鍚敤锛�0-绂佺敤 1-鍚敤
+     */
+    private Integer enabled;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiForecastDataDO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiForecastDataDO.java
new file mode 100644
index 0000000..9abdd50
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiForecastDataDO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.bi.dal.dataobject.decision;
+
+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;
+import java.time.LocalDateTime;
+
+/**
+ * 璐熻嵎棰勬祴鏁版嵁 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("bi_forecast_data")
+@KeySequence("bi_forecast_data_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BiForecastDataDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /**
+     * 棰勬祴缂栫爜锛屽锛歭oad_forecast_daily
+     */
+    private String forecastCode;
+
+    /**
+     * 棰勬祴鍚嶇О
+     */
+    private String forecastName;
+
+    /**
+     * 棰勬祴鏃堕棿鐐�
+     */
+    private LocalDateTime pointTime;
+
+    /**
+     * 棰勬祴鍊�
+     */
+    private BigDecimal forecastValue;
+
+    /**
+     * 瀹為檯鍊�(鍥炲~鐢ㄤ簬鏍¢獙)
+     */
+    private BigDecimal actualValue;
+
+    /**
+     * 缃俊鍖洪棿涓嬮檺
+     */
+    private BigDecimal lowerBound;
+
+    /**
+     * 缃俊鍖洪棿涓婇檺
+     */
+    private BigDecimal upperBound;
+
+    /**
+     * 缃俊姘村钩(%)
+     */
+    private BigDecimal confidenceLevel;
+
+    /**
+     * 妯″瀷鐗堟湰
+     */
+    private String modelVersion;
+
+    /**
+     * 缁村害锛屽锛歳egion/line/station
+     */
+    private String dimension;
+
+    /**
+     * 缁村害鍊�
+     */
+    private String dimensionValue;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiKpiDefinitionDO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiKpiDefinitionDO.java
new file mode 100644
index 0000000..ad7a151
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/dataobject/decision/BiKpiDefinitionDO.java
@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.bi.dal.dataobject.decision;
+
+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;
+
+/**
+ * KPI鎸囨爣瀹氫箟 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("bi_kpi_definition")
+@KeySequence("bi_kpi_definition_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BiKpiDefinitionDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /**
+     * KPI缂栫爜
+     */
+    private String code;
+
+    /**
+     * KPI鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鍒嗙被锛歱ower_supply/device_operation/production/quality/procurement/safety
+     */
+    private String category;
+
+    /**
+     * 鍗曚綅
+     */
+    private String unit;
+
+    /**
+     * 鏁版嵁鏉ユ簮锛歴ql/interface/manual
+     */
+    private String dataSource;
+
+    /**
+     * 鏌ヨSQL
+     */
+    private String querySql;
+
+    /**
+     * 榛樿鍥捐〃绫诲瀷
+     */
+    private String chartType;
+
+    /**
+     * 棰勮闃堝��-璀﹀憡
+     */
+    private BigDecimal thresholdWarn;
+
+    /**
+     * 棰勮闃堝��-涓ラ噸
+     */
+    private BigDecimal thresholdCritical;
+
+    /**
+     * 姣旇緝杩愮畻绗︼細>/</>=/<=
+     */
+    private String comparisonOperator;
+
+    /**
+     * 鍒锋柊闂撮殧(绉�)
+     */
+    private Integer refreshInterval;
+
+    /**
+     * 鎺掑簭
+     */
+    private Integer sort;
+
+    /**
+     * 鐘舵�侊細0-绂佺敤 1-鍚敤
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRecordMapper.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRecordMapper.java
new file mode 100644
index 0000000..b861cdd
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRecordMapper.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bi.dal.mysql.decision;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface BiAlertRecordMapper extends BaseMapperX<BiAlertRecordDO> {
+
+    default Long selectCountByStatus(Integer status) {
+        return selectCount(new LambdaQueryWrapper<BiAlertRecordDO>()
+                .eq(BiAlertRecordDO::getStatus, status));
+    }
+
+    default List<BiAlertRecordDO> selectListByStatus(Integer status) {
+        return selectList(new LambdaQueryWrapper<BiAlertRecordDO>()
+                .eq(BiAlertRecordDO::getStatus, status));
+    }
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRuleMapper.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRuleMapper.java
new file mode 100644
index 0000000..ebbedb5
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiAlertRuleMapper.java
@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.module.bi.dal.mysql.decision;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRuleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BiAlertRuleMapper extends BaseMapperX<BiAlertRuleDO> {
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiForecastDataMapper.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiForecastDataMapper.java
new file mode 100644
index 0000000..efd1fa8
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiForecastDataMapper.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bi.dal.mysql.decision;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Mapper
+public interface BiForecastDataMapper extends BaseMapperX<BiForecastDataDO> {
+
+    default List<BiForecastDataDO> selectListByCodeAndTimeRange(String forecastCode,
+                                                                 LocalDateTime beginTime,
+                                                                 LocalDateTime endTime) {
+        return selectList(new LambdaQueryWrapper<BiForecastDataDO>()
+                .eq(BiForecastDataDO::getForecastCode, forecastCode)
+                .between(BiForecastDataDO::getPointTime, beginTime, endTime)
+                .orderByAsc(BiForecastDataDO::getPointTime));
+    }
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiKpiDefinitionMapper.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiKpiDefinitionMapper.java
new file mode 100644
index 0000000..5c0f62d
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiKpiDefinitionMapper.java
@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.module.bi.dal.mysql.decision;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BiKpiDefinitionMapper extends BaseMapperX<BiKpiDefinitionDO> {
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
index 17cdf98..a1f8b40 100644
--- a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
@@ -25,4 +25,16 @@
     // ========== 浠撳簱鍧愭爣閰嶇疆鐩稿叧 1-080-151 ~ 1-080-170 ==========
     ErrorCode BI_WAREHOUSE_COORDINATE_NOT_EXISTS = new ErrorCode(1_080_151, "浠撳簱鍧愭爣閰嶇疆涓嶅瓨鍦�");
 
+    // ========== 鍐崇瓥鏀寔 - KPI鎸囨爣 1_080_152 ~ 1_080_180 ==========
+    ErrorCode KPI_NOT_EXISTS = new ErrorCode(1_080_152, "KPI鎸囨爣涓嶅瓨鍦�");
+    ErrorCode KPI_CODE_DUPLICATE = new ErrorCode(1_080_153, "KPI缂栫爜宸插瓨鍦�");
+
+    // ========== 鍐崇瓥鏀寔 - 棰勮瑙勫垯 1_080_181 ~ 1_080_200 ==========
+    ErrorCode ALERT_RULE_NOT_EXISTS = new ErrorCode(1_080_181, "棰勮瑙勫垯涓嶅瓨鍦�");
+    ErrorCode ALERT_RULE_KPI_NOT_EXISTS = new ErrorCode(1_080_182, "鍏宠仈鐨凨PI鎸囨爣涓嶅瓨鍦�");
+
+    // ========== 鍐崇瓥鏀寔 - 棰勮璁板綍 1_080_201 ~ 1_080_220 ==========
+    ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_080_201, "棰勮璁板綍涓嶅瓨鍦�");
+    ErrorCode ALERT_RECORD_ALREADY_HANDLED = new ErrorCode(1_080_202, "棰勮璁板綍宸插鐞�");
+
 }
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/AlertEvaluationTask.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/AlertEvaluationTask.java
new file mode 100644
index 0000000..81c00b5
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/AlertEvaluationTask.java
@@ -0,0 +1,139 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRecordDO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRuleDO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiAlertRecordMapper;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiAlertRuleMapper;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiKpiDefinitionMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 棰勮瀹氭椂璇勪及浠诲姟
+ *
+ * 瀹氭湡鎵弿鎵�鏈夋椿璺冮璀﹁鍒欙紝璇勪及闃堝�煎苟鐢熸垚棰勮璁板綍
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Slf4j
+@Component
+public class AlertEvaluationTask {
+
+    @Resource
+    private BiAlertRuleMapper alertRuleMapper;
+
+    @Resource
+    private BiKpiDefinitionMapper kpiMapper;
+
+    @Resource
+    private BiAlertRecordMapper alertRecordMapper;
+
+    @Resource
+    private JdbcTemplate jdbcTemplate;
+
+    /**
+     * 姣�5鍒嗛挓璇勪及涓�娆℃墍鏈夋椿璺冭鍒�
+     */
+    @Scheduled(fixedDelayString = "${bi.alert.evaluation.interval:300000}")
+    public void evaluateAllAlertRules() {
+        log.debug("[evaluateAllAlertRules][寮�濮嬭瘎浼伴璀﹁鍒橾");
+        List<BiAlertRuleDO> rules = alertRuleMapper.selectList(
+                BiAlertRuleDO::getEnabled, 1);
+        if (rules.isEmpty()) {
+            return;
+        }
+
+        for (BiAlertRuleDO rule : rules) {
+            try {
+                evaluateRule(rule);
+            } catch (Exception e) {
+                log.error("[evaluateAllAlertRules][瑙勫垯 {} 璇勪及寮傚父]", rule.getId(), e);
+            }
+        }
+    }
+
+    private void evaluateRule(BiAlertRuleDO rule) {
+        BiKpiDefinitionDO kpi = kpiMapper.selectById(rule.getKpiId());
+        if (kpi == null || kpi.getStatus() == 0) {
+            return;
+        }
+
+        // 鎵ц KPI 鏌ヨ
+        BigDecimal currentValue = executeQuery(kpi.getQuerySql());
+        if (currentValue == null) {
+            return;
+        }
+
+        // 姣旇緝闃堝��
+        int cmp = currentValue.compareTo(rule.getThresholdValue());
+        boolean triggered = switch (rule.getComparisonOperator()) {
+            case ">" -> cmp > 0;
+            case "<" -> cmp < 0;
+            case ">=" -> cmp >= 0;
+            case "<=" -> cmp <= 0;
+            default -> false;
+        };
+
+        if (!triggered) {
+            return;
+        }
+
+        // 妫�鏌ユ槸鍚﹀凡鏈夋湭澶勭悊鐨勫悓KPI棰勮锛堝幓閲嶏級
+        Long existingCount = alertRecordMapper.selectCountByStatus(0);
+        if (existingCount > 0) {
+            List<BiAlertRecordDO> existing = alertRecordMapper.selectListByStatus(0);
+            boolean dup = existing.stream().anyMatch(r -> r.getKpiId().equals(rule.getKpiId()));
+            if (dup) {
+                return;
+            }
+        }
+
+        // 鐢熸垚棰勮璁板綍
+        BiAlertRecordDO record = BiAlertRecordDO.builder()
+                .ruleId(rule.getId())
+                .kpiId(kpi.getId())
+                .kpiCode(kpi.getCode())
+                .kpiName(kpi.getName())
+                .actualValue(currentValue)
+                .thresholdValue(rule.getThresholdValue())
+                .severity(rule.getSeverity())
+                .alertMessage(String.format("%s 褰撳墠鍊� %s 瓒呭嚭闃堝�� %s",
+                        kpi.getName(), currentValue, rule.getThresholdValue()))
+                .status(0)
+                .triggeredTime(LocalDateTime.now())
+                .build();
+        alertRecordMapper.insert(record);
+        log.warn("[evaluateRule][瑙﹀彂棰勮] KPI={}, 褰撳墠鍊�={}, 闃堝��={}",
+                kpi.getName(), currentValue, rule.getThresholdValue());
+    }
+
+    private BigDecimal executeQuery(String sql) {
+        if (sql == null || sql.isBlank()) {
+            return null;
+        }
+        try {
+            List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
+            if (result.isEmpty()) {
+                return null;
+            }
+            Object value = result.get(0).values().iterator().next();
+            if (value instanceof Number) {
+                return BigDecimal.valueOf(((Number) value).doubleValue());
+            }
+            return null;
+        } catch (Exception e) {
+            log.error("[executeQuery][SQL鎵ц澶辫触] {}", sql, e);
+            return null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordService.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordService.java
new file mode 100644
index 0000000..c856809
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordService.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertPageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRecordDO;
+
+import jakarta.validation.Valid;
+import java.time.LocalDateTime;
+
+/**
+ * 棰勮璁板綍 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BiAlertRecordService {
+
+    Long createRecord(@Valid BiAlertRecordDO record);
+
+    void confirmRecord(Long id, Long handlerId);
+
+    void handleRecord(Long id, Long handlerId, String handleRemark);
+
+    PageResult<BiAlertRecordDO> getRecordPage(DecisionAlertPageReqVO pageReq);
+
+    Long getUnprocessedCount();
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordServiceImpl.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordServiceImpl.java
new file mode 100644
index 0000000..377f368
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRecordServiceImpl.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertPageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRecordDO;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiAlertRecordMapper;
+import cn.iocoder.yudao.module.bi.enums.ErrorCodeConstants;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 棰勮璁板綍 Service 瀹炵幇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class BiAlertRecordServiceImpl implements BiAlertRecordService {
+
+    @Resource
+    private BiAlertRecordMapper alertRecordMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRecord(BiAlertRecordDO record) {
+        record.setStatus(0); // 鏈鐞�
+        record.setTriggeredTime(LocalDateTime.now());
+        alertRecordMapper.insert(record);
+        return record.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void confirmRecord(Long id, Long handlerId) {
+        BiAlertRecordDO record = alertRecordMapper.selectById(id);
+        if (record == null) {
+            throw exception(ErrorCodeConstants.ALERT_RECORD_NOT_EXISTS);
+        }
+        if (record.getStatus() >= 1) {
+            throw exception(ErrorCodeConstants.ALERT_RECORD_ALREADY_HANDLED);
+        }
+        alertRecordMapper.updateById(BiAlertRecordDO.builder()
+                .id(id)
+                .status(1) // 宸茬‘璁�
+                .handlerId(handlerId)
+                .build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleRecord(Long id, Long handlerId, String handleRemark) {
+        BiAlertRecordDO record = alertRecordMapper.selectById(id);
+        if (record == null) {
+            throw exception(ErrorCodeConstants.ALERT_RECORD_NOT_EXISTS);
+        }
+        if (record.getStatus() >= 2) {
+            throw exception(ErrorCodeConstants.ALERT_RECORD_ALREADY_HANDLED);
+        }
+        alertRecordMapper.updateById(BiAlertRecordDO.builder()
+                .id(id)
+                .status(2) // 宸插鐞�
+                .handlerId(handlerId)
+                .handleTime(LocalDateTime.now())
+                .handleRemark(handleRemark)
+                .build());
+    }
+
+    @Override
+    public PageResult<BiAlertRecordDO> getRecordPage(DecisionAlertPageReqVO pageReq) {
+        return alertRecordMapper.selectPage(pageReq, new LambdaQueryWrapperX<BiAlertRecordDO>()
+                .eqIfPresent(BiAlertRecordDO::getStatus, pageReq.getStatus())
+                .eqIfPresent(BiAlertRecordDO::getSeverity, pageReq.getSeverity())
+                .eqIfPresent(BiAlertRecordDO::getKpiCode, pageReq.getKpiCode())
+                .orderByDesc(BiAlertRecordDO::getTriggeredTime));
+    }
+
+    @Override
+    public Long getUnprocessedCount() {
+        return alertRecordMapper.selectCountByStatus(0);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleService.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleService.java
new file mode 100644
index 0000000..fd7e96c
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertRulePageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRuleDO;
+
+import jakarta.validation.Valid;
+
+/**
+ * 棰勮瑙勫垯 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BiAlertRuleService {
+
+    Long createRule(@Valid BiAlertRuleDO rule);
+
+    void updateRule(@Valid BiAlertRuleDO rule);
+
+    void deleteRule(Long id);
+
+    BiAlertRuleDO getRule(Long id);
+
+    PageResult<BiAlertRuleDO> getRulePage(DecisionAlertRulePageReqVO pageReq);
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleServiceImpl.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleServiceImpl.java
new file mode 100644
index 0000000..23197ea
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiAlertRuleServiceImpl.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionAlertRulePageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiAlertRuleDO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiAlertRuleMapper;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiKpiDefinitionMapper;
+import cn.iocoder.yudao.module.bi.enums.ErrorCodeConstants;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 棰勮瑙勫垯 Service 瀹炵幇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class BiAlertRuleServiceImpl implements BiAlertRuleService {
+
+    @Resource
+    private BiAlertRuleMapper alertRuleMapper;
+
+    @Resource
+    private BiKpiDefinitionMapper kpiMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRule(BiAlertRuleDO rule) {
+        // 鏍¢獙KPI瀛樺湪
+        if (kpiMapper.selectById(rule.getKpiId()) == null) {
+            throw exception(ErrorCodeConstants.ALERT_RULE_KPI_NOT_EXISTS);
+        }
+        alertRuleMapper.insert(rule);
+        return rule.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateRule(BiAlertRuleDO rule) {
+        if (alertRuleMapper.selectById(rule.getId()) == null) {
+            throw exception(ErrorCodeConstants.ALERT_RULE_NOT_EXISTS);
+        }
+        if (rule.getKpiId() != null && kpiMapper.selectById(rule.getKpiId()) == null) {
+            throw exception(ErrorCodeConstants.ALERT_RULE_KPI_NOT_EXISTS);
+        }
+        alertRuleMapper.updateById(rule);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteRule(Long id) {
+        if (alertRuleMapper.selectById(id) == null) {
+            throw exception(ErrorCodeConstants.ALERT_RULE_NOT_EXISTS);
+        }
+        alertRuleMapper.deleteById(id);
+    }
+
+    @Override
+    public BiAlertRuleDO getRule(Long id) {
+        return alertRuleMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<BiAlertRuleDO> getRulePage(DecisionAlertRulePageReqVO pageReq) {
+        return alertRuleMapper.selectPage(pageReq, new LambdaQueryWrapperX<BiAlertRuleDO>()
+                .eqIfPresent(BiAlertRuleDO::getKpiId, pageReq.getKpiId())
+                .eqIfPresent(BiAlertRuleDO::getEnabled, pageReq.getEnabled())
+                .orderByDesc(BiAlertRuleDO::getId));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiService.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiService.java
new file mode 100644
index 0000000..8511efe
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiService.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+/**
+ * 鍐崇瓥鏀寔 KPI 鑱氬悎 Service 鎺ュ彛
+ *
+ * 璺ㄦā鍧楄仛鍚堝疄鏃� KPI 鏁版嵁
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BiDecisionKpiService {
+
+    /**
+     * 鑾峰彇 KPI 姒傝鏁版嵁
+     */
+    Map<String, Object> getKpiOverview();
+
+    /**
+     * 鑾峰彇鍗曚釜 KPI 璇︽儏锛堝惈鏃堕棿搴忓垪锛�
+     */
+    Map<String, Object> getKpiDetail(String kpiCode);
+
+    /**
+     * 鎸夊垎绫昏幏鍙� KPI
+     */
+    Map<String, Object> getKpiByCategory(String category);
+
+    /**
+     * 鍒锋柊 KPI 鍊�
+     */
+    BigDecimal refreshKpi(String kpiCode);
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiServiceImpl.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiServiceImpl.java
new file mode 100644
index 0000000..5eea031
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiDecisionKpiServiceImpl.java
@@ -0,0 +1,160 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiKpiDefinitionMapper;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+/**
+ * 鍐崇瓥鏀寔 KPI 鑱氬悎 Service 瀹炵幇
+ *
+ * 閫氳繃 JdbcTemplate 璺ㄦā鍧楁墽琛岃仛鍚� SQL
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Slf4j
+@Service
+public class BiDecisionKpiServiceImpl implements BiDecisionKpiService {
+
+    @Resource
+    private BiKpiDefinitionMapper kpiMapper;
+
+    @Resource
+    private JdbcTemplate jdbcTemplate;
+
+    @Override
+    public Map<String, Object> getKpiOverview() {
+        List<BiKpiDefinitionDO> kpis = kpiMapper.selectList();
+        List<Map<String, Object>> items = new ArrayList<>();
+
+        for (BiKpiDefinitionDO kpi : kpis) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("code", kpi.getCode());
+            item.put("name", kpi.getName());
+            item.put("category", kpi.getCategory());
+            item.put("unit", kpi.getUnit());
+            item.put("chartType", kpi.getChartType());
+            item.put("status", kpi.getStatus());
+
+            // 鎵ц鏌ヨ鑾峰彇褰撳墠鍊�
+            BigDecimal value = executeKpiSql(kpi.getQuerySql());
+            item.put("value", value);
+
+            // 鍒ゆ柇棰勮鐘舵��
+            String alertStatus = evaluateAlertStatus(kpi, value);
+            item.put("alertStatus", alertStatus);
+
+            items.add(item);
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("kpis", items);
+        result.put("total", items.size());
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getKpiDetail(String kpiCode) {
+        BiKpiDefinitionDO kpi = kpiMapper.selectOne(BiKpiDefinitionDO::getCode, kpiCode);
+        if (kpi == null) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("code", kpi.getCode());
+        result.put("name", kpi.getName());
+        result.put("unit", kpi.getUnit());
+        result.put("category", kpi.getCategory());
+        result.put("value", executeKpiSql(kpi.getQuerySql()));
+
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getKpiByCategory(String category) {
+        List<BiKpiDefinitionDO> kpis = kpiMapper.selectList(
+                BiKpiDefinitionDO::getCategory, category);
+        List<Map<String, Object>> items = new ArrayList<>();
+
+        for (BiKpiDefinitionDO kpi : kpis) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("code", kpi.getCode());
+            item.put("name", kpi.getName());
+            item.put("unit", kpi.getUnit());
+            item.put("value", executeKpiSql(kpi.getQuerySql()));
+            items.add(item);
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("category", category);
+        result.put("kpis", items);
+        return result;
+    }
+
+    @Override
+    public BigDecimal refreshKpi(String kpiCode) {
+        BiKpiDefinitionDO kpi = kpiMapper.selectOne(BiKpiDefinitionDO::getCode, kpiCode);
+        if (kpi == null) {
+            return BigDecimal.ZERO;
+        }
+        return executeKpiSql(kpi.getQuerySql());
+    }
+
+    /**
+     * 鎵ц KPI 鏌ヨ SQL
+     */
+    private BigDecimal executeKpiSql(String querySql) {
+        if (querySql == null || querySql.isBlank()) {
+            return BigDecimal.ZERO;
+        }
+        try {
+            List<Map<String, Object>> result = jdbcTemplate.queryForList(querySql);
+            if (result.isEmpty()) {
+                return BigDecimal.ZERO;
+            }
+            // 鍙栫涓�琛岀涓�鍒楃殑鍊�
+            Map<String, Object> row = result.get(0);
+            Object value = row.values().iterator().next();
+            if (value instanceof Number) {
+                return BigDecimal.valueOf(((Number) value).doubleValue());
+            }
+            return BigDecimal.ZERO;
+        } catch (Exception e) {
+            log.error("[executeKpiSql][SQL鎵ц澶辫触] {}", querySql, e);
+            return BigDecimal.ZERO;
+        }
+    }
+
+    /**
+     * 璇勪及棰勮鐘舵��
+     */
+    private String evaluateAlertStatus(BiKpiDefinitionDO kpi, BigDecimal value) {
+        if (kpi.getThresholdCritical() != null) {
+            int cmp = value.compareTo(kpi.getThresholdCritical());
+            boolean triggered = ">".equals(kpi.getComparisonOperator()) ? cmp > 0
+                    : "<".equals(kpi.getComparisonOperator()) ? cmp < 0
+                    : ">=".equals(kpi.getComparisonOperator()) ? cmp >= 0
+                    : "<=".equals(kpi.getComparisonOperator()) ? cmp <= 0 : false;
+            if (triggered) {
+                return "critical";
+            }
+        }
+        if (kpi.getThresholdWarn() != null) {
+            int cmp = value.compareTo(kpi.getThresholdWarn());
+            boolean triggered = ">".equals(kpi.getComparisonOperator()) ? cmp > 0
+                    : "<".equals(kpi.getComparisonOperator()) ? cmp < 0
+                    : ">=".equals(kpi.getComparisonOperator()) ? cmp >= 0
+                    : "<=".equals(kpi.getComparisonOperator()) ? cmp <= 0 : false;
+            if (triggered) {
+                return "warn";
+            }
+        }
+        return "normal";
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java
new file mode 100644
index 0000000..e494aac
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 璐熻嵎棰勬祴 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BiForecastService {
+
+    List<BiForecastDataDO> getForecastList(String forecastCode, LocalDateTime beginTime, LocalDateTime endTime);
+
+    void generateForecast(String forecastCode, LocalDateTime pointTime);
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java
new file mode 100644
index 0000000..5455fb5
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiForecastDataMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 璐熻嵎棰勬祴 Service 瀹炵幇
+ *
+ * 鏈湡浣跨敤绠�鍗曠Щ鍔ㄥ钩鍧囧崰浣嶏紝棰勭暀 ML 妯″瀷鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Slf4j
+@Service
+public class BiForecastServiceImpl implements BiForecastService {
+
+    @Resource
+    private BiForecastDataMapper forecastDataMapper;
+
+    @Override
+    public List<BiForecastDataDO> getForecastList(String forecastCode, LocalDateTime beginTime, LocalDateTime endTime) {
+        return forecastDataMapper.selectListByCodeAndTimeRange(forecastCode, beginTime, endTime);
+    }
+
+    @Override
+    public void generateForecast(String forecastCode, LocalDateTime pointTime) {
+        // 鏈湡鍗犱綅锛氱畝鍗曠Щ鍔ㄥ钩鍧囬娴�
+        // 瀹為檯椤圭洰搴旈泦鎴� ML 妯″瀷锛圓RIMA/Prophet/LSTM锛�
+        log.info("[generateForecast][鐢熸垚棰勬祴] forecastCode={}, pointTime={}, 褰撳墠涓哄崰浣嶅疄鐜�", forecastCode, pointTime);
+
+        BiForecastDataDO forecast = BiForecastDataDO.builder()
+                .forecastCode(forecastCode)
+                .forecastName("璐熻嵎棰勬祴(鍗犱綅)")
+                .pointTime(pointTime)
+                .forecastValue(BigDecimal.ZERO)
+                .lowerBound(BigDecimal.ZERO)
+                .upperBound(BigDecimal.ZERO)
+                .confidenceLevel(new BigDecimal("95.00"))
+                .modelVersion("v1.0-placeholder")
+                .build();
+        forecastDataMapper.insert(forecast);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionService.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionService.java
new file mode 100644
index 0000000..7ce65fe
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionKpiDefinitionPageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+
+import jakarta.validation.Valid;
+
+/**
+ * KPI鎸囨爣瀹氫箟 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BiKpiDefinitionService {
+
+    Long createKpi(@Valid BiKpiDefinitionDO kpi);
+
+    void updateKpi(@Valid BiKpiDefinitionDO kpi);
+
+    void deleteKpi(Long id);
+
+    BiKpiDefinitionDO getKpi(Long id);
+
+    BiKpiDefinitionDO getKpiByCode(String code);
+
+    PageResult<BiKpiDefinitionDO> getKpiPage(DecisionKpiDefinitionPageReqVO pageReq);
+
+}
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionServiceImpl.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionServiceImpl.java
new file mode 100644
index 0000000..95e6db0
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiKpiDefinitionServiceImpl.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.bi.service.decision;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionKpiDefinitionPageReqVO;
+import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO;
+import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiKpiDefinitionMapper;
+import cn.iocoder.yudao.module.bi.enums.ErrorCodeConstants;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * KPI鎸囨爣瀹氫箟 Service 瀹炵幇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class BiKpiDefinitionServiceImpl implements BiKpiDefinitionService {
+
+    @Resource
+    private BiKpiDefinitionMapper kpiMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createKpi(BiKpiDefinitionDO kpi) {
+        // 鏍¢獙缂栫爜鍞竴
+        if (kpiMapper.selectOne(BiKpiDefinitionDO::getCode, kpi.getCode()) != null) {
+            throw exception(ErrorCodeConstants.KPI_CODE_DUPLICATE);
+        }
+        kpiMapper.insert(kpi);
+        return kpi.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateKpi(BiKpiDefinitionDO kpi) {
+        // 鏍¢獙瀛樺湪
+        BiKpiDefinitionDO exist = kpiMapper.selectById(kpi.getId());
+        if (exist == null) {
+            throw exception(ErrorCodeConstants.KPI_NOT_EXISTS);
+        }
+        // 鏍¢獙缂栫爜鍞竴锛堟帓闄よ嚜韬級
+        BiKpiDefinitionDO byCode = kpiMapper.selectOne(BiKpiDefinitionDO::getCode, kpi.getCode());
+        if (byCode != null && !byCode.getId().equals(kpi.getId())) {
+            throw exception(ErrorCodeConstants.KPI_CODE_DUPLICATE);
+        }
+        kpiMapper.updateById(kpi);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteKpi(Long id) {
+        if (kpiMapper.selectById(id) == null) {
+            throw exception(ErrorCodeConstants.KPI_NOT_EXISTS);
+        }
+        kpiMapper.deleteById(id);
+    }
+
+    @Override
+    public BiKpiDefinitionDO getKpi(Long id) {
+        return kpiMapper.selectById(id);
+    }
+
+    @Override
+    public BiKpiDefinitionDO getKpiByCode(String code) {
+        return kpiMapper.selectOne(BiKpiDefinitionDO::getCode, code);
+    }
+
+    @Override
+    public PageResult<BiKpiDefinitionDO> getKpiPage(DecisionKpiDefinitionPageReqVO pageReq) {
+        return kpiMapper.selectPage(pageReq, new LambdaQueryWrapperX<BiKpiDefinitionDO>()
+                .likeIfPresent(BiKpiDefinitionDO::getName, pageReq.getName())
+                .eqIfPresent(BiKpiDefinitionDO::getCategory, pageReq.getCategory())
+                .eqIfPresent(BiKpiDefinitionDO::getStatus, pageReq.getStatus())
+                .orderByAsc(BiKpiDefinitionDO::getSort));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/pom.xml b/yudao-module-crm/pom.xml
index 5bcc084..aed0933 100644
--- a/yudao-module-crm/pom.xml
+++ b/yudao-module-crm/pom.xml
@@ -55,6 +55,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- MES 鐢熶骇宸ュ崟 API -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-mes-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 涓氬姟缁勪欢 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/CrmCancellationController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/CrmCancellationController.java
new file mode 100644
index 0000000..dcab261
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/CrmCancellationController.java
@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.crm.controller.admin.cancellation;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationAuditReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationSaveReqVO;
+import cn.iocoder.yudao.module.crm.service.cancellation.CrmCancellationService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - CRM 閿�鎴风敵璇�")
+@RestController
+@RequestMapping("/crm/cancellation")
+@Validated
+public class CrmCancellationController {
+
+    @Resource
+    private CrmCancellationService cancellationService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓閿�鎴风敵璇�")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:create')")
+    public CommonResult<Long> createCancellation(@Valid @RequestBody CrmCancellationSaveReqVO createReqVO) {
+        return success(cancellationService.createCancellation(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊閿�鎴风敵璇�")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:update')")
+    public CommonResult<Boolean> updateCancellation(@Valid @RequestBody CrmCancellationSaveReqVO updateReqVO) {
+        cancellationService.updateCancellation(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/audit")
+    @Operation(summary = "瀹℃壒閿�鎴风敵璇凤紙閫氳繃/椹冲洖锛�")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:update')")
+    public CommonResult<Boolean> auditCancellation(@Valid @RequestBody CrmCancellationAuditReqVO auditReqVO) {
+        cancellationService.auditCancellation(auditReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦閿�鎴风敵璇峰鎵�")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:update')")
+    public CommonResult<Boolean> submitCancellation(@RequestParam("id") Long id,
+                                                    @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        cancellationService.submitCancellation(id, processDefinitionKey, getLoginUserId());
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰緱閿�鎴风敵璇峰鎵规祦绋嬪畾涔夊垪琛�")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:query')")
+    public CommonResult<List<Map<String, Object>>> getCancellationApproveProcessDefinitionList() {
+        return success(cancellationService.getCancellationApproveProcessDefinitionList());
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎閿�鎴风敵璇�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:delete')")
+    public CommonResult<Boolean> deleteCancellation(@RequestParam("id") Long id) {
+        cancellationService.deleteCancellation(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱閿�鎴风敵璇�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:query')")
+    public CommonResult<CrmCancellationRespVO> getCancellation(@RequestParam("id") Long id) {
+        return success(cancellationService.getCancellation(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱閿�鎴风敵璇峰垎椤�")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:query')")
+    public CommonResult<PageResult<CrmCancellationRespVO>> getCancellationPage(@Valid CrmCancellationPageReqVO pageReqVO) {
+        return success(cancellationService.getCancellationPage(pageReqVO));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭閿�鎴风敵璇� Excel")
+    @PreAuthorize("@ss.hasPermission('crm:cancellation:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportCancellationExcel(@Valid CrmCancellationPageReqVO exportReqVO,
+                                        HttpServletResponse response) throws IOException {
+        exportReqVO.setPageSize(PAGE_SIZE_NONE);
+        List<CrmCancellationRespVO> list = cancellationService.getCancellationList(exportReqVO);
+        ExcelUtils.write(response, "閿�鎴风敵璇�.xls", "鏁版嵁", CrmCancellationRespVO.class, list);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationAuditReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationAuditReqVO.java
new file mode 100644
index 0000000..48844f9
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationAuditReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鎴风敵璇峰鎵� Request VO")
+@Data
+public class CrmCancellationAuditReqVO {
+
+    @Schema(description = "閿�鎴风敵璇风紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "閿�鎴风敵璇风紪鍙蜂笉鑳戒负绌�")
+    private Long id;
+
+    @Schema(description = "瀹℃壒缁撴灉锛�20=閫氳繃 / 30=椹冲洖", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "瀹℃壒缁撴灉涓嶈兘涓虹┖")
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒鎰忚")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationPageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationPageReqVO.java
new file mode 100644
index 0000000..b7fd444
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationPageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鎴风敵璇峰垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CrmCancellationPageReqVO extends PageParam {
+
+    @Schema(description = "閿�鎴风紪鍙�", example = "CL202608190001")
+    private String cancellationNo;
+
+    @Schema(description = "瀹㈡埛ID", example = "2048")
+    private Long customerId;
+
+    @Schema(description = "閿�鎴风被鍨�", example = "ELECTRICITY")
+    private String cancellationType;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer auditStatus;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationRespVO.java
new file mode 100644
index 0000000..8696a9c
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationRespVO.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_AUDIT_STATUS;
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CANCELLATION_TYPE;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鎴风敵璇� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmCancellationRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "閿�鎴风紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("閿�鎴风紪鍙�")
+    private String cancellationNo;
+
+    @Schema(description = "瀹㈡埛ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("瀹㈡埛ID")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    @ExcelProperty("瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "鍚堝悓ID")
+    @ExcelProperty("鍚堝悓ID")
+    private Long contractId;
+
+    @Schema(description = "鍚堝悓缂栧彿")
+    @ExcelProperty("鍚堝悓缂栧彿")
+    private String contractNo;
+
+    @Schema(description = "閿�鎴风被鍨�")
+    @ExcelProperty(value = "閿�鎴风被鍨�", converter = DictConvert.class)
+    @DictFormat(CRM_CANCELLATION_TYPE)
+    private String cancellationType;
+
+    @Schema(description = "閿�鎴峰師鍥�")
+    @ExcelProperty("閿�鎴峰師鍥�")
+    private String cancelReason;
+
+    @Schema(description = "棰勮閿�鎴锋棩鏈�")
+    @ExcelProperty("棰勮閿�鎴锋棩鏈�")
+    private LocalDate expectedCancelDate;
+
+    @Schema(description = "瀹為檯閿�鎴锋棩鏈�")
+    @ExcelProperty("瀹為檯閿�鎴锋棩鏈�")
+    private LocalDate actualCancelDate;
+
+    @Schema(description = "閫�璐归噾棰�")
+    @ExcelProperty("閫�璐归噾棰�")
+    private BigDecimal refundAmount;
+
+    @Schema(description = "鐢宠鐢ㄦ埛ID")
+    @ExcelProperty("鐢宠鐢ㄦ埛ID")
+    private Long applyUserId;
+
+    @Schema(description = "鐢宠鐢ㄦ埛濮撳悕")
+    @ExcelProperty("鐢宠鐢ㄦ埛濮撳悕")
+    private String applyUserName;
+
+    @Schema(description = "鐢宠鏃堕棿")
+    @ExcelProperty("鐢宠鏃堕棿")
+    private LocalDateTime applyTime;
+
+    @Schema(description = "瀹℃壒鐘舵��")
+    @ExcelProperty(value = "瀹℃壒鐘舵��", converter = DictConvert.class)
+    @DictFormat(CRM_AUDIT_STATUS)
+    private Integer auditStatus;
+
+    @Schema(description = "娴佺▼瀹炰緥ID")
+    private String processInstanceId;
+
+    @Schema(description = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationSaveReqVO.java
new file mode 100644
index 0000000..ff24197
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationSaveReqVO.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CANCELLATION_TYPE;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鎴风敵璇锋柊澧�/淇敼 Request VO")
+@Data
+public class CrmCancellationSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "閿�鎴风紪鍙凤紙涓嶄紶鍒欒嚜鍔ㄧ敓鎴愶級")
+    private String cancellationNo;
+
+    @Schema(description = "瀹㈡埛ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "瀹㈡埛ID涓嶈兘涓虹┖")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "鍚堝悓ID", example = "2048")
+    private Long contractId;
+
+    @Schema(description = "鍚堝悓缂栧彿")
+    private String contractNo;
+
+    @Schema(description = "閿�鎴风被鍨嬶紙鐢ㄧ數閿�鎴�/鏈嶅姟缁堟/鍚堝悓鍒版湡锛�", example = "ELECTRICITY")
+    @DictFormat(CRM_CANCELLATION_TYPE)
+    private String cancellationType;
+
+    @Schema(description = "閿�鎴峰師鍥�")
+    private String cancelReason;
+
+    @Schema(description = "棰勮閿�鎴锋棩鏈�")
+    private LocalDate expectedCancelDate;
+
+    @Schema(description = "瀹為檯閿�鎴锋棩鏈�")
+    private LocalDate actualCancelDate;
+
+    @Schema(description = "閫�璐归噾棰�")
+    private BigDecimal refundAmount;
+
+    @Schema(description = "鐢宠鐢ㄦ埛ID", example = "1")
+    private Long applyUserId;
+
+    @Schema(description = "鐢宠鐢ㄦ埛濮撳悕", example = "鏉庡伐")
+    private String applyUserName;
+
+    @Schema(description = "鐢宠鏃堕棿")
+    private LocalDateTime applyTime;
+
+    @Schema(description = "瀹℃壒鐘舵�侊紙0鑽夌/10寰呭鎵�/20閫氳繃/30椹冲洖锛�", example = "0")
+    private Integer auditStatus;
+
+    @Schema(description = "娴佺▼瀹炰緥ID")
+    private String processInstanceId;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractCommandController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractCommandController.java
new file mode 100644
index 0000000..2a35c93
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractCommandController.java
@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandSaveReqVO;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractCommandService;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - CRM 鍚堝悓鎸囦护")
+@RestController
+@RequestMapping("/crm/contract-command")
+@Validated
+public class CrmContractCommandController {
+
+    @Resource
+    private CrmContractCommandService commandService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鍚堝悓鎸囦护")
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:create')")
+    public CommonResult<Long> createCommand(@Valid @RequestBody CrmContractCommandSaveReqVO createReqVO) {
+        return success(commandService.createCommand(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鍚堝悓鎸囦护")
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:update')")
+    public CommonResult<Boolean> updateCommand(@Valid @RequestBody CrmContractCommandSaveReqVO updateReqVO) {
+        commandService.updateCommand(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鍚堝悓鎸囦护")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:delete')")
+    public CommonResult<Boolean> deleteCommand(@RequestParam("id") Long id) {
+        commandService.deleteCommand(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鍚堝悓鎸囦护")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:query')")
+    public CommonResult<CrmContractCommandRespVO> getCommand(@RequestParam("id") Long id) {
+        return success(commandService.getCommand(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鍚堝悓鎸囦护鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:query')")
+    public CommonResult<PageResult<CrmContractCommandRespVO>> getCommandPage(@Valid CrmContractCommandPageReqVO pageReqVO) {
+        return success(commandService.getCommandPage(pageReqVO));
+    }
+
+    @PutMapping("/execute")
+    @Operation(summary = "鎵ц鍚堝悓鎸囦护")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:update')")
+    public CommonResult<Boolean> executeCommand(@RequestParam("id") Long id) {
+        commandService.executeCommand(id);
+        return success(true);
+    }
+
+    @PutMapping("/cancel")
+    @Operation(summary = "鍙栨秷鍚堝悓鎸囦护")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:update')")
+    public CommonResult<Boolean> cancelCommand(@RequestParam("id") Long id) {
+        commandService.cancelCommand(id);
+        return success(true);
+    }
+
+    @GetMapping("/count-by-contract")
+    @Operation(summary = "缁熻鏌愬悎鍚屾寚浠ゆ暟閲�")
+    @Parameter(name = "contractId", description = "鍚堝悓ID", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:contract-command:query')")
+    public CommonResult<Long> countByContract(@RequestParam("contractId") Long contractId) {
+        return success(commandService.countByContractId(contractId));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
index d78cba5..279b947 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -24,6 +24,7 @@
 import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractCommandService;
 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService;
@@ -74,6 +75,8 @@
     private CrmReceivableService receivableService;
     @Resource
     private CrmInvoiceService invoiceService;
+    @Resource
+    private CrmContractCommandService contractCommandService;
 
     @Resource
     private MdmItemApi mdmItemApi;
@@ -247,6 +250,8 @@
             if (contractVO.getTotalPrice() != null && contractVO.getTotalInvoicePrice() != null) {
                 contractVO.setUnInvoicePrice(contractVO.getTotalPrice().subtract(contractVO.getTotalInvoicePrice()));
             }
+            // 2.8 璁剧疆鎸囦护涓嬪彂鏁伴噺
+            contractVO.setCommandCount(contractCommandService.countByContractId(contractVO.getId()));
         });
     }
 
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandPageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandPageReqVO.java
new file mode 100644
index 0000000..c8d3719
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandPageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 鍚堝悓鎸囦护鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CrmContractCommandPageReqVO extends PageParam {
+
+    @Schema(description = "鍚堝悓ID", example = "2048")
+    private Long contractId;
+
+    @Schema(description = "鎸囦护绫诲瀷", example = "METER_READ")
+    private String commandType;
+
+    @Schema(description = "鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "涓氬姟鍗曞彿", example = "MD001")
+    private String bizNo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandRespVO.java
new file mode 100644
index 0000000..272fb38
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandRespVO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_COMMAND_STATUS;
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_COMMAND_TYPE;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 鍚堝悓鎸囦护 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmContractCommandRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鍚堝悓ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍚堝悓ID")
+    private Long contractId;
+
+    @Schema(description = "鍚堝悓缂栧彿")
+    @ExcelProperty("鍚堝悓缂栧彿")
+    private String contractNo;
+
+    @Schema(description = "鎸囦护绫诲瀷")
+    @ExcelProperty(value = "鎸囦护绫诲瀷", converter = DictConvert.class)
+    @DictFormat(CRM_COMMAND_TYPE)
+    private String commandType;
+
+    @Schema(description = "鐩爣妯″潡")
+    @ExcelProperty("鐩爣妯″潡")
+    private String targetModule;
+
+    @Schema(description = "鐩爣璁板綍ID")
+    @ExcelProperty("鐩爣璁板綍ID")
+    private Long targetId;
+
+    @Schema(description = "涓氬姟鍗曞彿")
+    @ExcelProperty("涓氬姟鍗曞彿")
+    private String bizNo;
+
+    @Schema(description = "鎿嶄綔浜篒D")
+    @ExcelProperty("鎿嶄綔浜篒D")
+    private Long operatorId;
+
+    @Schema(description = "鎿嶄綔浜哄鍚�")
+    @ExcelProperty("鎿嶄綔浜哄鍚�")
+    private String operatorName;
+
+    @Schema(description = "鎿嶄綔鏃堕棿")
+    @ExcelProperty("鎿嶄綔鏃堕棿")
+    private LocalDateTime operateTime;
+
+    @Schema(description = "鐘舵��")
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(CRM_COMMAND_STATUS)
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandSaveReqVO.java
new file mode 100644
index 0000000..3daf72c
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/command/CrmContractCommandSaveReqVO.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_COMMAND_STATUS;
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_COMMAND_TYPE;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 鍚堝悓鎸囦护鏂板/淇敼 Request VO")
+@Data
+public class CrmContractCommandSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "鍚堝悓ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "鍚堝悓ID涓嶈兘涓虹┖")
+    private Long contractId;
+
+    @Schema(description = "鍚堝悓缂栧彿")
+    private String contractNo;
+
+    @Schema(description = "鎸囦护绫诲瀷锛堜笅鍙戣〃/鎶勮〃浠诲姟/鏈嶅姟宸ュ崟锛�", example = "METER_READ")
+    @DictFormat(CRM_COMMAND_TYPE)
+    private String commandType;
+
+    @Schema(description = "鐩爣妯″潡", example = "mes-metering")
+    private String targetModule;
+
+    @Schema(description = "鐩爣璁板綍ID")
+    private Long targetId;
+
+    @Schema(description = "涓氬姟鍗曞彿")
+    private String bizNo;
+
+    @Schema(description = "鎿嶄綔浜篒D", example = "1")
+    private Long operatorId;
+
+    @Schema(description = "鎿嶄綔浜哄鍚�", example = "鏉庡伐")
+    private String operatorName;
+
+    @Schema(description = "鎿嶄綔鏃堕棿")
+    private LocalDateTime operateTime;
+
+    @Schema(description = "鐘舵�侊紙0寰呮墽琛�/10宸蹭笅鍙�/20宸插畬鎴�/30宸插彇娑堬級", example = "0")
+    @DictFormat(CRM_COMMAND_STATUS)
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
index e9acd92..fa8793e 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
@@ -124,6 +124,30 @@
     @ExcelProperty(value = "澶囨敞", index = 11)
     private String remark;
 
+    @Schema(description = "鍚堝悓绫诲瀷", example = "power")
+    private String contractType;
+
+    @Schema(description = "鐢靛帇绛夌骇", example = "10kV")
+    private String voltageLevel;
+
+    @Schema(description = "鐢ㄧ數绫诲瀷", example = "industrial")
+    private String electricityType;
+
+    @Schema(description = "鍚堝悓瀹归噺锛坘VA锛�", example = "800")
+    private BigDecimal contractCapacity;
+
+    @Schema(description = "鐢典环妯″紡", example = "two-part")
+    private String tariffMode;
+
+    @Schema(description = "璁¢噺鏂瑰紡", example = "high-high")
+    private String meterMode;
+
+    @Schema(description = "缂磋垂鍛ㄦ湡锛堟湀锛�", example = "12")
+    private Integer payCycle;
+
+    @Schema(description = "鎸囦护鏁伴噺")
+    private Long commandCount;
+
     @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty(value = "鍒涘缓鏃堕棿", index = 18)
     private LocalDateTime createTime;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java
index 5e7b2c0..5cd0071 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java
@@ -85,6 +85,28 @@
     @DiffLogField(name = "澶囨敞")
     private String remark;
 
+    @Schema(description = "鍚堝悓绫诲瀷锛坧ower/energy/resident/solar锛�", example = "power")
+    @DiffLogField(name = "鍚堝悓绫诲瀷")
+    private String contractType;
+
+    @Schema(description = "鐢靛帇绛夌骇", example = "10kV")
+    private String voltageLevel;
+
+    @Schema(description = "鐢ㄧ數绫诲瀷", example = "industrial")
+    private String electricityType;
+
+    @Schema(description = "鍚堝悓瀹归噺锛坘VA锛�", example = "800")
+    private BigDecimal contractCapacity;
+
+    @Schema(description = "鐢典环妯″紡", example = "two-part")
+    private String tariffMode;
+
+    @Schema(description = "璁¢噺鏂瑰紡", example = "high-high")
+    private String meterMode;
+
+    @Schema(description = "缂磋垂鍛ㄦ湡锛堟湀锛�", example = "12")
+    private Integer payCycle;
+
     @Schema(description = "浜у搧鍒楄〃")
     private List<Product> products;
 
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/CrmCreditRatingController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/CrmCreditRatingController.java
new file mode 100644
index 0000000..2b143ec
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/CrmCreditRatingController.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.crm.controller.admin.credit;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingSaveReqVO;
+import cn.iocoder.yudao.module.crm.service.credit.CrmCreditRatingService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import 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.pojo.PageParam.PAGE_SIZE_NONE;
+
+@Tag(name = "绠$悊鍚庡彴 - CRM 瀹㈡埛淇$敤璇勭骇")
+@RestController
+@RequestMapping("/crm/credit-rating")
+@Validated
+public class CrmCreditRatingController {
+
+    @Resource
+    private CrmCreditRatingService creditRatingService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓瀹㈡埛淇$敤璇勭骇")
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:create')")
+    public CommonResult<Long> createCreditRating(@Valid @RequestBody CrmCreditRatingSaveReqVO createReqVO) {
+        return success(creditRatingService.createCreditRating(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊瀹㈡埛淇$敤璇勭骇")
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:update')")
+    public CommonResult<Boolean> updateCreditRating(@Valid @RequestBody CrmCreditRatingSaveReqVO updateReqVO) {
+        creditRatingService.updateCreditRating(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎瀹㈡埛淇$敤璇勭骇")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:delete')")
+    public CommonResult<Boolean> deleteCreditRating(@RequestParam("id") Long id) {
+        creditRatingService.deleteCreditRating(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱瀹㈡埛淇$敤璇勭骇")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:query')")
+    public CommonResult<CrmCreditRatingRespVO> getCreditRating(@RequestParam("id") Long id) {
+        return success(creditRatingService.getCreditRating(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱瀹㈡埛淇$敤璇勭骇鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:query')")
+    public CommonResult<PageResult<CrmCreditRatingRespVO>> getCreditRatingPage(@Valid CrmCreditRatingPageReqVO pageReqVO) {
+        return success(creditRatingService.getCreditRatingPage(pageReqVO));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭瀹㈡埛淇$敤璇勭骇 Excel")
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportCreditRatingExcel(@Valid CrmCreditRatingPageReqVO exportReqVO,
+                                       HttpServletResponse response) throws IOException {
+        exportReqVO.setPageSize(PAGE_SIZE_NONE);
+        List<CrmCreditRatingRespVO> list = creditRatingService.getCreditRatingList(exportReqVO);
+        ExcelUtils.write(response, "瀹㈡埛淇$敤璇勭骇.xls", "鏁版嵁", CrmCreditRatingRespVO.class, list);
+    }
+
+    @GetMapping("/latest-by-customer")
+    @Operation(summary = "鑾峰彇瀹㈡埛鏈�鏂颁俊鐢ㄨ瘎绾�")
+    @Parameter(name = "customerId", description = "瀹㈡埛ID", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:credit-rating:query')")
+    public CommonResult<CrmCreditRatingRespVO> getLatestByCustomer(@RequestParam("customerId") Long customerId) {
+        return success(creditRatingService.getLatestByCustomerId(customerId));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingPageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingPageReqVO.java
new file mode 100644
index 0000000..4dc90e1
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingPageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.crm.controller.admin.credit.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 瀹㈡埛淇$敤璇勭骇鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CrmCreditRatingPageReqVO extends PageParam {
+
+    @Schema(description = "瀹㈡埛ID", example = "2048")
+    private Long customerId;
+
+    @Schema(description = "淇$敤绛夌骇", example = "B")
+    private String level;
+
+    @Schema(description = "璇勫畾绫诲瀷", example = "INITIAL")
+    private String evalType;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingRespVO.java
new file mode 100644
index 0000000..7d6a983
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingRespVO.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.crm.controller.admin.credit.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CREDIT_LEVEL;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 瀹㈡埛淇$敤璇勭骇 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmCreditRatingRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "瀹㈡埛ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("瀹㈡埛ID")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "淇$敤绛夌骇")
+    @ExcelProperty(value = "淇$敤绛夌骇", converter = DictConvert.class)
+    @DictFormat(CRM_CREDIT_LEVEL)
+    private String level;
+
+    @Schema(description = "淇$敤鍒�")
+    @ExcelProperty("淇$敤鍒�")
+    private Integer score;
+
+    @Schema(description = "璇勫畾绫诲瀷")
+    @ExcelProperty("璇勫畾绫诲瀷")
+    private String evalType;
+
+    @Schema(description = "璇勫畾鐞嗙敱")
+    @ExcelProperty("璇勫畾鐞嗙敱")
+    private String evalReason;
+
+    @Schema(description = "鎿嶄綔浜篒D")
+    @ExcelProperty("鎿嶄綔浜篒D")
+    private Long operatorId;
+
+    @Schema(description = "鎿嶄綔浜哄鍚�")
+    @ExcelProperty("鎿嶄綔浜哄鍚�")
+    private String operatorName;
+
+    @Schema(description = "鎿嶄綔鏃堕棿")
+    @ExcelProperty("鎿嶄綔鏃堕棿")
+    private LocalDateTime operateTime;
+
+    @Schema(description = "鏈夋晥鏈熻嚦")
+    @ExcelProperty("鏈夋晥鏈熻嚦")
+    private LocalDateTime validUntil;
+
+    @Schema(description = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingSaveReqVO.java
new file mode 100644
index 0000000..85ed5e0
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/vo/CrmCreditRatingSaveReqVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.crm.controller.admin.credit.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CREDIT_LEVEL;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 瀹㈡埛淇$敤璇勭骇鏂板/淇敼 Request VO")
+@Data
+public class CrmCreditRatingSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "瀹㈡埛ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "瀹㈡埛ID涓嶈兘涓虹┖")
+    private Long customerId;
+
+    @Schema(description = "淇$敤绛夌骇锛圓+/A/B+/B/C/C-/D锛�", example = "B")
+    @DictFormat(CRM_CREDIT_LEVEL)
+    private String level;
+
+    @Schema(description = "淇$敤鍒嗭紙0-100锛�", example = "85")
+    private Integer score;
+
+    @Schema(description = "璇勫畾绫诲瀷锛堝垵璇�/澶嶈瘎/鍔ㄦ�佽皟鏁达級", example = "INITIAL")
+    private String evalType;
+
+    @Schema(description = "璇勫畾鐞嗙敱", example = "杩�12涓湀鐢ㄧ數淇$敤鑹ソ")
+    private String evalReason;
+
+    @Schema(description = "鎿嶄綔浜篒D", example = "1")
+    private Long operatorId;
+
+    @Schema(description = "鎿嶄綔浜哄鍚�", example = "鏉庡伐")
+    private String operatorName;
+
+    @Schema(description = "鎿嶄綔鏃堕棿")
+    private LocalDateTime operateTime;
+
+    @Schema(description = "鏈夋晥鏈熻嚦")
+    private LocalDateTime validUntil;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index bf22058..dd8251e 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -17,6 +17,7 @@
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivablePlanService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -33,10 +34,12 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -56,6 +59,8 @@
     private CrmCustomerService customerService;
     @Resource
     private CrmCustomerPoolConfigService customerPoolConfigService;
+    @Resource
+    private CrmReceivablePlanService receivablePlanService;
 
     @Resource
     private DeptApi deptApi;
@@ -139,6 +144,10 @@
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
         // 1.2 鑾峰彇璺濈杩涘叆鍏捣鐨勬椂闂�
         Map<Long, Long> poolDayMap = getPoolDayMap(list);
+        // 1.3 鑾峰彇娆犺垂閲戦銆佹瑺璐圭瑪鏁帮紙鏈洖娆� + 宸查�炬湡鐨勫洖娆捐鍒掞級
+        Set<Long> customerIds = convertSet(list, CrmCustomerDO::getId);
+        Map<Long, BigDecimal> overdueAmountMap = receivablePlanService.getOverdueAmountMapByCustomerIds(customerIds);
+        Map<Long, Integer> overdueCountMap = receivablePlanService.getOverdueCountMapByCustomerIds(customerIds);
         // 2. 杞崲鎴� VO
         return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> {
             customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
@@ -153,6 +162,9 @@
             if (customerVO.getOwnerUserId() != null) {
                 customerVO.setPoolDay(poolDayMap.get(customerVO.getId()));
             }
+            // 2.3 璁剧疆娆犺垂椋庨櫓锛堟瑺璐归噾棰�/娆犺垂绗旀暟锛�
+            customerVO.setOverdueAmount(overdueAmountMap.get(customerVO.getId()));
+            customerVO.setOverdueCount(overdueCountMap.get(customerVO.getId()));
         });
     }
 
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java
index 73af5d6..25b6214 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java
@@ -42,6 +42,12 @@
     @Schema(description = "瀹㈡埛鏉ユ簮", example = "1")
     private Integer source;
 
+    @Schema(description = "瀹㈡埛鍒嗙被", example = "industrial")
+    private String customerType;
+
+    @Schema(description = "褰撳墠淇$敤绛夌骇", example = "B")
+    private String creditLevel;
+
     @Schema(description = "鍦烘櫙绫诲瀷", example = "1")
     @InEnum(CrmSceneTypeEnum.class)
     private Integer sceneType; // 鍦烘櫙绫诲瀷锛屼负 null 鏃跺垯琛ㄧず鍏ㄩ儴
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
index acc167e..6a4f87d 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
@@ -8,6 +8,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @Schema(description = "绠$悊鍚庡彴 - CRM 瀹㈡埛 Response VO")
@@ -127,4 +128,26 @@
     @Schema(description = "璺濈鍔犲叆鍏捣鏃堕棿", example = "1")
     private Long poolDay;
 
+    @Schema(description = "瀹㈡埛鍒嗙被锛堢數鍔汣RM鎵╁睍锛�", example = "industrial")
+    @ExcelProperty(value = "瀹㈡埛鍒嗙被", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_TYPE)
+    private String customerType;
+
+    @Schema(description = "褰撳墠淇$敤绛夌骇", example = "B")
+    @ExcelProperty(value = "淇$敤绛夌骇", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CREDIT_LEVEL)
+    private String creditLevel;
+
+    @Schema(description = "褰撳墠淇$敤鍒�", example = "85")
+    @ExcelProperty("淇$敤鍒�")
+    private Integer creditScore;
+
+    @Schema(description = "娆犺垂閲戦锛堟湭鍥炴+閫炬湡鐨勫洖娆捐鍒掑悎璁★級", example = "1200.00")
+    @ExcelProperty("娆犺垂閲戦")
+    private BigDecimal overdueAmount;
+
+    @Schema(description = "娆犺垂绗旀暟", example = "3")
+    @ExcelProperty("娆犺垂绗旀暟")
+    private Integer overdueCount;
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java
index bf4a4f0..2236121 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java
@@ -22,6 +22,7 @@
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_TYPE;
 
 @Schema(description = "绠$悊鍚庡彴 - CRM 瀹㈡埛鏂板/淇敼 Request VO")
 @Data
@@ -96,4 +97,9 @@
     @DiffLogField(name = "澶囨敞")
     private String remark;
 
+    @Schema(description = "瀹㈡埛鍒嗙被锛堟斂浼�/宸ヤ笟/灞呮皯/鍟嗕笟/鍐滀笟锛�", example = "industrial")
+    @DiffLogField(name = "瀹㈡埛鍒嗙被")
+    @DictFormat(CRM_CUSTOMER_TYPE)
+    private String customerType;
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java
index 1dc2e15..8b345a3 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java
@@ -111,6 +111,34 @@
     @ExcelProperty("澶囨敞")
     private String remark;
 
+    @Schema(description = "鎶ヤ环绫诲瀷", example = "power")
+    @ExcelProperty("鎶ヤ环绫诲瀷")
+    private String quotationType;
+
+    @Schema(description = "鍚堝悓瀹归噺锛坘VA锛�", example = "800")
+    @ExcelProperty("鍚堝悓瀹归噺")
+    private BigDecimal capacity;
+
+    @Schema(description = "鏈�澶ч渶閲忥紙kW锛�", example = "400")
+    @ExcelProperty("鏈�澶ч渶閲�")
+    private BigDecimal maxDemand;
+
+    @Schema(description = "鐢靛害鍗曚环锛堝厓/kWh锛�", example = "0.5283")
+    @ExcelProperty("鐢靛害鍗曚环")
+    private BigDecimal unitPrice;
+
+    @Schema(description = "鐢典环妯″紡", example = "two-part")
+    @ExcelProperty("鐢典环妯″紡")
+    private String tariffMode;
+
+    @Schema(description = "鍔涜皟鐢佃垂锛堝厓锛�", example = "120.5")
+    @ExcelProperty("鍔涜皟鐢佃垂")
+    private BigDecimal powerFactorFee;
+
+    @Schema(description = "缁撶畻鍛ㄦ湡锛堟湀锛�", example = "12")
+    @ExcelProperty("缁撶畻鍛ㄦ湡")
+    private Integer billingCycle;
+
     @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("鍒涘缓鏃堕棿")
     private LocalDateTime createTime;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java
index 50323c1..264f0df 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java
@@ -59,6 +59,27 @@
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
 
+    @Schema(description = "鎶ヤ环绫诲瀷锛坓eneral=閫氱敤/power=鐢靛姏/energy=鑳芥簮鏈嶅姟锛�", example = "power")
+    private String quotationType;
+
+    @Schema(description = "鍚堝悓瀹归噺锛坘VA锛�", example = "800")
+    private BigDecimal capacity;
+
+    @Schema(description = "鏈�澶ч渶閲忥紙kW锛�", example = "400")
+    private BigDecimal maxDemand;
+
+    @Schema(description = "鐢靛害鍗曚环锛堝厓/kWh锛�", example = "0.5283")
+    private BigDecimal unitPrice;
+
+    @Schema(description = "鐢典环妯″紡锛坰ingle/two-part/tier/tou锛�", example = "two-part")
+    private String tariffMode;
+
+    @Schema(description = "鍔涜皟鐢佃垂锛堝厓锛�", example = "120.5")
+    private BigDecimal powerFactorFee;
+
+    @Schema(description = "缁撶畻鍛ㄦ湡锛堟湀锛�", example = "12")
+    private Integer billingCycle;
+
     @Schema(description = "鐗╂枡鏄庣粏鍒楄〃", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotEmpty(message = "鐗╂枡鏄庣粏涓嶈兘涓虹┖")
     private List<Item> items;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/CrmRefundController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/CrmRefundController.java
new file mode 100644
index 0000000..256bcda
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/CrmRefundController.java
@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.crm.controller.admin.refund;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundAuditReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundSaveReqVO;
+import cn.iocoder.yudao.module.crm.service.refund.CrmRefundService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - CRM 閫�璐瑰崟")
+@RestController
+@RequestMapping("/crm/refund")
+@Validated
+public class CrmRefundController {
+
+    @Resource
+    private CrmRefundService refundService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓閫�璐瑰崟")
+    @PreAuthorize("@ss.hasPermission('crm:refund:create')")
+    public CommonResult<Long> createRefund(@Valid @RequestBody CrmRefundSaveReqVO createReqVO) {
+        return success(refundService.createRefund(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊閫�璐瑰崟")
+    @PreAuthorize("@ss.hasPermission('crm:refund:update')")
+    public CommonResult<Boolean> updateRefund(@Valid @RequestBody CrmRefundSaveReqVO updateReqVO) {
+        refundService.updateRefund(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/audit")
+    @Operation(summary = "瀹℃壒閫�璐瑰崟锛堥�氳繃/椹冲洖锛�")
+    @PreAuthorize("@ss.hasPermission('crm:refund:update')")
+    public CommonResult<Boolean> auditRefund(@Valid @RequestBody CrmRefundAuditReqVO auditReqVO) {
+        refundService.auditRefund(auditReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦閫�璐瑰崟瀹℃壒")
+    @PreAuthorize("@ss.hasPermission('crm:refund:update')")
+    public CommonResult<Boolean> submitRefund(@RequestParam("id") Long id,
+                                              @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        refundService.submitRefund(id, processDefinitionKey, getLoginUserId());
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰緱閫�璐瑰崟瀹℃壒娴佺▼瀹氫箟鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('crm:refund:query')")
+    public CommonResult<List<Map<String, Object>>> getRefundApproveProcessDefinitionList() {
+        return success(refundService.getRefundApproveProcessDefinitionList());
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎閫�璐瑰崟")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:refund:delete')")
+    public CommonResult<Boolean> deleteRefund(@RequestParam("id") Long id) {
+        refundService.deleteRefund(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱閫�璐瑰崟")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:refund:query')")
+    public CommonResult<CrmRefundRespVO> getRefund(@RequestParam("id") Long id) {
+        return success(refundService.getRefund(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱閫�璐瑰崟鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('crm:refund:query')")
+    public CommonResult<PageResult<CrmRefundRespVO>> getRefundPage(@Valid CrmRefundPageReqVO pageReqVO) {
+        return success(refundService.getRefundPage(pageReqVO));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭閫�璐瑰崟 Excel")
+    @PreAuthorize("@ss.hasPermission('crm:refund:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportRefundExcel(@Valid CrmRefundPageReqVO exportReqVO,
+                                  HttpServletResponse response) throws IOException {
+        exportReqVO.setPageSize(PAGE_SIZE_NONE);
+        List<CrmRefundRespVO> list = refundService.getRefundList(exportReqVO);
+        ExcelUtils.write(response, "閫�璐瑰崟.xls", "鏁版嵁", CrmRefundRespVO.class, list);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundAuditReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundAuditReqVO.java
new file mode 100644
index 0000000..684448c
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundAuditReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.crm.controller.admin.refund.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閫�璐瑰崟瀹℃壒 Request VO")
+@Data
+public class CrmRefundAuditReqVO {
+
+    @Schema(description = "閫�璐瑰崟缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "閫�璐瑰崟缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "瀹℃壒缁撴灉锛�20=閫氳繃 / 30=椹冲洖", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "瀹℃壒缁撴灉涓嶈兘涓虹┖")
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒鎰忚")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundPageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundPageReqVO.java
new file mode 100644
index 0000000..1588dca
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundPageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.crm.controller.admin.refund.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閫�璐瑰崟鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CrmRefundPageReqVO extends PageParam {
+
+    @Schema(description = "閫�璐瑰崟鍙�", example = "RF202608190001")
+    private String refundNo;
+
+    @Schema(description = "瀹㈡埛ID", example = "2048")
+    private Long customerId;
+
+    @Schema(description = "閫�璐圭被鍨�", example = "ELECTRICITY")
+    private String refundType;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer auditStatus;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundRespVO.java
new file mode 100644
index 0000000..1069c2a
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundRespVO.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.crm.controller.admin.refund.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_AUDIT_STATUS;
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_REFUND_TYPE;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閫�璐瑰崟 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmRefundRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "閫�璐瑰崟鍙�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("閫�璐瑰崟鍙�")
+    private String refundNo;
+
+    @Schema(description = "瀹㈡埛ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("瀹㈡埛ID")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    @ExcelProperty("瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "鍚堝悓ID")
+    @ExcelProperty("鍚堝悓ID")
+    private Long contractId;
+
+    @Schema(description = "鍚堝悓缂栧彿")
+    @ExcelProperty("鍚堝悓缂栧彿")
+    private String contractNo;
+
+    @Schema(description = "閫�璐圭被鍨�")
+    @ExcelProperty(value = "閫�璐圭被鍨�", converter = DictConvert.class)
+    @DictFormat(CRM_REFUND_TYPE)
+    private String refundType;
+
+    @Schema(description = "閫�璐归噾棰�")
+    @ExcelProperty("閫�璐归噾棰�")
+    private BigDecimal refundAmount;
+
+    @Schema(description = "閫�璐瑰師鍥�")
+    @ExcelProperty("閫�璐瑰師鍥�")
+    private String refundReason;
+
+    @Schema(description = "鐢宠鐢ㄦ埛ID")
+    @ExcelProperty("鐢宠鐢ㄦ埛ID")
+    private Long applyUserId;
+
+    @Schema(description = "鐢宠鐢ㄦ埛濮撳悕")
+    @ExcelProperty("鐢宠鐢ㄦ埛濮撳悕")
+    private String applyUserName;
+
+    @Schema(description = "鐢宠鏃堕棿")
+    @ExcelProperty("鐢宠鏃堕棿")
+    private LocalDateTime applyTime;
+
+    @Schema(description = "瀹℃壒鐘舵��")
+    @ExcelProperty(value = "瀹℃壒鐘舵��", converter = DictConvert.class)
+    @DictFormat(CRM_AUDIT_STATUS)
+    private Integer auditStatus;
+
+    @Schema(description = "娴佺▼瀹炰緥ID")
+    private String processInstanceId;
+
+    @Schema(description = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundSaveReqVO.java
new file mode 100644
index 0000000..41855e8
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundSaveReqVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.crm.controller.admin.refund.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_REFUND_TYPE;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閫�璐瑰崟鏂板/淇敼 Request VO")
+@Data
+public class CrmRefundSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "閫�璐瑰崟鍙凤紙涓嶄紶鍒欒嚜鍔ㄧ敓鎴愶級")
+    private String refundNo;
+
+    @Schema(description = "瀹㈡埛ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "瀹㈡埛ID涓嶈兘涓虹┖")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "鍚堝悓ID", example = "2048")
+    private Long contractId;
+
+    @Schema(description = "鍚堝悓缂栧彿")
+    private String contractNo;
+
+    @Schema(description = "閫�璐圭被鍨嬶紙鐢佃垂閫�璐�/鏈嶅姟閫�璁�/瀹归噺閫�璐癸級", example = "ELECTRICITY")
+    @DictFormat(CRM_REFUND_TYPE)
+    private String refundType;
+
+    @Schema(description = "閫�璐归噾棰�", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00")
+    @NotNull(message = "閫�璐归噾棰濅笉鑳戒负绌�")
+    private BigDecimal refundAmount;
+
+    @Schema(description = "閫�璐瑰師鍥�")
+    private String refundReason;
+
+    @Schema(description = "鐢宠鐢ㄦ埛ID", example = "1")
+    private Long applyUserId;
+
+    @Schema(description = "鐢宠鐢ㄦ埛濮撳悕", example = "鏉庡伐")
+    private String applyUserName;
+
+    @Schema(description = "鐢宠鏃堕棿")
+    private LocalDateTime applyTime;
+
+    @Schema(description = "瀹℃壒鐘舵�侊紙0鑽夌/10寰呭鎵�/20閫氳繃/30椹冲洖锛�", example = "0")
+    private Integer auditStatus;
+
+    @Schema(description = "娴佺▼瀹炰緥ID")
+    private String processInstanceId;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/cancellation/CrmCancellationDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/cancellation/CrmCancellationDO.java
new file mode 100644
index 0000000..00b01ac
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/cancellation/CrmCancellationDO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.cancellation;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * CRM 閿�鎴风敵璇� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("crm_cancellation")
+@KeySequence("crm_cancellation_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmCancellationDO extends BaseDO {
+
+    @TableId
+    private Long id;
+    private String cancellationNo;
+    private Long customerId;
+    private String customerName;
+    private Long contractId;
+    private String contractNo;
+    /**
+     * 閿�鎴风被鍨嬶細鐢ㄧ數閿�鎴�/鏈嶅姟缁堟/鍚堝悓鍒版湡锛堝瓧鍏� crm_cancellation_type锛�
+     */
+    private String cancellationType;
+    private String cancelReason;
+    private LocalDate expectedCancelDate;
+    private LocalDate actualCancelDate;
+    private BigDecimal refundAmount;
+    private Long applyUserId;
+    private String applyUserName;
+    private LocalDateTime applyTime;
+    /**
+     * 瀹℃壒鐘舵�侊細0鑽夌/10寰呭鎵�/20閫氳繃/30椹冲洖
+     */
+    private Integer auditStatus;
+    private String processInstanceId;
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractCommandDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractCommandDO.java
new file mode 100644
index 0000000..49d22b9
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractCommandDO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * CRM 鍚堝悓鎸囦护涓嬪彂 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("crm_contract_command")
+@KeySequence("crm_contract_command_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmContractCommandDO extends BaseDO {
+
+    @TableId
+    private Long id;
+    private Long contractId;
+    private String contractNo;
+    /**
+     * 鎸囦护绫诲瀷锛氫笅鍙戣〃/鎶勮〃浠诲姟/鏈嶅姟宸ュ崟锛堝瓧鍏� crm_command_type锛�
+     */
+    private String commandType;
+    /**
+     * 鐩爣妯″潡锛歮es-work-order/mes-metering/srm-service
+     */
+    private String targetModule;
+    private Long targetId;
+    private String bizNo;
+    private Long operatorId;
+    private String operatorName;
+    private LocalDateTime operateTime;
+    /**
+     * 鐘舵�侊細0寰呮墽琛�/10宸蹭笅鍙�/20宸插畬鎴�/30宸插彇娑堬紙瀛楀吀 crm_command_status锛�
+     */
+    private Integer status;
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
index 61c072b..e86a1d3 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
@@ -132,4 +132,43 @@
      */
     private String remark;
 
+    /**
+     * 鍚堝悓绫诲瀷锛堢數鍔汣RM鎵╁睍锛�
+     *
+     * 瀛楀吀 crm_contract_type锛歱ower=渚涚敤鐢靛悎鍚� / energy=鑳芥簮鏈嶅姟鍚堝悓 / resident=灞呴厤鍚堝悓 / solar=鍏変紡鍚堝悓
+     */
+    private String contractType;
+    /**
+     * 鐢靛帇绛夌骇
+     *
+     * 瀛楀吀 crm_voltage_level
+     */
+    private String voltageLevel;
+    /**
+     * 鐢ㄧ數绫诲瀷
+     *
+     * 瀛楀吀 crm_electricity_type
+     */
+    private String electricityType;
+    /**
+     * 鍚堝悓瀹归噺锛坘VA锛�
+     */
+    private BigDecimal contractCapacity;
+    /**
+     * 鐢典环妯″紡
+     *
+     * 瀛楀吀 crm_tariff_mode
+     */
+    private String tariffMode;
+    /**
+     * 璁¢噺鏂瑰紡
+     *
+     * 瀛楀吀 crm_meter_mode
+     */
+    private String meterMode;
+    /**
+     * 缂磋垂鍛ㄦ湡锛堟湀锛�
+     */
+    private Integer payCycle;
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/credit/CrmCreditRatingDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/credit/CrmCreditRatingDO.java
new file mode 100644
index 0000000..c36ec60
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/credit/CrmCreditRatingDO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.credit;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * CRM 瀹㈡埛淇$敤璇勭骇 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("crm_credit_rating")
+@KeySequence("crm_credit_rating_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmCreditRatingDO extends BaseDO {
+
+    @TableId
+    private Long id;
+    private Long customerId;
+    private String customerName;
+    private String level;
+    private Integer score;
+    private String evalType;
+    private String evalReason;
+    private Long operatorId;
+    private String operatorName;
+    private LocalDateTime operateTime;
+    private LocalDateTime validUntil;
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
index 76d5111..102cea4 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
@@ -124,4 +124,21 @@
      */
     private String remark;
 
+    /**
+     * 瀹㈡埛鍒嗙被锛堢數鍔汣RM鎵╁睍锛�
+     *
+     * 瀵瑰簲瀛楀吀 {@link DictTypeConstants#CRM_CUSTOMER_TYPE}
+     */
+    private String customerType;
+    /**
+     * 褰撳墠淇$敤绛夌骇锛堟渶鏂颁竴娆′俊鐢ㄨ瘎绾х殑绛夌骇锛岀敱淇$敤璇勭骇妯″潡鍐欏叆锛�
+     *
+     * 瀵瑰簲瀛楀吀 {@link DictTypeConstants#CRM_CREDIT_LEVEL}
+     */
+    private String creditLevel;
+    /**
+     * 褰撳墠淇$敤鍒�
+     */
+    private Integer creditScore;
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java
index 1c47b67..365c416 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java
@@ -129,4 +129,37 @@
      */
     private String remark;
 
+    /**
+     * 鎶ヤ环绫诲瀷锛堢數鍔汣RM鎵╁睍锛�
+     *
+     * 瀛楀吀 crm_quotation_type锛歡eneral=閫氱敤 / power=鐢靛姏鎶ヤ环 / energy=鑳芥簮鏈嶅姟
+     */
+    private String quotationType;
+    /**
+     * 鍚堝悓瀹归噺锛坘VA锛屼粎鐢靛姏/鑳芥簮鎶ヤ环锛�
+     */
+    private BigDecimal capacity;
+    /**
+     * 鏈�澶ч渶閲忥紙kW锛屼粎涓ら儴鍒剁數浠凤級
+     */
+    private BigDecimal maxDemand;
+    /**
+     * 鐢靛害鍗曚环锛堝厓/kWh锛�
+     */
+    private BigDecimal unitPrice;
+    /**
+     * 鐢典环妯″紡
+     *
+     * 瀛楀吀 crm_tariff_mode锛歴ingle=鍗曚竴鍒� / two-part=涓ら儴鍒� / tier=闃舵 / tou=宄拌胺鍒嗘椂
+     */
+    private String tariffMode;
+    /**
+     * 鍔涜皟鐢佃垂锛堝厓锛�
+     */
+    private BigDecimal powerFactorFee;
+    /**
+     * 缁撶畻鍛ㄦ湡锛堟湀锛�
+     */
+    private Integer billingCycle;
+
 }
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/refund/CrmRefundDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/refund/CrmRefundDO.java
new file mode 100644
index 0000000..741ab0f
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/refund/CrmRefundDO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.refund;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+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;
+
+/**
+ * CRM 閫�璐瑰崟 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("crm_refund")
+@KeySequence("crm_refund_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmRefundDO extends BaseDO {
+
+    @TableId
+    private Long id;
+    private String refundNo;
+    private Long customerId;
+    private String customerName;
+    private Long contractId;
+    private String contractNo;
+    /**
+     * 閫�璐圭被鍨嬶細鐢佃垂閫�璐�/鏈嶅姟閫�璁�/瀹归噺閫�璐癸紙瀛楀吀 crm_refund_type锛�
+     */
+    private String refundType;
+    private BigDecimal refundAmount;
+    private String refundReason;
+    private Long applyUserId;
+    private String applyUserName;
+    private LocalDateTime applyTime;
+    /**
+     * 瀹℃壒鐘舵�侊細0鑽夌/10寰呭鎵�/20閫氳繃/30椹冲洖
+     */
+    private Integer auditStatus;
+    private String processInstanceId;
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/cancellation/CrmCancellationMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/cancellation/CrmCancellationMapper.java
new file mode 100644
index 0000000..b5fa2f7
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/cancellation/CrmCancellationMapper.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.cancellation;
+
+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.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.cancellation.CrmCancellationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface CrmCancellationMapper extends BaseMapperX<CrmCancellationDO> {
+
+    default PageResult<CrmCancellationDO> selectPage(CrmCancellationPageReqVO reqVO) {
+        return selectPage(reqVO, buildQuery(reqVO));
+    }
+
+    default List<CrmCancellationDO> selectList(CrmCancellationPageReqVO reqVO) {
+        return selectList(buildQuery(reqVO));
+    }
+
+    default CrmCancellationDO selectByCancellationNo(String cancellationNo) {
+        return selectOne(new LambdaQueryWrapperX<CrmCancellationDO>()
+                .eq(CrmCancellationDO::getCancellationNo, cancellationNo));
+    }
+
+    private LambdaQueryWrapperX<CrmCancellationDO> buildQuery(CrmCancellationPageReqVO reqVO) {
+        return new LambdaQueryWrapperX<CrmCancellationDO>()
+                .likeIfPresent(CrmCancellationDO::getCancellationNo, reqVO.getCancellationNo())
+                .eqIfPresent(CrmCancellationDO::getCustomerId, reqVO.getCustomerId())
+                .eqIfPresent(CrmCancellationDO::getCancellationType, reqVO.getCancellationType())
+                .eqIfPresent(CrmCancellationDO::getAuditStatus, reqVO.getAuditStatus())
+                .orderByDesc(CrmCancellationDO::getId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractCommandMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractCommandMapper.java
new file mode 100644
index 0000000..8de2a2f
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractCommandMapper.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.contract;
+
+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.crm.controller.admin.contract.vo.command.CrmContractCommandPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractCommandDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface CrmContractCommandMapper extends BaseMapperX<CrmContractCommandDO> {
+
+    default PageResult<CrmContractCommandDO> selectPage(CrmContractCommandPageReqVO reqVO) {
+        return selectPage(reqVO, buildQuery(reqVO));
+    }
+
+    default List<CrmContractCommandDO> selectListByContractId(Long contractId) {
+        return selectList(new LambdaQueryWrapperX<CrmContractCommandDO>()
+                .eq(CrmContractCommandDO::getContractId, contractId)
+                .orderByDesc(CrmContractCommandDO::getOperateTime));
+    }
+
+    private LambdaQueryWrapperX<CrmContractCommandDO> buildQuery(CrmContractCommandPageReqVO reqVO) {
+        return new LambdaQueryWrapperX<CrmContractCommandDO>()
+                .eqIfPresent(CrmContractCommandDO::getContractId, reqVO.getContractId())
+                .eqIfPresent(CrmContractCommandDO::getCommandType, reqVO.getCommandType())
+                .eqIfPresent(CrmContractCommandDO::getStatus, reqVO.getStatus())
+                .likeIfPresent(CrmContractCommandDO::getBizNo, reqVO.getBizNo())
+                .orderByDesc(CrmContractCommandDO::getOperateTime);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/credit/CrmCreditRatingMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/credit/CrmCreditRatingMapper.java
new file mode 100644
index 0000000..bbfc9ca
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/credit/CrmCreditRatingMapper.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.credit;
+
+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.crm.controller.admin.credit.vo.CrmCreditRatingPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.credit.CrmCreditRatingDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface CrmCreditRatingMapper extends BaseMapperX<CrmCreditRatingDO> {
+
+    default PageResult<CrmCreditRatingDO> selectPage(CrmCreditRatingPageReqVO reqVO) {
+        return selectPage(reqVO, buildQuery(reqVO));
+    }
+
+    default List<CrmCreditRatingDO> selectList(CrmCreditRatingPageReqVO reqVO) {
+        return selectList(buildQuery(reqVO));
+    }
+
+    default CrmCreditRatingDO selectLatestByCustomerId(Long customerId) {
+        return selectOne(new LambdaQueryWrapperX<CrmCreditRatingDO>()
+                .eq(CrmCreditRatingDO::getCustomerId, customerId)
+                .orderByDesc(CrmCreditRatingDO::getOperateTime)
+                .orderByDesc(CrmCreditRatingDO::getId)
+                .last("LIMIT 1"));
+    }
+
+    private LambdaQueryWrapperX<CrmCreditRatingDO> buildQuery(CrmCreditRatingPageReqVO reqVO) {
+        return new LambdaQueryWrapperX<CrmCreditRatingDO>()
+                .eqIfPresent(CrmCreditRatingDO::getCustomerId, reqVO.getCustomerId())
+                .eqIfPresent(CrmCreditRatingDO::getLevel, reqVO.getLevel())
+                .eqIfPresent(CrmCreditRatingDO::getEvalType, reqVO.getEvalType())
+                .orderByDesc(CrmCreditRatingDO::getOperateTime);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
index c27e561..19566a6 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
@@ -3,6 +3,7 @@
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
@@ -13,6 +14,8 @@
 import org.apache.ibatis.annotations.Mapper;
 
 import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -86,4 +89,22 @@
         return selectCount(query);
     }
 
+    /**
+     * 鏌ヨ涓�鎵瑰鎴风殑娆犺垂鍥炴璁″垝锛堟湭鍥炴 + 宸查�炬湡锛�
+     *
+     * 鍙e緞涓� {@link #selectPage(CrmReceivablePlanPageReqVO, Long)} 鐨勩�屽凡閫炬湡銆嶄竴鑷达細
+     * receivableId 涓虹┖锛堟湭鍥炴锛変笖 returnTime 鏃╀簬褰撳墠鏃堕棿
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @param now         褰撳墠鏃堕棿锛堥�炬湡鍒ゅ畾鏃堕棿鐐癸級
+     * @return 娆犺垂鐨勫洖娆捐鍒掑垪琛�
+     */
+    default List<CrmReceivablePlanDO> selectOverdueListByCustomerIds(Collection<Long> customerIds,
+                                                                     LocalDateTime now) {
+        return selectList(new LambdaQueryWrapperX<CrmReceivablePlanDO>()
+                .in(CrmReceivablePlanDO::getCustomerId, customerIds)
+                .isNull(CrmReceivablePlanDO::getReceivableId) // 鏈洖娆�
+                .lt(CrmReceivablePlanDO::getReturnTime, now)); // 宸查�炬湡
+    }
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/refund/CrmRefundMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/refund/CrmRefundMapper.java
new file mode 100644
index 0000000..d824d52
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/refund/CrmRefundMapper.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.refund;
+
+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.crm.controller.admin.refund.vo.CrmRefundPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.refund.CrmRefundDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface CrmRefundMapper extends BaseMapperX<CrmRefundDO> {
+
+    default PageResult<CrmRefundDO> selectPage(CrmRefundPageReqVO reqVO) {
+        return selectPage(reqVO, buildQuery(reqVO));
+    }
+
+    default List<CrmRefundDO> selectList(CrmRefundPageReqVO reqVO) {
+        return selectList(buildQuery(reqVO));
+    }
+
+    default CrmRefundDO selectByRefundNo(String refundNo) {
+        return selectOne(new LambdaQueryWrapperX<CrmRefundDO>().eq(CrmRefundDO::getRefundNo, refundNo));
+    }
+
+    private LambdaQueryWrapperX<CrmRefundDO> buildQuery(CrmRefundPageReqVO reqVO) {
+        return new LambdaQueryWrapperX<CrmRefundDO>()
+                .likeIfPresent(CrmRefundDO::getRefundNo, reqVO.getRefundNo())
+                .eqIfPresent(CrmRefundDO::getCustomerId, reqVO.getCustomerId())
+                .eqIfPresent(CrmRefundDO::getRefundType, reqVO.getRefundType())
+                .eqIfPresent(CrmRefundDO::getAuditStatus, reqVO.getAuditStatus())
+                .orderByDesc(CrmRefundDO::getId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
index 1dbd1b9..34938c2 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
@@ -20,4 +20,18 @@
     String CRM_SALES_TARGET_DIMENSION = "crm_sales_target_dimension"; // CRM 閿�鍞洰鏍囩淮搴�
     String CRM_SALES_TARGET_STATUS = "crm_sales_target_status"; // CRM 閿�鍞洰鏍囩姸鎬�
 
+    // ========== 鐢靛姏钀ラ攢鎵╁睍 ==========
+    String CRM_CUSTOMER_TYPE = "crm_customer_type"; // 鐢靛姏瀹㈡埛鍒嗙被锛堟斂浼�/宸ヤ笟/灞呮皯/鍟嗕笟/鍐滀笟锛�
+    String CRM_CREDIT_LEVEL = "crm_credit_level"; // 瀹㈡埛淇$敤绛夌骇锛圓+/A/B+/B/C/C-/D锛�
+    String CRM_QUOTATION_TYPE = "crm_quotation_type"; // 鎶ヤ环鍗曠被鍨嬶紙閫氱敤/鐢靛姏/鑳芥簮鏈嶅姟锛�
+    String CRM_TARIFF_MODE = "crm_tariff_mode"; // 鐢典环妯″紡锛堝崟涓�鍒�/涓ら儴鍒�/闃舵/宄拌胺鍒嗘椂锛�
+    String CRM_CONTRACT_TYPE = "crm_contract_type"; // 鍚堝悓绫诲瀷锛堜緵鐢ㄧ數/鑳芥簮鏈嶅姟/灞呴厤/鍏変紡锛�
+    String CRM_VOLTAGE_LEVEL = "crm_voltage_level"; // 鐢靛帇绛夌骇锛�10kV/35kV/110kV/220kV/380V/220V锛�
+    String CRM_ELECTRICITY_TYPE = "crm_electricity_type"; // 鐢ㄧ數绫诲瀷锛堝ぇ宸ヤ笟/涓�鑸伐鍟嗕笟/灞呮皯/鍐滀笟锛�
+    String CRM_METER_MODE = "crm_meter_mode"; // 璁¢噺鏂瑰紡锛堥珮渚涢珮璁�/楂樹緵浣庤/浣庝緵浣庤锛�
+    String CRM_COMMAND_TYPE = "crm_command_type"; // 鍚堝悓鎸囦护绫诲瀷锛堜笅鍙戣〃/鎶勮〃浠诲姟/鏈嶅姟宸ュ崟锛�
+    String CRM_COMMAND_STATUS = "crm_command_status"; // 鍚堝悓鎸囦护鐘舵�侊紙寰呮墽琛�/宸蹭笅鍙�/宸插畬鎴�/宸插彇娑堬級
+    String CRM_REFUND_TYPE = "crm_refund_type"; // 閫�璐圭被鍨嬶紙鐢佃垂閫�璐�/鏈嶅姟閫�璁�/瀹归噺閫�璐癸級
+    String CRM_CANCELLATION_TYPE = "crm_cancellation_type"; // 閿�鎴风被鍨嬶紙鐢ㄧ數閿�鎴�/鏈嶅姟缁堟/鍚堝悓鍒版湡锛�
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index ffcdf9c..c5fb7da 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -140,4 +140,26 @@
     ErrorCode CRM_SALES_TARGET_PERIOD_VALUE_INVALID = new ErrorCode(1_020_017_001, "鍛ㄦ湡鍊笺�恵}銆戞牸寮忎笉姝g‘锛屽簲涓� {}");
     ErrorCode CRM_SALES_TARGET_DIMENSION_TARGET_MISSING = new ErrorCode(1_020_017_002, "璇峰~鍐欑洰鏍噞}缂栧彿");
 
+    // ========== 鐢靛姏钀ラ攢鎵╁睍 1_020_018_000 ==========
+    ErrorCode CRM_CREDIT_RATING_NOT_EXISTS = new ErrorCode(1_020_018_000, "淇$敤璇勭骇璁板綍涓嶅瓨鍦�");
+    ErrorCode CRM_CREDIT_RATING_DUPLICATE = new ErrorCode(1_020_018_001, "鍚屼竴瀹㈡埛宸插瓨鍦ㄧ敓鏁堜腑鐨勪俊鐢ㄨ瘎绾�");
+
+    ErrorCode CRM_CONTRACT_COMMAND_NOT_EXISTS = new ErrorCode(1_020_018_002, "鍚堝悓鎸囦护涓嶅瓨鍦�");
+    ErrorCode CRM_COMMAND_EXECUTE_FAIL_STATUS = new ErrorCode(1_020_018_003, "鍚堝悓鎸囦护鎵ц澶辫触锛屽師鍥狅細鐘舵�佷笉鍏佽鎵ц");
+    ErrorCode CRM_COMMAND_DISPATCH_TARGET_MISSING = new ErrorCode(1_020_018_014, "鍚堝悓鎸囦护涓嬪彂澶辫触锛屽師鍥狅細鎸囦护鏈叧鑱旂洰鏍囧伐鍗曪紙targetId锛�");
+
+    ErrorCode CRM_REFUND_NOT_EXISTS = new ErrorCode(1_020_018_004, "閫�璐瑰崟涓嶅瓨鍦�");
+    ErrorCode CRM_REFUND_NO_DUPLICATE = new ErrorCode(1_020_018_005, "閫�璐瑰崟鍙峰凡瀛樺湪");
+
+    ErrorCode CRM_CANCELLATION_NOT_EXISTS = new ErrorCode(1_020_018_006, "閿�鎴风敵璇蜂笉瀛樺湪");
+    ErrorCode CRM_CANCELLATION_NO_DUPLICATE = new ErrorCode(1_020_018_007, "閿�鎴风敵璇风紪鍙峰凡瀛樺湪");
+
+    ErrorCode CRM_REFUND_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_018_008, "閫�璐瑰崟瀹℃壒澶辫触锛屽師鍥狅細閫�璐瑰崟涓嶅浜庤崏绋跨姸鎬�");
+    ErrorCode CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_020_018_009, "閫�璐瑰崟瀹℃壒澶辫触锛屽師鍥狅細BPM 瀹℃壒娴佺▼涓嶅瓨鍦紝璇峰厛鍦� BPM 绠$悊鍚庡彴閰嶇疆閫�璐瑰鎵规祦绋�");
+    ErrorCode CRM_REFUND_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_018_010, "閫�璐瑰崟瀹℃壒澶辫触锛屽師鍥狅細閫�璐瑰崟涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�");
+
+    ErrorCode CRM_CANCELLATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_018_011, "閿�鎴风敵璇峰鎵瑰け璐ワ紝鍘熷洜锛氶攢鎴风敵璇蜂笉澶勪簬鑽夌鐘舵��");
+    ErrorCode CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_020_018_012, "閿�鎴风敵璇峰鎵瑰け璐ワ紝鍘熷洜锛欱PM 瀹℃壒娴佺▼涓嶅瓨鍦紝璇峰厛鍦� BPM 绠$悊鍚庡彴閰嶇疆閿�鎴峰鎵规祦绋�");
+    ErrorCode CRM_CANCELLATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_018_013, "閿�鎴风敵璇峰鎵瑰け璐ワ紝鍘熷洜锛氶攢鎴风敵璇蜂笉澶勪簬瀹℃壒涓紝鏃犳硶鏇存柊瀹℃壒缁撴灉");
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationService.java
new file mode 100644
index 0000000..59de8ea
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationService.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.crm.service.cancellation;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationAuditReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationSaveReqVO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+public interface CrmCancellationService {
+
+    Long createCancellation(@Valid CrmCancellationSaveReqVO createReqVO);
+
+    void updateCancellation(@Valid CrmCancellationSaveReqVO updateReqVO);
+
+    void deleteCancellation(Long id);
+
+    CrmCancellationRespVO getCancellation(Long id);
+
+    PageResult<CrmCancellationRespVO> getCancellationPage(CrmCancellationPageReqVO pageReqVO);
+
+    List<CrmCancellationRespVO> getCancellationList(CrmCancellationPageReqVO reqVO);
+
+    void auditCancellation(@Valid CrmCancellationAuditReqVO auditReqVO);
+
+    void submitCancellation(Long id, String processDefinitionKey, Long userId);
+
+    void updateCancellationAuditStatus(Long id, Integer bpmResult);
+
+    List<Map<String, Object>> getCancellationApproveProcessDefinitionList();
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationServiceImpl.java
new file mode 100644
index 0000000..5ab0831
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationServiceImpl.java
@@ -0,0 +1,259 @@
+package cn.iocoder.yudao.module.crm.service.cancellation;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationAuditReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.cancellation.CrmCancellationDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.cancellation.CrmCancellationMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
+
+@Service
+@Validated
+@Slf4j
+public class CrmCancellationServiceImpl implements CrmCancellationService {
+
+    private static final Integer AUDIT_DRAFT = 0;
+
+    /**
+     * BPM 閿�鎴峰鎵瑰垎绫荤紪鐮�
+     */
+    private static final String CANCELLATION_APPROVE_CATEGORY_CODE = "cancellation_approve";
+
+    @Resource
+    private CrmCancellationMapper cancellationMapper;
+    @Resource
+    private CrmCustomerMapper customerMapper;
+    @Resource
+    private BpmProcessInstanceApi bpmProcessInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createCancellation(CrmCancellationSaveReqVO createReqVO) {
+        if (StrUtil.isBlank(createReqVO.getCancellationNo())) {
+            createReqVO.setCancellationNo(generateCancellationNo());
+        } else if (cancellationMapper.selectByCancellationNo(createReqVO.getCancellationNo()) != null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_NO_DUPLICATE);
+        }
+        CrmCustomerDO customer = customerMapper.selectById(createReqVO.getCustomerId());
+        if (customer == null) {
+            throw exception(ErrorCodeConstants.CUSTOMER_NOT_EXISTS);
+        }
+        if (StrUtil.isBlank(createReqVO.getCustomerName())) {
+            createReqVO.setCustomerName(customer.getName());
+        }
+        if (createReqVO.getApplyTime() == null) {
+            createReqVO.setApplyTime(LocalDateTime.now());
+        }
+        if (createReqVO.getAuditStatus() == null) {
+            createReqVO.setAuditStatus(AUDIT_DRAFT);
+        }
+        CrmCancellationDO entity = BeanUtils.toBean(createReqVO, CrmCancellationDO.class);
+        cancellationMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateCancellation(CrmCancellationSaveReqVO updateReqVO) {
+        CrmCancellationDO exists = cancellationMapper.selectById(updateReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS);
+        }
+        CrmCancellationDO entity = BeanUtils.toBean(updateReqVO, CrmCancellationDO.class);
+        cancellationMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteCancellation(Long id) {
+        CrmCancellationDO exists = cancellationMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS);
+        }
+        cancellationMapper.deleteById(id);
+    }
+
+    @Override
+    public CrmCancellationRespVO getCancellation(Long id) {
+        CrmCancellationDO entity = cancellationMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, CrmCancellationRespVO.class);
+    }
+
+    @Override
+    public PageResult<CrmCancellationRespVO> getCancellationPage(CrmCancellationPageReqVO pageReqVO) {
+        return BeanUtils.toBean(cancellationMapper.selectPage(pageReqVO), CrmCancellationRespVO.class);
+    }
+
+    @Override
+    public List<CrmCancellationRespVO> getCancellationList(CrmCancellationPageReqVO reqVO) {
+        return BeanUtils.toBean(cancellationMapper.selectList(reqVO), CrmCancellationRespVO.class);
+    }
+
+    @Override
+    public void auditCancellation(CrmCancellationAuditReqVO auditReqVO) {
+        CrmCancellationDO exists = cancellationMapper.selectById(auditReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS);
+        }
+        CrmCancellationDO update = new CrmCancellationDO();
+        update.setId(exists.getId());
+        update.setAuditStatus(auditReqVO.getAuditStatus());
+        if (StrUtil.isNotBlank(auditReqVO.getRemark())) {
+            update.setRemark(auditReqVO.getRemark());
+        }
+        cancellationMapper.updateById(update);
+    }
+
+    @Override
+    public void submitCancellation(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙閿�鎴风敵璇锋槸鍚﹀湪鑽夌鐘舵��
+        CrmCancellationDO cancellation = cancellationMapper.selectById(id);
+        if (cancellation == null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS);
+        }
+        if (ObjUtil.notEqual(cancellation.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_SUBMIT_FAIL_NOT_DRAFT);
+        }
+
+        // 2. 鏍¢獙閿�鎴峰鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪锛屼笖浼犲叆鐨勬祦绋嬪畾涔夊浜庢縺娲荤姸鎬�
+        validateCancellationApproveCategoryAndProcessDefinition();
+        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+        if (processDefinition == null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鍒涘缓閿�鎴峰鎵规祦绋嬪疄渚�
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 4. 鏇存柊閿�鎴风敵璇锋祦绋嬬紪鍙蜂笌瀹℃壒鐘舵��
+        cancellationMapper.updateById(new CrmCancellationDO().setId(id).setProcessInstanceId(processInstanceId)
+                .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
+    }
+
+    @Override
+    public void updateCancellationAuditStatus(Long id, Integer bpmResult) {
+        // 1.1 鏍¢獙閿�鎴风敵璇锋槸鍚﹀瓨鍦�
+        CrmCancellationDO cancellation = cancellationMapper.selectById(id);
+        if (cancellation == null) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS);
+        }
+        // 1.2 鍙湁瀹℃壒涓紝鍙互鏇存柊瀹℃壒缁撴灉
+        if (ObjUtil.notEqual(cancellation.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
+            log.error("[updateCancellationAuditStatus][cancellation({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+                    cancellation.getId(), bpmResult);
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+
+        // 2. 鏇存柊閿�鎴风敵璇峰鎵圭粨鏋�
+        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+        cancellationMapper.updateById(new CrmCancellationDO().setId(id).setAuditStatus(auditStatus));
+    }
+
+    @Override
+    public List<Map<String, Object>> getCancellationApproveProcessDefinitionList() {
+        // 1. 鏍¢獙閿�鎴峰鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪
+        validateCancellationApproveCategoryAndProcessDefinition();
+
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(CANCELLATION_APPROVE_CATEGORY_CODE);
+
+        // 3. 鑾峰彇娴佺▼瀹氫箟璇︽儏
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+
+        // 4. 杩囨护锛氬彧淇濈暀婵�娲荤姸鎬侊紝涓旂浉鍚� key 鍙繚鐣欐渶鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (ProcessDefinition pd : processDefinitions) {
+            if (pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+
+        // 5. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            item.put("version", pd.getVersion());
+            result.add(item);
+        }
+        return result;
+    }
+
+    /**
+     * 鏍¢獙 BPM 閿�鎴峰鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪
+     */
+    private void validateCancellationApproveCategoryAndProcessDefinition() {
+        // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(CANCELLATION_APPROVE_CATEGORY_CODE));
+        if (!categoryMap.containsKey(CANCELLATION_APPROVE_CATEGORY_CODE)) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 2. 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鍙敤鐨勬祦绋嬪畾涔�
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(CANCELLATION_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鏍¢獙鏄惁鏈夋縺娲荤姸鎬佺殑娴佺▼瀹氫箟
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+        boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
+        if (!hasActiveDefinition) {
+            throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+    private String generateCancellationNo() {
+        return "CL" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+                + RandomUtil.randomNumbers(4);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/listener/CrmCancellationStatusListener.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/listener/CrmCancellationStatusListener.java
new file mode 100644
index 0000000..8f14fc7
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/listener/CrmCancellationStatusListener.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.crm.service.cancellation.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.crm.dal.dataobject.cancellation.CrmCancellationDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.cancellation.CrmCancellationMapper;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import cn.iocoder.yudao.module.crm.service.cancellation.CrmCancellationService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閿�鎴峰鎵圭殑缁撴灉鐩戝惉鍣ㄥ疄鐜扮被
+ *
+ * 閫氳繃 businessKey锛堥攢鎴风敵璇稩D锛夊尮閰嶏紝鑰屼笉鏄浐瀹氱殑娴佺▼瀹氫箟 Key锛�
+ * 杩欐牱鍙互鏀寔鍒嗙被涓嬩换鎰� Key 鐨勬祦绋嬫ā鍨嬨��
+ */
+@Component
+@Slf4j
+public class CrmCancellationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+    @Resource
+    private CrmCancellationService cancellationService;
+    @Resource
+    private CrmCancellationMapper cancellationMapper;
+
+    @Override
+    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+        // 閫氳繃 businessKey 鏌ヨ閿�鎴风敵璇�
+        Long cancellationId;
+        try {
+            cancellationId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+        }
+
+        CrmCancellationDO cancellation = cancellationMapper.selectById(cancellationId);
+        if (cancellation == null) {
+            return; // 涓嶆槸閿�鎴风敵璇凤紝蹇界暐
+        }
+
+        // 鍙湁瀹℃壒涓姸鎬佺殑閿�鎴风敵璇锋墠澶勭悊
+        if (!CrmAuditStatusEnum.PROCESS.getStatus().equals(cancellation.getAuditStatus())) {
+            log.warn("[onApplicationEvent] 閿�鎴风敵璇�({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", cancellationId, cancellation.getAuditStatus());
+            return;
+        }
+
+        log.info("[onApplicationEvent] 閿�鎴风敵璇�({}) 瀹℃壒鐘舵�佹洿鏂�: {}", cancellationId, event.getStatus());
+        // 鏇存柊瀹℃壒鐘舵��
+        cancellationService.updateCancellationAuditStatus(cancellationId, event.getStatus());
+    }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandService.java
new file mode 100644
index 0000000..3591bcb
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandService.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.crm.service.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandSaveReqVO;
+import jakarta.validation.Valid;
+
+public interface CrmContractCommandService {
+
+    Long createCommand(@Valid CrmContractCommandSaveReqVO createReqVO);
+
+    void updateCommand(@Valid CrmContractCommandSaveReqVO updateReqVO);
+
+    void deleteCommand(Long id);
+
+    CrmContractCommandRespVO getCommand(Long id);
+
+    PageResult<CrmContractCommandRespVO> getCommandPage(CrmContractCommandPageReqVO pageReqVO);
+
+    /**
+     * 鎵ц鎸囦护锛堢姸鎬� 0/10 -> 20 宸插畬鎴愶級
+     */
+    void executeCommand(Long id);
+
+    /**
+     * 鍙栨秷鎸囦护锛堜粎寰呮墽琛�/宸蹭笅鍙戠姸鎬佸彲鍙栨秷锛�
+     */
+    void cancelCommand(Long id);
+
+    Long countByContractId(Long contractId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandServiceImpl.java
new file mode 100644
index 0000000..d3bed6b
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractCommandServiceImpl.java
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.crm.service.contract;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.command.CrmContractCommandSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractCommandDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractCommandMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
+import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.mes.api.workorder.MesProWorkOrderApi;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+@Service
+@Validated
+public class CrmContractCommandServiceImpl implements CrmContractCommandService {
+
+    private static final Integer STATUS_WAIT = 0;
+    private static final Integer STATUS_DISPATCHED = 10;
+    private static final Integer STATUS_DONE = 20;
+    private static final Integer STATUS_CANCELED = 30;
+
+    /** 鐩爣妯″潡锛歁ES 鐢熶骇宸ュ崟锛堟湁鐪熷疄钀藉湴鍗曟嵁锛屾墽琛屾椂纭宸ュ崟锛� */
+    private static final String TARGET_MODULE_MES_WORK_ORDER = "mes-work-order";
+
+    @Resource
+    private CrmContractCommandMapper commandMapper;
+    @Resource
+    private CrmContractMapper contractMapper;
+    @Resource
+    private MesProWorkOrderApi workOrderApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createCommand(CrmContractCommandSaveReqVO createReqVO) {
+        CrmContractDO contract = contractMapper.selectById(createReqVO.getContractId());
+        if (contract == null) {
+            throw exception(ErrorCodeConstants.CONTRACT_NOT_EXISTS);
+        }
+        if (StrUtil.isBlank(createReqVO.getContractNo())) {
+            createReqVO.setContractNo(contract.getNo());
+        }
+        if (createReqVO.getOperateTime() == null) {
+            createReqVO.setOperateTime(LocalDateTime.now());
+        }
+        if (createReqVO.getStatus() == null) {
+            createReqVO.setStatus(STATUS_WAIT);
+        }
+        CrmContractCommandDO entity = BeanUtils.toBean(createReqVO, CrmContractCommandDO.class);
+        commandMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateCommand(CrmContractCommandSaveReqVO updateReqVO) {
+        CrmContractCommandDO exists = commandMapper.selectById(updateReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CONTRACT_COMMAND_NOT_EXISTS);
+        }
+        CrmContractCommandDO entity = BeanUtils.toBean(updateReqVO, CrmContractCommandDO.class);
+        commandMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteCommand(Long id) {
+        CrmContractCommandDO exists = commandMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CONTRACT_COMMAND_NOT_EXISTS);
+        }
+        commandMapper.deleteById(id);
+    }
+
+    @Override
+    public CrmContractCommandRespVO getCommand(Long id) {
+        CrmContractCommandDO entity = commandMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, CrmContractCommandRespVO.class);
+    }
+
+    @Override
+    public PageResult<CrmContractCommandRespVO> getCommandPage(CrmContractCommandPageReqVO pageReqVO) {
+        return BeanUtils.toBean(commandMapper.selectPage(pageReqVO), CrmContractCommandRespVO.class);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void executeCommand(Long id) {
+        CrmContractCommandDO exists = commandMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CONTRACT_COMMAND_NOT_EXISTS);
+        }
+        if (!STATUS_WAIT.equals(exists.getStatus()) && !STATUS_DISPATCHED.equals(exists.getStatus())) {
+            throw exception(ErrorCodeConstants.CRM_COMMAND_EXECUTE_FAIL_STATUS);
+        }
+        // 璺ㄦā鍧楃湡瀹炰笅鍙戯細mes-work-order 鐩爣妯″潡锛岀‘璁ゆ寚浠ゅ叧鑱旂殑宸ュ崟
+        if (TARGET_MODULE_MES_WORK_ORDER.equals(exists.getTargetModule())) {
+            if (exists.getTargetId() == null) {
+                throw exception(ErrorCodeConstants.CRM_COMMAND_DISPATCH_TARGET_MISSING);
+            }
+            workOrderApi.confirmWorkOrder(exists.getTargetId());
+        }
+        // 鍏朵綑鐩爣妯″潡锛坢es-metering/srm-service锛夋殏鏃犵湡瀹炶惤鍦板崟鎹紝浠呰褰曠姸鎬佹祦杞�
+        CrmContractCommandDO update = new CrmContractCommandDO();
+        update.setId(id);
+        update.setStatus(STATUS_DONE);
+        commandMapper.updateById(update);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelCommand(Long id) {
+        CrmContractCommandDO exists = commandMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CONTRACT_COMMAND_NOT_EXISTS);
+        }
+        if (!STATUS_WAIT.equals(exists.getStatus()) && !STATUS_DISPATCHED.equals(exists.getStatus())) {
+            throw exception(ErrorCodeConstants.CRM_COMMAND_EXECUTE_FAIL_STATUS);
+        }
+        // 璺ㄦā鍧楄仈鍔ㄥ彇娑堬細宸蹭笅鍙戠殑 MES 宸ュ崟鍚屾鍙栨秷
+        if (TARGET_MODULE_MES_WORK_ORDER.equals(exists.getTargetModule())
+                && exists.getTargetId() != null) {
+            workOrderApi.cancelWorkOrder(exists.getTargetId());
+        }
+        CrmContractCommandDO update = new CrmContractCommandDO();
+        update.setId(id);
+        update.setStatus(STATUS_CANCELED);
+        commandMapper.updateById(update);
+    }
+
+    @Override
+    public Long countByContractId(Long contractId) {
+        List<CrmContractCommandDO> list = commandMapper.selectListByContractId(contractId);
+        return (long) list.size();
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingService.java
new file mode 100644
index 0000000..3fea73d
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingService.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.crm.service.credit;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingSaveReqVO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+public interface CrmCreditRatingService {
+
+    Long createCreditRating(@Valid CrmCreditRatingSaveReqVO createReqVO);
+
+    void updateCreditRating(@Valid CrmCreditRatingSaveReqVO updateReqVO);
+
+    void deleteCreditRating(Long id);
+
+    CrmCreditRatingRespVO getCreditRating(Long id);
+
+    PageResult<CrmCreditRatingRespVO> getCreditRatingPage(CrmCreditRatingPageReqVO pageReqVO);
+
+    List<CrmCreditRatingRespVO> getCreditRatingList(CrmCreditRatingPageReqVO reqVO);
+
+    CrmCreditRatingRespVO getLatestByCustomerId(Long customerId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingServiceImpl.java
new file mode 100644
index 0000000..adcb534
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/credit/CrmCreditRatingServiceImpl.java
@@ -0,0 +1,109 @@
+package cn.iocoder.yudao.module.crm.service.credit;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.credit.vo.CrmCreditRatingSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.credit.CrmCreditRatingDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.credit.CrmCreditRatingMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
+import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+@Service
+@Validated
+public class CrmCreditRatingServiceImpl implements CrmCreditRatingService {
+
+    @Resource
+    private CrmCreditRatingMapper creditRatingMapper;
+    @Resource
+    private CrmCustomerMapper customerMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createCreditRating(CrmCreditRatingSaveReqVO createReqVO) {
+        CrmCustomerDO customer = customerMapper.selectById(createReqVO.getCustomerId());
+        if (customer == null) {
+            throw exception(ErrorCodeConstants.CUSTOMER_NOT_EXISTS);
+        }
+        if (StrUtil.isBlank(createReqVO.getOperatorName())) {
+            createReqVO.setOperatorName(customer.getName());
+        }
+        if (createReqVO.getOperateTime() == null) {
+            createReqVO.setOperateTime(LocalDateTime.now());
+        }
+        CrmCreditRatingDO entity = BeanUtils.toBean(createReqVO, CrmCreditRatingDO.class, vo -> vo.setCustomerName(customer.getName()));
+        creditRatingMapper.insert(entity);
+        customer.setCreditLevel(entity.getLevel());
+        customer.setCreditScore(entity.getScore());
+        customerMapper.updateById(customer);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateCreditRating(CrmCreditRatingSaveReqVO updateReqVO) {
+        CrmCreditRatingDO exists = creditRatingMapper.selectById(updateReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CREDIT_RATING_NOT_EXISTS);
+        }
+        CrmCreditRatingDO entity = BeanUtils.toBean(updateReqVO, CrmCreditRatingDO.class);
+        creditRatingMapper.updateById(entity);
+        CrmCreditRatingDO latest = creditRatingMapper.selectLatestByCustomerId(exists.getCustomerId());
+        CrmCustomerDO customer = customerMapper.selectById(exists.getCustomerId());
+        if (customer != null && latest != null && latest.getId().equals(entity.getId())) {
+            customer.setCreditLevel(latest.getLevel());
+            customer.setCreditScore(latest.getScore());
+            customerMapper.updateById(customer);
+        }
+    }
+
+    @Override
+    public void deleteCreditRating(Long id) {
+        CrmCreditRatingDO exists = creditRatingMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_CREDIT_RATING_NOT_EXISTS);
+        }
+        creditRatingMapper.deleteById(id);
+        CrmCreditRatingDO latest = creditRatingMapper.selectLatestByCustomerId(exists.getCustomerId());
+        CrmCustomerDO customer = customerMapper.selectById(exists.getCustomerId());
+        if (customer != null) {
+            customer.setCreditLevel(latest == null ? null : latest.getLevel());
+            customer.setCreditScore(latest == null ? null : latest.getScore());
+            customerMapper.updateById(customer);
+        }
+    }
+
+    @Override
+    public CrmCreditRatingRespVO getCreditRating(Long id) {
+        CrmCreditRatingDO entity = creditRatingMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, CrmCreditRatingRespVO.class);
+    }
+
+    @Override
+    public PageResult<CrmCreditRatingRespVO> getCreditRatingPage(CrmCreditRatingPageReqVO pageReqVO) {
+        return BeanUtils.toBean(creditRatingMapper.selectPage(pageReqVO), CrmCreditRatingRespVO.class);
+    }
+
+    @Override
+    public List<CrmCreditRatingRespVO> getCreditRatingList(CrmCreditRatingPageReqVO reqVO) {
+        return BeanUtils.toBean(creditRatingMapper.selectList(reqVO), CrmCreditRatingRespVO.class);
+    }
+
+    @Override
+    public CrmCreditRatingRespVO getLatestByCustomerId(Long customerId) {
+        CrmCreditRatingDO entity = creditRatingMapper.selectLatestByCustomerId(customerId);
+        return entity == null ? null : BeanUtils.toBean(entity, CrmCreditRatingRespVO.class);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java
index f3bfe95..c6789d5 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java
@@ -7,8 +7,10 @@
 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
 import jakarta.validation.Valid;
 
+import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * CRM 鍥炴璁″垝 Service 鎺ュ彛
@@ -92,4 +94,20 @@
      */
     Long getReceivablePlanRemindCount(Long userId);
 
+    /**
+     * 鑾峰緱瀹㈡埛鐨勬瑺璐归噾棰� Map锛堟湭鍥炴 + 宸查�炬湡鐨勫洖娆捐鍒掗噾棰濆悎璁★級
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @return key=瀹㈡埛缂栧彿锛寁alue=娆犺垂閲戦
+     */
+    Map<Long, BigDecimal> getOverdueAmountMapByCustomerIds(Collection<Long> customerIds);
+
+    /**
+     * 鑾峰緱瀹㈡埛鐨勬瑺璐圭瑪鏁� Map锛堟湭鍥炴 + 宸查�炬湡鐨勫洖娆捐鍒掓暟閲忥級
+     *
+     * @param customerIds 瀹㈡埛缂栧彿闆嗗悎
+     * @return key=瀹㈡埛缂栧彿锛寁alue=娆犺垂绗旀暟
+     */
+    Map<Long, Integer> getOverdueCountMapByCustomerIds(Collection<Long> customerIds);
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
index 8fbb7de..c96eecf 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
@@ -24,8 +24,12 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -185,4 +189,32 @@
         return receivablePlanMapper.selectReceivablePlanCountByRemind(userId);
     }
 
+    @Override
+    public Map<Long, BigDecimal> getOverdueAmountMapByCustomerIds(Collection<Long> customerIds) {
+        Map<Long, BigDecimal> result = new HashMap<>();
+        if (CollUtil.isEmpty(customerIds)) {
+            return result;
+        }
+        List<CrmReceivablePlanDO> overdueList =
+                receivablePlanMapper.selectOverdueListByCustomerIds(customerIds, LocalDateTime.now());
+        for (CrmReceivablePlanDO plan : overdueList) {
+            result.merge(plan.getCustomerId(), plan.getPrice(), BigDecimal::add);
+        }
+        return result;
+    }
+
+    @Override
+    public Map<Long, Integer> getOverdueCountMapByCustomerIds(Collection<Long> customerIds) {
+        Map<Long, Integer> result = new HashMap<>();
+        if (CollUtil.isEmpty(customerIds)) {
+            return result;
+        }
+        List<CrmReceivablePlanDO> overdueList =
+                receivablePlanMapper.selectOverdueListByCustomerIds(customerIds, LocalDateTime.now());
+        for (CrmReceivablePlanDO plan : overdueList) {
+            result.merge(plan.getCustomerId(), 1, Integer::sum);
+        }
+        return result;
+    }
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundService.java
new file mode 100644
index 0000000..1b9125c
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundService.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.crm.service.refund;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundAuditReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundSaveReqVO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+public interface CrmRefundService {
+
+    Long createRefund(@Valid CrmRefundSaveReqVO createReqVO);
+
+    void updateRefund(@Valid CrmRefundSaveReqVO updateReqVO);
+
+    void deleteRefund(Long id);
+
+    CrmRefundRespVO getRefund(Long id);
+
+    PageResult<CrmRefundRespVO> getRefundPage(CrmRefundPageReqVO pageReqVO);
+
+    List<CrmRefundRespVO> getRefundList(CrmRefundPageReqVO reqVO);
+
+    void auditRefund(@Valid CrmRefundAuditReqVO auditReqVO);
+
+    void submitRefund(Long id, String processDefinitionKey, Long userId);
+
+    void updateRefundAuditStatus(Long id, Integer bpmResult);
+
+    List<Map<String, Object>> getRefundApproveProcessDefinitionList();
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundServiceImpl.java
new file mode 100644
index 0000000..9f8b733
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundServiceImpl.java
@@ -0,0 +1,259 @@
+package cn.iocoder.yudao.module.crm.service.refund;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundAuditReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.refund.CrmRefundDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.refund.CrmRefundMapper;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
+
+@Service
+@Validated
+@Slf4j
+public class CrmRefundServiceImpl implements CrmRefundService {
+
+    private static final Integer AUDIT_DRAFT = 0;
+    private static final Integer AUDIT_PASS = 20;
+
+    /**
+     * BPM 閫�璐瑰鎵瑰垎绫荤紪鐮�
+     */
+    private static final String REFUND_APPROVE_CATEGORY_CODE = "refund_approve";
+
+    @Resource
+    private CrmRefundMapper refundMapper;
+    @Resource
+    private CrmCustomerMapper customerMapper;
+    @Resource
+    private BpmProcessInstanceApi bpmProcessInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRefund(CrmRefundSaveReqVO createReqVO) {
+        if (StrUtil.isBlank(createReqVO.getRefundNo())) {
+            createReqVO.setRefundNo(generateRefundNo());
+        } else if (refundMapper.selectByRefundNo(createReqVO.getRefundNo()) != null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_NO_DUPLICATE);
+        }
+        CrmCustomerDO customer = customerMapper.selectById(createReqVO.getCustomerId());
+        if (customer == null) {
+            throw exception(ErrorCodeConstants.CUSTOMER_NOT_EXISTS);
+        }
+        if (StrUtil.isBlank(createReqVO.getCustomerName())) {
+            createReqVO.setCustomerName(customer.getName());
+        }
+        if (createReqVO.getApplyTime() == null) {
+            createReqVO.setApplyTime(LocalDateTime.now());
+        }
+        if (createReqVO.getAuditStatus() == null) {
+            createReqVO.setAuditStatus(AUDIT_DRAFT);
+        }
+        CrmRefundDO entity = BeanUtils.toBean(createReqVO, CrmRefundDO.class);
+        refundMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateRefund(CrmRefundSaveReqVO updateReqVO) {
+        CrmRefundDO exists = refundMapper.selectById(updateReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS);
+        }
+        CrmRefundDO entity = BeanUtils.toBean(updateReqVO, CrmRefundDO.class);
+        refundMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteRefund(Long id) {
+        CrmRefundDO exists = refundMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS);
+        }
+        refundMapper.deleteById(id);
+    }
+
+    @Override
+    public CrmRefundRespVO getRefund(Long id) {
+        CrmRefundDO entity = refundMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, CrmRefundRespVO.class);
+    }
+
+    @Override
+    public PageResult<CrmRefundRespVO> getRefundPage(CrmRefundPageReqVO pageReqVO) {
+        return BeanUtils.toBean(refundMapper.selectPage(pageReqVO), CrmRefundRespVO.class);
+    }
+
+    @Override
+    public List<CrmRefundRespVO> getRefundList(CrmRefundPageReqVO reqVO) {
+        return BeanUtils.toBean(refundMapper.selectList(reqVO), CrmRefundRespVO.class);
+    }
+
+    @Override
+    public void auditRefund(CrmRefundAuditReqVO auditReqVO) {
+        CrmRefundDO exists = refundMapper.selectById(auditReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS);
+        }
+        CrmRefundDO update = new CrmRefundDO();
+        update.setId(exists.getId());
+        update.setAuditStatus(auditReqVO.getAuditStatus());
+        if (StrUtil.isNotBlank(auditReqVO.getRemark())) {
+            update.setRemark(auditReqVO.getRemark());
+        }
+        refundMapper.updateById(update);
+    }
+
+    @Override
+    public void submitRefund(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙閫�璐瑰崟鏄惁鍦ㄨ崏绋跨姸鎬�
+        CrmRefundDO refund = refundMapper.selectById(id);
+        if (refund == null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS);
+        }
+        if (ObjUtil.notEqual(refund.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_SUBMIT_FAIL_NOT_DRAFT);
+        }
+
+        // 2. 鏍¢獙閫�璐瑰鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪锛屼笖浼犲叆鐨勬祦绋嬪畾涔夊浜庢縺娲荤姸鎬�
+        validateRefundApproveCategoryAndProcessDefinition();
+        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+        if (processDefinition == null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鍒涘缓閫�璐瑰鎵规祦绋嬪疄渚�
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 4. 鏇存柊閫�璐瑰崟娴佺▼缂栧彿涓庡鎵圭姸鎬�
+        refundMapper.updateById(new CrmRefundDO().setId(id).setProcessInstanceId(processInstanceId)
+                .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
+    }
+
+    @Override
+    public void updateRefundAuditStatus(Long id, Integer bpmResult) {
+        // 1.1 鏍¢獙閫�璐瑰崟鏄惁瀛樺湪
+        CrmRefundDO refund = refundMapper.selectById(id);
+        if (refund == null) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS);
+        }
+        // 1.2 鍙湁瀹℃壒涓紝鍙互鏇存柊瀹℃壒缁撴灉
+        if (ObjUtil.notEqual(refund.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
+            log.error("[updateRefundAuditStatus][refund({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]", refund.getId(), bpmResult);
+            throw exception(ErrorCodeConstants.CRM_REFUND_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+
+        // 2. 鏇存柊閫�璐瑰崟瀹℃壒缁撴灉
+        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+        refundMapper.updateById(new CrmRefundDO().setId(id).setAuditStatus(auditStatus));
+    }
+
+    @Override
+    public List<Map<String, Object>> getRefundApproveProcessDefinitionList() {
+        // 1. 鏍¢獙閫�璐瑰鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪
+        validateRefundApproveCategoryAndProcessDefinition();
+
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(REFUND_APPROVE_CATEGORY_CODE);
+
+        // 3. 鑾峰彇娴佺▼瀹氫箟璇︽儏
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+
+        // 4. 杩囨护锛氬彧淇濈暀婵�娲荤姸鎬侊紝涓旂浉鍚� key 鍙繚鐣欐渶鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (ProcessDefinition pd : processDefinitions) {
+            if (pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+
+        // 5. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            item.put("version", pd.getVersion());
+            result.add(item);
+        }
+        return result;
+    }
+
+    /**
+     * 鏍¢獙 BPM 閫�璐瑰鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪
+     */
+    private void validateRefundApproveCategoryAndProcessDefinition() {
+        // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(REFUND_APPROVE_CATEGORY_CODE));
+        if (!categoryMap.containsKey(REFUND_APPROVE_CATEGORY_CODE)) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 2. 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鍙敤鐨勬祦绋嬪畾涔�
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(REFUND_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鏍¢獙鏄惁鏈夋縺娲荤姸鎬佺殑娴佺▼瀹氫箟
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+        boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
+        if (!hasActiveDefinition) {
+            throw exception(ErrorCodeConstants.CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+    private String generateRefundNo() {
+        return "RF" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+                + RandomUtil.randomNumbers(4);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/listener/CrmRefundStatusListener.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/listener/CrmRefundStatusListener.java
new file mode 100644
index 0000000..a7ed588
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/listener/CrmRefundStatusListener.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.crm.service.refund.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.crm.dal.dataobject.refund.CrmRefundDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.refund.CrmRefundMapper;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import cn.iocoder.yudao.module.crm.service.refund.CrmRefundService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閫�璐瑰鎵圭殑缁撴灉鐩戝惉鍣ㄥ疄鐜扮被
+ *
+ * 閫氳繃 businessKey锛堥��璐瑰崟ID锛夊尮閰嶏紝鑰屼笉鏄浐瀹氱殑娴佺▼瀹氫箟 Key锛�
+ * 杩欐牱鍙互鏀寔鍒嗙被涓嬩换鎰� Key 鐨勬祦绋嬫ā鍨嬨��
+ */
+@Component
+@Slf4j
+public class CrmRefundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+    @Resource
+    private CrmRefundService refundService;
+    @Resource
+    private CrmRefundMapper refundMapper;
+
+    @Override
+    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+        // 閫氳繃 businessKey 鏌ヨ閫�璐瑰崟
+        Long refundId;
+        try {
+            refundId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+        }
+
+        CrmRefundDO refund = refundMapper.selectById(refundId);
+        if (refund == null) {
+            return; // 涓嶆槸閫�璐瑰崟锛屽拷鐣�
+        }
+
+        // 鍙湁瀹℃壒涓姸鎬佺殑閫�璐瑰崟鎵嶅鐞�
+        if (!CrmAuditStatusEnum.PROCESS.getStatus().equals(refund.getAuditStatus())) {
+            log.warn("[onApplicationEvent] 閫�璐瑰崟({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", refundId, refund.getAuditStatus());
+            return;
+        }
+
+        log.info("[onApplicationEvent] 閫�璐瑰崟({}) 瀹℃壒鐘舵�佹洿鏂�: {}", refundId, event.getStatus());
+        // 鏇存柊瀹℃壒鐘舵��
+        refundService.updateRefundAuditStatus(refundId, event.getStatus());
+    }
+
+}
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApi.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApi.java
new file mode 100644
index 0000000..2b40898
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApi.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.erp.api.purchase;
+
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchasePlanCreateReqDTO;
+
+/**
+ * ERP 閲囪喘璁″垝 API 鎺ュ彛
+ *
+ * <p>渚涘叾浠栨ā鍧楋紙濡� MES 璁″垝鎺掔▼锛夎嚜鍔ㄧ敓鎴愰噰璐鍒�
+ *
+ * @author xiaoyi
+ */
+public interface ErpPurchasePlanApi {
+
+    /**
+     * 鍒涘缓閲囪喘璁″垝锛堣崏绋匡級
+     *
+     * @param createReqDTO 鍒涘缓淇℃伅
+     * @return 閲囪喘璁″垝缂栧彿
+     */
+    Long createPurchasePlan(ErpPurchasePlanCreateReqDTO createReqDTO);
+
+}
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchasePlanCreateReqDTO.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchasePlanCreateReqDTO.java
new file mode 100644
index 0000000..55ca41b
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchasePlanCreateReqDTO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.erp.api.purchase.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘璁″垝鍒涘缓 Request DTO
+ *
+ * @author xiaoyi
+ */
+@Data
+public class ErpPurchasePlanCreateReqDTO {
+
+    /**
+     * 璁″垝鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 闇�姹傛潵婧�
+     *
+     * 1: 搴撳瓨棰勮
+     * 2: 杩愮淮/鐢熶骇璁″垝
+     */
+    private Integer demandSource;
+
+    /**
+     * 璁″垝鏃ユ湡
+     */
+    private LocalDateTime planDate;
+
+    /**
+     * 渚涘簲鍟嗙紪鍙凤紙鍙�夛級
+     */
+    private Long supplierId;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+    /**
+     * 璁″垝鏄庣粏鍒楄〃
+     */
+    private List<Item> items;
+
+    @Data
+    public static class Item {
+
+        /**
+         * 浜у搧缂栧彿锛圡DM 鐗╂枡缂栧彿锛�
+         */
+        private Long productId;
+
+        /**
+         * 浜у搧鍗曚綅缂栧彿
+         */
+        private Long productUnitId;
+
+        /**
+         * 璁″垝閲囪喘鏁伴噺
+         */
+        private BigDecimal count;
+
+        /**
+         * 澶囨敞
+         */
+        private String remark;
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApiImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApiImpl.java
new file mode 100644
index 0000000..8f85a34
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApiImpl.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.erp.api.purchase;
+
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchasePlanCreateReqDTO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanSaveReqVO;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchasePlanService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * ERP 閲囪喘璁″垝 API 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+public class ErpPurchasePlanApiImpl implements ErpPurchasePlanApi {
+
+    @Resource
+    private ErpPurchasePlanService purchasePlanService;
+
+    @Override
+    public Long createPurchasePlan(ErpPurchasePlanCreateReqDTO createReqDTO) {
+        ErpPurchasePlanSaveReqVO createReqVO = new ErpPurchasePlanSaveReqVO();
+        createReqVO.setName(createReqDTO.getName());
+        createReqVO.setDemandSource(createReqDTO.getDemandSource());
+        createReqVO.setPlanDate(createReqDTO.getPlanDate());
+        createReqVO.setSupplierId(createReqDTO.getSupplierId());
+        createReqVO.setRemark(createReqDTO.getRemark());
+        createReqVO.setItems(convertItems(createReqDTO.getItems()));
+        return purchasePlanService.createPurchasePlan(createReqVO);
+    }
+
+    private List<ErpPurchasePlanSaveReqVO.Item> convertItems(List<ErpPurchasePlanCreateReqDTO.Item> items) {
+        if (items == null) {
+            return List.of();
+        }
+        return items.stream().map(item -> {
+            ErpPurchasePlanSaveReqVO.Item vo = new ErpPurchasePlanSaveReqVO.Item();
+            vo.setProductId(item.getProductId());
+            vo.setProductUnitId(item.getProductUnitId());
+            vo.setCount(item.getCount());
+            vo.setRemark(item.getRemark());
+            return vo;
+        }).toList();
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java
new file mode 100644
index 0000000..47920de
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java
@@ -0,0 +1,210 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanRespVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanItemDO;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchasePlanService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchasePlanServiceImpl;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
+import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
+import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - ERP 閲囪喘璁″垝")
+@RestController
+@RequestMapping("/erp/purchase-plan")
+@Validated
+public class ErpPurchasePlanController {
+
+    @Resource
+    private ErpPurchasePlanService purchasePlanService;
+    @Resource
+    private SrmSupplierApi srmSupplierApi;
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private MdmUnitMeasureApi mdmUnitMeasureApi;
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓閲囪喘璁″垝")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:create')")
+    public CommonResult<Long> createPurchasePlan(@Valid @RequestBody ErpPurchasePlanSaveReqVO createReqVO) {
+        return success(purchasePlanService.createPurchasePlan(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊閲囪喘璁″垝")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:update')")
+    public CommonResult<Boolean> updatePurchasePlan(@Valid @RequestBody ErpPurchasePlanSaveReqVO updateReqVO) {
+        purchasePlanService.updatePurchasePlan(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎閲囪喘璁″垝")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:delete')")
+    public CommonResult<Boolean> deletePurchasePlan(@RequestParam("ids") List<Long> ids) {
+        purchasePlanService.deletePurchasePlan(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱閲囪喘璁″垝")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:query')")
+    public CommonResult<ErpPurchasePlanRespVO> getPurchasePlan(@RequestParam("id") Long id) {
+        ErpPurchasePlanDO plan = purchasePlanService.getPurchasePlan(id);
+        if (plan == null) {
+            return success(null);
+        }
+        List<ErpPurchasePlanItemDO> planItemList = purchasePlanService.getPurchasePlanItemListByPlanId(id);
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(planItemList, ErpPurchasePlanItemDO::getProductId));
+        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap(
+                convertSet(planItemList, ErpPurchasePlanItemDO::getProductUnitId));
+        return success(BeanUtils.toBean(plan, ErpPurchasePlanRespVO.class, planVO -> {
+            planVO.setDemandSourceName(convertDemandSourceName(plan.getDemandSource()));
+            planVO.setItems(BeanUtils.toBean(planItemList, ErpPurchasePlanRespVO.Item.class, item -> {
+                MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName()));
+                MapUtils.findAndThen(unitMeasureMap, item.getProductUnitId(), unit -> item.setProductUnitName(unit.getName()));
+            }));
+        }));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱閲囪喘璁″垝鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:query')")
+    public CommonResult<PageResult<ErpPurchasePlanRespVO>> getPurchasePlanPage(@Valid ErpPurchasePlanPageReqVO pageReqVO) {
+        PageResult<ErpPurchasePlanDO> pageResult = purchasePlanService.getPurchasePlanPage(pageReqVO);
+        return success(buildPurchasePlanVOPageResult(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭閲囪喘璁″垝 Excel")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportPurchasePlanExcel(@Valid ErpPurchasePlanPageReqVO pageReqVO,
+                                        HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<ErpPurchasePlanRespVO> list = buildPurchasePlanVOPageResult(purchasePlanService.getPurchasePlanPage(pageReqVO)).getList();
+        ExcelUtils.write(response, "閲囪喘璁″垝.xls", "鏁版嵁", ErpPurchasePlanRespVO.class, list);
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦閲囪喘璁″垝瀹℃壒")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:submit')")
+    public CommonResult<Boolean> submitPurchasePlan(@RequestParam("id") Long id,
+                                                    @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        Long userId = getLoginUserId();
+        purchasePlanService.submitPurchasePlan(id, processDefinitionKey, userId);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰彇閲囪喘璁″垝瀹℃壒娴佺▼鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:query')")
+    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
+        return success(purchasePlanService.getPurchasePlanApproveProcessDefinitionList());
+    }
+
+    @PostMapping("/generate-from-stock-alert")
+    @Operation(summary = "鏍规嵁搴撳瓨棰勮鐢熸垚閲囪喘璁″垝", description = "閬嶅巻鍚敤瀹夊叏搴撳瓨鐨勭墿鏂欙紝鎸夌己閲忕敓鎴愯崏绋块噰璐鍒�")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:create')")
+    public CommonResult<Long> generatePlanFromStockAlert(@RequestParam(value = "name", required = false) String name,
+                                                         @RequestParam(value = "supplierId", required = false) Long supplierId) {
+        ErpPurchasePlanDO plan = purchasePlanService.generatePlanFromStockAlert(name, supplierId);
+        return success(plan.getId());
+    }
+
+    @PostMapping("/generate-requests")
+    @Operation(summary = "鐢熸垚閲囪喘鐢宠")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-plan:generate-request')")
+    public CommonResult<List<Long>> generatePurchaseRequests(@RequestParam("id") Long id) {
+        return success(purchasePlanService.generatePurchaseRequests(id));
+    }
+
+    private PageResult<ErpPurchasePlanRespVO> buildPurchasePlanVOPageResult(PageResult<ErpPurchasePlanDO> pageResult) {
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return PageResult.empty(pageResult.getTotal());
+        }
+        // 1.1 璁″垝鏄庣粏
+        List<ErpPurchasePlanItemDO> planItemList = purchasePlanService.getPurchasePlanItemListByPlanIds(
+                convertSet(pageResult.getList(), ErpPurchasePlanDO::getId));
+        Map<Long, List<ErpPurchasePlanItemDO>> planItemMap = convertMultiMap(planItemList, ErpPurchasePlanItemDO::getPlanId);
+        // 1.2 鐗╂枡淇℃伅
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(planItemList, ErpPurchasePlanItemDO::getProductId));
+        // 1.3 渚涘簲鍟嗕俊鎭�
+        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
+                convertSet(pageResult.getList(), ErpPurchasePlanDO::getSupplierId)).getCheckedData().stream()
+                .collect(Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
+        // 1.4 鐢ㄦ埛淇℃伅
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(pageResult.getList(), ErpPurchasePlanDO::getPlanUserId));
+        // 1.5 閮ㄩ棬淇℃伅
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(pageResult.getList(), ErpPurchasePlanDO::getPlanDeptId));
+        // 2. 寮�濮嬫嫾鎺�
+        return BeanUtils.toBean(pageResult, ErpPurchasePlanRespVO.class, plan -> {
+            plan.setDemandSourceName(convertDemandSourceName(plan.getDemandSource()));
+            plan.setItems(BeanUtils.toBean(planItemMap.get(plan.getId()), ErpPurchasePlanRespVO.Item.class,
+                    item -> MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName()))));
+            plan.setProductNames(CollUtil.join(plan.getItems(), "锛�", ErpPurchasePlanRespVO.Item::getProductName));
+            MapUtils.findAndThen(supplierMap, plan.getSupplierId(), supplier -> plan.setSupplierName(supplier.getName()));
+            MapUtils.findAndThen(userMap, plan.getPlanUserId(), user -> plan.setPlanUserName(user.getNickname()));
+            MapUtils.findAndThen(deptMap, plan.getPlanDeptId(), dept -> plan.setPlanDeptName(dept.getName()));
+        });
+    }
+
+    private String convertDemandSourceName(Integer demandSource) {
+        if (ErpPurchasePlanServiceImpl.DEMAND_SOURCE_STOCK_ALERT.equals(demandSource)) {
+            return "搴撳瓨棰勮";
+        }
+        if (ErpPurchasePlanServiceImpl.DEMAND_SOURCE_PLAN.equals(demandSource)) {
+            return "杩愮淮/鐢熶骇璁″垝";
+        }
+        return null;
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanPageReqVO.java
new file mode 100644
index 0000000..3458aab
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanPageReqVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘璁″垝鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ErpPurchasePlanPageReqVO extends PageParam {
+
+    @Schema(description = "閲囪喘璁″垝鍗曞彿", example = "CGJH001")
+    private String no;
+
+    @Schema(description = "璁″垝鍚嶇О", example = "2026骞�8鏈堣繍缁村浠惰鍒�")
+    private String name;
+
+    @Schema(description = "鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "闇�姹傛潵婧愶紙1=搴撳瓨棰勮锛�2=杩愮淮/鐢熶骇璁″垝锛�", example = "1")
+    private Integer demandSource;
+
+    @Schema(description = "缂栧埗浜虹紪鍙�", example = "1")
+    private Long planUserId;
+
+    @Schema(description = "缂栧埗閮ㄩ棬缂栧彿", example = "1")
+    private Long planDeptId;
+
+    @Schema(description = "璁″垝鏃ユ湡")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDateTime[] planDate;
+
+    @Schema(description = "浜у搧缂栧彿", example = "1")
+    private Long productId;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanRespVO.java
new file mode 100644
index 0000000..8463903
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanRespVO.java
@@ -0,0 +1,116 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘璁″垝 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ErpPurchasePlanRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "閲囪喘璁″垝鍗曞彿", example = "CGJH001")
+    @ExcelProperty("璁″垝鍗曞彿")
+    private String no;
+
+    @Schema(description = "璁″垝鍚嶇О", example = "2026骞�8鏈堣繍缁村浠惰鍒�")
+    @ExcelProperty("璁″垝鍚嶇О")
+    private String name;
+
+    @Schema(description = "鐘舵��", example = "0")
+    @ExcelProperty(value = "鐘舵��", converter = cn.iocoder.yudao.module.erp.convert.ErpPurchasePlanStatusConvert.class)
+    private Integer status;
+
+    @Schema(description = "闇�姹傛潵婧愶紙1=搴撳瓨棰勮锛�2=杩愮淮/鐢熶骇璁″垝锛�", example = "1")
+    private Integer demandSource;
+
+    @Schema(description = "闇�姹傛潵婧愬悕绉�", example = "搴撳瓨棰勮")
+    private String demandSourceName;
+
+    @Schema(description = "璁″垝鏃ユ湡")
+    @ExcelProperty("璁″垝鏃ユ湡")
+    private LocalDateTime planDate;
+
+    @Schema(description = "缂栧埗浜虹紪鍙�", example = "1")
+    private Long planUserId;
+
+    @Schema(description = "缂栧埗浜哄鍚�", example = "寮犱笁")
+    @ExcelProperty("缂栧埗浜�")
+    private String planUserName;
+
+    @Schema(description = "缂栧埗閮ㄩ棬缂栧彿", example = "1")
+    private Long planDeptId;
+
+    @Schema(description = "缂栧埗閮ㄩ棬鍚嶇О", example = "杩愮淮閮�")
+    @ExcelProperty("缂栧埗閮ㄩ棬")
+    private String planDeptName;
+
+    @Schema(description = "渚涘簲鍟嗙紪鍙凤紙鍙�夛級", example = "1")
+    private Long supplierId;
+
+    @Schema(description = "渚涘簲鍟嗗悕绉�", example = "渚涘簲鍟咥")
+    @ExcelProperty("渚涘簲鍟�")
+    private String supplierName;
+
+    @Schema(description = "鍚堣鏁伴噺", example = "100")
+    @ExcelProperty("鍚堣鏁伴噺")
+    private BigDecimal totalCount;
+
+    @Schema(description = "鏄惁宸茬敓鎴愰噰璐敵璇�", example = "false")
+    private Boolean generatedFlag;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @Schema(description = "璁″垝鏄庣粏鍒楄〃")
+    private List<Item> items;
+
+    @Schema(description = "浜у搧鍚嶇О鍒楄〃锛堥�楀彿鎷兼帴锛�", example = "浜у搧A锛屼骇鍝丅")
+    private String productNames;
+
+    @Schema(description = "璁″垝鏄庣粏椤�")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缂栧彿", example = "1")
+        private Long id;
+
+        @Schema(description = "浜у搧缂栧彿", example = "1")
+        private Long productId;
+
+        @Schema(description = "浜у搧鍚嶇О", example = "浜у搧A")
+        private String productName;
+
+        @Schema(description = "浜у搧鍗曚綅缂栧彿", example = "1")
+        private Long productUnitId;
+
+        @Schema(description = "浜у搧鍗曚綅鍚嶇О", example = "涓�")
+        private String productUnitName;
+
+        @Schema(description = "璁″垝閲囪喘鏁伴噺", example = "10")
+        private BigDecimal count;
+
+        @Schema(description = "闇�姹傛棩鏈�")
+        private LocalDate demandTime;
+
+        @Schema(description = "澶囨敞", example = "澶囨敞")
+        private String remark;
+
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanSaveReqVO.java
new file mode 100644
index 0000000..3da8f1d
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/plan/ErpPurchasePlanSaveReqVO.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘璁″垝鏂板/淇敼 Request VO")
+@Data
+public class ErpPurchasePlanSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "璁″垝鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026骞�8鏈堣繍缁村浠惰鍒�")
+    @NotEmpty(message = "璁″垝鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "闇�姹傛潵婧愶紙1=搴撳瓨棰勮锛�2=杩愮淮/鐢熶骇璁″垝锛�", example = "1")
+    private Integer demandSource;
+
+    @Schema(description = "璁″垝鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "璁″垝鏃ユ湡涓嶈兘涓虹┖")
+    private LocalDateTime planDate;
+
+    @Schema(description = "渚涘簲鍟嗙紪鍙凤紙鍙�夛級", example = "1")
+    private Long supplierId;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "璁″垝鏄庣粏鍒楄〃", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "璁″垝鏄庣粏涓嶈兘涓虹┖")
+    @Valid
+    private List<Item> items;
+
+    @Schema(description = "璁″垝鏄庣粏椤�")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+        private Long id;
+
+        @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "浜у搧缂栧彿涓嶈兘涓虹┖")
+        private Long productId;
+
+        @Schema(description = "浜у搧鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "浜у搧鍗曚綅涓嶈兘涓虹┖")
+        private Long productUnitId;
+
+        @Schema(description = "璁″垝閲囪喘鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+        @NotNull(message = "鏁伴噺涓嶈兘涓虹┖")
+        private BigDecimal count;
+
+        @Schema(description = "闇�姹傛棩鏈�")
+        private LocalDate demandTime;
+
+        @Schema(description = "澶囨敞", example = "澶囨敞")
+        private String remark;
+
+        @Schema(description = "浜у搧鍚嶇О锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "浜у搧A")
+        private String productName;
+
+        @Schema(description = "浜у搧鍗曚綅鍚嶇О锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "涓�")
+        private String productUnitName;
+
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchasePlanStatusConvert.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchasePlanStatusConvert.java
new file mode 100644
index 0000000..911598c
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchasePlanStatusConvert.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.erp.convert;
+
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchasePlanStatusEnum;
+
+/**
+ * ERP 閲囪喘璁″垝鐘舵�� Excel 杞崲鍣�
+ *
+ * <p>灏� Integer 鐘舵�佺爜杞崲涓轰腑鏂囧悕绉帮紝鐢ㄤ簬 Excel 瀵煎嚭
+ *
+ * @author xiaoyi
+ */
+public class ErpPurchasePlanStatusConvert implements Converter<Integer> {
+
+    @Override
+    public Class<?> supportJavaTypeKey() {
+        return Integer.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty,
+                                               GlobalConfiguration globalConfiguration) {
+        if (value == null) {
+            return new WriteCellData<>("");
+        }
+        ErpPurchasePlanStatusEnum statusEnum = ErpPurchasePlanStatusEnum.fromStatus(value);
+        return new WriteCellData<>(statusEnum != null ? statusEnum.getName() : String.valueOf(value));
+    }
+
+    @Override
+    public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
+                                     GlobalConfiguration globalConfiguration) {
+        String label = cellData.getStringValue();
+        ErpPurchasePlanStatusEnum statusEnum = ErpPurchasePlanStatusEnum.fromName(label);
+        return statusEnum != null ? statusEnum.getStatus() : null;
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanDO.java
new file mode 100644
index 0000000..72c6d97
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanDO.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
+
+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;
+import java.time.LocalDateTime;
+
+/**
+ * ERP 閲囪喘璁″垝 DO
+ *
+ * @author xiaoyi
+ */
+@TableName("erp_purchase_plan")
+@KeySequence("erp_purchase_plan_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpPurchasePlanDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 閲囪喘璁″垝鍗曞彿
+     */
+    private String no;
+
+    /**
+     * 璁″垝鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鏈彁浜�
+     * 10: 瀹℃壒涓�
+     * 20: 瀹℃牳閫氳繃
+     * 30: 瀹℃牳涓嶉�氳繃
+     * 40: 宸插彇娑�
+     */
+    private Integer status;
+
+    /**
+     * 闇�姹傛潵婧�
+     *
+     * 1: 搴撳瓨棰勮
+     * 2: 杩愮淮/鐢熶骇璁″垝
+     */
+    private Integer demandSource;
+
+    /**
+     * 璁″垝鏃ユ湡
+     */
+    private LocalDateTime planDate;
+
+    /**
+     * 缂栧埗浜虹紪鍙�
+     */
+    private Long planUserId;
+
+    /**
+     * 缂栧埗閮ㄩ棬缂栧彿
+     */
+    private Long planDeptId;
+
+    /**
+     * 渚涘簲鍟嗙紪鍙凤紙鍙�夛級
+     */
+    private Long supplierId;
+
+    /**
+     * 鍚堣鏁伴噺
+     */
+    private BigDecimal totalCount;
+
+    /**
+     * 鏄惁宸茬敓鎴愰噰璐敵璇�
+     */
+    private Boolean generatedFlag;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+    /**
+     * BPM 娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanItemDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanItemDO.java
new file mode 100644
index 0000000..494d8d6
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchasePlanItemDO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
+
+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;
+import java.time.LocalDate;
+
+/**
+ * ERP 閲囪喘璁″垝鏄庣粏 DO
+ *
+ * @author xiaoyi
+ */
+@TableName("erp_purchase_plan_items")
+@KeySequence("erp_purchase_plan_items_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpPurchasePlanItemDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 閲囪喘璁″垝缂栧彿
+     */
+    private Long planId;
+
+    /**
+     * 浜у搧缂栧彿锛圡DM 鐗╂枡缂栧彿锛�
+     */
+    private Long productId;
+
+    /**
+     * 浜у搧鍗曚綅缂栧彿
+     */
+    private Long productUnitId;
+
+    /**
+     * 璁″垝閲囪喘鏁伴噺
+     */
+    private BigDecimal count;
+
+    /**
+     * 闇�姹傛棩鏈�
+     */
+    private LocalDate demandTime;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanItemMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanItemMapper.java
new file mode 100644
index 0000000..d3b2fb7
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanItemMapper.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘璁″垝鏄庣粏 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface ErpPurchasePlanItemMapper extends BaseMapperX<ErpPurchasePlanItemDO> {
+
+    default List<ErpPurchasePlanItemDO> selectListByPlanId(Long planId) {
+        return selectList(ErpPurchasePlanItemDO::getPlanId, planId);
+    }
+
+    default List<ErpPurchasePlanItemDO> selectListByPlanIds(Collection<Long> planIds) {
+        return selectList(ErpPurchasePlanItemDO::getPlanId, planIds);
+    }
+
+    default void deleteByPlanId(Long planId) {
+        delete(new LambdaQueryWrapperX<ErpPurchasePlanItemDO>().eq(ErpPurchasePlanItemDO::getPlanId, planId));
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanMapper.java
new file mode 100644
index 0000000..473577e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchasePlanMapper.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanPageReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘璁″垝 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface ErpPurchasePlanMapper extends BaseMapperX<ErpPurchasePlanDO> {
+
+    default ErpPurchasePlanDO selectByNo(String no) {
+        return selectOne(ErpPurchasePlanDO::getNo, no);
+    }
+
+    default List<ErpPurchasePlanDO> selectPlanListByIds(Collection<Long> ids) {
+        return selectList(ErpPurchasePlanDO::getId, ids);
+    }
+
+    default PageResult<ErpPurchasePlanDO> selectPage(ErpPurchasePlanPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<ErpPurchasePlanDO>()
+                .likeIfPresent(ErpPurchasePlanDO::getNo, pageReqVO.getNo())
+                .likeIfPresent(ErpPurchasePlanDO::getName, pageReqVO.getName())
+                .eqIfPresent(ErpPurchasePlanDO::getStatus, pageReqVO.getStatus())
+                .eqIfPresent(ErpPurchasePlanDO::getDemandSource, pageReqVO.getDemandSource())
+                .eqIfPresent(ErpPurchasePlanDO::getPlanUserId, pageReqVO.getPlanUserId())
+                .eqIfPresent(ErpPurchasePlanDO::getPlanDeptId, pageReqVO.getPlanDeptId())
+                .betweenIfPresent(ErpPurchasePlanDO::getPlanDate, pageReqVO.getPlanDate())
+                .orderByDesc(ErpPurchasePlanDO::getId));
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
index bdce48c..6a2c405 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
@@ -35,6 +35,11 @@
     public static final String PURCHASE_REQUEST_NO_PREFIX = "CGSQ";
 
     /**
+     * 閲囪喘璁″垝 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanDO}
+     */
+    public static final String PURCHASE_PLAN_NO_PREFIX = "CGJH";
+
+    /**
      * 閲囪喘鏉ョエ {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO}
      */
     public static final String PURCHASE_INVOICE_NO_PREFIX = "FPD";
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchasePlanStatusEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchasePlanStatusEnum.java
new file mode 100644
index 0000000..81b9458
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchasePlanStatusEnum.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.erp.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * ERP 閲囪喘璁″垝鐘舵�佹灇涓�
+ *
+ * @author xiaoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum ErpPurchasePlanStatusEnum implements ArrayValuable<Integer> {
+
+    DRAFT(0, "鏈彁浜�"),
+    PROCESS(10, "瀹℃壒涓�"),
+    APPROVE(20, "瀹℃牳閫氳繃"),
+    REJECT(30, "瀹℃牳涓嶉�氳繃"),
+    CANCEL(40, "宸插彇娑�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(ErpPurchasePlanStatusEnum::getStatus).toArray(Integer[]::new);
+
+    public static ErpPurchasePlanStatusEnum fromStatus(Integer status) {
+        if (status == null) {
+            return null;
+        }
+        return Arrays.stream(values()).filter(item -> item.getStatus().equals(status)).findFirst().orElse(null);
+    }
+
+    public static ErpPurchasePlanStatusEnum fromName(String name) {
+        if (name == null) {
+            return null;
+        }
+        return Arrays.stream(values()).filter(item -> item.getName().equals(name)).findFirst().orElse(null);
+    }
+
+    /**
+     * 鐘舵��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
index 958f6fc..44382f7 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -54,6 +54,19 @@
     ErrorCode PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_104_008, "閲囪喘鐢宠瀹℃壒鍒嗙被涓嶅瓨鍦紝璇峰厛鍦˙PM妯″潡鍒涘缓閲囪喘鐢宠瀹℃壒娴佺▼鍒嗙被");
     ErrorCode PURCHASE_REQUEST_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_104_009, "閲囪喘鐢宠瀹℃壒鍒嗙被涓嬫病鏈夊彲鐢ㄧ殑娴佺▼妯″瀷锛岃鍏堝垱寤哄苟鍙戝竷娴佺▼");
 
+    // ========== ERP 閲囪喘璁″垝锛�1-030-104-010锛� ==========
+    ErrorCode PURCHASE_PLAN_NOT_EXISTS = new ErrorCode(1_030_104_010, "閲囪喘璁″垝涓嶅瓨鍦�");
+    ErrorCode PURCHASE_PLAN_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_011, "閲囪喘璁″垝({})涓嶆槸鑽夌鐘舵�侊紝鏃犳硶鍒犻櫎");
+    ErrorCode PURCHASE_PLAN_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_012, "閲囪喘璁″垝({})涓嶆槸鑽夌鐘舵�侊紝鏃犳硶淇敼");
+    ErrorCode PURCHASE_PLAN_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_013, "鎻愪氦澶辫触锛屽彧鏈夎崏绋跨姸鎬佺殑閲囪喘璁″垝鎵嶈兘鎻愪氦");
+    ErrorCode PURCHASE_PLAN_NO_EXISTS = new ErrorCode(1_030_104_014, "鐢熸垚璁″垝鍗曞彿澶辫触锛岃閲嶆柊鎻愪氦");
+    ErrorCode PURCHASE_PLAN_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_104_015, "鏇存柊瀹℃壒鐘舵�佸け璐ワ紝鍙湁瀹℃壒涓殑閲囪喘璁″垝鎵嶈兘鏇存柊");
+    ErrorCode PURCHASE_PLAN_NOT_APPROVE = new ErrorCode(1_030_104_016, "閲囪喘璁″垝鏈鏍搁�氳繃锛屾棤娉曟搷浣�");
+    ErrorCode PURCHASE_PLAN_REQUEST_EXISTS = new ErrorCode(1_030_104_017, "閲囪喘璁″垝宸茬敓鎴愰噰璐敵璇凤紝璇峰嬁閲嶅鎿嶄綔");
+    ErrorCode PURCHASE_PLAN_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_104_018, "閲囪喘璁″垝瀹℃壒鍒嗙被涓嶅瓨鍦紝璇峰厛鍦˙PM妯″潡鍒涘缓閲囪喘璁″垝瀹℃壒娴佺▼鍒嗙被");
+    ErrorCode PURCHASE_PLAN_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_104_019, "閲囪喘璁″垝瀹℃壒鍒嗙被涓嬫病鏈夊彲鐢ㄧ殑娴佺▼妯″瀷锛岃鍏堝垱寤哄苟鍙戝竷娴佺▼");
+    ErrorCode PURCHASE_PLAN_ITEM_EMPTY = new ErrorCode(1_030_104_020, "閲囪喘璁″垝鏄庣粏涓嶈兘涓虹┖");
+
     // ========== ERP 閲囪喘鏉ョエ锛�1-030-105-000锛� ==========
     ErrorCode PURCHASE_INVOICE_NOT_EXISTS = new ErrorCode(1_030_105_000, "鏉ョエ涓嶅瓨鍦�");
     ErrorCode PURCHASE_INVOICE_NO_EXISTS = new ErrorCode(1_030_105_001, "鐢熸垚鏉ョエ缂栧彿澶辫触锛岃閲嶆柊鎻愪氦");
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/job/purchase/ErpPurchasePlanStockAlertJob.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/job/purchase/ErpPurchasePlanStockAlertJob.java
new file mode 100644
index 0000000..a862ea8
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/job/purchase/ErpPurchasePlanStockAlertJob.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.erp.job.purchase;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanDO;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchasePlanService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 搴撳瓨棰勮鑷姩鐢熸垚閲囪喘璁″垝 Job
+ *
+ * <p>瀹氭椂鎵弿鍚敤瀹夊叏搴撳瓨鐨勭墿鏂欙紝褰撳墠搴撳瓨浣庝簬瀹夊叏搴撳瓨涓嬮檺鏃惰嚜鍔ㄧ敓鎴愯崏绋块噰璐鍒掞紝
+ * 婊¤冻銆岀粨鍚堝簱瀛橀璀﹀埗瀹氶噰璐鍒掋�嶇殑鑷姩鍖栬瘔姹傘��
+ */
+@Component
+@Slf4j
+public class ErpPurchasePlanStockAlertJob implements JobHandler {
+
+    @Resource
+    private ErpPurchasePlanService purchasePlanService;
+
+    @Override
+    @TenantJob
+    public String execute(String param) {
+        try {
+            ErpPurchasePlanDO plan = purchasePlanService.generatePlanFromStockAlert("搴撳瓨棰勮鑷姩鐢熸垚", null);
+            return plan == null ? "搴撳瓨棰勮鑷姩鐢熸垚閲囪喘璁″垝锛氭棤缂洪噺鐗╂枡锛岃烦杩�"
+                    : String.format("搴撳瓨棰勮鑷姩鐢熸垚閲囪喘璁″垝鎴愬姛锛岃鍒掔紪鍙凤細%s", plan.getId());
+        } catch (Exception e) {
+            log.warn("[execute][搴撳瓨棰勮鑷姩鐢熸垚閲囪喘璁″垝璺宠繃/澶辫触锛屽師鍥狅細{}]", e.getMessage());
+            return "搴撳瓨棰勮鑷姩鐢熸垚閲囪喘璁″垝璺宠繃锛�" + e.getMessage();
+        }
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanService.java
new file mode 100644
index 0000000..954d1d4
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanService.java
@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.erp.service.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanItemDO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ERP 閲囪喘璁″垝 Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface ErpPurchasePlanService {
+
+    /**
+     * 鍒涘缓閲囪喘璁″垝
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createPurchasePlan(ErpPurchasePlanSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊閲囪喘璁″垝
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updatePurchasePlan(ErpPurchasePlanSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎閲囪喘璁″垝
+     *
+     * @param ids 缂栧彿鏁扮粍
+     */
+    void deletePurchasePlan(Collection<Long> ids);
+
+    /**
+     * 鑾峰緱閲囪喘璁″垝
+     *
+     * @param id 缂栧彿
+     * @return 閲囪喘璁″垝
+     */
+    ErpPurchasePlanDO getPurchasePlan(Long id);
+
+    /**
+     * 鑾峰緱閲囪喘璁″垝鏄庣粏鍒楄〃
+     *
+     * @param planId 閲囪喘璁″垝缂栧彿
+     * @return 鏄庣粏鍒楄〃
+     */
+    List<ErpPurchasePlanItemDO> getPurchasePlanItemListByPlanId(Long planId);
+
+    /**
+     * 鎸夐噰璐鍒掔紪鍙烽泦鍚堣幏寰楁槑缁嗗垪琛�
+     *
+     * @param planIds 閲囪喘璁″垝缂栧彿闆嗗悎
+     * @return 鏄庣粏鍒楄〃
+     */
+    List<ErpPurchasePlanItemDO> getPurchasePlanItemListByPlanIds(Collection<Long> planIds);
+
+    /**
+     * 鑾峰緱閲囪喘璁″垝鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 閲囪喘璁″垝鍒嗛〉
+     */
+    PageResult<ErpPurchasePlanDO> getPurchasePlanPage(ErpPurchasePlanPageReqVO pageReqVO);
+
+    /**
+     * 鎻愪氦閲囪喘璁″垝瀹℃壒
+     *
+     * @param id                   缂栧彿
+     * @param processDefinitionKey BPM 娴佺▼瀹氫箟 Key
+     * @param userId               鐢ㄦ埛缂栧彿
+     */
+    void submitPurchasePlan(Long id, String processDefinitionKey, Long userId);
+
+    /**
+     * 鑾峰彇閲囪喘璁″垝瀹℃壒娴佺▼鍒楄〃
+     *
+     * @return 娴佺▼瀹氫箟鍒楄〃
+     */
+    List<Map<String, Object>> getPurchasePlanApproveProcessDefinitionList();
+
+    /**
+     * BPM 瀹℃壒缁撴灉鍥炶皟鏇存柊閲囪喘璁″垝鐘舵��
+     *
+     * @param id        閲囪喘璁″垝缂栧彿
+     * @param bpmResult BPM 瀹℃壒缁撴灉
+     */
+    void updatePurchasePlanAuditStatus(Long id, Integer bpmResult);
+
+    /**
+     * 鏍规嵁搴撳瓨棰勮鑷姩鐢熸垚閲囪喘璁″垝锛堣崏绋匡級
+     *
+     * <p>閬嶅巻鍚敤瀹夊叏搴撳瓨鐨勭墿鏂欙紝褰撳墠搴撳瓨浣庝簬瀹夊叏搴撳瓨涓嬮檺鏃讹紝
+     * 鎸夌己閲忕敓鎴愯鍒掓槑缁嗐�傝繑鍥炶崏绋胯鍒掞紝鐢辩紪鍒朵汉鏍稿鍚庢彁浜ゅ鎵广��
+     *
+     * @param name       璁″垝鍚嶇О
+     * @param supplierId 渚涘簲鍟嗙紪鍙凤紙鍙�夛級
+     * @return 鐢熸垚鐨勯噰璐鍒�
+     */
+    ErpPurchasePlanDO generatePlanFromStockAlert(String name, Long supplierId);
+
+    /**
+     * 瀹℃壒閫氳繃鍚庣敓鎴愰噰璐敵璇�
+     *
+     * @param id 閲囪喘璁″垝缂栧彿
+     * @return 鐢熸垚鐨勯噰璐敵璇风紪鍙峰垪琛�
+     */
+    List<Long> generatePurchaseRequests(Long id);
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanServiceImpl.java
new file mode 100644
index 0000000..c049142
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchasePlanServiceImpl.java
@@ -0,0 +1,431 @@
+package cn.iocoder.yudao.module.erp.service.purchase;
+
+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.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.plan.ErpPurchasePlanSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchasePlanItemDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchasePlanItemMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchasePlanMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
+import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchasePlanStatusEnum;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
+import cn.iocoder.yudao.module.mes.api.item.MesMdItemApi;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSafetyStockRespDTO;
+import cn.iocoder.yudao.module.mes.api.wms.MesWmStockApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
+
+/**
+ * ERP 閲囪喘璁″垝 Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+@Slf4j
+public class ErpPurchasePlanServiceImpl implements ErpPurchasePlanService {
+
+    /**
+     * BPM 閲囪喘璁″垝瀹℃壒鍒嗙被缂栫爜
+     */
+    private static final String PURCHASE_PLAN_APPROVE_CATEGORY_CODE = "purchase_plan_approve";
+
+    /**
+     * 闇�姹傛潵婧愶細搴撳瓨棰勮
+     */
+    public static final Integer DEMAND_SOURCE_STOCK_ALERT = 1;
+    /**
+     * 闇�姹傛潵婧愶細杩愮淮/鐢熶骇璁″垝
+     */
+    public static final Integer DEMAND_SOURCE_PLAN = 2;
+
+    @Resource
+    private ErpPurchasePlanMapper purchasePlanMapper;
+    @Resource
+    private ErpPurchasePlanItemMapper purchasePlanItemMapper;
+
+    @Resource
+    private ErpPurchaseRequestMapper purchaseRequestMapper;
+    @Resource
+    private ErpPurchaseRequestItemMapper purchaseRequestItemMapper;
+
+    @Resource
+    private ErpNoRedisDAO noRedisDAO;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Resource
+    private MesMdItemApi mesMdItemApi;
+    @Resource
+    private MesWmStockApi mesWmStockApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createPurchasePlan(ErpPurchasePlanSaveReqVO createReqVO) {
+        // 1. 鏍¢獙鏄庣粏
+        List<ErpPurchasePlanItemDO> planItems = convertList(createReqVO.getItems(), item ->
+                BeanUtils.toBean(item, ErpPurchasePlanItemDO.class));
+        if (CollUtil.isEmpty(planItems)) {
+            throw exception(PURCHASE_PLAN_ITEM_EMPTY);
+        }
+        // 2. 鐢熸垚璁″垝鍗曞彿
+        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_PLAN_NO_PREFIX);
+        if (purchasePlanMapper.selectByNo(no) != null) {
+            throw exception(PURCHASE_PLAN_NO_EXISTS);
+        }
+        // 3. 鑾峰彇褰撳墠鐢ㄦ埛淇℃伅
+        Long userId = Long.parseLong(getCurrentUserId());
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        Long deptId = user != null ? user.getDeptId() : null;
+        // 4. 鎻掑叆閲囪喘璁″垝
+        ErpPurchasePlanDO plan = BeanUtils.toBean(createReqVO, ErpPurchasePlanDO.class, req -> req
+                .setNo(no)
+                .setStatus(ErpPurchasePlanStatusEnum.DRAFT.getStatus())
+                .setPlanUserId(userId)
+                .setPlanDeptId(deptId)
+                .setGeneratedFlag(false)
+                .setTotalCount(planItems.stream()
+                        .map(ErpPurchasePlanItemDO::getCount).filter(Objects::nonNull)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add)));
+        purchasePlanMapper.insert(plan);
+        // 5. 鎻掑叆鏄庣粏
+        purchasePlanItemMapper.insertBatch(convertList(planItems, item -> item.setPlanId(plan.getId())));
+        return plan.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePurchasePlan(ErpPurchasePlanSaveReqVO updateReqVO) {
+        // 1.1 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        ErpPurchasePlanDO oldPlan = validatePurchasePlanCanModify(updateReqVO.getId());
+        // 1.2 鏍¢獙鏄庣粏
+        List<ErpPurchasePlanItemDO> planItems = convertList(updateReqVO.getItems(), item ->
+                BeanUtils.toBean(item, ErpPurchasePlanItemDO.class));
+        if (CollUtil.isEmpty(planItems)) {
+            throw exception(PURCHASE_PLAN_ITEM_EMPTY);
+        }
+        // 2. 鏇存柊涓昏〃
+        ErpPurchasePlanDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchasePlanDO.class, req -> req
+                .setNo(oldPlan.getNo())
+                .setStatus(oldPlan.getStatus())
+                .setPlanUserId(oldPlan.getPlanUserId())
+                .setPlanDeptId(oldPlan.getPlanDeptId())
+                .setGeneratedFlag(oldPlan.getGeneratedFlag())
+                .setTotalCount(planItems.stream()
+                        .map(ErpPurchasePlanItemDO::getCount).filter(Objects::nonNull)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add)));
+        purchasePlanMapper.updateById(updateObj);
+        // 3. 鏇存柊鏄庣粏锛氬厛鍒犲悗鎻�
+        purchasePlanItemMapper.deleteByPlanId(updateReqVO.getId());
+        purchasePlanItemMapper.insertBatch(convertList(planItems, item -> item.setPlanId(updateReqVO.getId())));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deletePurchasePlan(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+            validatePurchasePlanCanModify(id);
+            // 2. 鍒犻櫎鏄庣粏
+            purchasePlanItemMapper.deleteByPlanId(id);
+            // 3. 鍒犻櫎涓昏〃
+            purchasePlanMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public ErpPurchasePlanDO getPurchasePlan(Long id) {
+        return purchasePlanMapper.selectById(id);
+    }
+
+    @Override
+    public List<ErpPurchasePlanItemDO> getPurchasePlanItemListByPlanId(Long planId) {
+        return purchasePlanItemMapper.selectListByPlanId(planId);
+    }
+
+    @Override
+    public List<ErpPurchasePlanItemDO> getPurchasePlanItemListByPlanIds(Collection<Long> planIds) {
+        return purchasePlanItemMapper.selectListByPlanIds(planIds);
+    }
+
+    @Override
+    public PageResult<ErpPurchasePlanDO> getPurchasePlanPage(ErpPurchasePlanPageReqVO pageReqVO) {
+        return purchasePlanMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitPurchasePlan(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        ErpPurchasePlanDO plan = validatePurchasePlanCanModify(id);
+        // 2. 鏍¢獙鏄惁鏈夋槑缁�
+        List<ErpPurchasePlanItemDO> items = purchasePlanItemMapper.selectListByPlanId(id);
+        if (CollUtil.isEmpty(items)) {
+            throw exception(PURCHASE_PLAN_ITEM_EMPTY);
+        }
+        // 3. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        String processInstanceId = processInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+        // 4. 鏇存柊閲囪喘璁″垝鐘舵�佷负瀹℃壒涓�
+        purchasePlanMapper.updateById(new ErpPurchasePlanDO()
+                .setId(id)
+                .setStatus(ErpPurchasePlanStatusEnum.PROCESS.getStatus())
+                .setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    public List<Map<String, Object>> getPurchasePlanApproveProcessDefinitionList() {
+        // 1. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+        validatePurchasePlanApproveCategoryAndProcessDefinition();
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(PURCHASE_PLAN_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            return Collections.emptyList();
+        }
+        // 3. 閬嶅巻鑾峰彇娴佺▼瀹氫箟璇︽儏锛岃繃婊ゆ縺娲荤姸鎬侊紝淇濈暀鏈�鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+            if (pd == null || pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+        // 4. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePurchasePlanAuditStatus(Long id, Integer bpmResult) {
+        // 1. 鏍¢獙瀛樺湪
+        ErpPurchasePlanDO plan = validatePurchasePlanExists(id);
+        if (!ErpPurchasePlanStatusEnum.PROCESS.getStatus().equals(plan.getStatus())) {
+            log.warn("[updatePurchasePlanAuditStatus] 閲囪喘璁″垝({}) 涓嶅浜庡鎵逛腑鐘舵��", id);
+            return;
+        }
+        // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵��
+        Integer newStatus = convertBpmResultToStatus(bpmResult);
+        if (newStatus != null) {
+            purchasePlanMapper.updateById(new ErpPurchasePlanDO().setId(id).setStatus(newStatus));
+        }
+        // 3. 瀹℃壒閫氳繃涓旀湭鐢熸垚鐢宠锛岃嚜鍔ㄧ敓鎴愰噰璐敵璇�
+        if (ErpPurchasePlanStatusEnum.APPROVE.getStatus().equals(newStatus)
+                && !Boolean.TRUE.equals(plan.getGeneratedFlag())) {
+            try {
+                List<Long> requestIds = doGeneratePurchaseRequests(plan);
+                log.info("[updatePurchasePlanAuditStatus] 閲囪喘璁″垝({}) 鑷姩鐢熸垚閲囪喘鐢宠({})", id, requestIds);
+            } catch (Exception e) {
+                log.error("[updatePurchasePlanAuditStatus] 閲囪喘璁″垝({}) 鑷姩鐢熸垚閲囪喘鐢宠澶辫触", id, e);
+                // 涓嶆姏鍑哄紓甯革紝閬垮厤褰卞搷鐘舵�佹洿鏂�
+            }
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ErpPurchasePlanDO generatePlanFromStockAlert(String name, Long supplierId) {
+        // 1. 鑾峰彇鍚敤瀹夊叏搴撳瓨鐨勭墿鏂�
+        List<MesMdItemSafetyStockRespDTO> safetyStockItems = mesMdItemApi.getSafetyStockItemList();
+        if (CollUtil.isEmpty(safetyStockItems)) {
+            throw exception(PURCHASE_PLAN_ITEM_EMPTY);
+        }
+        // 2. 璁$畻缂洪噺锛岀敓鎴愯鍒掓槑缁�
+        List<ErpPurchasePlanSaveReqVO.Item> items = new ArrayList<>();
+        for (MesMdItemSafetyStockRespDTO item : safetyStockItems) {
+            if (item.getMinStock() == null) {
+                continue;
+            }
+            BigDecimal stock = mesWmStockApi.getStockQuantity(item.getId());
+            BigDecimal shortage = item.getMinStock().subtract(stock == null ? BigDecimal.ZERO : stock);
+            if (shortage.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            ErpPurchasePlanSaveReqVO.Item planItem = new ErpPurchasePlanSaveReqVO.Item();
+            planItem.setProductId(item.getMdmItemId());
+            planItem.setProductUnitId(item.getUnitMeasureId());
+            planItem.setCount(shortage);
+            items.add(planItem);
+        }
+        if (CollUtil.isEmpty(items)) {
+            throw exception(PURCHASE_PLAN_ITEM_EMPTY);
+        }
+        // 3. 鍒涘缓鑽夌閲囪喘璁″垝
+        ErpPurchasePlanSaveReqVO createReqVO = new ErpPurchasePlanSaveReqVO();
+        createReqVO.setName(name != null ? name : "搴撳瓨棰勮閲囪喘璁″垝");
+        createReqVO.setDemandSource(DEMAND_SOURCE_STOCK_ALERT);
+        createReqVO.setPlanDate(LocalDateTime.now());
+        createReqVO.setSupplierId(supplierId);
+        createReqVO.setItems(items);
+        Long planId = createPurchasePlan(createReqVO);
+        return purchasePlanMapper.selectById(planId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public List<Long> generatePurchaseRequests(Long id) {
+        // 1. 鏍¢獙瀛樺湪 + 瀹℃牳閫氳繃鐘舵��
+        ErpPurchasePlanDO plan = validatePurchasePlanExists(id);
+        if (!ErpPurchasePlanStatusEnum.APPROVE.getStatus().equals(plan.getStatus())) {
+            throw exception(PURCHASE_PLAN_NOT_APPROVE);
+        }
+        // 2. 鏍¢獙鏄惁宸茬敓鎴愮敵璇�
+        if (Boolean.TRUE.equals(plan.getGeneratedFlag())) {
+            throw exception(PURCHASE_PLAN_REQUEST_EXISTS);
+        }
+        // 3. 鐢熸垚閲囪喘鐢宠
+        return doGeneratePurchaseRequests(plan);
+    }
+
+    /**
+     * 鎵ц鐢熸垚閲囪喘鐢宠锛堜竴涓鍒掔敓鎴愪竴寮犵敵璇峰崟锛�
+     *
+     * @param plan 閲囪喘璁″垝
+     * @return 閲囪喘鐢宠缂栧彿鍒楄〃
+     */
+    private List<Long> doGeneratePurchaseRequests(ErpPurchasePlanDO plan) {
+        // 1. 鑾峰彇璁″垝鏄庣粏
+        List<ErpPurchasePlanItemDO> planItems = purchasePlanItemMapper.selectListByPlanId(plan.getId());
+        if (CollUtil.isEmpty(planItems)) {
+            throw exception(PURCHASE_PLAN_ITEM_EMPTY);
+        }
+        // 2. 鐢熸垚鐢宠鍗曞彿
+        String requestNo = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_REQUEST_NO_PREFIX);
+        if (purchaseRequestMapper.selectByNo(requestNo) != null) {
+            throw exception(PURCHASE_REQUEST_NO_EXISTS);
+        }
+        // 3. 鍒涘缓閲囪喘鐢宠
+        ErpPurchaseRequestDO request = new ErpPurchaseRequestDO();
+        request.setNo(requestNo);
+        request.setStatus(ErpPurchaseRequestStatusEnum.DRAFT.getStatus());
+        request.setRequestUserId(plan.getPlanUserId());
+        request.setRequestDeptId(plan.getPlanDeptId());
+        request.setRequestTime(LocalDateTime.now());
+        request.setSupplierId(plan.getSupplierId());
+        request.setTotalCount(planItems.stream()
+                .map(ErpPurchasePlanItemDO::getCount).filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add));
+        request.setTotalPrice(BigDecimal.ZERO);
+        request.setTotalProductPrice(BigDecimal.ZERO);
+        request.setTotalTaxPrice(BigDecimal.ZERO);
+        request.setRemark(plan.getRemark());
+        request.setInStatus(0);
+        purchaseRequestMapper.insert(request);
+        // 4. 鍒涘缓閲囪喘鐢宠鏄庣粏
+        List<ErpPurchaseRequestItemDO> requestItems = convertList(planItems, item -> {
+            ErpPurchaseRequestItemDO requestItem = new ErpPurchaseRequestItemDO();
+            requestItem.setRequestId(request.getId());
+            requestItem.setProductId(item.getProductId());
+            requestItem.setProductUnitId(item.getProductUnitId());
+            requestItem.setCount(item.getCount());
+            requestItem.setDemandTime(item.getDemandTime());
+            requestItem.setRemark(item.getRemark());
+            requestItem.setInCount(BigDecimal.ZERO);
+            return requestItem;
+        });
+        purchaseRequestItemMapper.insertBatch(requestItems);
+        // 5. 鍥炲啓璁″垝宸茬敓鎴愭爣璁�
+        purchasePlanMapper.updateById(new ErpPurchasePlanDO()
+                .setId(plan.getId()).setGeneratedFlag(true));
+        return List.of(request.getId());
+    }
+
+    private Integer convertBpmResultToStatus(Integer bpmResult) {
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            return ErpPurchasePlanStatusEnum.APPROVE.getStatus();
+        }
+        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            return ErpPurchasePlanStatusEnum.REJECT.getStatus();
+        }
+        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            return ErpPurchasePlanStatusEnum.CANCEL.getStatus();
+        }
+        return null;
+    }
+
+    private void validatePurchasePlanApproveCategoryAndProcessDefinition() {
+        List<cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO> categories =
+                bpmCategoryService.getCategoryListByCode(Collections.singletonList(PURCHASE_PLAN_APPROVE_CATEGORY_CODE));
+        if (CollUtil.isEmpty(categories)) {
+            throw exception(PURCHASE_PLAN_BPM_CATEGORY_NOT_EXISTS);
+        }
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(PURCHASE_PLAN_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            throw exception(PURCHASE_PLAN_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+    private ErpPurchasePlanDO validatePurchasePlanExists(Long id) {
+        ErpPurchasePlanDO plan = purchasePlanMapper.selectById(id);
+        if (plan == null) {
+            throw exception(PURCHASE_PLAN_NOT_EXISTS);
+        }
+        return plan;
+    }
+
+    private ErpPurchasePlanDO validatePurchasePlanCanModify(Long id) {
+        ErpPurchasePlanDO plan = validatePurchasePlanExists(id);
+        if (!ErpPurchasePlanStatusEnum.DRAFT.getStatus().equals(plan.getStatus())) {
+            throw exception(PURCHASE_PLAN_UPDATE_FAIL_NOT_DRAFT, plan.getNo());
+        }
+        return plan;
+    }
+
+    private String getCurrentUserId() {
+        return cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId().toString();
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchasePlanStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchasePlanStatusListener.java
new file mode 100644
index 0000000..b34e86d
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchasePlanStatusListener.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.erp.service.purchase.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchasePlanService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閲囪喘璁″垝瀹℃壒鐘舵�佺洃鍚櫒
+ *
+ * 鐩戝惉 BPM 娴佺▼瀹炰緥鐘舵�佸彉鏇翠簨浠讹紝鏇存柊閲囪喘璁″垝鐨勫鎵圭姸鎬�
+ *
+ * @author xiaoyi
+ */
+@Component
+@Slf4j
+public class ErpPurchasePlanStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    /**
+     * 閲囪喘璁″垝瀹℃壒娴佺▼瀹氫箟 Key
+     */
+    private static final String PROCESS_DEFINITION_KEY = "purchase_plan_approve";
+
+    @Resource
+    private ErpPurchasePlanService purchasePlanService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Long planId;
+        try {
+            planId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勯噰璐鍒掔紪鍙�", event.getBusinessKey());
+            return;
+        }
+
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宲lanId={}, status={}", planId, event.getStatus());
+
+        try {
+            purchasePlanService.updatePurchasePlanAuditStatus(planId, event.getStatus());
+            log.info("[onEvent] 閲囪喘璁″垝({}) 瀹℃壒鐘舵�佹洿鏂板畬鎴�", planId);
+        } catch (Exception e) {
+            log.error("[onEvent] 鏇存柊閲囪喘璁″垝瀹℃壒鐘舵�佸け璐ワ紝planId={}", planId, e);
+        }
+    }
+
+}
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
index dd2eb29..94c57cd 100644
--- a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -26,6 +26,8 @@
     ErrorCode EMPLOYEE_CONTRACT_END_DATE_REQUIRE_FOR_FIXED = new ErrorCode(1_030_000_013, "鍥哄畾鏈熼檺鍚堝悓蹇呴』濉啓缁撴潫鏃ユ湡");
     ErrorCode EMPLOYEE_CONTRACT_DATE_INVALID = new ErrorCode(1_030_000_014, "鍚堝悓鏃ユ湡涓嶅悎娉曪紝寮�濮嬫棩鏈熶笉鑳芥櫄浜庣粨鏉熸棩鏈�");
     ErrorCode EMPLOYEE_CONTRACT_DATE_OVERLAP = new ErrorCode(1_030_000_015, "璇ュ憳宸ュ湪鎵�閫夋椂闂存宸插瓨鍦ㄦ湭瑙i櫎/鏈粓姝㈢殑鍚堝悓锛岃鍏堣В闄�/缁堟鍚庡啀绛捐");
+    ErrorCode HRM_CERTIFICATE_NOT_EXISTS = new ErrorCode(1_030_000_016, "璇佷功璧勮川涓嶅瓨鍦�");
+    ErrorCode HRM_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_000_017, "鍛樺伐涓嶅瓨鍦�");
 
     // ========== 璇峰亣鐢宠 1-030-001-000 ==========
     ErrorCode LEAVE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_001_000, "璇峰亣鐢宠涓嶅瓨鍦�");
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java
new file mode 100644
index 0000000..3854488
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java
@@ -0,0 +1,133 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.certificate;
+
+import cn.hutool.core.collection.CollUtil;
+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.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo.HrmCertificatePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo.HrmCertificateRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo.HrmCertificateSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.service.certificate.HrmCertificateService;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+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.time.LocalDate;
+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.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 璇佷功璧勮川")
+@RestController
+@RequestMapping("/hrm/certificate")
+@Validated
+public class HrmCertificateController {
+
+    @Resource
+    private HrmCertificateService certificateService;
+    @Resource
+    private HrmEmployeeService employeeService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璇佷功璧勮川")
+    @PreAuthorize("@ss.hasPermission('hrm:certificate:create')")
+    public CommonResult<Long> createCertificate(@Valid @RequestBody HrmCertificateSaveReqVO createReqVO) {
+        return success(certificateService.createCertificate(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊璇佷功璧勮川")
+    @PreAuthorize("@ss.hasPermission('hrm:certificate:update')")
+    public CommonResult<Boolean> updateCertificate(@Valid @RequestBody HrmCertificateSaveReqVO updateReqVO) {
+        certificateService.updateCertificate(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璇佷功璧勮川")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:certificate:delete')")
+    public CommonResult<Boolean> deleteCertificate(@RequestParam("ids") List<Long> ids) {
+        certificateService.deleteCertificate(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璇佷功璧勮川")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:certificate:query')")
+    public CommonResult<HrmCertificateRespVO> getCertificate(@RequestParam("id") Long id) {
+        HrmCertificateDO certificate = certificateService.getCertificate(id);
+        return success(certificate != null ? buildRespVO(certificate) : null);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璇佷功璧勮川鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('hrm:certificate:query')")
+    public CommonResult<PageResult<HrmCertificateRespVO>> getCertificatePage(@Valid HrmCertificatePageReqVO pageReqVO) {
+        PageResult<HrmCertificateDO> pageResult = certificateService.getCertificatePage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        Map<Long, HrmEmployeeDO> employeeMap = employeeService.getEmployeeList(
+                convertSet(pageResult.getList(), HrmCertificateDO::getEmployeeId)).stream()
+                .collect(java.util.stream.Collectors.toMap(HrmEmployeeDO::getId, e -> e, (a, b) -> a));
+        return success(BeanUtils.toBean(pageResult, HrmCertificateRespVO.class,
+                vo -> buildRespVOItem(vo, employeeMap.get(vo.getEmployeeId()))));
+    }
+
+    private HrmCertificateRespVO buildRespVO(HrmCertificateDO certificate) {
+        HrmCertificateRespVO vo = BeanUtils.toBean(certificate, HrmCertificateRespVO.class);
+        HrmEmployeeDO employee = employeeService.getEmployee(certificate.getEmployeeId());
+        buildRespVOItem(vo, employee);
+        return vo;
+    }
+
+    private void buildRespVOItem(HrmCertificateRespVO vo, HrmEmployeeDO employee) {
+        if (employee != null) {
+            vo.setEmployeeName(employee.getName());
+        }
+        vo.setCertTypeName(convertCertTypeName(vo.getCertType()));
+        vo.setExpireStatus(calcExpireStatus(vo.getExpireDate()));
+    }
+
+    private String convertCertTypeName(Integer certType) {
+        return switch (certType == null ? 0 : certType) {
+            case 1 -> "鐗圭浣滀笟璇佷功";
+            case 2 -> "浠庝笟璧勮川";
+            case 3 -> "鎶�鑳界瓑绾ц瘉涔�";
+            case 4 -> "瀹夊叏鍩硅鍚堟牸璇�";
+            default -> "鍏朵粬";
+        };
+    }
+
+    /**
+     * 鍒版湡鐘舵�侊細0=鏈夋晥锛�1=鍗冲皢鍒版湡锛�30澶╁唴锛夛紝2=宸茶繃鏈�
+     */
+    private Integer calcExpireStatus(LocalDate expireDate) {
+        if (expireDate == null) {
+            return 0;
+        }
+        LocalDate today = LocalDate.now();
+        if (expireDate.isBefore(today)) {
+            return 2;
+        }
+        if (expireDate.isBefore(today.plusDays(30))) {
+            return 1;
+        }
+        return 0;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java
new file mode 100644
index 0000000..c816d40
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.certificate.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 = "绠$悊鍚庡彴 - HRM 璇佷功璧勮川鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmCertificatePageReqVO extends PageParam {
+
+    @Schema(description = "鍛樺伐缂栧彿", example = "1")
+    private Long employeeId;
+
+    @Schema(description = "璇佷功绫诲瀷锛�1=鐗圭浣滀笟璇佷功锛�2=浠庝笟璧勮川锛�3=鎶�鑳界瓑绾ц瘉涔︼紝4=瀹夊叏鍩硅鍚堟牸璇侊級", example = "1")
+    private Integer certType;
+
+    @Schema(description = "璇佷功鍚嶇О", example = "楂樺帇鐢靛伐鐗圭浣滀笟鎿嶄綔璇�")
+    private String certName;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateRespVO.java
new file mode 100644
index 0000000..0723d69
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 璇佷功璧勮川 Response VO")
+@Data
+public class HrmCertificateRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍛樺伐缂栧彿", example = "1")
+    private Long employeeId;
+
+    @Schema(description = "鍛樺伐濮撳悕", example = "寮犱笁")
+    private String employeeName;
+
+    @Schema(description = "璇佷功绫诲瀷", example = "1")
+    private Integer certType;
+
+    @Schema(description = "璇佷功绫诲瀷鍚嶇О", example = "鐗圭浣滀笟璇佷功")
+    private String certTypeName;
+
+    @Schema(description = "璇佷功鍚嶇О", example = "楂樺帇鐢靛伐鐗圭浣滀笟鎿嶄綔璇�")
+    private String certName;
+
+    @Schema(description = "璇佷功缂栧彿", example = "T2019-0001")
+    private String certNo;
+
+    @Schema(description = "鍙戣瘉鏈烘瀯", example = "鍥藉鑳芥簮灞�")
+    private String issueOrg;
+
+    @Schema(description = "鍙戣瘉鏃ユ湡")
+    private LocalDate issueDate;
+
+    @Schema(description = "鏈夋晥鏈熻嚦")
+    private LocalDate expireDate;
+
+    @Schema(description = "鍒版湡鐘舵�侊紙0=鏈夋晥锛�1=鍗冲皢鍒版湡锛�2=宸茶繃鏈燂級")
+    private Integer expireStatus;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateSaveReqVO.java
new file mode 100644
index 0000000..136d80e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificateSaveReqVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 璇佷功璧勮川鏂板/淇敼 Request VO")
+@Data
+public class HrmCertificateSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鍛樺伐缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍛樺伐涓嶈兘涓虹┖")
+    private Long employeeId;
+
+    @Schema(description = "璇佷功绫诲瀷锛�1=鐗圭浣滀笟璇佷功锛�2=浠庝笟璧勮川锛�3=鎶�鑳界瓑绾ц瘉涔︼紝4=瀹夊叏鍩硅鍚堟牸璇侊級", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璇佷功绫诲瀷涓嶈兘涓虹┖")
+    private Integer certType;
+
+    @Schema(description = "璇佷功鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "楂樺帇鐢靛伐鐗圭浣滀笟鎿嶄綔璇�")
+    @NotEmpty(message = "璇佷功鍚嶇О涓嶈兘涓虹┖")
+    private String certName;
+
+    @Schema(description = "璇佷功缂栧彿", example = "T2019-0001")
+    private String certNo;
+
+    @Schema(description = "鍙戣瘉鏈烘瀯", example = "鍥藉鑳芥簮灞�")
+    private String issueOrg;
+
+    @Schema(description = "鍙戣瘉鏃ユ湡")
+    private LocalDate issueDate;
+
+    @Schema(description = "鏈夋晥鏈熻嚦", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "鏈夋晥鏈熻嚦涓嶈兘涓虹┖")
+    private LocalDate expireDate;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/certificate/HrmCertificateDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/certificate/HrmCertificateDO.java
new file mode 100644
index 0000000..3025735
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/certificate/HrmCertificateDO.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.certificate;
+
+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.LocalDate;
+
+/**
+ * HRM 璇佷功璧勮川 DO
+ *
+ * <p>绠$悊瀹夊叏鍩硅銆佹妧鑳藉煿璁�佺壒绉嶄綔涓氳瘉涔︺�佷粠涓氳祫璐紝婊¤冻鐢靛姏琛屼笟鍚堣绠$悊瑕佹眰
+ *
+ * @author xiaoyi
+ */
+@TableName("hrm_certificate")
+@KeySequence("hrm_certificate_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmCertificateDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鍛樺伐缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO#getId()}
+     */
+    private Long employeeId;
+
+    /**
+     * 璇佷功绫诲瀷
+     *
+     * 1: 鐗圭浣滀笟璇佷功
+     * 2: 浠庝笟璧勮川
+     * 3: 鎶�鑳界瓑绾ц瘉涔�
+     * 4: 瀹夊叏鍩硅鍚堟牸璇�
+     */
+    private Integer certType;
+
+    /**
+     * 璇佷功鍚嶇О
+     */
+    private String certName;
+
+    /**
+     * 璇佷功缂栧彿
+     */
+    private String certNo;
+
+    /**
+     * 鍙戣瘉鏈烘瀯
+     */
+    private String issueOrg;
+
+    /**
+     * 鍙戣瘉鏃ユ湡
+     */
+    private LocalDate issueDate;
+
+    /**
+     * 鏈夋晥鏈熻嚦
+     */
+    private LocalDate expireDate;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java
new file mode 100644
index 0000000..2f6adbe
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.certificate;
+
+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.hrm.controller.admin.certificate.vo.HrmCertificatePageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * HRM 璇佷功璧勮川 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface HrmCertificateMapper extends BaseMapperX<HrmCertificateDO> {
+
+    default PageResult<HrmCertificateDO> selectPage(HrmCertificatePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmCertificateDO>()
+                .eqIfPresent(HrmCertificateDO::getEmployeeId, reqVO.getEmployeeId())
+                .eqIfPresent(HrmCertificateDO::getCertType, reqVO.getCertType())
+                .likeIfPresent(HrmCertificateDO::getCertName, reqVO.getCertName())
+                .orderByDesc(HrmCertificateDO::getId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateService.java
new file mode 100644
index 0000000..6155c2c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateService.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.service.certificate;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo.HrmCertificatePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo.HrmCertificateSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
+
+import java.util.Collection;
+
+/**
+ * HRM 璇佷功璧勮川 Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface HrmCertificateService {
+
+    Long createCertificate(HrmCertificateSaveReqVO createReqVO);
+
+    void updateCertificate(HrmCertificateSaveReqVO updateReqVO);
+
+    void deleteCertificate(Collection<Long> ids);
+
+    HrmCertificateDO getCertificate(Long id);
+
+    PageResult<HrmCertificateDO> getCertificatePage(HrmCertificatePageReqVO pageReqVO);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateServiceImpl.java
new file mode 100644
index 0000000..3ca256f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/certificate/HrmCertificateServiceImpl.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.hrm.service.certificate;
+
+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.hrm.controller.admin.certificate.vo.HrmCertificatePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.certificate.vo.HrmCertificateSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.certificate.HrmCertificateMapper;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.HRM_CERTIFICATE_NOT_EXISTS;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.HRM_EMPLOYEE_NOT_EXISTS;
+
+/**
+ * HRM 璇佷功璧勮川 Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+public class HrmCertificateServiceImpl implements HrmCertificateService {
+
+    @Resource
+    private HrmCertificateMapper certificateMapper;
+    @Resource
+    private HrmEmployeeService employeeService;
+
+    @Override
+    public Long createCertificate(HrmCertificateSaveReqVO createReqVO) {
+        validateEmployee(createReqVO.getEmployeeId());
+        HrmCertificateDO certificate = BeanUtils.toBean(createReqVO, HrmCertificateDO.class);
+        certificateMapper.insert(certificate);
+        return certificate.getId();
+    }
+
+    @Override
+    public void updateCertificate(HrmCertificateSaveReqVO updateReqVO) {
+        validateCertificateExists(updateReqVO.getId());
+        validateEmployee(updateReqVO.getEmployeeId());
+        certificateMapper.updateById(BeanUtils.toBean(updateReqVO, HrmCertificateDO.class));
+    }
+
+    @Override
+    public void deleteCertificate(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validateCertificateExists(id);
+            certificateMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public HrmCertificateDO getCertificate(Long id) {
+        return certificateMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<HrmCertificateDO> getCertificatePage(HrmCertificatePageReqVO pageReqVO) {
+        return certificateMapper.selectPage(pageReqVO);
+    }
+
+    private void validateEmployee(Long employeeId) {
+        if (employeeService.getEmployee(employeeId) == null) {
+            throw exception(HRM_EMPLOYEE_NOT_EXISTS);
+        }
+    }
+
+    private void validateCertificateExists(Long id) {
+        if (certificateMapper.selectById(id) == null) {
+            throw exception(HRM_CERTIFICATE_NOT_EXISTS);
+        }
+    }
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
index c601c79..ee641c8 100644
--- a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.mes.api.item;
 
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSafetyStockRespDTO;
 import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -59,4 +61,13 @@
      */
     Set<Long> getSyncedMdmItemIds(Collection<Long> mdmItemIds);
 
+    /**
+     * 鑾峰彇鍚敤瀹夊叏搴撳瓨鐨勭墿鏂欏垪琛�
+     *
+     * <p>渚� ERP 閲囪喘璁″垝鍋氬簱瀛橀璀︾己閲忚绠楋紙safeStockFlag=true 涓� minStock 闈炵┖锛夈��
+     *
+     * @return 瀹夊叏搴撳瓨鐗╂枡鍒楄〃
+     */
+    List<MesMdItemSafetyStockRespDTO> getSafetyStockItemList();
+
 }
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSafetyStockRespDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSafetyStockRespDTO.java
new file mode 100644
index 0000000..bc057c3
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSafetyStockRespDTO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mes.api.item.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * MES 瀹夊叏搴撳瓨鐗╂枡 Response DTO
+ *
+ * 渚� ERP 閲囪喘璁″垝鍋氬簱瀛橀璀︾己閲忚绠�
+ *
+ * @author xiaoyi
+ */
+@Data
+public class MesMdItemSafetyStockRespDTO {
+
+    /**
+     * MES 鐗╂枡缂栧彿
+     */
+    private Long id;
+
+    /**
+     * MDM 鐗╂枡缂栧彿锛圗RP/MDM 涓绘暟鎹� ID锛�
+     */
+    private Long mdmItemId;
+
+    /**
+     * 鐗╂枡缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鐗╂枡鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鏈�浣庡簱瀛橀噺锛堝畨鍏ㄥ簱瀛樹笅闄愶級
+     */
+    private BigDecimal minStock;
+
+    /**
+     * 鏈�楂樺簱瀛橀噺
+     */
+    private BigDecimal maxStock;
+
+    /**
+     * 璁¢噺鍗曚綅缂栧彿
+     */
+    private Long unitMeasureId;
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java
new file mode 100644
index 0000000..7534a17
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.api.workorder;
+
+/**
+ * MES 鐢熶骇宸ュ崟 API 鎺ュ彛
+ *
+ * <p>渚� CRM 妯″潡璋冪敤锛屽疄鐜板悎鍚屾寚浠ゃ�屼笅鍙戙�嶇湡瀹炶法妯″潡鑱斿姩锛氭墽琛屾寚浠ゆ椂纭鐩爣宸ュ崟銆�
+ * 鍙栨秷鎸囦护鏃跺彇娑堢洰鏍囧伐鍗曪紝浣� CRM 鍚堝悓鎸囦护涓嶅啀鍙槸璁板綍涓庣姸鎬佹祦杞��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesProWorkOrderApi {
+
+    /**
+     * 纭宸ュ崟锛堣崏绋� 鈫� 宸茬‘璁わ級
+     *
+     * <p>鐢ㄤ簬鍚堝悓鎸囦护銆屼笅鍙戣〃銆嶆墽琛屾椂锛屽皢鎸囦护鍏宠仈鐨勫伐鍗曚粠鑽夌鐘舵�佷笅鍙戜负宸茬‘璁ゃ��
+     *
+     * @param id 宸ュ崟缂栧彿
+     */
+    void confirmWorkOrder(Long id);
+
+    /**
+     * 鍙栨秷宸ュ崟锛堝凡纭 鈫� 宸插彇娑堬級
+     *
+     * <p>鐢ㄤ簬鍚堝悓鎸囦护鍙栨秷鏃讹紝鍚屾鍙栨秷宸蹭笅鍙戠殑鐩爣宸ュ崟銆�
+     *
+     * @param id 宸ュ崟缂栧彿
+     */
+    void cancelWorkOrder(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
index 3376239..abc6f59 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.mes.api.item;
 
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSafetyStockRespDTO;
 import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemSaveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.batchconfig.MesMdItemBatchConfigSaveReqVO;
@@ -18,6 +20,7 @@
 import org.springframework.validation.annotation.Validated;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -135,6 +138,14 @@
         return itemMapper.selectMdmItemIdsByMdmItemIds(mdmItemIds);
     }
 
+    @Override
+    public List<MesMdItemSafetyStockRespDTO> getSafetyStockItemList() {
+        List<MesMdItemDO> items = itemMapper.selectList(new LambdaQueryWrapperX<MesMdItemDO>()
+                .eq(MesMdItemDO::getSafeStockFlag, true)
+                .isNotNull(MesMdItemDO::getMinStock));
+        return BeanUtils.toBean(items, MesMdItemSafetyStockRespDTO.class);
+    }
+
     /**
      * 淇濆瓨鎵规閰嶇疆
      */
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java
new file mode 100644
index 0000000..559ef11
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.api.workorder;
+
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * MES 鐢熶骇宸ュ崟 API 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesProWorkOrderApiImpl implements MesProWorkOrderApi {
+
+    @Resource
+    private MesProWorkOrderService workOrderService;
+
+    @Override
+    public void confirmWorkOrder(Long id) {
+        workOrderService.confirmWorkOrder(id);
+    }
+
+    @Override
+    public void cancelWorkOrder(Long id) {
+        workOrderService.cancelWorkOrder(id);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
index 9834bbe..7637381 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
@@ -23,6 +23,10 @@
     @ExcelProperty("璁惧缂栫爜")
     private String code;
 
+    @Schema(description = "鏁伴噰璁惧ID锛堝閮ㄦ暟閲� tbDeviceId锛�", example = "tb_001")
+    @ExcelProperty("鏁伴噰璁惧ID")
+    private String tbDeviceId;
+
     @Schema(description = "璁惧鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNC 鍔犲伐涓績")
     @ExcelProperty("璁惧鍚嶇О")
     private String name;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
index c9fecbc..04d53fd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
@@ -18,6 +18,9 @@
     @NotEmpty(message = "璁惧缂栫爜涓嶈兘涓虹┖")
     private String code;
 
+    @Schema(description = "鏁伴噰璁惧ID锛堝閮ㄦ暟閲� tbDeviceId锛�", example = "tb_001")
+    private String tbDeviceId;
+
     @Schema(description = "璁惧鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNC 鍔犲伐涓績")
     @NotEmpty(message = "璁惧鍚嶇О涓嶈兘涓虹┖")
     private String name;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/MesDvStatisticsController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/MesDvStatisticsController.java
new file mode 100644
index 0000000..bd98fc1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/MesDvStatisticsController.java
@@ -0,0 +1,221 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.statistics;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.statistics.vo.MesDvOeeRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.statistics.vo.MesDvStatisticsRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryTypeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.maintenrecord.MesDvMaintenRecordDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.repair.MesDvRepairDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryTypeMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.maintenrecord.MesDvMaintenRecordMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.repair.MesDvRepairMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
+import cn.iocoder.yudao.module.mes.enums.dv.MesDvMachineryStatusEnum;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 璁惧缁熻")
+@RestController
+@RequestMapping("/mes/dv/statistics")
+@Validated
+public class MesDvStatisticsController {
+
+    @Resource
+    private MesDvMachineryMapper machineryMapper;
+    @Resource
+    private MesDvMachineryTypeMapper machineryTypeMapper;
+    @Resource
+    private MesDvRepairMapper repairMapper;
+    @Resource
+    private MesDvMaintenRecordMapper maintenRecordMapper;
+    @Resource
+    private MesProTaskMapper taskMapper;
+
+    @GetMapping("/summary")
+    @Operation(summary = "璁惧缁熻姹囨�伙紙鍙拌处/缁翠慨/淇濆吇/绫诲瀷鍒嗗竷锛�")
+    @PreAuthorize("@ss.hasPermission('mes:dv-machinery:query')")
+    public CommonResult<MesDvStatisticsRespVO> getSummary() {
+        MesDvStatisticsRespVO vo = new MesDvStatisticsRespVO();
+
+        // 1. 璁惧鍙拌处缁熻
+        Long total = machineryMapper.selectCount();
+        Long producing = machineryMapper.selectCount(new LambdaQueryWrapperX<MesDvMachineryDO>()
+                .eq(MesDvMachineryDO::getStatus, MesDvMachineryStatusEnum.PRODUCING.getStatus()));
+        Long stopped = machineryMapper.selectCount(new LambdaQueryWrapperX<MesDvMachineryDO>()
+                .eq(MesDvMachineryDO::getStatus, MesDvMachineryStatusEnum.STOP.getStatus()));
+        Long maintaining = machineryMapper.selectCount(new LambdaQueryWrapperX<MesDvMachineryDO>()
+                .eq(MesDvMachineryDO::getStatus, MesDvMachineryStatusEnum.MAINTENANCE.getStatus()));
+        vo.setTotalMachinery(total);
+        vo.setProducingCount(producing);
+        vo.setStoppedCount(stopped);
+        vo.setMaintainingCount(maintaining);
+        vo.setFaultRate(total != null && total > 0
+                ? (double) (stopped + maintaining) / total * 100 : 0D);
+
+        // 2. 缁翠慨缁熻
+        vo.setTotalRepair(repairMapper.selectCount());
+        vo.setFinishedRepair(repairMapper.selectCount(new LambdaQueryWrapperX<MesDvRepairDO>()
+                .eq(MesDvRepairDO::getStatus, cn.iocoder.yudao.module.mes.enums.dv.MesDvRepairStatusEnum.FINISHED.getStatus())));
+        vo.setProcessingRepair((vo.getTotalRepair() != null ? vo.getTotalRepair() : 0L)
+                - (vo.getFinishedRepair() != null ? vo.getFinishedRepair() : 0L));
+
+        // 3. 淇濆吇缁熻
+        vo.setTotalMainten(maintenRecordMapper.selectCount());
+        vo.setFinishedMainten(maintenRecordMapper.selectCount(new LambdaQueryWrapperX<MesDvMaintenRecordDO>()
+                .eq(MesDvMaintenRecordDO::getStatus, cn.iocoder.yudao.module.mes.enums.dv.MesDvMaintenRecordStatusEnum.SUBMITTED.getStatus())));
+
+        // 4. 鎸夎澶囩被鍨嬪垎甯�
+        List<MesDvMachineryDO> machineryList = machineryMapper.selectList();
+        Map<Long, Long> typeCountMap = new HashMap<>();
+        for (MesDvMachineryDO machinery : machineryList) {
+            if (machinery.getMachineryTypeId() == null) {
+                continue;
+            }
+            typeCountMap.merge(machinery.getMachineryTypeId(), 1L, Long::sum);
+        }
+        List<MesDvMachineryTypeDO> typeList = machineryTypeMapper.selectList();
+        Map<Long, String> typeNameMap = new HashMap<>();
+        for (MesDvMachineryTypeDO type : typeList) {
+            typeNameMap.put(type.getId(), type.getName());
+        }
+        List<MesDvStatisticsRespVO.TypeItem> typeDistribution = new ArrayList<>();
+        typeCountMap.forEach((typeId, count) -> {
+            MesDvStatisticsRespVO.TypeItem item = new MesDvStatisticsRespVO.TypeItem();
+            item.setMachineryTypeId(typeId);
+            item.setMachineryTypeName(typeNameMap.get(typeId));
+            item.setCount(count);
+            typeDistribution.add(item);
+        });
+        vo.setTypeDistribution(typeDistribution);
+        return success(vo);
+    }
+
+    @GetMapping("/oee")
+    @Operation(summary = "璁惧 OEE 缁熻锛堢鍔ㄧ巼脳鎬ц兘寮�鍔ㄧ巼脳鑹搧鐜囷級")
+    @PreAuthorize("@ss.hasPermission('mes:dv-machinery:query')")
+    public CommonResult<MesDvOeeRespVO> getOee(
+            @RequestParam(value = "startTime", required = false)
+            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
+            @RequestParam(value = "endTime", required = false)
+            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
+        // 1. 鏌ヨ缁熻鍖洪棿鍐呬换鍔★紙鎸夊畬鎴愭椂闂磋繃婊わ紱涓嶄紶鍖洪棿鍒欑粺璁″叏閮級
+        List<MesProTaskDO> allTasks = taskMapper.selectList(new LambdaQueryWrapperX<MesProTaskDO>()
+                .betweenIfPresent(MesProTaskDO::getFinishDate, new LocalDateTime[]{startTime, endTime}));
+
+        // 2. 鏁翠綋 OEE
+        MesDvOeeRespVO vo = new MesDvOeeRespVO();
+        OeeCalc overall = calcOee(allTasks);
+        vo.setOverallOee(overall.oee);
+        vo.setOverallAvailability(overall.availability);
+        vo.setOverallPerformance(overall.performance);
+        vo.setOverallQuality(overall.quality);
+        vo.setTaskCount((long) allTasks.size());
+
+        // 3. 鎸夎澶囪仛鍚堬細浠诲姟鎸夊伐浣滅珯鍒嗙粍锛岃澶囬�氳繃 workstationId 鍏宠仈宸ヤ綔绔�
+        Map<Long, List<MesProTaskDO>> tasksByWorkstation = new HashMap<>();
+        for (MesProTaskDO task : allTasks) {
+            if (task.getWorkstationId() == null) {
+                continue;
+            }
+            tasksByWorkstation.computeIfAbsent(task.getWorkstationId(), k -> new ArrayList<>()).add(task);
+        }
+        List<MesDvOeeRespVO.MachineryOee> items = new ArrayList<>();
+        for (MesDvMachineryDO machinery : machineryMapper.selectList()) {
+            if (machinery.getWorkstationId() == null) {
+                continue; // 鏈粦瀹氬伐浣滅珯锛屾棤娉曞叧鑱斾换鍔�
+            }
+            List<MesProTaskDO> tasks = tasksByWorkstation.getOrDefault(machinery.getWorkstationId(), Collections.emptyList());
+            OeeCalc calc = calcOee(tasks);
+            MesDvOeeRespVO.MachineryOee item = new MesDvOeeRespVO.MachineryOee();
+            item.setMachineryId(machinery.getId());
+            item.setMachineryCode(machinery.getCode());
+            item.setMachineryName(machinery.getName());
+            item.setOee(calc.oee);
+            item.setAvailability(calc.availability);
+            item.setPerformance(calc.performance);
+            item.setQuality(calc.quality);
+            item.setTaskCount((long) tasks.size());
+            item.setPlannedQuantity(calc.plannedQuantity);
+            item.setProducedQuantity(calc.producedQuantity);
+            items.add(item);
+        }
+        vo.setItems(items);
+        return success(vo);
+    }
+
+    private static OeeCalc calcOee(List<MesProTaskDO> tasks) {
+        double totalPlannedHours = 0, totalActualHours = 0;
+        BigDecimal totalPlannedQty = BigDecimal.ZERO, totalProducedQty = BigDecimal.ZERO, totalQualifyQty = BigDecimal.ZERO;
+        for (MesProTaskDO task : tasks) {
+            if (task.getDuration() != null) {
+                totalPlannedHours += task.getDuration() * 8D; // 1 宸ヤ綔鏃� = 8 灏忔椂
+            }
+            if (task.getFirstFeedbackTime() != null && task.getLastFeedbackTime() != null
+                    && task.getLastFeedbackTime().isAfter(task.getFirstFeedbackTime())) {
+                totalActualHours += Duration.between(task.getFirstFeedbackTime(), task.getLastFeedbackTime()).toMinutes() / 60D;
+            }
+            if (task.getQuantity() != null) {
+                totalPlannedQty = totalPlannedQty.add(task.getQuantity());
+            }
+            if (task.getProducedQuantity() != null) {
+                totalProducedQty = totalProducedQty.add(task.getProducedQuantity());
+            }
+            if (task.getQualifyQuantity() != null) {
+                totalQualifyQty = totalQualifyQty.add(task.getQualifyQuantity());
+            }
+        }
+        OeeCalc calc = new OeeCalc();
+        // 绋煎姩鐜� = 瀹為檯鎶ュ伐鏃堕暱 / 璁″垝鏃堕暱锛堟棤璁″垝鏃堕暱鏃舵寜 1 澶勭悊锛�
+        calc.availability = percent(totalPlannedHours > 0 ? Math.min(1D, totalActualHours / totalPlannedHours) : 1D);
+        // 鎬ц兘寮�鍔ㄧ巼 = 瀹為檯浜ч噺 / 璁″垝浜ч噺
+        calc.performance = totalPlannedQty.compareTo(BigDecimal.ZERO) > 0
+                ? percent(Math.min(1D, totalProducedQty.divide(totalPlannedQty, 4, RoundingMode.HALF_UP).doubleValue())) : 0D;
+        // 鑹搧鐜� = 鍚堟牸鍝� / 瀹為檯浜ч噺锛堟棤浜ч噺鏃舵寜 1 澶勭悊锛�
+        calc.quality = totalProducedQty.compareTo(BigDecimal.ZERO) > 0
+                ? percent(Math.min(1D, totalQualifyQty.divide(totalProducedQty, 4, RoundingMode.HALF_UP).doubleValue())) : 100D;
+        // OEE(%) = 绋煎姩鐜�(%) 脳 鎬ц兘(%) 脳 鑹搧鐜�(%) / 10000锛屼繚鐣欎袱浣嶅皬鏁�
+        calc.oee = Math.round(calc.availability * calc.performance * calc.quality / 10000D * 100D) / 100D;
+        calc.plannedQuantity = totalPlannedQty;
+        calc.producedQuantity = totalProducedQty;
+        return calc;
+    }
+
+    /** 姣斾緥(0-1) 杞櫨鍒嗘瘮锛屼繚鐣欎袱浣嶅皬鏁� */
+    private static double percent(double ratio) {
+        return Math.round(ratio * 10000D) / 100D;
+    }
+
+    private static class OeeCalc {
+        private Double availability;
+        private Double performance;
+        private Double quality;
+        private Double oee;
+        private BigDecimal plannedQuantity;
+        private BigDecimal producedQuantity;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvOeeRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvOeeRespVO.java
new file mode 100644
index 0000000..cc41788
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvOeeRespVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.statistics.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧 OEE 缁熻 Response VO")
+@Data
+public class MesDvOeeRespVO {
+
+    @Schema(description = "鏁翠綋 OEE锛�%锛�")
+    private Double overallOee;
+
+    @Schema(description = "鏁翠綋绋煎姩鐜囷紙%锛�")
+    private Double overallAvailability;
+
+    @Schema(description = "鏁翠綋鎬ц兘寮�鍔ㄧ巼锛�%锛�")
+    private Double overallPerformance;
+
+    @Schema(description = "鏁翠綋鑹搧鐜囷紙%锛�")
+    private Double overallQuality;
+
+    @Schema(description = "缁熻浠诲姟鎬绘暟")
+    private Long taskCount;
+
+    @Schema(description = "鎸夎澶� OEE 鏄庣粏")
+    private List<MachineryOee> items;
+
+    @Schema(description = "璁惧 OEE 鏄庣粏椤�")
+    @Data
+    public static class MachineryOee {
+
+        @Schema(description = "璁惧缂栧彿")
+        private Long machineryId;
+
+        @Schema(description = "璁惧缂栫爜")
+        private String machineryCode;
+
+        @Schema(description = "璁惧鍚嶇О")
+        private String machineryName;
+
+        @Schema(description = "OEE锛�%锛�")
+        private Double oee;
+
+        @Schema(description = "绋煎姩鐜囷紙%锛�")
+        private Double availability;
+
+        @Schema(description = "鎬ц兘寮�鍔ㄧ巼锛�%锛�")
+        private Double performance;
+
+        @Schema(description = "鑹搧鐜囷紙%锛�")
+        private Double quality;
+
+        @Schema(description = "鍏宠仈浠诲姟鏁�")
+        private Long taskCount;
+
+        @Schema(description = "璁″垝浜ч噺")
+        private java.math.BigDecimal plannedQuantity;
+
+        @Schema(description = "瀹為檯浜ч噺")
+        private java.math.BigDecimal producedQuantity;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvStatisticsRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvStatisticsRespVO.java
new file mode 100644
index 0000000..3108b66
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/statistics/vo/MesDvStatisticsRespVO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.statistics.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧缁熻 Response VO")
+@Data
+public class MesDvStatisticsRespVO {
+
+    @Schema(description = "璁惧鎬绘暟")
+    private Long totalMachinery;
+
+    @Schema(description = "鐢熶骇涓澶囨暟")
+    private Long producingCount;
+
+    @Schema(description = "鍋滄満璁惧鏁�")
+    private Long stoppedCount;
+
+    @Schema(description = "缁存姢涓澶囨暟")
+    private Long maintainingCount;
+
+    @Schema(description = "璁惧鏁呴殰鐜囷紙鍋滄満+缁存姢 / 鎬绘暟 * 100锛�%锛�")
+    private Double faultRate;
+
+    @Schema(description = "缁翠慨鍗曟�绘暟")
+    private Long totalRepair;
+
+    @Schema(description = "缁翠慨涓�/寰呴獙鏀跺崟鏁�")
+    private Long processingRepair;
+
+    @Schema(description = "宸茬‘璁ょ淮淇崟鏁�")
+    private Long finishedRepair;
+
+    @Schema(description = "淇濆吇璁板綍鎬绘暟")
+    private Long totalMainten;
+
+    @Schema(description = "宸插畬鎴愪繚鍏昏褰曟暟")
+    private Long finishedMainten;
+
+    @Schema(description = "鎸夎澶囩被鍨嬪垎甯�")
+    private List<TypeItem> typeDistribution;
+
+    @Schema(description = "璁惧绫诲瀷鍒嗗竷椤�")
+    @Data
+    public static class TypeItem {
+
+        @Schema(description = "璁惧绫诲瀷缂栧彿")
+        private Long machineryTypeId;
+
+        @Schema(description = "璁惧绫诲瀷鍚嶇О")
+        private String machineryTypeName;
+
+        @Schema(description = "璁惧鏁伴噺")
+        private Long count;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java
new file mode 100644
index 0000000..05fa244
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry;
+
+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.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryDeviceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPullRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.telemetry.MesDvTelemetryDO;
+import cn.iocoder.yudao.module.mes.service.dv.telemetry.MesDvTelemetryService;
+import io.swagger.v3.oas.annotations.Operation;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰")
+@RestController
+@RequestMapping("/mes/dv/telemetry")
+@Validated
+public class MesDvTelemetryController {
+
+    @Resource
+    private MesDvTelemetryService telemetryService;
+
+    @GetMapping("/pull")
+    @Operation(summary = "鎷夊彇鏁伴噰鏁版嵁骞跺叆搴擄紙杩� 5 鍒嗛挓璁惧鏁版嵁锛�")
+    @PreAuthorize("@ss.hasPermission('mes:dv-telemetry:query')")
+    public CommonResult<MesDvTelemetryPullRespVO> pullTelemetry() {
+        return success(telemetryService.pullTelemetry());
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鍒嗛〉鏌ヨ鏁伴噰閬ユ祴鏁版嵁")
+    @PreAuthorize("@ss.hasPermission('mes:dv-telemetry:query')")
+    public CommonResult<PageResult<MesDvTelemetryRespVO>> getTelemetryPage(@Valid MesDvTelemetryPageReqVO pageReqVO) {
+        PageResult<MesDvTelemetryDO> pageResult = telemetryService.getTelemetryPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, MesDvTelemetryRespVO.class));
+    }
+
+    @GetMapping("/latest")
+    @Operation(summary = "鏌ヨ鍚勮澶囨渶鏂伴仴娴嬫暟鎹紙瀹炴椂鐩戞帶锛�")
+    @PreAuthorize("@ss.hasPermission('mes:dv-telemetry:query')")
+    public CommonResult<List<MesDvTelemetryRespVO>> getLatestTelemetry(MesDvTelemetryLatestReqVO reqVO) {
+        List<MesDvTelemetryDO> list = telemetryService.getLatestTelemetry(reqVO);
+        return success(BeanUtils.toBean(list, MesDvTelemetryRespVO.class));
+    }
+
+    @GetMapping("/device-list")
+    @Operation(summary = "鏌ヨ鏁伴噰璁惧涓嬫媺鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:dv-telemetry:query')")
+    public CommonResult<List<MesDvTelemetryDeviceRespVO>> getDeviceList() {
+        return success(telemetryService.getDeviceList());
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryDeviceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryDeviceRespVO.java
new file mode 100644
index 0000000..14eac9f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryDeviceRespVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰璁惧涓嬫媺 Response VO")
+@Data
+public class MesDvTelemetryDeviceRespVO {
+
+    @Schema(description = "鏁伴噰璁惧ID")
+    private String tbDeviceId;
+
+    @Schema(description = "璁惧鍚嶇О")
+    private String deviceName;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryLatestReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryLatestReqVO.java
new file mode 100644
index 0000000..703533d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryLatestReqVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰鏈�鏂伴仴娴� Request VO")
+@Data
+public class MesDvTelemetryLatestReqVO {
+
+    @Schema(description = "鏁伴噰璁惧ID", example = "tb_001")
+    private String tbDeviceId;
+
+    @Schema(description = "璁惧鍚嶇О", example = "1鍙峰彉鍘嬪櫒")
+    private String deviceName;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPageReqVO.java
new file mode 100644
index 0000000..311a582
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPageReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰閬ユ祴鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesDvTelemetryPageReqVO extends PageParam {
+
+    @Schema(description = "鏁伴噰璁惧ID", example = "tb_001")
+    private String tbDeviceId;
+
+    @Schema(description = "璁惧鍚嶇О", example = "1鍙峰彉鍘嬪櫒")
+    private String deviceName;
+
+    @Schema(description = "淇″彿鍚嶇О", example = "娓╁害")
+    private String paramName;
+
+    @Schema(description = "鏄惁寮傚父")
+    private Boolean whetherAnomaly;
+
+    @Schema(description = "鐝", example = "鏃╃彮")
+    private String shiftName;
+
+    @Schema(description = "閬ユ祴鏁版嵁鏃堕棿")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] telemetryDataTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPullRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPullRespVO.java
new file mode 100644
index 0000000..c5a8045
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryPullRespVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰鎷夊彇缁撴灉 Response VO")
+@Data
+public class MesDvTelemetryPullRespVO {
+
+    @Schema(description = "鏈鎷夊彇鏂板鏁版嵁鏉℃暟")
+    private Integer recordCount;
+
+    @Schema(description = "鏈鎷夊彇娑夊強璁惧鏁�")
+    private Integer deviceCount;
+
+    @Schema(description = "鎷夊彇鏃堕棿")
+    private LocalDateTime pullTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryRespVO.java
new file mode 100644
index 0000000..7826525
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryRespVO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰閬ユ祴 Response VO")
+@Data
+public class MesDvTelemetryRespVO {
+
+    @Schema(description = "缂栧彿")
+    private Long id;
+
+    @Schema(description = "鏁伴噰璁惧ID")
+    private String tbDeviceId;
+
+    @Schema(description = "璁惧鍚嶇О")
+    private String deviceName;
+
+    @Schema(description = "淇″彿鍚嶇О")
+    private String paramName;
+
+    @Schema(description = "璁惧KEY鍚嶇О")
+    private String paramKeyName;
+
+    @Schema(description = "鏍囧噯鍊�")
+    private String standardValue;
+
+    @Schema(description = "淇″彿鍊硷紙瀹炴椂锛�")
+    private String timelyValue;
+
+    @Schema(description = "鍧囧��")
+    private String avgValue;
+
+    @Schema(description = "鏈�澶у��")
+    private String maxValue;
+
+    @Schema(description = "鏈�灏忓��")
+    private String minValue;
+
+    @Schema(description = "鏄惁寮傚父")
+    private Boolean whetherAnomaly;
+
+    @Schema(description = "閬ユ祴鏁版嵁鏃堕棿")
+    private LocalDateTime telemetryDataTime;
+
+    @Schema(description = "鍗曟嵁鍙�")
+    private String billNo;
+
+    @Schema(description = "鐝")
+    private String shiftName;
+
+    @Schema(description = "鎷夊彇鏃堕棿")
+    private LocalDateTime pullTime;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemRespVO.java
index 9ce6d95..73d4022 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemRespVO.java
@@ -65,6 +65,11 @@
     @ExcelProperty("鏈�楂樺簱瀛橀噺")
     private BigDecimal maxStock;
 
+    @Schema(description = "鐢靛姏鐗╄祫鍒嗙被", example = "1")
+    @ExcelProperty(value = "鐢靛姏鐗╄祫鍒嗙被", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_MATERIAL_CATEGORY)
+    private Integer materialCategory;
+
     @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
     @ExcelProperty("鏄惁楂樺�肩墿鏂�")
     private Boolean highValue;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java
index 6de4a79..f48be15 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java
@@ -42,6 +42,9 @@
     @Schema(description = "鏈�楂樺簱瀛橀噺", example = "10000.0000")
     private BigDecimal maxStock;
 
+    @Schema(description = "鐢靛姏鐗╄祫鍒嗙被锛堝瓧鍏� mes_material_category锛�", example = "1")
+    private Integer materialCategory;
+
     @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
     private Boolean highValue;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/MesPdBaseDataController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/MesPdBaseDataController.java
new file mode 100644
index 0000000..85be5c4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/MesPdBaseDataController.java
@@ -0,0 +1,386 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdElectricityTypeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdServicePackageDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdVoltageLevelDO;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdDiscountPolicyService;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdElectricityTypeService;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdPriceStandardService;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdServicePackageService;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdTieredPriceService;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdVoltageLevelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 浜у搧鍩虹鏁版嵁")
+@RestController
+@RequestMapping("/mes/pd")
+@Validated
+public class MesPdBaseDataController {
+
+    @Resource
+    private MesPdElectricityTypeService electricityTypeService;
+    @Resource
+    private MesPdVoltageLevelService voltageLevelService;
+    @Resource
+    private MesPdServicePackageService servicePackageService;
+    @Resource
+    private MesPdPriceStandardService priceStandardService;
+    @Resource
+    private MesPdDiscountPolicyService discountPolicyService;
+    @Resource
+    private MesPdTieredPriceService tieredPriceService;
+
+    // ==================== 鐢ㄧ數绫诲瀷 ====================
+
+    @PostMapping("/electricity-type/create")
+    @Operation(summary = "鍒涘缓鐢ㄧ數绫诲瀷")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:create')")
+    public CommonResult<Long> createElectricityType(@Valid @RequestBody MesPdElectricityTypeSaveReqVO createReqVO) {
+        return success(electricityTypeService.createElectricityType(createReqVO));
+    }
+
+    @PutMapping("/electricity-type/update")
+    @Operation(summary = "鏇存柊鐢ㄧ數绫诲瀷")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:update')")
+    public CommonResult<Boolean> updateElectricityType(@Valid @RequestBody MesPdElectricityTypeSaveReqVO updateReqVO) {
+        electricityTypeService.updateElectricityType(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/electricity-type/delete")
+    @Operation(summary = "鍒犻櫎鐢ㄧ數绫诲瀷")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:delete')")
+    public CommonResult<Boolean> deleteElectricityType(@RequestParam("id") Long id) {
+        electricityTypeService.deleteElectricityType(id);
+        return success(true);
+    }
+
+    @GetMapping("/electricity-type/get")
+    @Operation(summary = "鑾峰緱鐢ㄧ數绫诲瀷")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdElectricityTypeRespVO> getElectricityType(@RequestParam("id") Long id) {
+        return success(electricityTypeService.getElectricityType(id));
+    }
+
+    @GetMapping("/electricity-type/page")
+    @Operation(summary = "鑾峰緱鐢ㄧ數绫诲瀷鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<PageResult<MesPdElectricityTypeRespVO>> getElectricityTypePage(@Valid MesPdElectricityTypePageReqVO pageReqVO) {
+        return success(electricityTypeService.getElectricityTypePage(pageReqVO));
+    }
+
+    @GetMapping("/electricity-type/list")
+    @Operation(summary = "鑾峰緱鐢ㄧ數绫诲瀷鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<List<MesPdElectricityTypeDO>> getElectricityTypeList() {
+        return success(electricityTypeService.getElectricityTypeList());
+    }
+
+    @GetMapping("/electricity-type/export-excel")
+    @Operation(summary = "瀵煎嚭鐢ㄧ數绫诲瀷 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportElectricityTypeExcel(@Valid MesPdElectricityTypePageReqVO pageReqVO,
+                                           HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        ExcelUtils.write(response, "鐢ㄧ數绫诲瀷.xls", "鏁版嵁", MesPdElectricityTypeRespVO.class,
+                electricityTypeService.getElectricityTypePage(pageReqVO).getList());
+    }
+
+    // ==================== 鐢靛帇绛夌骇 ====================
+
+    @PostMapping("/voltage-level/create")
+    @Operation(summary = "鍒涘缓鐢靛帇绛夌骇")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:create')")
+    public CommonResult<Long> createVoltageLevel(@Valid @RequestBody MesPdVoltageLevelSaveReqVO createReqVO) {
+        return success(voltageLevelService.createVoltageLevel(createReqVO));
+    }
+
+    @PutMapping("/voltage-level/update")
+    @Operation(summary = "鏇存柊鐢靛帇绛夌骇")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:update')")
+    public CommonResult<Boolean> updateVoltageLevel(@Valid @RequestBody MesPdVoltageLevelSaveReqVO updateReqVO) {
+        voltageLevelService.updateVoltageLevel(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/voltage-level/delete")
+    @Operation(summary = "鍒犻櫎鐢靛帇绛夌骇")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:delete')")
+    public CommonResult<Boolean> deleteVoltageLevel(@RequestParam("id") Long id) {
+        voltageLevelService.deleteVoltageLevel(id);
+        return success(true);
+    }
+
+    @GetMapping("/voltage-level/get")
+    @Operation(summary = "鑾峰緱鐢靛帇绛夌骇")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdVoltageLevelRespVO> getVoltageLevel(@RequestParam("id") Long id) {
+        return success(voltageLevelService.getVoltageLevel(id));
+    }
+
+    @GetMapping("/voltage-level/page")
+    @Operation(summary = "鑾峰緱鐢靛帇绛夌骇鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<PageResult<MesPdVoltageLevelRespVO>> getVoltageLevelPage(@Valid MesPdVoltageLevelPageReqVO pageReqVO) {
+        return success(voltageLevelService.getVoltageLevelPage(pageReqVO));
+    }
+
+    @GetMapping("/voltage-level/list")
+    @Operation(summary = "鑾峰緱鐢靛帇绛夌骇鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<List<MesPdVoltageLevelDO>> getVoltageLevelList() {
+        return success(voltageLevelService.getVoltageLevelList());
+    }
+
+    @GetMapping("/voltage-level/export-excel")
+    @Operation(summary = "瀵煎嚭鐢靛帇绛夌骇 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportVoltageLevelExcel(@Valid MesPdVoltageLevelPageReqVO pageReqVO,
+                                        HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        ExcelUtils.write(response, "鐢靛帇绛夌骇.xls", "鏁版嵁", MesPdVoltageLevelRespVO.class,
+                voltageLevelService.getVoltageLevelPage(pageReqVO).getList());
+    }
+
+    // ==================== 鏈嶅姟濂楅 ====================
+
+    @PostMapping("/service-package/create")
+    @Operation(summary = "鍒涘缓鏈嶅姟濂楅")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:create')")
+    public CommonResult<Long> createServicePackage(@Valid @RequestBody MesPdServicePackageSaveReqVO createReqVO) {
+        return success(servicePackageService.createServicePackage(createReqVO));
+    }
+
+    @PutMapping("/service-package/update")
+    @Operation(summary = "鏇存柊鏈嶅姟濂楅")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:update')")
+    public CommonResult<Boolean> updateServicePackage(@Valid @RequestBody MesPdServicePackageSaveReqVO updateReqVO) {
+        servicePackageService.updateServicePackage(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/service-package/delete")
+    @Operation(summary = "鍒犻櫎鏈嶅姟濂楅")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:delete')")
+    public CommonResult<Boolean> deleteServicePackage(@RequestParam("id") Long id) {
+        servicePackageService.deleteServicePackage(id);
+        return success(true);
+    }
+
+    @GetMapping("/service-package/get")
+    @Operation(summary = "鑾峰緱鏈嶅姟濂楅")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdServicePackageRespVO> getServicePackage(@RequestParam("id") Long id) {
+        return success(servicePackageService.getServicePackage(id));
+    }
+
+    @GetMapping("/service-package/page")
+    @Operation(summary = "鑾峰緱鏈嶅姟濂楅鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<PageResult<MesPdServicePackageRespVO>> getServicePackagePage(@Valid MesPdServicePackagePageReqVO pageReqVO) {
+        return success(servicePackageService.getServicePackagePage(pageReqVO));
+    }
+
+    @GetMapping("/service-package/list")
+    @Operation(summary = "鑾峰緱鏈嶅姟濂楅鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<List<MesPdServicePackageDO>> getServicePackageList() {
+        return success(servicePackageService.getServicePackageList());
+    }
+
+    @GetMapping("/service-package/export-excel")
+    @Operation(summary = "瀵煎嚭鏈嶅姟濂楅 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportServicePackageExcel(@Valid MesPdServicePackagePageReqVO pageReqVO,
+                                          HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        ExcelUtils.write(response, "鏈嶅姟濂楅.xls", "鏁版嵁", MesPdServicePackageRespVO.class,
+                servicePackageService.getServicePackagePage(pageReqVO).getList());
+    }
+
+    // ==================== 瀹氫环鏍囧噯 ====================
+
+    @PostMapping("/price-standard/create")
+    @Operation(summary = "鍒涘缓瀹氫环鏍囧噯")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:create')")
+    public CommonResult<Long> createPriceStandard(@Valid @RequestBody MesPdPriceStandardSaveReqVO createReqVO) {
+        return success(priceStandardService.createPriceStandard(createReqVO));
+    }
+
+    @PutMapping("/price-standard/update")
+    @Operation(summary = "鏇存柊瀹氫环鏍囧噯")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:update')")
+    public CommonResult<Boolean> updatePriceStandard(@Valid @RequestBody MesPdPriceStandardSaveReqVO updateReqVO) {
+        priceStandardService.updatePriceStandard(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/price-standard/delete")
+    @Operation(summary = "鍒犻櫎瀹氫环鏍囧噯")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:delete')")
+    public CommonResult<Boolean> deletePriceStandard(@RequestParam("id") Long id) {
+        priceStandardService.deletePriceStandard(id);
+        return success(true);
+    }
+
+    @GetMapping("/price-standard/get")
+    @Operation(summary = "鑾峰緱瀹氫环鏍囧噯")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdPriceStandardRespVO> getPriceStandard(@RequestParam("id") Long id) {
+        return success(priceStandardService.getPriceStandard(id));
+    }
+
+    @GetMapping("/price-standard/page")
+    @Operation(summary = "鑾峰緱瀹氫环鏍囧噯鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<PageResult<MesPdPriceStandardRespVO>> getPriceStandardPage(@Valid MesPdPriceStandardPageReqVO pageReqVO) {
+        return success(priceStandardService.getPriceStandardPage(pageReqVO));
+    }
+
+    @GetMapping("/price-standard/export-excel")
+    @Operation(summary = "瀵煎嚭瀹氫环鏍囧噯 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportPriceStandardExcel(@Valid MesPdPriceStandardPageReqVO pageReqVO,
+                                         HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        ExcelUtils.write(response, "瀹氫环鏍囧噯.xls", "鏁版嵁", MesPdPriceStandardRespVO.class,
+                priceStandardService.getPriceStandardPage(pageReqVO).getList());
+    }
+
+    // ==================== 浼樻儬绛栫暐 ====================
+
+    @PostMapping("/discount-policy/create")
+    @Operation(summary = "鍒涘缓浼樻儬绛栫暐")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:create')")
+    public CommonResult<Long> createDiscountPolicy(@Valid @RequestBody MesPdDiscountPolicySaveReqVO createReqVO) {
+        return success(discountPolicyService.createDiscountPolicy(createReqVO));
+    }
+
+    @PutMapping("/discount-policy/update")
+    @Operation(summary = "鏇存柊浼樻儬绛栫暐")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:update')")
+    public CommonResult<Boolean> updateDiscountPolicy(@Valid @RequestBody MesPdDiscountPolicySaveReqVO updateReqVO) {
+        discountPolicyService.updateDiscountPolicy(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/discount-policy/delete")
+    @Operation(summary = "鍒犻櫎浼樻儬绛栫暐")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:delete')")
+    public CommonResult<Boolean> deleteDiscountPolicy(@RequestParam("id") Long id) {
+        discountPolicyService.deleteDiscountPolicy(id);
+        return success(true);
+    }
+
+    @GetMapping("/discount-policy/get")
+    @Operation(summary = "鑾峰緱浼樻儬绛栫暐")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdDiscountPolicyRespVO> getDiscountPolicy(@RequestParam("id") Long id) {
+        return success(discountPolicyService.getDiscountPolicy(id));
+    }
+
+    @GetMapping("/discount-policy/page")
+    @Operation(summary = "鑾峰緱浼樻儬绛栫暐鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<PageResult<MesPdDiscountPolicyRespVO>> getDiscountPolicyPage(@Valid MesPdDiscountPolicyPageReqVO pageReqVO) {
+        return success(discountPolicyService.getDiscountPolicyPage(pageReqVO));
+    }
+
+    @GetMapping("/discount-policy/export-excel")
+    @Operation(summary = "瀵煎嚭浼樻儬绛栫暐 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportDiscountPolicyExcel(@Valid MesPdDiscountPolicyPageReqVO pageReqVO,
+                                          HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        ExcelUtils.write(response, "浼樻儬绛栫暐.xls", "鏁版嵁", MesPdDiscountPolicyRespVO.class,
+                discountPolicyService.getDiscountPolicyPage(pageReqVO).getList());
+    }
+
+    // ==================== 闃舵鐢典环 ====================
+
+    @PostMapping("/tiered-price/create")
+    @Operation(summary = "鍒涘缓闃舵鐢典环")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:create')")
+    public CommonResult<Long> createTieredPrice(@Valid @RequestBody MesPdTieredPriceSaveReqVO createReqVO) {
+        return success(tieredPriceService.createTieredPrice(createReqVO));
+    }
+
+    @PutMapping("/tiered-price/update")
+    @Operation(summary = "鏇存柊闃舵鐢典环")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:update')")
+    public CommonResult<Boolean> updateTieredPrice(@Valid @RequestBody MesPdTieredPriceSaveReqVO updateReqVO) {
+        tieredPriceService.updateTieredPrice(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/tiered-price/delete")
+    @Operation(summary = "鍒犻櫎闃舵鐢典环")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:delete')")
+    public CommonResult<Boolean> deleteTieredPrice(@RequestParam("id") Long id) {
+        tieredPriceService.deleteTieredPrice(id);
+        return success(true);
+    }
+
+    @GetMapping("/tiered-price/get")
+    @Operation(summary = "鑾峰緱闃舵鐢典环")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdTieredPriceRespVO> getTieredPrice(@RequestParam("id") Long id) {
+        return success(tieredPriceService.getTieredPrice(id));
+    }
+
+    @GetMapping("/tiered-price/page")
+    @Operation(summary = "鑾峰緱闃舵鐢典环鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<PageResult<MesPdTieredPriceRespVO>> getTieredPricePage(@Valid MesPdTieredPricePageReqVO pageReqVO) {
+        return success(tieredPriceService.getTieredPricePage(pageReqVO));
+    }
+
+    @GetMapping("/tiered-price/export-excel")
+    @Operation(summary = "瀵煎嚭闃舵鐢典环 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportTieredPriceExcel(@Valid MesPdTieredPricePageReqVO pageReqVO,
+                                       HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        ExcelUtils.write(response, "闃舵鐢典环.xls", "鏁版嵁", MesPdTieredPriceRespVO.class,
+                tieredPriceService.getTieredPricePage(pageReqVO).getList());
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyPageReqVO.java
new file mode 100644
index 0000000..999108a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyPageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.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 MesPdDiscountPolicyPageReqVO extends PageParam {
+
+    @Schema(description = "绛栫暐鍚嶇О")
+    private String name;
+
+    @Schema(description = "绛栫暐绫诲瀷锛�1鎶樻墸/2绔嬪噺锛�", example = "1")
+    private Integer policyType;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyRespVO.java
new file mode 100644
index 0000000..54ae5b2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicyRespVO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 浼樻儬绛栫暐 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdDiscountPolicyRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "绛栫暐鍚嶇О")
+    @ExcelProperty("绛栫暐鍚嶇О")
+    private String name;
+
+    @Schema(description = "绛栫暐绫诲瀷锛�1鎶樻墸/2绔嬪噺锛�", example = "1")
+    @ExcelProperty(value = "绛栫暐绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_PD_DISCOUNT_POLICY_TYPE)
+    private Integer policyType;
+
+    @Schema(description = "绛栫暐绫诲瀷鍚嶇О")
+    private String policyTypeName;
+
+    @Schema(description = "绛栫暐鍊硷紙鎶樻墸锛�0-1 姣斾緥锛涚珛鍑忥細閲戦锛�")
+    @ExcelProperty("绛栫暐鍊�")
+    private BigDecimal policyValue;
+
+    @Schema(description = "閫傜敤瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛屽彲绌�=鍏ㄥ眬锛�")
+    @ExcelProperty("閫傜敤瀵硅薄绫诲瀷")
+    private Integer targetType;
+
+    @Schema(description = "閫傜敤瀵硅薄ID")
+    @ExcelProperty("閫傜敤瀵硅薄ID")
+    private Long targetId;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    @ExcelProperty("鐢熸晥鏃ユ湡")
+    private LocalDateTime startDate;
+
+    @Schema(description = "澶辨晥鏃ユ湡")
+    @ExcelProperty("澶辨晥鏃ユ湡")
+    private LocalDateTime endDate;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    @ExcelProperty("鍚敤鐘舵��")
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicySaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicySaveReqVO.java
new file mode 100644
index 0000000..de15296
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdDiscountPolicySaveReqVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 浼樻儬绛栫暐鏂板/淇敼 Request VO")
+@Data
+public class MesPdDiscountPolicySaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "绛栫暐鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "绛栫暐鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "绛栫暐绫诲瀷锛�1鎶樻墸/2绔嬪噺锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "绛栫暐绫诲瀷涓嶈兘涓虹┖")
+    private Integer policyType;
+
+    @Schema(description = "绛栫暐鍊硷紙鎶樻墸锛�0-1 姣斾緥锛涚珛鍑忥細閲戦锛�")
+    private BigDecimal policyValue;
+
+    @Schema(description = "閫傜敤瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛屽彲绌�=鍏ㄥ眬锛�")
+    private Integer targetType;
+
+    @Schema(description = "閫傜敤瀵硅薄ID")
+    private Long targetId;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    private LocalDateTime startDate;
+
+    @Schema(description = "澶辨晥鏃ユ湡")
+    private LocalDateTime endDate;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypePageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypePageReqVO.java
new file mode 100644
index 0000000..9dc01a8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypePageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.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 MesPdElectricityTypePageReqVO extends PageParam {
+
+    @Schema(description = "绫诲瀷鍚嶇О")
+    private String name;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeRespVO.java
new file mode 100644
index 0000000..6d9cc01
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeRespVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢ㄧ數绫诲瀷鍩虹淇℃伅 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdElectricityTypeRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "绫诲瀷缂栫爜")
+    @ExcelProperty("绫诲瀷缂栫爜")
+    private String code;
+
+    @Schema(description = "绫诲瀷鍚嶇О")
+    @ExcelProperty("绫诲瀷鍚嶇О")
+    private String name;
+
+    @Schema(description = "绫诲瀷璇存槑")
+    @ExcelProperty("绫诲瀷璇存槑")
+    private String description;
+
+    @Schema(description = "鎺掑簭")
+    @ExcelProperty("鎺掑簭")
+    private Integer sort;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    @ExcelProperty("鍚敤鐘舵��")
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeSaveReqVO.java
new file mode 100644
index 0000000..4214627
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdElectricityTypeSaveReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢ㄧ數绫诲瀷鍩虹淇℃伅鏂板/淇敼 Request VO")
+@Data
+public class MesPdElectricityTypeSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "绫诲瀷缂栫爜")
+    private String code;
+
+    @Schema(description = "绫诲瀷鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "绫诲瀷鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "绫诲瀷璇存槑")
+    private String description;
+
+    @Schema(description = "鎺掑簭")
+    private Integer sort;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardPageReqVO.java
new file mode 100644
index 0000000..2f0142b
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardPageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.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 MesPdPriceStandardPageReqVO extends PageParam {
+
+    @Schema(description = "瀹氫环瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛�", example = "1")
+    private Integer objectType;
+
+    @Schema(description = "瀹氫环瀵硅薄ID")
+    private Long objectId;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardRespVO.java
new file mode 100644
index 0000000..24b4a04
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardRespVO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 瀹氫环鏍囧噯 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdPriceStandardRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "瀹氫环瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛�", example = "1")
+    @ExcelProperty(value = "瀹氫环瀵硅薄绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_PD_PRICE_OBJECT_TYPE)
+    private Integer objectType;
+
+    @Schema(description = "瀹氫环瀵硅薄绫诲瀷鍚嶇О")
+    private String objectTypeName;
+
+    @Schema(description = "瀹氫环瀵硅薄ID")
+    @ExcelProperty("瀹氫环瀵硅薄ID")
+    private Long objectId;
+
+    @Schema(description = "瀹氫环瀵硅薄鍚嶇О")
+    @ExcelProperty("瀹氫环瀵硅薄鍚嶇О")
+    private String objectName;
+
+    @Schema(description = "浠锋牸")
+    @ExcelProperty("浠锋牸")
+    private BigDecimal price;
+
+    @Schema(description = "璁′环鍗曚綅")
+    @ExcelProperty("璁′环鍗曚綅")
+    private String unit;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    @ExcelProperty("鐢熸晥鏃ユ湡")
+    private LocalDateTime effectiveDate;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    @ExcelProperty("鍚敤鐘舵��")
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardSaveReqVO.java
new file mode 100644
index 0000000..ac84a1c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdPriceStandardSaveReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 瀹氫环鏍囧噯鏂板/淇敼 Request VO")
+@Data
+public class MesPdPriceStandardSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "瀹氫环瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "瀹氫环瀵硅薄绫诲瀷涓嶈兘涓虹┖")
+    private Integer objectType;
+
+    @Schema(description = "瀹氫环瀵硅薄ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "瀹氫环瀵硅薄ID涓嶈兘涓虹┖")
+    private Long objectId;
+
+    @Schema(description = "瀹氫环瀵硅薄鍚嶇О锛堝啑浣欙紝渚夸簬灞曠ず锛�")
+    private String objectName;
+
+    @Schema(description = "浠锋牸")
+    private BigDecimal price;
+
+    @Schema(description = "璁′环鍗曚綅")
+    private String unit;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    private LocalDateTime effectiveDate;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackagePageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackagePageReqVO.java
new file mode 100644
index 0000000..1be0ecb
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackagePageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.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 MesPdServicePackagePageReqVO extends PageParam {
+
+    @Schema(description = "濂楅鍚嶇О")
+    private String name;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageRespVO.java
new file mode 100644
index 0000000..8884403
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鏈嶅姟濂楅鍩虹淇℃伅 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdServicePackageRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "濂楅缂栫爜")
+    @ExcelProperty("濂楅缂栫爜")
+    private String code;
+
+    @Schema(description = "濂楅鍚嶇О")
+    @ExcelProperty("濂楅鍚嶇О")
+    private String name;
+
+    @Schema(description = "濂楅璇存槑")
+    @ExcelProperty("濂楅璇存槑")
+    private String description;
+
+    @Schema(description = "濂楅浠锋牸")
+    @ExcelProperty("濂楅浠锋牸")
+    private BigDecimal price;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    @ExcelProperty("鐢熸晥鏃ユ湡")
+    private LocalDateTime effectiveDate;
+
+    @Schema(description = "澶辨晥鏃ユ湡")
+    @ExcelProperty("澶辨晥鏃ユ湡")
+    private LocalDateTime expireDate;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    @ExcelProperty("鍚敤鐘舵��")
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageSaveReqVO.java
new file mode 100644
index 0000000..bb22d21
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdServicePackageSaveReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鏈嶅姟濂楅鍩虹淇℃伅鏂板/淇敼 Request VO")
+@Data
+public class MesPdServicePackageSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "濂楅缂栫爜锛堢紪鐮佽鍒� PD_PACKAGE_CODE 鑷姩鐢熸垚锛�")
+    private String code;
+
+    @Schema(description = "濂楅鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "濂楅鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "濂楅璇存槑")
+    private String description;
+
+    @Schema(description = "濂楅浠锋牸")
+    private BigDecimal price;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    private LocalDateTime effectiveDate;
+
+    @Schema(description = "澶辨晥鏃ユ湡")
+    private LocalDateTime expireDate;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPricePageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPricePageReqVO.java
new file mode 100644
index 0000000..5b980d5
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPricePageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.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 MesPdTieredPricePageReqVO extends PageParam {
+
+    @Schema(description = "鏈嶅姟濂楅ID锛堜笌鐢靛帇绛夌骇浜岄�変竴锛�")
+    private Long packageId;
+
+    @Schema(description = "鐢靛帇绛夌骇ID锛堜笌鏈嶅姟濂楅浜岄�変竴锛�")
+    private Long voltageLevelId;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceRespVO.java
new file mode 100644
index 0000000..f48fa27
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 闃舵鐢典环 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdTieredPriceRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鏈嶅姟濂楅ID锛堜笌鐢靛帇绛夌骇浜岄�変竴锛�")
+    @ExcelProperty("鏈嶅姟濂楅ID")
+    private Long packageId;
+
+    @Schema(description = "鐢靛帇绛夌骇ID锛堜笌鏈嶅姟濂楅浜岄�変竴锛�")
+    @ExcelProperty("鐢靛帇绛夌骇ID")
+    private Long voltageLevelId;
+
+    @Schema(description = "妗d綅涓嬮檺锛堝惈锛宬Wh锛�")
+    @ExcelProperty("妗d綅涓嬮檺")
+    private BigDecimal startValue;
+
+    @Schema(description = "妗d綅涓婇檺锛堜笉鍚紝kWh锛屽彲绌�=涓婁笉灏侀《锛�")
+    @ExcelProperty("妗d綅涓婇檺")
+    private BigDecimal endValue;
+
+    @Schema(description = "妗d綅鍗曚环")
+    @ExcelProperty("妗d綅鍗曚环")
+    private BigDecimal pricePerUnit;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    @ExcelProperty(value = "鍚敤鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_PD_TIERED_PRICE_STATUS)
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceSaveReqVO.java
new file mode 100644
index 0000000..5e6d0bd
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdTieredPriceSaveReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 闃舵鐢典环鏂板/淇敼 Request VO")
+@Data
+public class MesPdTieredPriceSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "鏈嶅姟濂楅ID锛堜笌鐢靛帇绛夌骇浜岄�変竴锛�")
+    private Long packageId;
+
+    @Schema(description = "鐢靛帇绛夌骇ID锛堜笌鏈嶅姟濂楅浜岄�変竴锛�")
+    private Long voltageLevelId;
+
+    @Schema(description = "妗d綅涓嬮檺锛堝惈锛宬Wh锛�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "妗d綅涓嬮檺涓嶈兘涓虹┖")
+    private BigDecimal startValue;
+
+    @Schema(description = "妗d綅涓婇檺锛堜笉鍚紝kWh锛屽彲绌�=涓婁笉灏侀《锛�")
+    private BigDecimal endValue;
+
+    @Schema(description = "妗d綅鍗曚环", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "妗d綅鍗曚环涓嶈兘涓虹┖")
+    private BigDecimal pricePerUnit;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelPageReqVO.java
new file mode 100644
index 0000000..ee584ac
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelPageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.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 MesPdVoltageLevelPageReqVO extends PageParam {
+
+    @Schema(description = "绛夌骇鍚嶇О")
+    private String name;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelRespVO.java
new file mode 100644
index 0000000..a677755
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelRespVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢靛帇绛夌骇鍩虹淇℃伅 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdVoltageLevelRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "绛夌骇缂栫爜")
+    @ExcelProperty("绛夌骇缂栫爜")
+    private String code;
+
+    @Schema(description = "绛夌骇鍚嶇О")
+    @ExcelProperty("绛夌骇鍚嶇О")
+    private String name;
+
+    @Schema(description = "鐢靛帇鏁板�硷紙kV锛�")
+    @ExcelProperty("鐢靛帇鏁板�硷紙kV锛�")
+    private BigDecimal voltageValue;
+
+    @Schema(description = "鎺掑簭")
+    @ExcelProperty("鎺掑簭")
+    private Integer sort;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", example = "1")
+    @ExcelProperty("鍚敤鐘舵��")
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelSaveReqVO.java
new file mode 100644
index 0000000..fa30f06
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/basedata/vo/MesPdVoltageLevelSaveReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢靛帇绛夌骇鍩虹淇℃伅鏂板/淇敼 Request VO")
+@Data
+public class MesPdVoltageLevelSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "绛夌骇缂栫爜")
+    private String code;
+
+    @Schema(description = "绛夌骇鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "绛夌骇鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "鐢靛帇鏁板�硷紙kV锛�")
+    private BigDecimal voltageValue;
+
+    @Schema(description = "鎺掑簭")
+    private Integer sort;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/ProductCtcController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/ProductCtcController.java
new file mode 100644
index 0000000..7b32418
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/ProductCtcController.java
@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc.ProductCtcDO;
+import cn.iocoder.yudao.module.mes.service.pd.ctc.ProductCtcService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - 鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁�")
+@RestController
+@RequestMapping("/mes/pd/ctc")
+@Validated
+public class ProductCtcController {
+
+    @Resource
+    private ProductCtcService ctcService;
+
+    @Resource
+    private MdmItemApi mdmItemApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁�")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:create')")
+    public CommonResult<Long> createCtc(@Valid @RequestBody ProductCtcSaveReqVO createReqVO) {
+        return success(ctcService.createCtc(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁�")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:update')")
+    public CommonResult<Boolean> updateCtc(@Valid @RequestBody ProductCtcSaveReqVO updateReqVO) {
+        ctcService.updateCtc(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:delete')")
+    public CommonResult<Boolean> deleteCtc(@RequestParam("id") Long id) {
+        ctcService.deleteCtc(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:query')")
+    public CommonResult<ProductCtcRespVO> getCtc(@RequestParam("id") Long id) {
+        ProductCtcDO ctc = ctcService.getCtc(id);
+        if (ctc == null) {
+            return success(null);
+        }
+        return success(buildCtcRespVOList(Collections.singletonList(ctc)).get(0));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁板垎椤�")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:query')")
+    public CommonResult<PageResult<ProductCtcRespVO>> getCtcPage(@Valid ProductCtcPageReqVO pageReqVO) {
+        PageResult<ProductCtcDO> pageResult = ctcService.getCtcPage(pageReqVO);
+        return success(new PageResult<>(buildCtcRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁� Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportCtcExcel(@Valid ProductCtcPageReqVO pageReqVO,
+                               HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<ProductCtcDO> list = ctcService.getCtcPage(pageReqVO).getList();
+        ExcelUtils.write(response, "鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁�.xls", "鏁版嵁", ProductCtcRespVO.class,
+                buildCtcRespVOList(list));
+    }
+
+    @PostMapping("/calc-angle")
+    @Operation(summary = "缁曞寘瑙掑害璁$畻锛堝凡鐭ョ焊甯﹀搴︼級")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:query')")
+    public CommonResult<ProductCtcAngleCalcRespVO> calcAngle(@Valid @RequestBody ProductCtcAngleCalcReqVO reqVO) {
+        return success(ctcService.calcAngle(reqVO));
+    }
+
+    @PostMapping("/calc-resistance")
+    @Operation(summary = "鐩存祦鐢甸樆璁$畻锛堟寜娓╁害鎹㈢畻锛�")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:query')")
+    public CommonResult<ProductCtcResistanceCalcRespVO> calcResistance(@Valid @RequestBody ProductCtcResistanceCalcReqVO reqVO) {
+        return success(ctcService.calcResistance(reqVO));
+    }
+
+    @PostMapping("/calc-reel")
+    @Operation(summary = "鐩樺叿瀹归噺璁$畻锛堟瘡鐩樻渶澶ч暱搴︼級")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc:query')")
+    public CommonResult<ProductCtcReelCalcRespVO> calcReelCapacity(@Valid @RequestBody ProductCtcReelCalcReqVO reqVO) {
+        return success(ctcService.calcReelCapacity(reqVO));
+    }
+
+    // ==================== 鎷兼帴 VO ====================
+
+    private List<ProductCtcRespVO> buildCtcRespVOList(List<ProductCtcDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        // 鎵归噺鑾峰彇鐗╂枡淇℃伅
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(convertSet(list, ProductCtcDO::getItemId));
+        // 鎷兼帴 VO
+        return BeanUtils.toBean(list, ProductCtcRespVO.class, vo -> {
+            MapUtils.findAndThen(itemMap, vo.getItemId(),
+                    item -> {
+                        vo.setItemName(item.getName());
+                        vo.setItemCode(item.getCode());
+                    });
+        });
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcReqVO.java
new file mode 100644
index 0000000..13fa6a4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 缁曞寘瑙掑害璁$畻 Request VO")
+@Data
+public class ProductCtcAngleCalcReqVO {
+
+    @Schema(description = "瀵间綋鍘氬害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "19.08")
+    @NotNull(message = "瀵间綋鍘氬害涓嶈兘涓虹┖")
+    private BigDecimal conductorThickness;
+
+    @Schema(description = "瀵间綋瀹藉害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "12.6")
+    @NotNull(message = "瀵间綋瀹藉害涓嶈兘涓虹┖")
+    private BigDecimal conductorWidth;
+
+    @Schema(description = "鍦嗚鍗婂緞锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍦嗚鍗婂緞涓嶈兘涓虹┖")
+    private BigDecimal cornerRadius;
+
+    @Schema(description = "缁濈紭鍘氬害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.75")
+    @NotNull(message = "缁濈紭鍘氬害涓嶈兘涓虹┖")
+    private BigDecimal insulationThickness;
+
+    @Schema(description = "绾稿甫瀹藉害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "22")
+    @NotNull(message = "绾稿甫瀹藉害涓嶈兘涓虹┖")
+    private BigDecimal tapeWidth;
+
+    @Schema(description = "閲嶅彔瀹藉害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "閲嶅彔瀹藉害涓嶈兘涓虹┖")
+    private BigDecimal overlapWidth;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcRespVO.java
new file mode 100644
index 0000000..3fbe4e3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcAngleCalcRespVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 缁曞寘瑙掑害璁$畻 Response VO")
+@Data
+public class ProductCtcAngleCalcRespVO {
+
+    @Schema(description = "瀵肩嚎鍛ㄩ暱锛坢m锛�", example = "64.64")
+    private BigDecimal circumference;
+
+    @Schema(description = "缁曞寘瑙掑害锛埪帮級", example = "18.02")
+    private BigDecimal wrappingAngle;
+
+    @Schema(description = "缁曞寘鑺傝窛锛坢m锛�", example = "21.03")
+    private BigDecimal wrappingPitch;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcPageReqVO.java
new file mode 100644
index 0000000..c419414
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.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 ProductCtcPageReqVO extends PageParam {
+
+    @Schema(description = "鐗╂枡ID", example = "2048")
+    private Long itemId;
+
+    @Schema(description = "璁㈣揣瑙勬牸", example = "QQNB-2/120")
+    private String orderSpec;
+
+    @Schema(description = "瀵间綋绫诲瀷", example = "copper")
+    private String conductorType;
+
+    @Schema(description = "鎵ц鏍囧噯浠e彿", example = "GB/T 7095-2008")
+    private String standardCode;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcReqVO.java
new file mode 100644
index 0000000..7974161
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐩樺叿瀹归噺璁$畻 Request VO")
+@Data
+public class ProductCtcReelCalcReqVO {
+
+    @Schema(description = "鐩樺叿澶栧緞 d1锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1600")
+    @NotNull(message = "鐩樺叿澶栧緞涓嶈兘涓虹┖")
+    private BigDecimal reelOuterDiameter;
+
+    @Schema(description = "鐩樿姱鍐呭緞 d2锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1100")
+    @NotNull(message = "鐩樿姱鍐呭緞涓嶈兘涓虹┖")
+    private BigDecimal reelInnerDiameter;
+
+    @Schema(description = "鐩樺 l2锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "500")
+    @NotNull(message = "鐩樺涓嶈兘涓虹┖")
+    private BigDecimal reelWidth;
+
+    @Schema(description = "CTC 鏈�澶ч珮搴�/鍘氬害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "19.08")
+    @NotNull(message = "CTC鏈�澶ч珮搴︿笉鑳戒负绌�")
+    private BigDecimal ctcThickness;
+
+    @Schema(description = "CTC 鏈�澶у搴︼紙mm锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "12.6")
+    @NotNull(message = "CTC鏈�澶у搴︿笉鑳戒负绌�")
+    private BigDecimal ctcWidth;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcRespVO.java
new file mode 100644
index 0000000..9819637
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcReelCalcRespVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐩樺叿瀹归噺璁$畻 Response VO")
+@Data
+public class ProductCtcReelCalcRespVO {
+
+    @Schema(description = "鐩樺叿瀹归噺/姣忕洏闀垮害锛坢锛�", example = "1670.09")
+    private BigDecimal capacityMeter;
+
+    @Schema(description = "鐩樺叿瀹归噺/姣忕洏闀垮害锛坘m锛�", example = "1.67")
+    private BigDecimal capacityKm;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcReqVO.java
new file mode 100644
index 0000000..4c59053
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐩存祦鐢甸樆璁$畻 Request VO")
+@Data
+public class ProductCtcResistanceCalcReqVO {
+
+    @Schema(description = "瀵间綋鍘氬害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.27")
+    @NotNull(message = "瀵间綋鍘氬害涓嶈兘涓虹┖")
+    private BigDecimal conductorThickness;
+
+    @Schema(description = "瀵间綋瀹藉害锛坢m锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.7")
+    @NotNull(message = "瀵间綋瀹藉害涓嶈兘涓虹┖")
+    private BigDecimal conductorWidth;
+
+    @Schema(description = "璁$畻娓╁害锛堚剝锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "75")
+    @NotNull(message = "璁$畻娓╁害涓嶈兘涓虹┖")
+    private BigDecimal temperature;
+
+    @Schema(description = "瀵间綋鏉愯川锛坈opper=閾�/aluminum=閾濓級", requiredMode = Schema.RequiredMode.REQUIRED, example = "copper")
+    @NotNull(message = "瀵间綋鏉愯川涓嶈兘涓虹┖")
+    private String material;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcRespVO.java
new file mode 100644
index 0000000..c7c9b8b
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcResistanceCalcRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐩存祦鐢甸樆璁$畻 Response VO")
+@Data
+public class ProductCtcResistanceCalcRespVO {
+
+    @Schema(description = "鎴潰绉紙mm虏锛�", example = "7.239")
+    private BigDecimal crossSection;
+
+    @Schema(description = "20鈩冪洿娴佺數闃伙紙惟/km锛�", example = "2.418")
+    private BigDecimal resistance20cKm;
+
+    @Schema(description = "鎸囧畾娓╁害鐩存祦鐢甸樆锛埼�/m锛�", example = "0.002937")
+    private BigDecimal resistanceAtTemp;
+
+    @Schema(description = "鎸囧畾娓╁害鐩存祦鐢甸樆锛埼�/km锛�", example = "2.937")
+    private BigDecimal resistanceAtTempKm;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcRespVO.java
new file mode 100644
index 0000000..076d3b2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcRespVO.java
@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ProductCtcRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鐗╂枡ID锛堝叧鑱� mdm_item.id锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    private Long itemId;
+
+    @Schema(description = "鐗╂枡鍚嶇О", example = "鎹綅瀵肩嚎")
+    private String itemName;
+
+    @Schema(description = "鐗╂枡缂栫爜", example = "ML0001")
+    private String itemCode;
+
+    @Schema(description = "璁㈣揣瑙勬牸", example = "QQNB-2/120")
+    @ExcelProperty("璁㈣揣瑙勬牸")
+    private String orderSpec;
+
+    @Schema(description = "瑁稿绾胯鏍�", example = "1.27脳5.7")
+    @ExcelProperty("瑁稿绾胯鏍�")
+    private String bareConductorSpec;
+
+    @Schema(description = "婕嗗寘绾胯鏍�", example = "0.11脳5.7")
+    @ExcelProperty("婕嗗寘绾胯鏍�")
+    private String enameledSpec;
+
+    @Schema(description = "鎹綅瑙勬牸", example = "19.08脳12.6")
+    @ExcelProperty("鎹綅瑙勬牸")
+    private String transpositionSpec;
+
+    @Schema(description = "瀵间綋绫诲瀷锛堥摐/閾濓級", example = "copper")
+    @ExcelProperty(value = "瀵间綋绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.PRODUCT_CONDUCTOR_TYPE)
+    private String conductorType;
+
+    @Schema(description = "鎴潰绉紙mm虏锛�", example = "120.00")
+    @ExcelProperty("鎴潰绉�")
+    private BigDecimal crossSection;
+
+    @Schema(description = "澶栧緞锛坢m锛�", example = "19.08")
+    @ExcelProperty("澶栧緞")
+    private BigDecimal outerDiameter;
+
+    @Schema(description = "20鈩冪洿娴佺數闃伙紙惟/km锛�", example = "0.002937")
+    @ExcelProperty("20鈩冪洿娴佺數闃�")
+    private BigDecimal resistance20c;
+
+    @Schema(description = "缁曞寘瑙掑害锛埪帮級", example = "18.02")
+    @ExcelProperty("缁曞寘瑙掑害")
+    private BigDecimal wrappingAngle;
+
+    @Schema(description = "缁濈紭鍘氬害锛坢m锛�", example = "0.75")
+    @ExcelProperty("缁濈紭鍘氬害")
+    private BigDecimal insulationThickness;
+
+    @Schema(description = "棰濆畾鐢靛帇锛坘V锛�", example = "110")
+    @ExcelProperty("棰濆畾鐢靛帇")
+    private String ratedVoltage;
+
+    @Schema(description = "璇曢獙鐢靛帇锛坘V锛�", example = "230")
+    @ExcelProperty("璇曢獙鐢靛帇")
+    private String testVoltage;
+
+    @Schema(description = "鏈�灏忓集鏇插崐寰勶紙mm锛�", example = "500")
+    @ExcelProperty("鏈�灏忓集鏇插崐寰�")
+    private BigDecimal minBendingRadius;
+
+    @Schema(description = "姣忓叕閲岄噸閲忥紙kg/km锛�", example = "1200")
+    @ExcelProperty("姣忓叕閲岄噸閲�")
+    private BigDecimal weightPerKm;
+
+    @Schema(description = "鎵ц鏍囧噯浠e彿", example = "GB/T 7095-2008")
+    @ExcelProperty("鎵ц鏍囧噯")
+    private String standardCode;
+
+    @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcSaveReqVO.java
new file mode 100644
index 0000000..b36c772
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/ctc/vo/ProductCtcSaveReqVO.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁版柊澧�/淇敼 Request VO")
+@Data
+public class ProductCtcSaveReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "鐗╂枡ID锛堝叧鑱� mdm_item.id锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "鐗╂枡ID涓嶈兘涓虹┖")
+    private Long itemId;
+
+    @Schema(description = "璁㈣揣瑙勬牸", example = "QQNB-2/120")
+    private String orderSpec;
+
+    @Schema(description = "瑁稿绾胯鏍�", example = "1.27脳5.7")
+    private String bareConductorSpec;
+
+    @Schema(description = "婕嗗寘绾胯鏍�", example = "0.11脳5.7")
+    private String enameledSpec;
+
+    @Schema(description = "鎹綅瑙勬牸", example = "19.08脳12.6")
+    private String transpositionSpec;
+
+    @Schema(description = "瀵间綋绫诲瀷锛堥摐/閾濓級", example = "copper")
+    private String conductorType;
+
+    @Schema(description = "鎴潰绉紙mm虏锛�", example = "120.00")
+    private BigDecimal crossSection;
+
+    @Schema(description = "澶栧緞锛坢m锛�", example = "19.08")
+    private BigDecimal outerDiameter;
+
+    @Schema(description = "20鈩冪洿娴佺數闃伙紙惟/km锛�", example = "0.002937")
+    private BigDecimal resistance20c;
+
+    @Schema(description = "缁曞寘瑙掑害锛埪帮級", example = "18.02")
+    private BigDecimal wrappingAngle;
+
+    @Schema(description = "缁濈紭鍘氬害锛坢m锛�", example = "0.75")
+    private BigDecimal insulationThickness;
+
+    @Schema(description = "棰濆畾鐢靛帇锛坘V锛�", example = "110")
+    private String ratedVoltage;
+
+    @Schema(description = "璇曢獙鐢靛帇锛坘V锛�", example = "230")
+    private String testVoltage;
+
+    @Schema(description = "鏈�灏忓集鏇插崐寰勶紙mm锛�", example = "500")
+    private BigDecimal minBendingRadius;
+
+    @Schema(description = "姣忓叕閲岄噸閲忥紙kg/km锛�", example = "1200")
+    private BigDecimal weightPerKm;
+
+    @Schema(description = "鎵ц鏍囧噯浠e彿", example = "GB/T 7095-2008")
+    private String standardCode;
+
+    @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/MesPdPriceController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/MesPdPriceController.java
new file mode 100644
index 0000000..ad67e34
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/MesPdPriceController.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.price;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcRespVO;
+import cn.iocoder.yudao.module.mes.service.pd.price.MesPdPriceService;
+import io.swagger.v3.oas.annotations.Operation;
+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.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 闃舵鐢典环娴嬬畻")
+@RestController
+@RequestMapping("/mes/pd/price")
+@Validated
+public class MesPdPriceController {
+
+    @Resource
+    private MesPdPriceService priceService;
+
+    @PostMapping("/calc")
+    @Operation(summary = "闃舵鐢典环娴嬬畻锛氬垎妗g疮璁� + 浼樻儬绛栫暐")
+    @PreAuthorize("@ss.hasPermission('mes:pd-basedata:query')")
+    public CommonResult<MesPdPriceCalcRespVO> calcPrice(@Valid @RequestBody MesPdPriceCalcReqVO calcReqVO) {
+        return success(priceService.calcPrice(calcReqVO));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcDetailRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcDetailRespVO.java
new file mode 100644
index 0000000..d200d6d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcDetailRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 闃舵鐢典环娴嬬畻鍒嗘。鏄庣粏 Response VO")
+@Data
+public class MesPdPriceCalcDetailRespVO {
+
+    @Schema(description = "妗d綅涓嬮檺锛堝惈锛宬Wh锛�")
+    private BigDecimal startValue;
+
+    @Schema(description = "妗d綅涓婇檺锛堜笉鍚紝kWh锛屽彲绌�=涓婁笉灏侀《锛�")
+    private BigDecimal endValue;
+
+    @Schema(description = "钀藉叆鏈。鐢甸噺锛坘Wh锛�")
+    private BigDecimal tierQuantity;
+
+    @Schema(description = "妗d綅鍗曚环")
+    private BigDecimal pricePerUnit;
+
+    @Schema(description = "灏忚")
+    private BigDecimal subtotal;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcReqVO.java
new file mode 100644
index 0000000..cdd4722
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 闃舵鐢典环娴嬬畻 Request VO")
+@Data
+public class MesPdPriceCalcReqVO {
+
+    @Schema(description = "娴嬬畻绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "娴嬬畻绫诲瀷涓嶈兘涓虹┖")
+    private Integer calcType;
+
+    @Schema(description = "鏈嶅姟濂楅ID锛坈alcType=1 鏃跺繀濉級")
+    private Long packageId;
+
+    @Schema(description = "鐢靛帇绛夌骇ID锛坈alcType=2 鏃跺繀濉級")
+    private Long voltageLevelId;
+
+    @Schema(description = "鐢ㄧ數閲忥紙kWh锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "500")
+    @NotNull(message = "鐢ㄧ數閲忎笉鑳戒负绌�")
+    private BigDecimal quantity;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcRespVO.java
new file mode 100644
index 0000000..11115de
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/price/vo/MesPdPriceCalcRespVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 闃舵鐢典环娴嬬畻 Response VO")
+@Data
+public class MesPdPriceCalcRespVO {
+
+    @Schema(description = "鏈嶅姟濂楅鍚嶇О")
+    private String packageName;
+
+    @Schema(description = "鐢靛帇绛夌骇鍚嶇О")
+    private String voltageLevelName;
+
+    @Schema(description = "鍘熶环锛堟湭浼樻儬锛�")
+    private BigDecimal originalPrice;
+
+    @Schema(description = "浼樻儬閲戦")
+    private BigDecimal discountAmount;
+
+    @Schema(description = "浼樻儬鍚庢�讳环")
+    private BigDecimal finalPrice;
+
+    @Schema(description = "鍒嗘。鏄庣粏")
+    private List<MesPdPriceCalcDetailRespVO> details;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/MesPdProductController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/MesPdProductController.java
new file mode 100644
index 0000000..5e62315
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/MesPdProductController.java
@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.product;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductAuditReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductSaveReqVO;
+import cn.iocoder.yudao.module.mes.service.pd.product.MesPdProductService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 浜у搧妗f")
+@RestController
+@RequestMapping("/mes/pd/product")
+@Validated
+public class MesPdProductController {
+
+    @Resource
+    private MesPdProductService productService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓浜у搧妗f")
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:create')")
+    public CommonResult<Long> createProduct(@Valid @RequestBody MesPdProductSaveReqVO createReqVO) {
+        return success(productService.createProduct(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊浜у搧妗f")
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:update')")
+    public CommonResult<Boolean> updateProduct(@Valid @RequestBody MesPdProductSaveReqVO updateReqVO) {
+        productService.updateProduct(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎浜у搧妗f")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:delete')")
+    public CommonResult<Boolean> deleteProduct(@RequestParam("id") Long id) {
+        productService.deleteProduct(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱浜у搧妗f")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:query')")
+    public CommonResult<MesPdProductRespVO> getProduct(@RequestParam("id") Long id) {
+        return success(productService.getProduct(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱浜у搧妗f鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:query')")
+    public CommonResult<PageResult<MesPdProductRespVO>> getProductPage(@Valid MesPdProductPageReqVO pageReqVO) {
+        return success(productService.getProductPage(pageReqVO));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭浜у搧妗f Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportProductExcel(@Valid MesPdProductPageReqVO pageReqVO,
+                                   HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        PageResult<MesPdProductRespVO> pageResult = productService.getProductPage(pageReqVO);
+        ExcelUtils.write(response, "浜у搧妗f.xls", "鏁版嵁", MesPdProductRespVO.class, pageResult.getList());
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦瀹℃壒锛堟湭鎻愪氦/椹冲洖鈫掑鎵逛腑锛�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:submit')")
+    public CommonResult<Boolean> submitProduct(@RequestParam("id") Long id) {
+        productService.submitProduct(id);
+        return success(true);
+    }
+
+    @PutMapping("/audit")
+    @Operation(summary = "瀹℃壒浜у搧妗f锛堝鎵逛腑鈫掗�氳繃/椹冲洖锛�")
+    @PreAuthorize("@ss.hasPermission('mes:pd-product:audit')")
+    public CommonResult<Boolean> auditProduct(@Valid @RequestBody MesPdProductAuditReqVO auditReqVO) {
+        productService.auditProduct(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getAuditRemark());
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductAuditReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductAuditReqVO.java
new file mode 100644
index 0000000..edaf44f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductAuditReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 浜у搧妗f瀹℃壒 Request VO")
+@Data
+public class MesPdProductAuditReqVO {
+
+    @Schema(description = "浜у搧妗f缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "浜у搧妗f缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "瀹℃壒缁撴灉锛歵rue=閫氳繃 / false=椹冲洖", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "瀹℃壒缁撴灉涓嶈兘涓虹┖")
+    private Boolean pass;
+
+    @Schema(description = "瀹℃壒鎰忚")
+    private String auditRemark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductPageReqVO.java
new file mode 100644
index 0000000..2913d8a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.product.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 = "绠$悊鍚庡彴 - 浜у搧妗f鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesPdProductPageReqVO extends PageParam {
+
+    @Schema(description = "妗f绫诲瀷", example = "1")
+    private Integer archiveType;
+
+    @Schema(description = "妗f鍚嶇О", example = "鐢靛姏鎶慨鏈嶅姟")
+    private String name;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer auditStatus;
+
+    @Schema(description = "鍚敤鐘舵��", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductRespVO.java
new file mode 100644
index 0000000..e692bf1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductRespVO.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 浜у搧妗f Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesPdProductRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "妗f绫诲瀷", example = "1")
+    @ExcelProperty(value = "妗f绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_PD_PRODUCT_ARCHIVE_TYPE)
+    private Integer archiveType;
+
+    @Schema(description = "妗f绫诲瀷鍚嶇О", example = "鐢靛姏鏈嶅姟")
+    private String archiveTypeName;
+
+    @Schema(description = "妗f缂栫爜", example = "P_20260820_0001")
+    @ExcelProperty("妗f缂栫爜")
+    private String code;
+
+    @Schema(description = "妗f鍚嶇О", example = "鐢靛姏鎶慨鏈嶅姟")
+    @ExcelProperty("妗f鍚嶇О")
+    private String name;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "24h")
+    @ExcelProperty("瑙勬牸鍨嬪彿")
+    private String spec;
+
+    @Schema(description = "鍗曚綅", example = "娆�")
+    @ExcelProperty("鍗曚綅")
+    private String unit;
+
+    @Schema(description = "鍩哄噯浠锋牸", example = "500.00")
+    @ExcelProperty("鍩哄噯浠锋牸")
+    private BigDecimal price;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    @ExcelProperty(value = "瀹℃壒鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_PD_PRODUCT_AUDIT_STATUS)
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒鐘舵�佸悕绉�", example = "鏈彁浜�")
+    private String auditStatusName;
+
+    @Schema(description = "瀹℃壒鎰忚")
+    private String auditRemark;
+
+    @Schema(description = "瀹℃壒鏃堕棿")
+    private LocalDateTime auditTime;
+
+    @Schema(description = "鍚敤鐘舵��", example = "1")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductSaveReqVO.java
new file mode 100644
index 0000000..1085add
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/product/vo/MesPdProductSaveReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 浜у搧妗f鏂板/淇敼 Request VO")
+@Data
+public class MesPdProductSaveReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "妗f绫诲瀷锛�1鐢靛姏鏈嶅姟/2渚涚數浜у搧/3澧炲�艰兘婧愪骇鍝侊級", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "妗f绫诲瀷涓嶈兘涓虹┖")
+    private Integer archiveType;
+
+    @Schema(description = "妗f鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "妗f鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "瑙勬牸鍨嬪彿")
+    private String spec;
+
+    @Schema(description = "鍗曚綅")
+    private String unit;
+
+    @Schema(description = "鍩哄噯浠锋牸")
+    private BigDecimal price;
+
+    @Schema(description = "鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍚敤鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/ProductCtcReelController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/ProductCtcReelController.java
new file mode 100644
index 0000000..8dca240
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/ProductCtcReelController.java
@@ -0,0 +1,123 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.reel;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc.ProductCtcDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.reel.ProductCtcReelDO;
+import cn.iocoder.yudao.module.mes.service.pd.ctc.ProductCtcService;
+import cn.iocoder.yudao.module.mes.service.pd.reel.ProductCtcReelService;
+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.Collections;
+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.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - 鎹綅瀵肩嚎鐩樺叿璁$畻")
+@RestController
+@RequestMapping("/mes/pd/ctc-reel")
+@Validated
+public class ProductCtcReelController {
+
+    @Resource
+    private ProductCtcReelService reelService;
+
+    @Resource
+    private ProductCtcService ctcService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鐩樺叿璁$畻璁板綍")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc-reel:create')")
+    public CommonResult<Long> createReel(@Valid @RequestBody ProductCtcReelSaveReqVO createReqVO) {
+        return success(reelService.createReel(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鐩樺叿璁$畻璁板綍")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc-reel:update')")
+    public CommonResult<Boolean> updateReel(@Valid @RequestBody ProductCtcReelSaveReqVO updateReqVO) {
+        reelService.updateReel(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鐩樺叿璁$畻璁板綍")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc-reel:delete')")
+    public CommonResult<Boolean> deleteReel(@RequestParam("id") Long id) {
+        reelService.deleteReel(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鐩樺叿璁$畻璁板綍")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc-reel:query')")
+    public CommonResult<ProductCtcReelRespVO> getReel(@RequestParam("id") Long id) {
+        ProductCtcReelDO reel = reelService.getReel(id);
+        if (reel == null) {
+            return success(null);
+        }
+        return success(buildReelRespVOList(Collections.singletonList(reel)).get(0));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鐩樺叿璁$畻璁板綍鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc-reel:query')")
+    public CommonResult<PageResult<ProductCtcReelRespVO>> getReelPage(@Valid ProductCtcReelPageReqVO pageReqVO) {
+        PageResult<ProductCtcReelDO> pageResult = reelService.getReelPage(pageReqVO);
+        return success(new PageResult<>(buildReelRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    @GetMapping("/list-by-ctc")
+    @Operation(summary = "鑾峰緱鎸囧畾鎹綅瀵肩嚎浜у搧鐨勭洏鍏疯绠楄褰曞垪琛�")
+    @Parameter(name = "ctcId", description = "鎹綅瀵肩嚎浜у搧ID", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-ctc-reel:query')")
+    public CommonResult<List<ProductCtcReelRespVO>> getReelListByCtcId(@RequestParam("ctcId") Long ctcId) {
+        List<ProductCtcReelDO> list = reelService.getReelListByCtcId(ctcId);
+        return success(buildReelRespVOList(list));
+    }
+
+    // ==================== 鎷兼帴 VO ====================
+
+    private List<ProductCtcReelRespVO> buildReelRespVOList(List<ProductCtcReelDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        // 鎵归噺鑾峰彇鎹綅瀵肩嚎浜у搧璁㈣揣瑙勬牸
+        Map<Long, ProductCtcDO> ctcFullMap = loadCtcMap(convertSet(list, ProductCtcReelDO::getCtcId));
+        // 鎷兼帴 VO
+        return BeanUtils.toBean(list, ProductCtcReelRespVO.class, vo -> {
+            MapUtils.findAndThen(ctcFullMap, vo.getCtcId(),
+                    ctc -> vo.setCtcOrderSpec(ctc.getOrderSpec()));
+        });
+    }
+
+    private Map<Long, ProductCtcDO> loadCtcMap(java.util.Set<Long> ctcIds) {
+        if (CollUtil.isEmpty(ctcIds)) {
+            return Collections.emptyMap();
+        }
+        java.util.List<ProductCtcDO> list = ctcIds.stream()
+                .map(ctcService::getCtc)
+                .filter(java.util.Objects::nonNull)
+                .toList();
+        return convertMap(list, ProductCtcDO::getId);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelPageReqVO.java
new file mode 100644
index 0000000..d012c96
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelPageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.reel.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 ProductCtcReelPageReqVO extends PageParam {
+
+    @Schema(description = "鎹綅瀵肩嚎浜у搧ID", example = "2048")
+    private Long ctcId;
+
+    @Schema(description = "鐩樺叿绫诲瀷", example = "wood")
+    private String reelType;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelRespVO.java
new file mode 100644
index 0000000..9542f44
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelRespVO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎹綅瀵肩嚎鐩樺叿璁$畻 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ProductCtcReelRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鎹綅瀵肩嚎浜у搧ID锛堝叧鑱� product_ctc.id锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    private Long ctcId;
+
+    @Schema(description = "鎹綅瀵肩嚎璁㈣揣瑙勬牸", example = "QQNB-2/120")
+    private String ctcOrderSpec;
+
+    @Schema(description = "鐩樺叿绫诲瀷锛堟湪鐩�/閾佺洏/閽㈢洏锛�", example = "wood")
+    @ExcelProperty(value = "鐩樺叿绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.PRODUCT_REEL_TYPE)
+    private String reelType;
+
+    @Schema(description = "鐩樺叿澶栧緞锛坢m锛�", example = "1600")
+    @ExcelProperty("鐩樺叿澶栧緞")
+    private BigDecimal reelOuterDiameter;
+
+    @Schema(description = "鐩樿姱鍐呭緞锛坢m锛�", example = "1100")
+    @ExcelProperty("鐩樿姱鍐呭緞")
+    private BigDecimal reelInnerDiameter;
+
+    @Schema(description = "鐩樺锛坢m锛�", example = "500")
+    @ExcelProperty("鐩樺")
+    private BigDecimal reelWidth;
+
+    @Schema(description = "鏈�澶у閲忥紙km锛�", example = "1.67")
+    @ExcelProperty("鏈�澶у閲�")
+    private BigDecimal maxCapacity;
+
+    @Schema(description = "鎺ㄨ崘姣忕洏闀垮害锛坘m锛�", example = "1.50")
+    @ExcelProperty("鎺ㄨ崘姣忕洏闀垮害")
+    private BigDecimal recommendedLength;
+
+    @Schema(description = "鍑�閲嶏紙kg锛�", example = "800")
+    @ExcelProperty("鍑�閲�")
+    private BigDecimal netWeight;
+
+    @Schema(description = "姣涢噸锛坘g锛�", example = "1200")
+    @ExcelProperty("姣涢噸")
+    private BigDecimal grossWeight;
+
+    @Schema(description = "璁$畻鍏紡锛堜繚瀛樿绠楅�昏緫锛�", example = "((d1-60)+(d2+10))*INT(l2/(瀹�+1))*(d1-d2-70)/(楂�+0.2)/1274")
+    private String calcFormula;
+
+    @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelSaveReqVO.java
new file mode 100644
index 0000000..ed2f55a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/reel/vo/ProductCtcReelSaveReqVO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎹綅瀵肩嚎鐩樺叿璁$畻鏂板/淇敼 Request VO")
+@Data
+public class ProductCtcReelSaveReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "鎹綅瀵肩嚎浜у搧ID锛堝叧鑱� product_ctc.id锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "鎹綅瀵肩嚎浜у搧ID涓嶈兘涓虹┖")
+    private Long ctcId;
+
+    @Schema(description = "鐩樺叿绫诲瀷锛堟湪鐩�/閾佺洏/閽㈢洏锛�", example = "wood")
+    private String reelType;
+
+    @Schema(description = "鐩樺叿澶栧緞锛坢m锛�", example = "1600")
+    private BigDecimal reelOuterDiameter;
+
+    @Schema(description = "鐩樿姱鍐呭緞锛坢m锛�", example = "1100")
+    private BigDecimal reelInnerDiameter;
+
+    @Schema(description = "鐩樺锛坢m锛�", example = "500")
+    private BigDecimal reelWidth;
+
+    @Schema(description = "鏈�澶у閲忥紙km锛�", example = "1.67")
+    private BigDecimal maxCapacity;
+
+    @Schema(description = "鎺ㄨ崘姣忕洏闀垮害锛坘m锛�", example = "1.50")
+    private BigDecimal recommendedLength;
+
+    @Schema(description = "鍑�閲嶏紙kg锛�", example = "800")
+    private BigDecimal netWeight;
+
+    @Schema(description = "姣涢噸锛坘g锛�", example = "1200")
+    private BigDecimal grossWeight;
+
+    @Schema(description = "璁$畻鍏紡锛堜繚瀛樿绠楅�昏緫锛�", example = "((d1-60)+(d2+10))*INT(l2/(瀹�+1))*(d1-d2-70)/(楂�+0.2)/1274")
+    private String calcFormula;
+
+    @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/ProductTechConfirmController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/ProductTechConfirmController.java
new file mode 100644
index 0000000..05cdfc2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/ProductTechConfirmController.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
+import cn.iocoder.yudao.module.mes.service.pd.techconfirm.ProductTechConfirmService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 浜у搧鎶�鏈‘璁ゅ嚱")
+@RestController
+@RequestMapping("/mes/pd/tech-confirm")
+@Validated
+public class ProductTechConfirmController {
+
+    @Resource
+    private ProductTechConfirmService techConfirmService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓浜у搧鎶�鏈‘璁ゅ嚱")
+    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:create')")
+    public CommonResult<Long> createTechConfirm(@Valid @RequestBody ProductTechConfirmSaveReqVO createReqVO) {
+        return success(techConfirmService.createTechConfirm(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊浜у搧鎶�鏈‘璁ゅ嚱")
+    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:update')")
+    public CommonResult<Boolean> updateTechConfirm(@Valid @RequestBody ProductTechConfirmSaveReqVO updateReqVO) {
+        techConfirmService.updateTechConfirm(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎浜у搧鎶�鏈‘璁ゅ嚱")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:delete')")
+    public CommonResult<Boolean> deleteTechConfirm(@RequestParam("id") Long id) {
+        techConfirmService.deleteTechConfirm(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱浜у搧鎶�鏈‘璁ゅ嚱")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:query')")
+    public CommonResult<ProductTechConfirmRespVO> getTechConfirm(@RequestParam("id") Long id) {
+        ProductTechConfirmDO techConfirm = techConfirmService.getTechConfirm(id);
+        if (techConfirm == null) {
+            return success(null);
+        }
+        return success(BeanUtils.toBean(techConfirm, ProductTechConfirmRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱浜у搧鎶�鏈‘璁ゅ嚱鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:query')")
+    public CommonResult<PageResult<ProductTechConfirmRespVO>> getTechConfirmPage(@Valid ProductTechConfirmPageReqVO pageReqVO) {
+        PageResult<ProductTechConfirmDO> pageResult = techConfirmService.getTechConfirmPage(pageReqVO);
+        return success(new PageResult<>(BeanUtils.toBean(pageResult.getList(), ProductTechConfirmRespVO.class),
+                pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭浜у搧鎶�鏈‘璁ゅ嚱 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportTechConfirmExcel(@Valid ProductTechConfirmPageReqVO pageReqVO,
+                                       HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<ProductTechConfirmDO> list = techConfirmService.getTechConfirmPage(pageReqVO).getList();
+        ExcelUtils.write(response, "浜у搧鎶�鏈‘璁ゅ嚱.xls", "鏁版嵁", ProductTechConfirmRespVO.class,
+                BeanUtils.toBean(list, ProductTechConfirmRespVO.class));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmPageReqVO.java
new file mode 100644
index 0000000..9900a52
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.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 ProductTechConfirmPageReqVO extends PageParam {
+
+    @Schema(description = "纭鍑界紪鍙�", example = "TC20260801001")
+    private String confirmNo;
+
+    @Schema(description = "瀹㈡埛ID", example = "2048")
+    private Long customerId;
+
+    @Schema(description = "浜у搧鍚嶇О", example = "婕嗗寘鎵侀摐绾�")
+    private String productName;
+
+    @Schema(description = "妯℃澘绫诲瀷", example = "general")
+    private String templateType;
+
+    @Schema(description = "鐘舵��", example = "0")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmRespVO.java
new file mode 100644
index 0000000..12281cd
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmRespVO.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 浜у搧鎶�鏈‘璁ゅ嚱 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ProductTechConfirmRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "纭鍑界紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "TC20260801001")
+    @ExcelProperty("纭鍑界紪鍙�")
+    private String confirmNo;
+
+    @Schema(description = "瀹㈡埛ID锛堝叧鑱� crm_customer.id锛�", example = "2048")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О", example = "姹熻嫃鍗庨箯鍙樺帇鍣ㄦ湁闄愬叕鍙�")
+    @ExcelProperty("瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "浜у搧鍚嶇О", example = "婕嗗寘鎵侀摐绾�")
+    @ExcelProperty("浜у搧鍚嶇О")
+    private String productName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "QQNB-2/120")
+    @ExcelProperty("瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @Schema(description = "鎶�鏈弬鏁帮紙JSON 鏍煎紡锛�", example = "{\"thickness\":\"1.27\"}")
+    private String techParams;
+
+    @Schema(description = "妯℃澘绫诲瀷锛堝ゥ鍦板埄/鏃ユ湰/閫氱敤锛�", example = "general")
+    @ExcelProperty(value = "妯℃澘绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.PRODUCT_TECH_CONFIRM_TEMPLATE)
+    private String templateType;
+
+    @Schema(description = "缂栧埗浜篒D", example = "1")
+    private Long drafterId;
+
+    @Schema(description = "缂栧埗浜哄鍚�", example = "鑳′笢")
+    @ExcelProperty("缂栧埗浜�")
+    private String drafterName;
+
+    @Schema(description = "缂栧埗鏃堕棿")
+    @ExcelProperty("缂栧埗鏃堕棿")
+    private LocalDateTime draftTime;
+
+    @Schema(description = "瀹℃牳浜篒D", example = "2")
+    private Long reviewerId;
+
+    @Schema(description = "瀹℃牳浜哄鍚�", example = "鏉庡伐")
+    @ExcelProperty("瀹℃牳浜�")
+    private String reviewerName;
+
+    @Schema(description = "瀹℃牳鏃堕棿")
+    @ExcelProperty("瀹℃牳鏃堕棿")
+    private LocalDateTime reviewTime;
+
+    @Schema(description = "瀹㈡埛纭浜�", example = "鐜嬬粡鐞�")
+    @ExcelProperty("瀹㈡埛纭浜�")
+    private String customerConfirmUser;
+
+    @Schema(description = "瀹㈡埛纭鏃堕棿")
+    @ExcelProperty("瀹㈡埛纭鏃堕棿")
+    private LocalDateTime customerConfirmTime;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�1=寰呭鏍革紝2=宸插鏍革紝3=瀹㈡埛宸茬‘璁わ級", example = "0")
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.PRODUCT_TECH_CONFIRM_STATUS)
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmSaveReqVO.java
new file mode 100644
index 0000000..2891aaa
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmSaveReqVO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 浜у搧鎶�鏈‘璁ゅ嚱鏂板/淇敼 Request VO")
+@Data
+public class ProductTechConfirmSaveReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "纭鍑界紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "TC20260801001")
+    private String confirmNo;
+
+    @Schema(description = "瀹㈡埛ID锛堝叧鑱� crm_customer.id锛�", example = "2048")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О", example = "姹熻嫃鍗庨箯鍙樺帇鍣ㄦ湁闄愬叕鍙�")
+    private String customerName;
+
+    @Schema(description = "浜у搧鍚嶇О", example = "婕嗗寘鎵侀摐绾�")
+    private String productName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "QQNB-2/120")
+    private String specification;
+
+    @Schema(description = "鎶�鏈弬鏁帮紙JSON 鏍煎紡锛�", example = "{\"thickness\":\"1.27\"}")
+    private String techParams;
+
+    @Schema(description = "妯℃澘绫诲瀷锛堝ゥ鍦板埄/鏃ユ湰/閫氱敤锛�", example = "general")
+    private String templateType;
+
+    @Schema(description = "缂栧埗浜篒D", example = "1")
+    private Long drafterId;
+
+    @Schema(description = "缂栧埗浜哄鍚�", example = "鑳′笢")
+    private String drafterName;
+
+    @Schema(description = "缂栧埗鏃堕棿")
+    private LocalDateTime draftTime;
+
+    @Schema(description = "瀹℃牳浜篒D", example = "2")
+    private Long reviewerId;
+
+    @Schema(description = "瀹℃牳浜哄鍚�", example = "鏉庡伐")
+    private String reviewerName;
+
+    @Schema(description = "瀹℃牳鏃堕棿")
+    private LocalDateTime reviewTime;
+
+    @Schema(description = "瀹㈡埛纭浜�", example = "鐜嬬粡鐞�")
+    private String customerConfirmUser;
+
+    @Schema(description = "瀹㈡埛纭鏃堕棿")
+    private LocalDateTime customerConfirmTime;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�1=寰呭鏍革紝2=宸插鏍革紝3=瀹㈡埛宸茬‘璁わ級", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/MesProEmergencyPlanController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/MesProEmergencyPlanController.java
new file mode 100644
index 0000000..8dc7394
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/MesProEmergencyPlanController.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan;
+
+import cn.hutool.core.collection.CollUtil;
+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.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
+import cn.iocoder.yudao.module.mes.service.pro.emergencyplan.MesProEmergencyPlanService;
+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 = "绠$悊鍚庡彴 - MES 搴旀�ュ缃妗�")
+@RestController
+@RequestMapping("/mes/pro/emergency-plan")
+@Validated
+public class MesProEmergencyPlanController {
+
+    @Resource
+    private MesProEmergencyPlanService planService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓搴旀�ュ缃妗�")
+    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:create')")
+    public CommonResult<Long> createPlan(@Valid @RequestBody MesProEmergencyPlanSaveReqVO createReqVO) {
+        return success(planService.createPlan(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊搴旀�ュ缃妗�")
+    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:update')")
+    public CommonResult<Boolean> updatePlan(@Valid @RequestBody MesProEmergencyPlanSaveReqVO updateReqVO) {
+        planService.updatePlan(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎搴旀�ュ缃妗�")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:delete')")
+    public CommonResult<Boolean> deletePlan(@RequestParam("ids") List<Long> ids) {
+        planService.deletePlan(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱搴旀�ュ缃妗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:query')")
+    public CommonResult<MesProEmergencyPlanRespVO> getPlan(@RequestParam("id") Long id) {
+        MesProEmergencyPlanDO plan = planService.getPlan(id);
+        return success(plan != null ? BeanUtils.toBean(plan, MesProEmergencyPlanRespVO.class) : null);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱搴旀�ュ缃妗堝垎椤�")
+    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:query')")
+    public CommonResult<PageResult<MesProEmergencyPlanRespVO>> getPlanPage(@Valid MesProEmergencyPlanPageReqVO pageReqVO) {
+        PageResult<MesProEmergencyPlanDO> pageResult = planService.getPlanPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        return success(BeanUtils.toBean(pageResult, MesProEmergencyPlanRespVO.class));
+    }
+
+    @PutMapping("/publish")
+    @Operation(summary = "鍙戝竷搴旀�ュ缃妗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:update')")
+    public CommonResult<Boolean> publishPlan(@RequestParam("id") Long id) {
+        planService.publishPlan(id);
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanPageReqVO.java
new file mode 100644
index 0000000..48f7426
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.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 = "绠$悊鍚庡彴 - MES 搴旀�ュ缃妗堝垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesProEmergencyPlanPageReqVO extends PageParam {
+
+    @Schema(description = "棰勬缂栫爜", example = "YJ20260818001")
+    private String code;
+
+    @Schema(description = "棰勬鍚嶇О", example = "鍙樼數绔欏仠鐢靛缃妗�")
+    private String name;
+
+    @Schema(description = "棰勬绫诲瀷锛�1=鍋滅數锛�2=璁惧鏁呴殰锛�3=鐏伨锛�4=鑷劧鐏惧锛�5=鍏朵粬锛�", example = "1")
+    private Integer planType;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸插彂甯冿級", example = "0")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanRespVO.java
new file mode 100644
index 0000000..c192d73
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 搴旀�ュ缃妗� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesProEmergencyPlanRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "棰勬缂栫爜", example = "YJ20260818001")
+    @ExcelProperty("棰勬缂栫爜")
+    private String code;
+
+    @Schema(description = "棰勬鍚嶇О", example = "鍙樼數绔欏仠鐢靛缃妗�")
+    @ExcelProperty("棰勬鍚嶇О")
+    private String name;
+
+    @Schema(description = "棰勬绫诲瀷", example = "1")
+    @ExcelProperty(value = "棰勬绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_PRO_EMERGENCY_PLAN_TYPE)
+    private Integer planType;
+
+    @Schema(description = "澶勭疆鍐呭", example = "1. 绔嬪嵆闅旂鏁呴殰...")
+    private String content;
+
+    @Schema(description = "璐d换鍗曚綅/閮ㄩ棬", example = "杩愮淮閮�")
+    @ExcelProperty("璐d换鍗曚綅")
+    private String orgUnit;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸插彂甯冿級", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanSaveReqVO.java
new file mode 100644
index 0000000..f363547
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanSaveReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 搴旀�ュ缃妗堟柊澧�/淇敼 Request VO")
+@Data
+public class MesProEmergencyPlanSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "棰勬鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍙樼數绔欏仠鐢靛缃妗�")
+    @NotEmpty(message = "棰勬鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "棰勬绫诲瀷锛�1=鍋滅數锛�2=璁惧鏁呴殰锛�3=鐏伨锛�4=鑷劧鐏惧锛�5=鍏朵粬锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "棰勬绫诲瀷涓嶈兘涓虹┖")
+    private Integer planType;
+
+    @Schema(description = "澶勭疆鍐呭", example = "1. 绔嬪嵆闅旂鏁呴殰...")
+    private String content;
+
+    @Schema(description = "璐d换鍗曚綅/閮ㄩ棬", example = "杩愮淮閮�")
+    private String orgUnit;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
index db782f7..c67f8d6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
@@ -17,6 +17,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackQuickCreateRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSummaryRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
@@ -43,6 +44,7 @@
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
+import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -61,6 +63,7 @@
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 
 import java.io.IOException;
+import java.time.LocalDate;
 import java.util.*;
 import java.util.stream.Stream;
 
@@ -251,6 +254,16 @@
         return success(trace);
     }
 
+    @GetMapping("/summary")
+    @Operation(summary = "浣滀笟鎴愭灉姹囨�荤粺璁�", description = "鎸夋椂闂磋寖鍥存眹鎬诲凡鎵瑰噯鎶ュ伐鏁伴噺/鍚堟牸鏁伴噺锛屾寜宸ュ崟绫诲瀷銆佹棩鏈熷垎缁�")
+    @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')")
+    public CommonResult<MesProFeedbackSummaryRespVO> getFeedbackSummary(
+            @RequestParam(value = "beginDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate beginDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate,
+            @RequestParam(value = "workOrderType", required = false) Integer workOrderType) {
+        return success(feedbackService.getFeedbackSummary(beginDate, endDate, workOrderType));
+    }
+
     private List<MesProFeedbackRespVO> buildFeedbackRespVOList(List<MesProFeedbackDO> list) {
         if (CollUtil.isEmpty(list)) {
             return Collections.emptyList();
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSummaryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSummaryRespVO.java
new file mode 100644
index 0000000..fe2f86a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSummaryRespVO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢熶骇鎶ュ伐浣滀笟鎴愭灉姹囨�� Response VO")
+@Data
+public class MesProFeedbackSummaryRespVO {
+
+    @Schema(description = "绱鎶ュ伐鏁伴噺")
+    private BigDecimal totalFeedbackQuantity;
+
+    @Schema(description = "绱鍚堟牸鏁伴噺")
+    private BigDecimal totalQualifiedQuantity;
+
+    @Schema(description = "绱鎶ュ伐鍗曟暟")
+    private Integer totalFeedbackCount;
+
+    @Schema(description = "鎸夊伐鍗曠被鍨嬫眹鎬�")
+    private List<TypeSummaryItem> byType;
+
+    @Schema(description = "鎸夋棩鏈熸眹鎬�")
+    private List<DateSummaryItem> byDate;
+
+    @Schema(description = "鎸夊伐鍗曠被鍨嬫眹鎬婚」")
+    @Data
+    public static class TypeSummaryItem {
+
+        @Schema(description = "宸ュ崟绫诲瀷")
+        private Integer workOrderType;
+
+        @Schema(description = "宸ュ崟绫诲瀷鍚嶇О")
+        private String workOrderTypeName;
+
+        @Schema(description = "鎶ュ伐鏁伴噺")
+        private BigDecimal feedbackQuantity;
+
+        @Schema(description = "鍚堟牸鏁伴噺")
+        private BigDecimal qualifiedQuantity;
+
+        @Schema(description = "鎶ュ伐鍗曟暟")
+        private Integer feedbackCount;
+    }
+
+    @Schema(description = "鎸夋棩鏈熸眹鎬婚」")
+    @Data
+    public static class DateSummaryItem {
+
+        @Schema(description = "鏃ユ湡")
+        private LocalDate date;
+
+        @Schema(description = "鎶ュ伐鏁伴噺")
+        private BigDecimal feedbackQuantity;
+
+        @Schema(description = "鍚堟牸鏁伴噺")
+        private BigDecimal qualifiedQuantity;
+
+        @Schema(description = "鎶ュ伐鍗曟暟")
+        private Integer feedbackCount;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/MesProMaintenancePlanController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/MesProMaintenancePlanController.java
new file mode 100644
index 0000000..a280a0f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/MesProMaintenancePlanController.java
@@ -0,0 +1,175 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanItemDO;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.pro.maintenanceplan.MesProMaintenancePlanService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 鐢靛姏浣滀笟璁″垝")
+@RestController
+@RequestMapping("/mes/pro/maintenance-plan")
+@Validated
+public class MesProMaintenancePlanController {
+
+    @Resource
+    private MesProMaintenancePlanService planService;
+    @Resource
+    private MesMdItemService itemService;
+    @Resource
+    private MesMdUnitMeasureService unitMeasureService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鐢靛姏浣滀笟璁″垝")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:create')")
+    public CommonResult<Long> createPlan(@Valid @RequestBody MesProMaintenancePlanSaveReqVO createReqVO) {
+        return success(planService.createPlan(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鐢靛姏浣滀笟璁″垝")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:update')")
+    public CommonResult<Boolean> updatePlan(@Valid @RequestBody MesProMaintenancePlanSaveReqVO updateReqVO) {
+        planService.updatePlan(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鐢靛姏浣滀笟璁″垝")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:delete')")
+    public CommonResult<Boolean> deletePlan(@RequestParam("ids") List<Long> ids) {
+        planService.deletePlan(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鐢靛姏浣滀笟璁″垝")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:query')")
+    public CommonResult<MesProMaintenancePlanRespVO> getPlan(@RequestParam("id") Long id) {
+        MesProMaintenancePlanDO plan = planService.getPlan(id);
+        if (plan == null) {
+            return success(null);
+        }
+        List<MesProMaintenancePlanItemDO> planItemList = planService.getPlanItemListByPlanId(id);
+        return success(buildPlanRespVO(plan, planItemList));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鐢靛姏浣滀笟璁″垝鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:query')")
+    public CommonResult<PageResult<MesProMaintenancePlanRespVO>> getPlanPage(@Valid MesProMaintenancePlanPageReqVO pageReqVO) {
+        PageResult<MesProMaintenancePlanDO> pageResult = planService.getPlanPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        List<MesProMaintenancePlanItemDO> planItemList = planService.getPlanItemListByPlanIds(
+                convertSet(pageResult.getList(), MesProMaintenancePlanDO::getId));
+        Map<Long, List<MesProMaintenancePlanItemDO>> planItemMap = convertMultiMap(planItemList, MesProMaintenancePlanItemDO::getPlanId);
+        return success(BeanUtils.toBean(pageResult, MesProMaintenancePlanRespVO.class,
+                plan -> buildPlanRespVOItems(plan, planItemMap.get(plan.getId()))));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭鐢靛姏浣滀笟璁″垝 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportPlanExcel(@Valid MesProMaintenancePlanPageReqVO pageReqVO, HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<MesProMaintenancePlanRespVO> list = planService.getPlanPage(pageReqVO).getList().stream()
+                .map(plan -> buildPlanRespVO(plan, planService.getPlanItemListByPlanId(plan.getId())))
+                .collect(java.util.stream.Collectors.toList());
+        ExcelUtils.write(response, "鐢靛姏浣滀笟璁″垝.xls", "鏁版嵁", MesProMaintenancePlanRespVO.class, list);
+    }
+
+    @PutMapping("/issue")
+    @Operation(summary = "涓嬪彂鐢靛姏浣滀笟璁″垝")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:update')")
+    public CommonResult<Boolean> issuePlan(@RequestParam("id") Long id) {
+        planService.issuePlan(id);
+        return success(true);
+    }
+
+    @PutMapping("/finish")
+    @Operation(summary = "瀹屾垚鐢靛姏浣滀笟璁″垝")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:update')")
+    public CommonResult<Boolean> finishPlan(@RequestParam("id") Long id) {
+        planService.finishPlan(id);
+        return success(true);
+    }
+
+    @PostMapping("/generate-purchase-plan")
+    @Operation(summary = "鏍规嵁澶囦欢闇�姹傜敓鎴愰噰璐鍒�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:generate-purchase-plan')")
+    public CommonResult<Long> generatePurchasePlan(@RequestParam("id") Long id) {
+        return success(planService.generatePurchasePlan(id));
+    }
+
+    @PostMapping("/generate-work-order")
+    @Operation(summary = "鐢熸垚杩愮淮/鍙戠數/妫�淇伐鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-plan:generate-work-order')")
+    public CommonResult<Long> generateWorkOrder(@RequestParam("id") Long id) {
+        return success(planService.generateWorkOrder(id));
+    }
+
+    private MesProMaintenancePlanRespVO buildPlanRespVO(MesProMaintenancePlanDO plan,
+                                                        List<MesProMaintenancePlanItemDO> planItems) {
+        MesProMaintenancePlanRespVO vo = BeanUtils.toBean(plan, MesProMaintenancePlanRespVO.class);
+        buildPlanRespVOItems(vo, planItems);
+        return vo;
+    }
+
+    private void buildPlanRespVOItems(MesProMaintenancePlanRespVO vo, List<MesProMaintenancePlanItemDO> planItems) {
+        if (CollUtil.isEmpty(planItems)) {
+            vo.setItems(Collections.emptyList());
+            return;
+        }
+        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(convertSet(planItems, MesProMaintenancePlanItemDO::getItemId));
+        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
+                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
+        vo.setItems(BeanUtils.toBean(planItems, MesProMaintenancePlanRespVO.Item.class, item -> {
+            MapUtils.findAndThen(itemMap, item.getItemId(), mesItem -> item.setItemCode(mesItem.getCode())
+                    .setItemName(mesItem.getName())
+                    .setUnitMeasureName(unitMeasureMap.get(mesItem.getUnitMeasureId()) != null
+                            ? unitMeasureMap.get(mesItem.getUnitMeasureId()).getName() : null));
+        }));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanPageReqVO.java
new file mode 100644
index 0000000..482edf0
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanPageReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢靛姏浣滀笟璁″垝鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesProMaintenancePlanPageReqVO extends PageParam {
+
+    @Schema(description = "璁″垝缂栫爜", example = "MP20260818001")
+    private String code;
+
+    @Schema(description = "璁″垝鍚嶇О", example = "8鏈堝彉鐢佃繍缁磋鍒�")
+    private String name;
+
+    @Schema(description = "璁″垝绫诲瀷锛�1=鐢电綉杩愯锛�2=鍙戠數锛�3=鍙樼數杩愮淮锛�4=绾胯矾妫�淇級", example = "3")
+    private Integer planType;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸蹭笅鍙戯紝20=宸插畬鎴愶紝30=宸插彇娑堬級", example = "0")
+    private Integer status;
+
+    @Schema(description = "璁″垝鏃ユ湡")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDateTime[] planDate;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanRespVO.java
new file mode 100644
index 0000000..9fd6f17
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanRespVO.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢靛姏浣滀笟璁″垝 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesProMaintenancePlanRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "璁″垝缂栫爜", example = "MP20260818001")
+    @ExcelProperty("璁″垝缂栫爜")
+    private String code;
+
+    @Schema(description = "璁″垝鍚嶇О", example = "8鏈堝彉鐢佃繍缁磋鍒�")
+    @ExcelProperty("璁″垝鍚嶇О")
+    private String name;
+
+    @Schema(description = "璁″垝绫诲瀷", example = "3")
+    @ExcelProperty(value = "璁″垝绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_PRO_MAINTENANCE_PLAN_TYPE)
+    private Integer planType;
+
+    @Schema(description = "绔欑偣", example = "220kV鍩庝笢鍙樼數绔�")
+    @ExcelProperty("绔欑偣")
+    private String station;
+
+    @Schema(description = "绾胯矾", example = "鍩庝笢-宸ヤ笟绾胯矾")
+    @ExcelProperty("绾胯矾")
+    private String line;
+
+    @Schema(description = "鐝粍缂栧彿", example = "1")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鍚嶇О", example = "杩愮淮涓�鐝�")
+    @ExcelProperty("鐝粍")
+    private String teamName;
+
+    @Schema(description = "璁″垝鏃ユ湡")
+    @ExcelProperty("璁″垝鏃ユ湡")
+    private LocalDateTime planDate;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸蹭笅鍙戯紝20=宸插畬鎴愶紝30=宸插彇娑堬級", example = "0")
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_PRO_MAINTENANCE_PLAN_STATUS)
+    private Integer status;
+
+    @Schema(description = "鐢熸垚鐨勫伐鍗曠紪鍙�", example = "1")
+    private Long workOrderId;
+
+    @Schema(description = "鐢熸垚鐨勫伐鍗曠紪鐮�", example = "WO-001")
+    @ExcelProperty("鐢熸垚鐨勫伐鍗�")
+    private String workOrderCode;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @Schema(description = "澶囦欢/鐗╄祫闇�姹傛槑缁�")
+    private List<Item> items;
+
+    @Schema(description = "澶囦欢/鐗╄祫闇�姹傛槑缁嗛」")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缂栧彿", example = "1")
+        private Long id;
+
+        @Schema(description = "鐗╂枡/澶囦欢缂栧彿", example = "1")
+        private Long itemId;
+
+        @Schema(description = "鐗╂枡鍚嶇О", example = "缁濈紭瀛�")
+        private String itemName;
+
+        @Schema(description = "鐗╂枡缂栫爜", example = "ITEM001")
+        private String itemCode;
+
+        @Schema(description = "璁¢噺鍗曚綅鍚嶇О", example = "涓�")
+        private String unitMeasureName;
+
+        @Schema(description = "闇�姹傛暟閲�", example = "10")
+        private BigDecimal quantity;
+
+        @Schema(description = "澶囨敞", example = "澶囨敞")
+        private String remark;
+
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanSaveReqVO.java
new file mode 100644
index 0000000..90ba877
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/vo/MesProMaintenancePlanSaveReqVO.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢靛姏浣滀笟璁″垝鏂板/淇敼 Request VO")
+@Data
+public class MesProMaintenancePlanSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "璁″垝鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "8鏈堝彉鐢佃繍缁磋鍒�")
+    @NotEmpty(message = "璁″垝鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "璁″垝绫诲瀷锛�1=鐢电綉杩愯锛�2=鍙戠數锛�3=鍙樼數杩愮淮锛�4=绾胯矾妫�淇級", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+    @NotNull(message = "璁″垝绫诲瀷涓嶈兘涓虹┖")
+    private Integer planType;
+
+    @Schema(description = "绔欑偣", example = "220kV鍩庝笢鍙樼數绔�")
+    private String station;
+
+    @Schema(description = "绾胯矾", example = "鍩庝笢-宸ヤ笟绾胯矾")
+    private String line;
+
+    @Schema(description = "鐝粍缂栧彿", example = "1")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鍚嶇О", example = "杩愮淮涓�鐝�")
+    private String teamName;
+
+    @Schema(description = "璁″垝鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "璁″垝鏃ユ湡涓嶈兘涓虹┖")
+    private LocalDateTime planDate;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "澶囦欢/鐗╄祫闇�姹傛槑缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "澶囦欢/鐗╄祫闇�姹傛槑缁嗕笉鑳戒负绌�")
+    @Valid
+    private List<Item> items;
+
+    @Schema(description = "澶囦欢/鐗╄祫闇�姹傛槑缁嗛」")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+        private Long id;
+
+        @Schema(description = "鐗╂枡/澶囦欢缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "鐗╂枡/澶囦欢涓嶈兘涓虹┖")
+        private Long itemId;
+
+        @Schema(description = "闇�姹傛暟閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+        @NotNull(message = "闇�姹傛暟閲忎笉鑳戒负绌�")
+        private BigDecimal quantity;
+
+        @Schema(description = "澶囨敞", example = "澶囨敞")
+        private String remark;
+
+        @Schema(description = "鐗╂枡鍚嶇О锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "缁濈紭瀛�")
+        private String itemName;
+
+        @Schema(description = "鐗╂枡缂栫爜锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "ITEM001")
+        private String itemCode;
+
+        @Schema(description = "璁¢噺鍗曚綅鍚嶇О锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "涓�")
+        private String unitMeasureName;
+
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessRespVO.java
index 5cdd462..f772604 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessRespVO.java
@@ -25,6 +25,11 @@
     @Schema(description = "宸ュ簭鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "涓嬫枡宸ュ簭")
     private String name;
 
+    @ExcelProperty(value = "鐢靛姏宸ヨ壓鍒嗙被", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_PRO_PROCESS_TYPE)
+    @Schema(description = "鐢靛姏宸ヨ壓鍒嗙被", example = "2")
+    private Integer type;
+
     @ExcelProperty("宸ヨ壓瑕佹眰")
     @Schema(description = "宸ヨ壓瑕佹眰", example = "鎸夌収鍥剧焊灏哄杩涜鍒囧壊")
     private String attention;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessSaveReqVO.java
index 2abbd0c..a54cd81 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/process/vo/MesProProcessSaveReqVO.java
@@ -20,6 +20,9 @@
     @NotBlank(message = "宸ュ簭鍚嶇О涓嶈兘涓虹┖")
     private String name;
 
+    @Schema(description = "鐢靛姏宸ヨ壓鍒嗙被锛堝瓧鍏� mes_pro_process_type锛�", example = "2")
+    private Integer type;
+
     @Schema(description = "宸ヨ壓瑕佹眰", example = "鎸夌収鍥剧焊灏哄杩涜鍒囧壊")
     private String attention;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
index f4c0dd1..f08dea8 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
@@ -47,6 +47,12 @@
     @Schema(description = "宸ュ簭鍚嶇О", example = "娉ㄥ")
     private String processName;
 
+    @Schema(description = "鐝粍缂栧彿", example = "1")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鍚嶇О", example = "杩愮淮涓�鐝�")
+    private String teamName;
+
     @Schema(description = "浜у搧鐗╂枡缂栧彿", example = "100")
     private Long itemId;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java
index db9f5e2..f486afd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java
@@ -28,6 +28,12 @@
     @Schema(description = "宸ュ簭缂栧彿", example = "1")
     private Long processId; // 缂栬緫鏃讹紝鍙笉浼犻��
 
+    @Schema(description = "鐝粍缂栧彿", example = "1")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鍚嶇О", example = "杩愮淮涓�鐝�")
+    private String teamName;
+
     @Schema(description = "浜у搧鐗╂枡缂栧彿", example = "100")
     private Long itemId; // 缂栬緫鏃讹紝鍙笉浼犻��
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java
index beb178f..41c2958 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java
@@ -92,6 +92,13 @@
     @ExcelProperty("瀹㈡埛鍚嶇О")
     private String clientName;
 
+    @Schema(description = "鍏宠仈钀ラ攢璁㈠崟锛圗RP閿�鍞鍗曪級缂栧彿", example = "500")
+    private Long saleOrderId;
+
+    @Schema(description = "鍏宠仈钀ラ攢璁㈠崟锛圗RP閿�鍞鍗曪級鍗曞彿", example = "SO-001")
+    @ExcelProperty("钀ラ攢璁㈠崟鍙�")
+    private String saleOrderNo;
+
     @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "400")
     private Long vendorId;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java
index b8527bc..026bd5d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java
@@ -34,8 +34,7 @@
     @Schema(description = "鏉ユ簮鍗曟嵁缂栧彿", example = "SO-001")
     private String orderSourceCode;
 
-    @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
-    @NotNull(message = "浜у搧涓嶈兘涓虹┖")
+    @Schema(description = "浜у搧缂栧彿锛堣繍缁�/鍙戠數/妫�淇被宸ュ崟鍙笉濉級", example = "100")
     private Long productId;
 
     @Schema(description = "鐢熶骇鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
@@ -54,6 +53,12 @@
     @Schema(description = "瀹㈡埛缂栧彿", example = "300")
     private Long clientId;
 
+    @Schema(description = "鍏宠仈钀ラ攢璁㈠崟锛圗RP閿�鍞鍗曪級缂栧彿", example = "500")
+    private Long saleOrderId;
+
+    @Schema(description = "鍏宠仈钀ラ攢璁㈠崟锛圗RP閿�鍞鍗曪級鍗曞彿", example = "SO-001")
+    private String saleOrderNo;
+
     @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "400")
     private Long vendorId;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java
new file mode 100644
index 0000000..ba1f42f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java
@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest;
+
+import cn.hutool.core.collection.CollUtil;
+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.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.outsourcetest.MesQcOutsourceTestDO;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.qc.outsourcetest.MesQcOutsourceTestService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
+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.Collections;
+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 = "绠$悊鍚庡彴 - MES 濮斿妫�娴�/璇曢獙鍗�")
+@RestController
+@RequestMapping("/mes/qc/outsource-test")
+@Validated
+public class MesQcOutsourceTestController {
+
+    @Resource
+    private MesQcOutsourceTestService testService;
+    @Resource
+    private MesMdItemService itemService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓濮斿妫�娴�/璇曢獙鍗�")
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:create')")
+    public CommonResult<Long> createTest(@Valid @RequestBody MesQcOutsourceTestSaveReqVO createReqVO) {
+        return success(testService.createTest(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊濮斿妫�娴�/璇曢獙鍗�")
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:update')")
+    public CommonResult<Boolean> updateTest(@Valid @RequestBody MesQcOutsourceTestSaveReqVO updateReqVO) {
+        testService.updateTest(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎濮斿妫�娴�/璇曢獙鍗�")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:delete')")
+    public CommonResult<Boolean> deleteTest(@RequestParam("ids") List<Long> ids) {
+        testService.deleteTest(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱濮斿妫�娴�/璇曢獙鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:query')")
+    public CommonResult<MesQcOutsourceTestRespVO> getTest(@RequestParam("id") Long id) {
+        MesQcOutsourceTestDO test = testService.getTest(id);
+        if (test == null) {
+            return success(null);
+        }
+        return success(buildRespVO(test));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱濮斿妫�娴�/璇曢獙鍗曞垎椤�")
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:query')")
+    public CommonResult<PageResult<MesQcOutsourceTestRespVO>> getTestPage(@Valid MesQcOutsourceTestPageReqVO pageReqVO) {
+        PageResult<MesQcOutsourceTestDO> pageResult = testService.getTestPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        return success(new PageResult<>(BeanUtils.toBean(pageResult.getList(), MesQcOutsourceTestRespVO.class,
+                vo -> fillItemName(vo, vo.getItemId())), pageResult.getTotal()));
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦濮斿妫�娴�/璇曢獙鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:update')")
+    public CommonResult<Boolean> submitTest(@RequestParam("id") Long id) {
+        testService.submitTest(id);
+        return success(true);
+    }
+
+    @PutMapping("/finish")
+    @Operation(summary = "瀹屾垚濮斿妫�娴�/璇曢獙鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:qc-outsource-test:update')")
+    public CommonResult<Boolean> finishTest(@RequestParam("id") Long id) {
+        testService.finishTest(id);
+        return success(true);
+    }
+
+    private MesQcOutsourceTestRespVO buildRespVO(MesQcOutsourceTestDO test) {
+        MesQcOutsourceTestRespVO vo = BeanUtils.toBean(test, MesQcOutsourceTestRespVO.class);
+        fillItemName(vo, test.getItemId());
+        // 闄勪欢
+        vo.setAttachmentList(BeanUtils.toBean(storageAttachmentApi.listAttachments(
+                "mes_qc_outsource_test", test.getId()), MesQcOutsourceTestRespVO.Attachment.class));
+        return vo;
+    }
+
+    private void fillItemName(MesQcOutsourceTestRespVO vo, Long itemId) {
+        if (itemId == null) {
+            return;
+        }
+        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(Collections.singleton(itemId));
+        MapUtils.findAndThen(itemMap, itemId, item -> vo.setItemName(item.getName()));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestPageReqVO.java
new file mode 100644
index 0000000..da5f883
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestPageReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 濮斿妫�娴�/璇曢獙鍗曞垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesQcOutsourceTestPageReqVO extends PageParam {
+
+    @Schema(description = "鍗曟嵁缂栫爜", example = "WT20260818001")
+    private String code;
+
+    @Schema(description = "鍗曟嵁鍚嶇О", example = "鍙樺帇鍣ㄧ涓夋柟妫�娴�")
+    private String name;
+
+    @Schema(description = "妫�娴嬬被鍨嬶紙1=绗笁鏂规娴嬶紝2=濮斿璇曢獙锛�", example = "1")
+    private Integer testType;
+
+    @Schema(description = "妫�娴嬬粨鏋滐紙1=鍚堟牸锛�2=涓嶅悎鏍硷級", example = "1")
+    private Integer result;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸叉彁浜わ紝20=宸插畬鎴愶級", example = "0")
+    private Integer status;
+
+    @Schema(description = "妫�娴嬫棩鏈�")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDateTime[] testDate;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestRespVO.java
new file mode 100644
index 0000000..aea38e2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestRespVO.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 濮斿妫�娴�/璇曢獙鍗� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesQcOutsourceTestRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鍗曟嵁缂栫爜", example = "WT20260818001")
+    @ExcelProperty("鍗曟嵁缂栫爜")
+    private String code;
+
+    @Schema(description = "鍗曟嵁鍚嶇О", example = "鍙樺帇鍣ㄧ涓夋柟妫�娴�")
+    @ExcelProperty("鍗曟嵁鍚嶇О")
+    private String name;
+
+    @Schema(description = "妫�娴嬬被鍨�", example = "1")
+    @ExcelProperty(value = "妫�娴嬬被鍨�", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_QC_OUTSOURCE_TEST_TYPE)
+    private Integer testType;
+
+    @Schema(description = "妫�娴嬪璞★紙鐗╂枡/璁惧缂栧彿锛�", example = "1")
+    private Long itemId;
+
+    @Schema(description = "妫�娴嬪璞″悕绉�", example = "鍙樺帇鍣ˋ")
+    @ExcelProperty("妫�娴嬪璞�")
+    private String itemName;
+
+    @Schema(description = "妫�娴嬫満鏋�", example = "鍥藉鍙樺帇鍣ㄨ川妫�涓績")
+    @ExcelProperty("妫�娴嬫満鏋�")
+    private String testOrg;
+
+    @Schema(description = "妫�娴嬫棩鏈�")
+    @ExcelProperty("妫�娴嬫棩鏈�")
+    private LocalDateTime testDate;
+
+    @Schema(description = "妫�娴嬬粨鏋�", example = "1")
+    @ExcelProperty(value = "妫�娴嬬粨鏋�", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_QC_OUTSOURCE_TEST_RESULT)
+    private Integer result;
+
+    @Schema(description = "妫�娴嬫姤鍛婄紪鍙�", example = "BG-2026-0001")
+    @ExcelProperty("鎶ュ憡缂栧彿")
+    private String reportNo;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸叉彁浜わ紝20=宸插畬鎴愶級", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @Schema(description = "闄勪欢鍒楄〃锛堟娴嬫姤鍛�/璧勮川鏂囦欢锛�")
+    private List<Attachment> attachmentList;
+
+    @Schema(description = "闄勪欢椤�")
+    @Data
+    public static class Attachment {
+        private Long id;
+        private String name;
+        private String url;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestSaveReqVO.java
new file mode 100644
index 0000000..b3ccdab
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/vo/MesQcOutsourceTestSaveReqVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 濮斿妫�娴�/璇曢獙鍗曟柊澧�/淇敼 Request VO")
+@Data
+public class MesQcOutsourceTestSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鍗曟嵁鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍙樺帇鍣ㄧ涓夋柟妫�娴�")
+    @NotEmpty(message = "鍗曟嵁鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "妫�娴嬬被鍨嬶紙1=绗笁鏂规娴嬶紝2=濮斿璇曢獙锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "妫�娴嬬被鍨嬩笉鑳戒负绌�")
+    private Integer testType;
+
+    @Schema(description = "妫�娴嬪璞★紙鐗╂枡/璁惧缂栧彿锛�", example = "1")
+    private Long itemId;
+
+    @Schema(description = "妫�娴嬫満鏋�", example = "鍥藉鍙樺帇鍣ㄨ川妫�涓績")
+    private String testOrg;
+
+    @Schema(description = "妫�娴嬫棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "妫�娴嬫棩鏈熶笉鑳戒负绌�")
+    private LocalDateTime testDate;
+
+    @Schema(description = "妫�娴嬬粨鏋滐紙1=鍚堟牸锛�2=涓嶅悎鏍硷級", example = "1")
+    private Integer result;
+
+    @Schema(description = "妫�娴嬫姤鍛婄紪鍙�", example = "BG-2026-0001")
+    private String reportNo;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "闄勪欢锛堟娴嬫姤鍛�/璧勮川鏂囦欢锛塨lobId 鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueRespVO.java
index e6b4ca3..a60c044 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueRespVO.java
@@ -51,6 +51,9 @@
     @Schema(description = "闇�姹傛椂闂�", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime requiredTime;
 
+    @Schema(description = "鍗曟嵁绫诲瀷锛�1=姝e父棰嗘枡锛�2=绱ф�ヨˉ鏂欙級", example = "1")
+    private Integer issueType;
+
     @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer status;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueSaveReqVO.java
index b84e0ea..eaddbe1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/MesWmProductIssueSaveReqVO.java
@@ -32,6 +32,9 @@
     @Schema(description = "宸ヤ綔绔� ID", example = "1")
     private Long workstationId;
 
+    @Schema(description = "鍗曟嵁绫诲瀷锛�1=姝e父棰嗘枡锛�2=绱ф�ヨˉ鏂欙級", example = "1")
+    private Integer issueType;
+
     @Schema(description = "闇�姹傛椂闂�", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "闇�姹傛椂闂翠笉鑳戒负绌�")
     private LocalDateTime requiredTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskLineController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskLineController.java
index 88c2270..4385c79 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskLineController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskLineController.java
@@ -4,6 +4,9 @@
 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.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLinePageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineSaveReqVO;
@@ -25,11 +28,14 @@
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -112,6 +118,26 @@
         return success(buildStockTakingTaskLineRespVOList(list));
     }
 
+    @PostMapping("/import-excel")
+    @Operation(summary = "瀵煎叆鐩樼偣瀹炵洏鏁伴噺", description = "鎸� 鐗╂枡缂栫爜 + 鎵规鍙� 鍖归厤鐩樼偣琛岋紝鍐欏叆瀹炵洏鏁伴噺")
+    @Parameter(name = "file", description = "Excel 鏂囦欢", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')")
+    public CommonResult<MesWmStockTakingTaskLineImportRespVO> importStockTakingQuantity(
+            @RequestParam("taskId") Long taskId,
+            @RequestParam("file") MultipartFile file) throws IOException {
+        List<MesWmStockTakingTaskLineImportExcelVO> list =
+                ExcelUtils.read(file, MesWmStockTakingTaskLineImportExcelVO.class);
+        return success(stockTakingTaskLineService.importTakingQuantity(taskId, list));
+    }
+
+    @GetMapping("/get-import-template")
+    @Operation(summary = "涓嬭浇鐩樼偣瀵煎叆妯℃澘")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:query')")
+    public void getImportTemplate(HttpServletResponse response) throws IOException {
+        ExcelUtils.write(response, "鐩樼偣瀹炵洏鏁板鍏ユā鏉�.xls", "瀹炵洏鏁�",
+                MesWmStockTakingTaskLineImportExcelVO.class, Collections.emptyList());
+    }
+
     // ==================== 鎷兼帴 VO ====================
 
     private List<MesWmStockTakingTaskLineRespVO> buildStockTakingTaskLineRespVOList(List<MesWmStockTakingTaskLineDO> list) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportExcelVO.java
new file mode 100644
index 0000000..bf3ae84
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportExcelVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * MES 鐩樼偣浠诲姟琛� Excel 瀵煎叆 VO
+ *
+ * <p>瀵煎叆瀹炵洏鏁伴噺锛屾寜 鐗╂枡缂栫爜 + 鎵规鍙� 鍖归厤鐩樼偣浠诲姟琛�
+ *
+ * @author xiaoyi
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MesWmStockTakingTaskLineImportExcelVO {
+
+    @ExcelProperty("鐗╂枡缂栫爜")
+    private String itemCode;
+
+    @ExcelProperty("鎵规鍙�")
+    private String batchCode;
+
+    @ExcelProperty("瀹炵洏鏁伴噺")
+    private BigDecimal takingQuantity;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportRespVO.java
new file mode 100644
index 0000000..76571b8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/line/MesWmStockTakingTaskLineImportRespVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * MES 鐩樼偣浠诲姟琛� Excel 瀵煎叆缁撴灉 VO
+ *
+ * @author xiaoyi
+ */
+@Schema(description = "绠$悊鍚庡彴 - MES 鐩樼偣浠诲姟琛屽鍏ョ粨鏋� Response VO")
+@Data
+public class MesWmStockTakingTaskLineImportRespVO {
+
+    @Schema(description = "瀵煎叆鎴愬姛鏉℃暟")
+    private Integer successCount;
+
+    @Schema(description = "瀵煎叆澶辫触鏉℃暟")
+    private Integer failureCount;
+
+    @Schema(description = "澶辫触鍘熷洜锛堜笌瀵煎叆琛岄『搴忓搴旓級")
+    private List<String> failureMessages;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java
index 6c85e3b..da3b393 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java
@@ -2,6 +2,8 @@
 
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -25,6 +27,11 @@
     @ExcelProperty("浠撳簱鍚嶇О")
     private String name;
 
+    @Schema(description = "浠撳簱绫诲瀷", example = "1")
+    @ExcelProperty(value = "浠撳簱绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_WM_WAREHOUSE_TYPE)
+    private Integer type;
+
     @Schema(description = "浠撳簱鍦板潃", example = "A 鍖轰竴鍙锋ゼ")
     @ExcelProperty("浠撳簱鍦板潃")
     private String address;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java
index a05c861..ea2cd37 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java
@@ -22,6 +22,9 @@
     @NotEmpty(message = "浠撳簱鍚嶇О涓嶈兘涓虹┖")
     private String name;
 
+    @Schema(description = "浠撳簱绫诲瀷锛堝瓧鍏� mes_wm_warehouse_type锛�", example = "1")
+    private Integer type;
+
     @Schema(description = "浠撳簱鍦板潃", example = "A 鍖轰竴鍙锋ゼ")
     private String address;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaRespVO.java
index 0cd11b0..eed3aed 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaRespVO.java
@@ -2,6 +2,8 @@
 
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -25,6 +27,11 @@
     @ExcelProperty("搴撲綅鍚嶇О")
     private String name;
 
+    @Schema(description = "搴撳尯绫诲瀷", example = "1")
+    @ExcelProperty(value = "搴撳尯绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_WM_WAREHOUSE_TYPE)
+    private Integer type;
+
     @Schema(description = "搴撳尯缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long locationId;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaSaveReqVO.java
index 60322e1..f704c4b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaSaveReqVO.java
@@ -22,6 +22,9 @@
     @NotEmpty(message = "搴撲綅鍚嶇О涓嶈兘涓虹┖")
     private String name;
 
+    @Schema(description = "搴撳尯绫诲瀷锛堝瓧鍏� mes_wm_warehouse_type锛�", example = "1")
+    private Integer type;
+
     @Schema(description = "搴撳尯缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "搴撳尯涓嶈兘涓虹┖")
     private Long locationId;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
index d533b23..ae3949d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
@@ -34,6 +34,10 @@
      */
     private String code;
     /**
+     * 鏁伴噰璁惧ID锛堝閮ㄦ暟閲� tbDeviceId锛岀敤浜庡叧鑱旀暟閲囬仴娴嬫暟鎹級
+     */
+    private String tbDeviceId;
+    /**
      * 璁惧鍚嶇О
      */
     private String name;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/telemetry/MesDvTelemetryDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/telemetry/MesDvTelemetryDO.java
new file mode 100644
index 0000000..8641b7f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/telemetry/MesDvTelemetryDO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.dv.telemetry;
+
+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;
+
+/**
+ * MES 璁惧鏁伴噰閬ユ祴鏁版嵁 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_dv_telemetry")
+@KeySequence("mes_dv_telemetry_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesDvTelemetryDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鏁伴噰璁惧ID锛堝閮� tbDeviceId锛�
+     */
+    private String tbDeviceId;
+    /**
+     * 璁惧鍚嶇О
+     */
+    private String deviceName;
+    /**
+     * 淇″彿鍚嶇О
+     */
+    private String paramName;
+    /**
+     * 璁惧KEY鍚嶇О
+     */
+    private String paramKeyName;
+    /**
+     * 鏍囧噯鍊�
+     */
+    private String standardValue;
+    /**
+     * 淇″彿鍊硷紙瀹炴椂锛�
+     */
+    private String timelyValue;
+    /**
+     * 鍧囧��
+     */
+    private String avgValue;
+    /**
+     * 鏈�澶у��
+     */
+    private String maxValue;
+    /**
+     * 鏈�灏忓��
+     */
+    private String minValue;
+    /**
+     * 鏄惁寮傚父
+     */
+    private Boolean whetherAnomaly;
+    /**
+     * 閬ユ祴鏁版嵁鏃堕棿
+     */
+    private LocalDateTime telemetryDataTime;
+    /**
+     * 鍗曟嵁鍙�
+     */
+    private String billNo;
+    /**
+     * 鐝
+     *
+     * 瀛楀吀 {@link cn.iocoder.yudao.module.mes.enums.DictTypeConstants#MES_CAL_SHIFT_TYPE}
+     */
+    private String shiftName;
+    /**
+     * 鎷夊彇鏃堕棿锛堟湰娆″叆搴撴椂闂达級
+     */
+    private LocalDateTime pullTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/md/item/MesMdItemDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/md/item/MesMdItemDO.java
index 99a4b8b..e5b2a2f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/md/item/MesMdItemDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/md/item/MesMdItemDO.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.mes.dal.dataobject.md.item;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -78,6 +79,12 @@
      */
     private BigDecimal maxStock;
     /**
+     * 鐢靛姏鐗╄祫鍒嗙被
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_MATERIAL_CATEGORY}
+     */
+    private Integer materialCategory;
+    /**
      * 鏄惁楂樺�肩墿鏂�
      */
     private Boolean highValue;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdDiscountPolicyDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdDiscountPolicyDO.java
new file mode 100644
index 0000000..bd3d6e2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdDiscountPolicyDO.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+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;
+
+/**
+ * MES 浜у搧璁捐-浼樻儬绛栫暐 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_discount_policy")
+@KeySequence("mes_pd_discount_policy_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdDiscountPolicyDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 绛栫暐鍚嶇О
+     */
+    private String name;
+    /**
+     * 绛栫暐绫诲瀷锛�1鎶樻墸/2绔嬪噺锛�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PD_DISCOUNT_POLICY_TYPE}
+     */
+    private Integer policyType;
+    /**
+     * 绛栫暐鍊硷紙鎶樻墸锛�0-1 姣斾緥锛涚珛鍑忥細閲戦锛�
+     */
+    private BigDecimal policyValue;
+    /**
+     * 閫傜敤瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛屽彲绌�=鍏ㄥ眬锛�
+     */
+    private Integer targetType;
+    /**
+     * 閫傜敤瀵硅薄ID
+     */
+    private Long targetId;
+    /**
+     * 鐢熸晥鏃ユ湡
+     */
+    private LocalDateTime startDate;
+    /**
+     * 澶辨晥鏃ユ湡
+     */
+    private LocalDateTime endDate;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     */
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdElectricityTypeDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdElectricityTypeDO.java
new file mode 100644
index 0000000..e4d1903
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdElectricityTypeDO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata;
+
+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.*;
+
+/**
+ * MES 浜у搧璁捐-鐢ㄧ數绫诲瀷鍩虹淇℃伅 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_electricity_type")
+@KeySequence("mes_pd_electricity_type_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdElectricityTypeDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 绫诲瀷缂栫爜
+     */
+    private String code;
+    /**
+     * 绫诲瀷鍚嶇О
+     */
+    private String name;
+    /**
+     * 绫诲瀷璇存槑
+     */
+    private String description;
+    /**
+     * 鎺掑簭
+     */
+    private Integer sort;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     */
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdPriceStandardDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdPriceStandardDO.java
new file mode 100644
index 0000000..1a7994a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdPriceStandardDO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+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;
+
+/**
+ * MES 浜у搧璁捐-瀹氫环鏍囧噯 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_price_standard")
+@KeySequence("mes_pd_price_standard_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdPriceStandardDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 瀹氫环瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PD_PRICE_OBJECT_TYPE}
+     */
+    private Integer objectType;
+    /**
+     * 瀹氫环瀵硅薄ID
+     */
+    private Long objectId;
+    /**
+     * 瀹氫环瀵硅薄鍚嶇О锛堝啑浣欙紝渚夸簬灞曠ず锛�
+     */
+    private String objectName;
+    /**
+     * 浠锋牸
+     */
+    private BigDecimal price;
+    /**
+     * 璁′环鍗曚綅
+     */
+    private String unit;
+    /**
+     * 鐢熸晥鏃ユ湡
+     */
+    private LocalDateTime effectiveDate;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     */
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdServicePackageDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdServicePackageDO.java
new file mode 100644
index 0000000..f3d68cf
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdServicePackageDO.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata;
+
+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;
+import java.time.LocalDateTime;
+
+/**
+ * MES 浜у搧璁捐-鏈嶅姟濂楅鍩虹淇℃伅 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_service_package")
+@KeySequence("mes_pd_service_package_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdServicePackageDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 濂楅缂栫爜锛堢紪鐮佽鍒� PD_PACKAGE_CODE 鑷姩鐢熸垚锛�
+     */
+    private String code;
+    /**
+     * 濂楅鍚嶇О
+     */
+    private String name;
+    /**
+     * 濂楅璇存槑
+     */
+    private String description;
+    /**
+     * 濂楅浠锋牸
+     */
+    private BigDecimal price;
+    /**
+     * 鐢熸晥鏃ユ湡
+     */
+    private LocalDateTime effectiveDate;
+    /**
+     * 澶辨晥鏃ユ湡
+     */
+    private LocalDateTime expireDate;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     */
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdTieredPriceDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdTieredPriceDO.java
new file mode 100644
index 0000000..8b74b12
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdTieredPriceDO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MES 浜у搧璁捐-闃舵鐢典环 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_tiered_price")
+@KeySequence("mes_pd_tiered_price_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdTieredPriceDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鏈嶅姟濂楅ID锛堜笌鐢靛帇绛夌骇浜岄�変竴锛�
+     */
+    private Long packageId;
+    /**
+     * 鐢靛帇绛夌骇ID锛堜笌鏈嶅姟濂楅浜岄�変竴锛�
+     */
+    private Long voltageLevelId;
+    /**
+     * 妗d綅涓嬮檺锛堝惈锛宬Wh锛�
+     */
+    private BigDecimal startValue;
+    /**
+     * 妗d綅涓婇檺锛堜笉鍚紝kWh锛屽彲绌�=涓婁笉灏侀《锛�
+     */
+    private BigDecimal endValue;
+    /**
+     * 妗d綅鍗曚环
+     */
+    private BigDecimal pricePerUnit;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PD_TIERED_PRICE_STATUS}
+     */
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdVoltageLevelDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdVoltageLevelDO.java
new file mode 100644
index 0000000..af47255
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/basedata/MesPdVoltageLevelDO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata;
+
+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;
+
+/**
+ * MES 浜у搧璁捐-鐢靛帇绛夌骇鍩虹淇℃伅 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_voltage_level")
+@KeySequence("mes_pd_voltage_level_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdVoltageLevelDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 绛夌骇缂栫爜
+     */
+    private String code;
+    /**
+     * 绛夌骇鍚嶇О
+     */
+    private String name;
+    /**
+     * 鐢靛帇鏁板�硷紙kV锛�
+     */
+    private BigDecimal voltageValue;
+    /**
+     * 鎺掑簭
+     */
+    private Integer sort;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     */
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/ctc/ProductCtcDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/ctc/ProductCtcDO.java
new file mode 100644
index 0000000..51f2144
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/ctc/ProductCtcDO.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MES 鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("product_ctc")
+@KeySequence("product_ctc_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductCtcDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鐗╂枡ID锛堝叧鑱� mdm_item.id锛�
+     */
+    private Long itemId;
+    /**
+     * 璁㈣揣瑙勬牸
+     */
+    private String orderSpec;
+    /**
+     * 瑁稿绾胯鏍�
+     */
+    private String bareConductorSpec;
+    /**
+     * 婕嗗寘绾胯鏍�
+     */
+    private String enameledSpec;
+    /**
+     * 鎹綅瑙勬牸
+     */
+    private String transpositionSpec;
+    /**
+     * 瀵间綋绫诲瀷锛堥摐/閾濓級
+     *
+     * 瀛楀吀 {@link DictTypeConstants#PRODUCT_CONDUCTOR_TYPE}
+     */
+    private String conductorType;
+    /**
+     * 鎴潰绉紙mm虏锛�
+     */
+    private BigDecimal crossSection;
+    /**
+     * 澶栧緞锛坢m锛�
+     */
+    private BigDecimal outerDiameter;
+    /**
+     * 20鈩冪洿娴佺數闃伙紙惟/km锛�
+     */
+    private BigDecimal resistance20c;
+    /**
+     * 缁曞寘瑙掑害锛埪帮級
+     */
+    private BigDecimal wrappingAngle;
+    /**
+     * 缁濈紭鍘氬害锛坢m锛�
+     */
+    private BigDecimal insulationThickness;
+    /**
+     * 棰濆畾鐢靛帇锛坘V锛�
+     */
+    private String ratedVoltage;
+    /**
+     * 璇曢獙鐢靛帇锛坘V锛�
+     */
+    private String testVoltage;
+    /**
+     * 鏈�灏忓集鏇插崐寰勶紙mm锛�
+     */
+    private BigDecimal minBendingRadius;
+    /**
+     * 姣忓叕閲岄噸閲忥紙kg/km锛�
+     */
+    private BigDecimal weightPerKm;
+    /**
+     * 鎵ц鏍囧噯浠e彿
+     */
+    private String standardCode;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/product/MesPdProductDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/product/MesPdProductDO.java
new file mode 100644
index 0000000..0831ea3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/product/MesPdProductDO.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.product;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.pd.MesPdAuditStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pd.MesPdProductArchiveTypeEnum;
+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;
+
+/**
+ * MES 浜у搧璁捐-浜у搧妗f DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_pd_product")
+@KeySequence("mes_pd_product_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesPdProductDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 妗f绫诲瀷锛�1鐢靛姏鏈嶅姟/2渚涚數浜у搧/3澧炲�艰兘婧愪骇鍝侊級
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PD_PRODUCT_ARCHIVE_TYPE}
+     */
+    private Integer archiveType;
+    /**
+     * 妗f缂栫爜锛堢紪鐮佽鍒� PD_PRODUCT_CODE 鑷姩鐢熸垚锛�
+     */
+    private String code;
+    /**
+     * 妗f鍚嶇О
+     */
+    private String name;
+    /**
+     * 瑙勬牸鍨嬪彿
+     */
+    private String spec;
+    /**
+     * 鍗曚綅
+     */
+    private String unit;
+    /**
+     * 鍩哄噯浠锋牸
+     */
+    private BigDecimal price;
+    /**
+     * 瀹℃壒鐘舵�侊紙0鏈彁浜�/10瀹℃壒涓�/20瀹℃牳閫氳繃/30瀹℃牳涓嶉�氳繃锛�
+     *
+     * 鏋氫妇 {@link MesPdAuditStatusEnum}
+     */
+    private Integer auditStatus;
+    /**
+     * 瀹℃壒鎰忚
+     */
+    private String auditRemark;
+    /**
+     * 瀹℃壒鏃堕棿
+     */
+    private LocalDateTime auditTime;
+    /**
+     * 鍚敤鐘舵�侊紙1鍚敤/0绂佺敤锛�
+     */
+    private Integer status;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/reel/ProductCtcReelDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/reel/ProductCtcReelDO.java
new file mode 100644
index 0000000..68bc9ae
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/reel/ProductCtcReelDO.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.reel;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * MES 鎹綅瀵肩嚎鐩樺叿璁$畻 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("product_ctc_reel")
+@KeySequence("product_ctc_reel_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductCtcReelDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鎹綅瀵肩嚎浜у搧ID锛堝叧鑱� product_ctc.id锛�
+     */
+    private Long ctcId;
+    /**
+     * 鐩樺叿绫诲瀷锛堟湪鐩�/閾佺洏/閽㈢洏锛�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#PRODUCT_REEL_TYPE}
+     */
+    private String reelType;
+    /**
+     * 鐩樺叿澶栧緞锛坢m锛�
+     */
+    private BigDecimal reelOuterDiameter;
+    /**
+     * 鐩樿姱鍐呭緞锛坢m锛�
+     */
+    private BigDecimal reelInnerDiameter;
+    /**
+     * 鐩樺锛坢m锛�
+     */
+    private BigDecimal reelWidth;
+    /**
+     * 鏈�澶у閲忥紙km锛�
+     */
+    private BigDecimal maxCapacity;
+    /**
+     * 鎺ㄨ崘姣忕洏闀垮害锛坘m锛�
+     */
+    private BigDecimal recommendedLength;
+    /**
+     * 鍑�閲嶏紙kg锛�
+     */
+    private BigDecimal netWeight;
+    /**
+     * 姣涢噸锛坘g锛�
+     */
+    private BigDecimal grossWeight;
+    /**
+     * 璁$畻鍏紡锛堜繚瀛樿绠楅�昏緫锛�
+     */
+    private String calcFormula;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/techconfirm/ProductTechConfirmDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/techconfirm/ProductTechConfirmDO.java
new file mode 100644
index 0000000..f9c7570
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/techconfirm/ProductTechConfirmDO.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * MES 浜у搧鎶�鏈‘璁ゅ嚱 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("product_tech_confirm")
+@KeySequence("product_tech_confirm_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductTechConfirmDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 纭鍑界紪鍙�
+     */
+    private String confirmNo;
+    /**
+     * 瀹㈡埛ID锛堝叧鑱� crm_customer.id锛�
+     */
+    private Long customerId;
+    /**
+     * 瀹㈡埛鍚嶇О
+     */
+    private String customerName;
+    /**
+     * 浜у搧鍚嶇О
+     */
+    private String productName;
+    /**
+     * 瑙勬牸鍨嬪彿
+     */
+    private String specification;
+    /**
+     * 鎶�鏈弬鏁帮紙JSON 鏍煎紡锛�
+     */
+    private String techParams;
+    /**
+     * 妯℃澘绫诲瀷锛堝ゥ鍦板埄/鏃ユ湰/閫氱敤锛�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#PRODUCT_TECH_CONFIRM_TEMPLATE}
+     */
+    private String templateType;
+    /**
+     * 缂栧埗浜篒D
+     */
+    private Long drafterId;
+    /**
+     * 缂栧埗浜哄鍚�
+     */
+    private String drafterName;
+    /**
+     * 缂栧埗鏃堕棿
+     */
+    private LocalDateTime draftTime;
+    /**
+     * 瀹℃牳浜篒D
+     */
+    private Long reviewerId;
+    /**
+     * 瀹℃牳浜哄鍚�
+     */
+    private String reviewerName;
+    /**
+     * 瀹℃牳鏃堕棿
+     */
+    private LocalDateTime reviewTime;
+    /**
+     * 瀹㈡埛纭浜�
+     */
+    private String customerConfirmUser;
+    /**
+     * 瀹㈡埛纭鏃堕棿
+     */
+    private LocalDateTime customerConfirmTime;
+    /**
+     * 鐘舵�侊紙0=鑽夌锛�1=寰呭鏍革紝2=宸插鏍革紝3=瀹㈡埛宸茬‘璁わ級
+     *
+     * 瀛楀吀 {@link DictTypeConstants#PRODUCT_TECH_CONFIRM_STATUS}
+     */
+    private Integer status;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/emergencyplan/MesProEmergencyPlanDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/emergencyplan/MesProEmergencyPlanDO.java
new file mode 100644
index 0000000..7f6babf
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/emergencyplan/MesProEmergencyPlanDO.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * MES 搴旀�ュ缃妗� DO
+ *
+ * @author xiaoyi
+ */
+@TableName("mes_pro_emergency_plan")
+@KeySequence("mes_pro_emergency_plan_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesProEmergencyPlanDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 棰勬缂栫爜
+     */
+    private String code;
+
+    /**
+     * 棰勬鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 棰勬绫诲瀷
+     *
+     * 1: 鍋滅數
+     * 2: 璁惧鏁呴殰
+     * 3: 鐏伨
+     * 4: 鑷劧鐏惧
+     * 5: 鍏朵粬
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PRO_EMERGENCY_PLAN_TYPE}
+     */
+    private Integer planType;
+
+    /**
+     * 澶勭疆鍐呭
+     */
+    private String content;
+
+    /**
+     * 璐d换鍗曚綅/閮ㄩ棬
+     */
+    private String orgUnit;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鑽夌
+     * 10: 宸插彂甯�
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanDO.java
new file mode 100644
index 0000000..c178170
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanDO.java
@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProMaintenancePlanTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝 DO
+ *
+ * @author xiaoyi
+ */
+@TableName("mes_pro_maintenance_plan")
+@KeySequence("mes_pro_maintenance_plan_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesProMaintenancePlanDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 璁″垝缂栫爜
+     */
+    private String code;
+
+    /**
+     * 璁″垝鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 璁″垝绫诲瀷
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PRO_MAINTENANCE_PLAN_TYPE}
+     * 鏋氫妇 {@link MesProMaintenancePlanTypeEnum}
+     */
+    private Integer planType;
+
+    /**
+     * 绔欑偣
+     */
+    private String station;
+
+    /**
+     * 绾胯矾
+     */
+    private String line;
+
+    /**
+     * 鐝粍缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO#getId()}
+     */
+    private Long teamId;
+
+    /**
+     * 鐝粍鍚嶇О
+     */
+    private String teamName;
+
+    /**
+     * 璁″垝鏃ユ湡
+     */
+    private LocalDateTime planDate;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鑽夌
+     * 10: 宸蹭笅鍙�
+     * 20: 宸插畬鎴�
+     * 30: 宸插彇娑�
+     */
+    private Integer status;
+
+    /**
+     * 鐢熸垚鐨勫伐鍗曠紪鍙�
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO#getId()}
+     */
+    private Long workOrderId;
+
+    /**
+     * 鐢熸垚鐨勫伐鍗曠紪鐮�
+     */
+    private String workOrderCode;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanItemDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanItemDO.java
new file mode 100644
index 0000000..ec44125
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenanceplan/MesProMaintenancePlanItemDO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan;
+
+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;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝鏄庣粏锛堝浠�/鐗╄祫闇�姹傦級 DO
+ *
+ * @author xiaoyi
+ */
+@TableName("mes_pro_maintenance_plan_item")
+@KeySequence("mes_pro_maintenance_plan_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesProMaintenancePlanItemDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鐢靛姏浣滀笟璁″垝缂栧彿
+     */
+    private Long planId;
+
+    /**
+     * 鐗╂枡/澶囦欢缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO#getId()}
+     */
+    private Long itemId;
+
+    /**
+     * 闇�姹傛暟閲�
+     */
+    private BigDecimal quantity;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/process/MesProProcessDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/process/MesProProcessDO.java
index 4c8a24a..514d08b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/process/MesProProcessDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/process/MesProProcessDO.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.mes.dal.dataobject.pro.process;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -35,6 +36,12 @@
      */
     private String name;
     /**
+     * 鐢靛姏宸ヨ壓鍒嗙被
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_PRO_PROCESS_TYPE}
+     */
+    private Integer type;
+    /**
      * 宸ヨ壓瑕佹眰
      */
     private String attention;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java
index 9adcf53..78017b2 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java
@@ -69,6 +69,16 @@
      */
     private Long processId;
     /**
+     * 鐝粍缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO#getId()}
+     */
+    private Long teamId;
+    /**
+     * 鐝粍鍚嶇О
+     */
+    private String teamName;
+    /**
      * 浜у搧鐗╂枡缂栧彿
      *
      * 鍏宠仈 {@link MesMdItemDO#getId()}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
index 82cd251..1dc274f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
@@ -94,6 +94,16 @@
      */
     private Long clientId;
     /**
+     * 鍏宠仈钀ラ攢璁㈠崟锛圗RP 閿�鍞鍗曪級缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO#getId()}
+     */
+    private Long saleOrderId;
+    /**
+     * 鍏宠仈钀ラ攢璁㈠崟锛圗RP 閿�鍞鍗曪級鍗曞彿
+     */
+    private String saleOrderNo;
+    /**
      * 渚涘簲鍟嗙紪鍙�
      *
      * 鍏宠仈 ERP 渚涘簲鍟� {@link cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO#getId()}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/outsourcetest/MesQcOutsourceTestDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/outsourcetest/MesQcOutsourceTestDO.java
new file mode 100644
index 0000000..40dc41e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/outsourcetest/MesQcOutsourceTestDO.java
@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.qc.outsourcetest;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * MES 濮斿妫�娴�/璇曢獙鍗� DO
+ *
+ * <p>绗笁鏂圭數鍔涙娴嬨�佸澶栬瘯楠屽崟鎹鐞嗭紝鐣欏瓨妫�娴嬫姤鍛婁笌璧勮川鏂囦欢
+ *
+ * @author xiaoyi
+ */
+@TableName("mes_qc_outsource_test")
+@KeySequence("mes_qc_outsource_test_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesQcOutsourceTestDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鍗曟嵁缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鍗曟嵁鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 妫�娴嬬被鍨�
+     *
+     * 1: 绗笁鏂规娴�
+     * 2: 濮斿璇曢獙
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_QC_OUTSOURCE_TEST_TYPE}
+     */
+    private Integer testType;
+
+    /**
+     * 妫�娴嬪璞★紙鐗╂枡/璁惧缂栧彿锛�
+     */
+    private Long itemId;
+
+    /**
+     * 妫�娴嬫満鏋�
+     */
+    private String testOrg;
+
+    /**
+     * 妫�娴嬫棩鏈�
+     */
+    private LocalDateTime testDate;
+
+    /**
+     * 妫�娴嬬粨鏋�
+     *
+     * 1: 鍚堟牸
+     * 2: 涓嶅悎鏍�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_QC_OUTSOURCE_TEST_RESULT}
+     */
+    private Integer result;
+
+    /**
+     * 妫�娴嬫姤鍛婄紪鍙�
+     */
+    private String reportNo;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鑽夌
+     * 10: 宸叉彁浜�
+     * 20: 宸插畬鎴�
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueDO.java
index 7aa3254..b65c308 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueDO.java
@@ -5,6 +5,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -58,6 +59,13 @@
      */
     private Long taskId;
     /**
+     * 鍗曟嵁绫诲瀷
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_WM_PRODUCT_ISSUE_TYPE}
+     * 鏋氫妇 {@link MesWmProductIssueTypeEnum}
+     */
+    private Integer issueType;
+    /**
      * 棰嗘枡鏃ユ湡
      */
     private LocalDateTime issueDate;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java
index ea9a3ab..fc79560 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -40,6 +41,12 @@
      */
     private String name;
     /**
+     * 搴撳尯绫诲瀷
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_WM_WAREHOUSE_TYPE}
+     */
+    private Integer type;
+    /**
      * 搴撳尯缂栧彿
      *
      * 鍏宠仈 {@link MesWmWarehouseLocationDO#getId()}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
index 0c8a9b2..1680f97 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -44,6 +45,12 @@
      */
     private String name;
     /**
+     * 浠撳簱绫诲瀷
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_WM_WAREHOUSE_TYPE}
+     */
+    private Integer type;
+    /**
      * 浠撳簱鍦板潃
      */
     private String address;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/telemetry/MesDvTelemetryMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/telemetry/MesDvTelemetryMapper.java
new file mode 100644
index 0000000..cf333b1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/telemetry/MesDvTelemetryMapper.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.dv.telemetry;
+
+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.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.telemetry.MesDvTelemetryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * MES 璁惧鏁伴噰閬ユ祴鏁版嵁 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface MesDvTelemetryMapper extends BaseMapperX<MesDvTelemetryDO> {
+
+    default PageResult<MesDvTelemetryDO> selectPage(MesDvTelemetryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesDvTelemetryDO>()
+                .eqIfPresent(MesDvTelemetryDO::getTbDeviceId, reqVO.getTbDeviceId())
+                .likeIfPresent(MesDvTelemetryDO::getDeviceName, reqVO.getDeviceName())
+                .likeIfPresent(MesDvTelemetryDO::getParamName, reqVO.getParamName())
+                .eqIfPresent(MesDvTelemetryDO::getWhetherAnomaly, reqVO.getWhetherAnomaly())
+                .eqIfPresent(MesDvTelemetryDO::getShiftName, reqVO.getShiftName())
+                .betweenIfPresent(MesDvTelemetryDO::getTelemetryDataTime, reqVO.getTelemetryDataTime())
+                .orderByDesc(MesDvTelemetryDO::getTelemetryDataTime)
+                .orderByDesc(MesDvTelemetryDO::getId));
+    }
+
+    default List<MesDvTelemetryDO> selectLatest(MesDvTelemetryLatestReqVO reqVO, int limit) {
+        return selectList(new LambdaQueryWrapperX<MesDvTelemetryDO>()
+                .eqIfPresent(MesDvTelemetryDO::getTbDeviceId, reqVO.getTbDeviceId())
+                .likeIfPresent(MesDvTelemetryDO::getDeviceName, reqVO.getDeviceName())
+                .orderByDesc(MesDvTelemetryDO::getTelemetryDataTime)
+                .orderByDesc(MesDvTelemetryDO::getId)
+                .last("LIMIT " + limit));
+    }
+
+    default List<MesDvTelemetryDO> selectDeviceList() {
+        return selectList(new LambdaQueryWrapperX<MesDvTelemetryDO>()
+                .select(MesDvTelemetryDO::getTbDeviceId, MesDvTelemetryDO::getDeviceName)
+                .groupBy(MesDvTelemetryDO::getTbDeviceId, MesDvTelemetryDO::getDeviceName)
+                .orderByAsc(MesDvTelemetryDO::getDeviceName));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdDiscountPolicyMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdDiscountPolicyMapper.java
new file mode 100644
index 0000000..091d5fe
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdDiscountPolicyMapper.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicyPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdDiscountPolicyDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MesPdDiscountPolicyMapper extends BaseMapperX<MesPdDiscountPolicyDO> {
+
+    default PageResult<MesPdDiscountPolicyDO> selectPage(MesPdDiscountPolicyPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdDiscountPolicyDO>()
+                .likeIfPresent(MesPdDiscountPolicyDO::getName, reqVO.getName())
+                .eqIfPresent(MesPdDiscountPolicyDO::getPolicyType, reqVO.getPolicyType())
+                .eqIfPresent(MesPdDiscountPolicyDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesPdDiscountPolicyDO::getId));
+    }
+
+    default List<MesPdDiscountPolicyDO> selectListByTarget(Integer targetType, Long targetId) {
+        return selectList(new LambdaQueryWrapperX<MesPdDiscountPolicyDO>()
+                .eq(MesPdDiscountPolicyDO::getStatus, 1)
+                .and(w -> w.eq(MesPdDiscountPolicyDO::getTargetType, targetType)
+                        .eq(MesPdDiscountPolicyDO::getTargetId, targetId)
+                        .or()
+                        .isNull(MesPdDiscountPolicyDO::getTargetType))
+                .orderByDesc(MesPdDiscountPolicyDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdElectricityTypeMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdElectricityTypeMapper.java
new file mode 100644
index 0000000..f62c25f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdElectricityTypeMapper.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypePageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdElectricityTypeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MesPdElectricityTypeMapper extends BaseMapperX<MesPdElectricityTypeDO> {
+
+    default PageResult<MesPdElectricityTypeDO> selectPage(MesPdElectricityTypePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdElectricityTypeDO>()
+                .likeIfPresent(MesPdElectricityTypeDO::getName, reqVO.getName())
+                .eqIfPresent(MesPdElectricityTypeDO::getStatus, reqVO.getStatus())
+                .orderByAsc(MesPdElectricityTypeDO::getSort)
+                .orderByDesc(MesPdElectricityTypeDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdPriceStandardMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdPriceStandardMapper.java
new file mode 100644
index 0000000..26f66b5
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdPriceStandardMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdPriceStandardDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MesPdPriceStandardMapper extends BaseMapperX<MesPdPriceStandardDO> {
+
+    default PageResult<MesPdPriceStandardDO> selectPage(MesPdPriceStandardPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdPriceStandardDO>()
+                .eqIfPresent(MesPdPriceStandardDO::getObjectType, reqVO.getObjectType())
+                .eqIfPresent(MesPdPriceStandardDO::getObjectId, reqVO.getObjectId())
+                .eqIfPresent(MesPdPriceStandardDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesPdPriceStandardDO::getId));
+    }
+
+    default List<MesPdPriceStandardDO> selectListByObjectTypeAndId(Integer objectType, Long objectId) {
+        return selectList(new LambdaQueryWrapperX<MesPdPriceStandardDO>()
+                .eq(MesPdPriceStandardDO::getObjectType, objectType)
+                .eq(MesPdPriceStandardDO::getObjectId, objectId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdServicePackageMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdServicePackageMapper.java
new file mode 100644
index 0000000..7ef402e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdServicePackageMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdServicePackagePageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdServicePackageDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MesPdServicePackageMapper extends BaseMapperX<MesPdServicePackageDO> {
+
+    default PageResult<MesPdServicePackageDO> selectPage(MesPdServicePackagePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdServicePackageDO>()
+                .likeIfPresent(MesPdServicePackageDO::getName, reqVO.getName())
+                .eqIfPresent(MesPdServicePackageDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesPdServicePackageDO::getId));
+    }
+
+    default MesPdServicePackageDO selectByCode(String code) {
+        return selectOne(MesPdServicePackageDO::getCode, code);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdTieredPriceMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdTieredPriceMapper.java
new file mode 100644
index 0000000..c7b48a4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdTieredPriceMapper.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdTieredPricePageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdTieredPriceDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MesPdTieredPriceMapper extends BaseMapperX<MesPdTieredPriceDO> {
+
+    default PageResult<MesPdTieredPriceDO> selectPage(MesPdTieredPricePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdTieredPriceDO>()
+                .eqIfPresent(MesPdTieredPriceDO::getPackageId, reqVO.getPackageId())
+                .eqIfPresent(MesPdTieredPriceDO::getVoltageLevelId, reqVO.getVoltageLevelId())
+                .eqIfPresent(MesPdTieredPriceDO::getStatus, reqVO.getStatus())
+                .orderByAsc(MesPdTieredPriceDO::getStartValue)
+                .orderByDesc(MesPdTieredPriceDO::getId));
+    }
+
+    default List<MesPdTieredPriceDO> selectListByPackageId(Long packageId) {
+        return selectList(new LambdaQueryWrapperX<MesPdTieredPriceDO>()
+                .eq(MesPdTieredPriceDO::getPackageId, packageId)
+                .eq(MesPdTieredPriceDO::getStatus, 1)
+                .orderByAsc(MesPdTieredPriceDO::getStartValue));
+    }
+
+    default List<MesPdTieredPriceDO> selectListByVoltageLevelId(Long voltageLevelId) {
+        return selectList(new LambdaQueryWrapperX<MesPdTieredPriceDO>()
+                .eq(MesPdTieredPriceDO::getVoltageLevelId, voltageLevelId)
+                .eq(MesPdTieredPriceDO::getStatus, 1)
+                .orderByAsc(MesPdTieredPriceDO::getStartValue));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdVoltageLevelMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdVoltageLevelMapper.java
new file mode 100644
index 0000000..3675493
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/basedata/MesPdVoltageLevelMapper.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdVoltageLevelDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MesPdVoltageLevelMapper extends BaseMapperX<MesPdVoltageLevelDO> {
+
+    default PageResult<MesPdVoltageLevelDO> selectPage(MesPdVoltageLevelPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdVoltageLevelDO>()
+                .likeIfPresent(MesPdVoltageLevelDO::getName, reqVO.getName())
+                .eqIfPresent(MesPdVoltageLevelDO::getStatus, reqVO.getStatus())
+                .orderByAsc(MesPdVoltageLevelDO::getSort)
+                .orderByDesc(MesPdVoltageLevelDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/ctc/ProductCtcMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/ctc/ProductCtcMapper.java
new file mode 100644
index 0000000..4c34fa9
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/ctc/ProductCtcMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.ctc;
+
+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.mes.controller.admin.pd.ctc.vo.ProductCtcPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc.ProductCtcDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ProductCtcMapper extends BaseMapperX<ProductCtcDO> {
+
+    default PageResult<ProductCtcDO> selectPage(ProductCtcPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductCtcDO>()
+                .eqIfPresent(ProductCtcDO::getItemId, reqVO.getItemId())
+                .likeIfPresent(ProductCtcDO::getOrderSpec, reqVO.getOrderSpec())
+                .eqIfPresent(ProductCtcDO::getConductorType, reqVO.getConductorType())
+                .likeIfPresent(ProductCtcDO::getStandardCode, reqVO.getStandardCode())
+                .orderByDesc(ProductCtcDO::getId));
+    }
+
+    default ProductCtcDO selectByItemId(Long itemId) {
+        return selectOne(ProductCtcDO::getItemId, itemId);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/product/MesPdProductMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/product/MesPdProductMapper.java
new file mode 100644
index 0000000..e33002c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/product/MesPdProductMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.product;
+
+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.mes.controller.admin.pd.product.vo.MesPdProductPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.product.MesPdProductDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MesPdProductMapper extends BaseMapperX<MesPdProductDO> {
+
+    default PageResult<MesPdProductDO> selectPage(MesPdProductPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesPdProductDO>()
+                .eqIfPresent(MesPdProductDO::getArchiveType, reqVO.getArchiveType())
+                .likeIfPresent(MesPdProductDO::getName, reqVO.getName())
+                .eqIfPresent(MesPdProductDO::getAuditStatus, reqVO.getAuditStatus())
+                .eqIfPresent(MesPdProductDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesPdProductDO::getId));
+    }
+
+    default MesPdProductDO selectByCode(String code) {
+        return selectOne(MesPdProductDO::getCode, code);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/reel/ProductCtcReelMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/reel/ProductCtcReelMapper.java
new file mode 100644
index 0000000..e1bb668
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/reel/ProductCtcReelMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.reel;
+
+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.mes.controller.admin.pd.reel.vo.ProductCtcReelPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.reel.ProductCtcReelDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ProductCtcReelMapper extends BaseMapperX<ProductCtcReelDO> {
+
+    default PageResult<ProductCtcReelDO> selectPage(ProductCtcReelPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductCtcReelDO>()
+                .eqIfPresent(ProductCtcReelDO::getCtcId, reqVO.getCtcId())
+                .eqIfPresent(ProductCtcReelDO::getReelType, reqVO.getReelType())
+                .orderByDesc(ProductCtcReelDO::getId));
+    }
+
+    default List<ProductCtcReelDO> selectListByCtcId(Long ctcId) {
+        return selectList(ProductCtcReelDO::getCtcId, ctcId);
+    }
+
+    default int deleteByCtcId(Long ctcId) {
+        return delete(ProductCtcReelDO::getCtcId, ctcId);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/techconfirm/ProductTechConfirmMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/techconfirm/ProductTechConfirmMapper.java
new file mode 100644
index 0000000..befbb93
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/techconfirm/ProductTechConfirmMapper.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pd.techconfirm;
+
+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.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ProductTechConfirmMapper extends BaseMapperX<ProductTechConfirmDO> {
+
+    default PageResult<ProductTechConfirmDO> selectPage(ProductTechConfirmPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductTechConfirmDO>()
+                .likeIfPresent(ProductTechConfirmDO::getConfirmNo, reqVO.getConfirmNo())
+                .eqIfPresent(ProductTechConfirmDO::getCustomerId, reqVO.getCustomerId())
+                .likeIfPresent(ProductTechConfirmDO::getProductName, reqVO.getProductName())
+                .eqIfPresent(ProductTechConfirmDO::getTemplateType, reqVO.getTemplateType())
+                .eqIfPresent(ProductTechConfirmDO::getStatus, reqVO.getStatus())
+                .orderByDesc(ProductTechConfirmDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/emergencyplan/MesProEmergencyPlanMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/emergencyplan/MesProEmergencyPlanMapper.java
new file mode 100644
index 0000000..477a058
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/emergencyplan/MesProEmergencyPlanMapper.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.emergencyplan;
+
+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.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 搴旀�ュ缃妗� Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface MesProEmergencyPlanMapper extends BaseMapperX<MesProEmergencyPlanDO> {
+
+    default MesProEmergencyPlanDO selectByCode(String code) {
+        return selectOne(MesProEmergencyPlanDO::getCode, code);
+    }
+
+    default PageResult<MesProEmergencyPlanDO> selectPage(MesProEmergencyPlanPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesProEmergencyPlanDO>()
+                .likeIfPresent(MesProEmergencyPlanDO::getCode, reqVO.getCode())
+                .likeIfPresent(MesProEmergencyPlanDO::getName, reqVO.getName())
+                .eqIfPresent(MesProEmergencyPlanDO::getPlanType, reqVO.getPlanType())
+                .eqIfPresent(MesProEmergencyPlanDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesProEmergencyPlanDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanItemMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanItemMapper.java
new file mode 100644
index 0000000..c673b37
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanItemMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenanceplan;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝鏄庣粏 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface MesProMaintenancePlanItemMapper extends BaseMapperX<MesProMaintenancePlanItemDO> {
+
+    default List<MesProMaintenancePlanItemDO> selectListByPlanId(Long planId) {
+        return selectList(MesProMaintenancePlanItemDO::getPlanId, planId);
+    }
+
+    default List<MesProMaintenancePlanItemDO> selectListByPlanIds(Collection<Long> planIds) {
+        return selectList(MesProMaintenancePlanItemDO::getPlanId, planIds);
+    }
+
+    default void deleteByPlanId(Long planId) {
+        delete(new LambdaQueryWrapperX<MesProMaintenancePlanItemDO>()
+                .eq(MesProMaintenancePlanItemDO::getPlanId, planId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanMapper.java
new file mode 100644
index 0000000..f618372
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenanceplan/MesProMaintenancePlanMapper.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenanceplan;
+
+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.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface MesProMaintenancePlanMapper extends BaseMapperX<MesProMaintenancePlanDO> {
+
+    default MesProMaintenancePlanDO selectByCode(String code) {
+        return selectOne(MesProMaintenancePlanDO::getCode, code);
+    }
+
+    default List<MesProMaintenancePlanDO> selectPlanListByIds(Collection<Long> ids) {
+        return selectList(MesProMaintenancePlanDO::getId, ids);
+    }
+
+    default PageResult<MesProMaintenancePlanDO> selectPage(MesProMaintenancePlanPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<MesProMaintenancePlanDO>()
+                .likeIfPresent(MesProMaintenancePlanDO::getCode, pageReqVO.getCode())
+                .likeIfPresent(MesProMaintenancePlanDO::getName, pageReqVO.getName())
+                .eqIfPresent(MesProMaintenancePlanDO::getPlanType, pageReqVO.getPlanType())
+                .eqIfPresent(MesProMaintenancePlanDO::getStatus, pageReqVO.getStatus())
+                .betweenIfPresent(MesProMaintenancePlanDO::getPlanDate, pageReqVO.getPlanDate())
+                .orderByDesc(MesProMaintenancePlanDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/outsourcetest/MesQcOutsourceTestMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/outsourcetest/MesQcOutsourceTestMapper.java
new file mode 100644
index 0000000..301c9b8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/outsourcetest/MesQcOutsourceTestMapper.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.qc.outsourcetest;
+
+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.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.outsourcetest.MesQcOutsourceTestDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 濮斿妫�娴�/璇曢獙鍗� Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface MesQcOutsourceTestMapper extends BaseMapperX<MesQcOutsourceTestDO> {
+
+    default MesQcOutsourceTestDO selectByCode(String code) {
+        return selectOne(MesQcOutsourceTestDO::getCode, code);
+    }
+
+    default PageResult<MesQcOutsourceTestDO> selectPage(MesQcOutsourceTestPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesQcOutsourceTestDO>()
+                .likeIfPresent(MesQcOutsourceTestDO::getCode, reqVO.getCode())
+                .likeIfPresent(MesQcOutsourceTestDO::getName, reqVO.getName())
+                .eqIfPresent(MesQcOutsourceTestDO::getTestType, reqVO.getTestType())
+                .eqIfPresent(MesQcOutsourceTestDO::getResult, reqVO.getResult())
+                .eqIfPresent(MesQcOutsourceTestDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(MesQcOutsourceTestDO::getTestDate, reqVO.getTestDate())
+                .orderByDesc(MesQcOutsourceTestDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
index 0fd3d74..08b2d3a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
@@ -9,6 +9,13 @@
 
     // ========== 鍩虹鏁版嵁 (MD) ==========
     String MES_MD_ITEM_OR_PRODUCT = "mes_md_item_or_product"; // MES 鐗╂枡浜у搧鏍囪瘑
+    String MES_MATERIAL_CATEGORY = "mes_material_category"; // MES 鐢靛姏鐗╄祫鍒嗙被
+    String MES_PRO_MAINTENANCE_PLAN_TYPE = "mes_pro_maintenance_plan_type"; // MES 鐢靛姏浣滀笟璁″垝绫诲瀷
+    String MES_PRO_MAINTENANCE_PLAN_STATUS = "mes_pro_maintenance_plan_status"; // MES 鐢靛姏浣滀笟璁″垝鐘舵��
+    String MES_QC_OUTSOURCE_TEST_TYPE = "mes_qc_outsource_test_type"; // MES 濮斿妫�娴嬬被鍨�
+    String MES_QC_OUTSOURCE_TEST_RESULT = "mes_qc_outsource_test_result"; // MES 濮斿妫�娴嬬粨鏋�
+    String MES_PRO_PROCESS_TYPE = "mes_pro_process_type"; // MES 鐢靛姏宸ヨ壓鍒嗙被
+    String MES_PRO_EMERGENCY_PLAN_TYPE = "mes_pro_emergency_plan_type"; // MES 搴旀�ュ缃妗堢被鍨�
     String MES_MD_AUTO_CODE_PART_TYPE = "mes_md_auto_code_part_type"; // MES 鍒嗘绫诲瀷
     String MES_MD_AUTO_CODE_CYCLE_METHOD = "mes_md_auto_code_cycle_method"; // MES 寰幆鏂瑰紡
     String MES_MD_AUTO_CODE_PADDED_METHOD = "mes_md_auto_code_padded_method"; // MES 琛ラ綈鏂瑰紡
@@ -34,6 +41,7 @@
     String MES_BARCODE_FORMAT = "mes_barcode_format"; // MES 鏉$爜鏍煎紡
     String MES_BARCODE_BIZ_TYPE = "mes_barcode_biz_type"; // MES 鏉$爜涓氬姟绫诲瀷
     String MES_WM_TRANSACTION_TYPE = "mes_wm_transaction_type"; // MES 搴撳瓨浜嬪姟绫诲瀷
+    String MES_WM_WAREHOUSE_TYPE = "mes_wm_warehouse_type"; // MES 浠撳簱/搴撳尯绫诲瀷锛堟櫘閫�/璁惧浠�/澶囦欢浠�/鑰楁潗浠�/鍗卞寲鍝佷粨锛�
     String MES_WM_QUALITY_STATUS = "mes_wm_quality_status"; // MES 璐ㄩ噺鐘舵�侊紙寰呮/鍚堟牸/涓嶅悎鏍硷級
 
     // ========== 浠撳偍鍏ュ簱 (WM Receipt) ==========
@@ -56,6 +64,7 @@
     String MES_WM_PRODUCT_SALES_STATUS = "mes_wm_product_sales_status"; // MES 閿�鍞嚭搴撳崟鐘舵��
     String MES_WM_OUTSOURCE_ISSUE_STATUS = "mes_wm_outsource_issue_status"; // MES 濮斿鍑哄簱鐘舵��
     String MES_WM_PRODUCT_ISSUE_STATUS = "mes_wm_product_issue_status"; // MES 鐢熶骇棰嗘枡鐘舵��
+    String MES_WM_PRODUCT_ISSUE_TYPE = "mes_wm_product_issue_type"; // MES 鐢熶骇棰嗘枡鍗曟嵁绫诲瀷
     String MES_WM_RETURN_VENDOR_STATUS = "mes_wm_return_vendor_status"; // MES 渚涘簲鍟嗛��璐у崟鐘舵��
 
     // ========== 浠撳偍绉诲簱涓庣洏鐐� (WM Transfer) ==========
@@ -96,6 +105,7 @@
     String MES_DV_METERING_CATEGORY = "mes_dv_metering_category"; // MES 璁¢噺鍣ㄥ叿绫诲埆
     String MES_DV_METERING_STATUS = "mes_dv_metering_status"; // MES 璁¢噺鍣ㄥ叿鐘舵��
     String MES_DV_METERING_CHECK_RESULT = "mes_dv_metering_check_result"; // MES 璁¢噺妫�娴嬬粨璁�
+    String MES_DV_TELEMETRY_ANOMALY = "mes_dv_telemetry_anomaly"; // MES 鏁伴噰鏄惁寮傚父
 
     // ========== 鎺掔彮鏃ュ巻 (CAL) ==========
     String MES_CAL_HOLIDAY_TYPE = "mes_cal_holiday_type"; // MES 鍋囨湡绫诲瀷
@@ -115,4 +125,18 @@
     String MES_PD_ARCHIVE_STATUS = "mes_pd_archive_status";               // 褰掓。鐘舵��
     String MES_PD_PROCESS_PARAM_TYPE = "mes_pd_process_param_type";                   // 宸ヨ壓鍙傛暟绫诲瀷
     String MES_PD_PROCESS_PARAM_TEMPLATE_TYPE = "mes_pd_process_param_template_type"; // 宸ヨ壓鍙傛暟妯℃澘绫诲瀷
+
+    // ========== 浜у搧璁捐-鎹綅瀵肩嚎 (CTC) ==========
+    String PRODUCT_CONDUCTOR_TYPE = "product_conductor_type";             // 鎹綅瀵肩嚎瀵间綋绫诲瀷
+    String PRODUCT_REEL_TYPE = "product_reel_type";                       // 鎹綅瀵肩嚎鐩樺叿绫诲瀷
+    String PRODUCT_TECH_CONFIRM_TEMPLATE = "product_tech_confirm_template"; // 鎶�鏈‘璁ゅ嚱妯℃澘绫诲瀷
+    String PRODUCT_TECH_CONFIRM_STATUS = "product_tech_confirm_status";   // 鎶�鏈‘璁ゅ嚱鐘舵��
+
+    // ========== 浜у搧璁捐-浜у搧妗f涓庡熀纭�鏁版嵁 (PD-EXT) ==========
+    String MES_PD_PRODUCT_ARCHIVE_TYPE = "mes_pd_product_archive_type"; // 浜у搧妗f绫诲瀷锛�1鐢靛姏鏈嶅姟/2渚涚數浜у搧/3澧炲�艰兘婧愪骇鍝侊級
+    String MES_PD_PRODUCT_AUDIT_STATUS = "mes_pd_product_audit_status"; // 浜у搧妗f瀹℃壒鐘舵�侊紙0/10/20/30锛�
+    String MES_PD_PRODUCT_STATUS = "mes_pd_product_status";             // 浜у搧妗f鍚敤鐘舵��
+    String MES_PD_PRICE_OBJECT_TYPE = "mes_pd_price_object_type";       // 瀹氫环瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛�
+    String MES_PD_DISCOUNT_POLICY_TYPE = "mes_pd_discount_policy_type"; // 浼樻儬绛栫暐绫诲瀷锛�1鎶樻墸/2绔嬪噺锛�
+    String MES_PD_TIERED_PRICE_STATUS = "mes_pd_tiered_price_status";   // 闃舵鐢典环鍚敤鐘舵��
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index dec84a2..fbf7bba 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -169,6 +169,11 @@
     ErrorCode DV_MACHINERY_HAS_REPAIR = new ErrorCode(1_040_301_006, "璁惧宸插叧鑱旂淮淇伐鍗曪紝鏃犳硶鍒犻櫎");
     ErrorCode DV_MACHINERY_WORKSTATION_NOT_EXISTS = new ErrorCode(1_040_301_008, "宸ヤ綔绔欑紪鐮佷笉瀛樺湪");
 
+    // ========== MES 璁惧绠$悊-璁惧鏁伴噰锛�1-040-308-000锛� ==========
+    ErrorCode DV_TELEMETRY_NOT_EXISTS = new ErrorCode(1_040_308_000, "鏁伴噰閬ユ祴鏁版嵁涓嶅瓨鍦�");
+    ErrorCode DV_TELEMETRY_PULL_ERROR = new ErrorCode(1_040_308_001, "鏁伴噰鏁版嵁鎷夊彇澶辫触锛歿}");
+    ErrorCode DV_TELEMETRY_PULL_EMPTY = new ErrorCode(1_040_308_002, "鏁伴噰鎺ュ彛鏈繑鍥炴湁鏁堟暟鎹�");
+
     // ========== MES 璁惧绠$悊-鐐规淇濆吇椤圭洰锛�1-040-304-000锛� ==========
     ErrorCode DV_SUBJECT_NOT_EXISTS = new ErrorCode(1_040_304_000, "鐐规淇濆吇椤圭洰涓嶅瓨鍦�");
     ErrorCode DV_SUBJECT_CODE_DUPLICATE = new ErrorCode(1_040_304_001, "椤圭洰缂栫爜宸插瓨鍦�");
@@ -286,6 +291,7 @@
     ErrorCode PRO_WORK_ORDER_PARAM_RECORD_DETAIL_NOT_IN_TEMPLATE = new ErrorCode(1_040_502_202, "鍙傛暟鏄庣粏涓嶅睘浜庢墍閫夋ā鏉�");
     ErrorCode PRO_WORK_ORDER_PARAM_RECORD_REQUIRED_PARAM_MISSING = new ErrorCode(1_040_502_203, "蹇呭~鍙傛暟銆恵}銆戞湭濉啓");
     ErrorCode PRO_WORK_ORDER_PARAM_RECORD_TEMPLATE_NOT_CHANGE = new ErrorCode(1_040_502_204, "涓嶅厑璁告洿鎹㈠弬鏁版ā鏉匡紝璇峰垹闄ゅ悗閲嶆柊鍒涘缓");
+    ErrorCode PRO_WORK_ORDER_SALE_ORDER_NOT_EXISTS = new ErrorCode(1_040_502_205, "鍏宠仈鐨勮惀閿�璁㈠崟锛堥攢鍞鍗曪級涓嶅瓨鍦�");
 
     // ========== MES 鐢熶骇绠$悊-鐢熶骇浠诲姟锛�1-040-503-000锛� ==========
     ErrorCode PRO_TASK_NOT_EXISTS = new ErrorCode(1_040_503_000, "鐢熶骇浠诲姟涓嶅瓨鍦�");
@@ -798,6 +804,30 @@
     ErrorCode PD_PROCESS_PARAM_TEMPLATE_NOT_EXISTS = new ErrorCode(1_040_805_000, "宸ヨ壓鍙傛暟妯℃澘涓嶅瓨鍦�");
     ErrorCode PD_PROCESS_PARAM_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_040_805_001, "宸ヨ壓鍙傛暟妯℃澘缂栫爜宸插瓨鍦�");
 
+    // ========== MES 浜у搧璁捐-鎹綅瀵肩嚎鎶�鏈弬鏁帮紙1-040-806-000锛� ==========
+    ErrorCode PD_CTC_NOT_EXISTS = new ErrorCode(1_040_806_000, "鎹綅瀵肩嚎鎶�鏈弬鏁颁笉瀛樺湪");
+    ErrorCode PD_CTC_ITEM_DUPLICATE = new ErrorCode(1_040_806_001, "璇ョ墿鏂欏凡瀛樺湪鎹綅瀵肩嚎鎶�鏈弬鏁帮紝璇峰嬁閲嶅娣诲姞");
+    ErrorCode PD_CTC_CALC_PARAM_INVALID = new ErrorCode(1_040_806_002, "璁$畻鍙傛暟涓嶅悎娉曪紝璇锋鏌ヨ緭鍏�");
+
+    // ========== MES 浜у搧璁捐-鐩樺叿璁$畻锛�1-040-807-000锛� ==========
+    ErrorCode PD_CTC_REEL_NOT_EXISTS = new ErrorCode(1_040_807_000, "鐩樺叿璁$畻璁板綍涓嶅瓨鍦�");
+
+    // ========== MES 浜у搧璁捐-鎶�鏈‘璁ゅ嚱锛�1-040-808-000锛� ==========
+    ErrorCode PD_TECH_CONFIRM_NOT_EXISTS = new ErrorCode(1_040_808_000, "鎶�鏈‘璁ゅ嚱涓嶅瓨鍦�");
+    ErrorCode PD_TECH_CONFIRM_NO_DUPLICATE = new ErrorCode(1_040_808_001, "纭鍑界紪鍙峰凡瀛樺湪");
+
+    // ========== MES 浜у搧璁捐-浜у搧妗f涓庡熀纭�鏁版嵁锛�1-040-809-000锛� ==========
+    ErrorCode PD_PRODUCT_NOT_EXISTS = new ErrorCode(1_040_809_000, "浜у搧妗f涓嶅瓨鍦�");
+    ErrorCode PD_PRODUCT_AUDIT_STATUS_INVALID = new ErrorCode(1_040_809_001, "浜у搧妗f鐘舵�佷笉姝g‘锛屾棤娉曟墽琛岃鎿嶄綔");
+    ErrorCode PD_PRODUCT_SUBMIT_DUPLICATE = new ErrorCode(1_040_809_002, "浜у搧妗f宸叉彁浜ゅ鎵癸紝璇峰嬁閲嶅鎻愪氦");
+    ErrorCode PD_ELECTRICITY_TYPE_NOT_EXISTS = new ErrorCode(1_040_809_100, "鐢ㄧ數绫诲瀷涓嶅瓨鍦�");
+    ErrorCode PD_VOLTAGE_LEVEL_NOT_EXISTS = new ErrorCode(1_040_809_101, "鐢靛帇绛夌骇涓嶅瓨鍦�");
+    ErrorCode PD_SERVICE_PACKAGE_NOT_EXISTS = new ErrorCode(1_040_809_102, "鏈嶅姟濂楅涓嶅瓨鍦�");
+    ErrorCode PD_PRICE_STANDARD_NOT_EXISTS = new ErrorCode(1_040_809_103, "瀹氫环鏍囧噯涓嶅瓨鍦�");
+    ErrorCode PD_DISCOUNT_POLICY_NOT_EXISTS = new ErrorCode(1_040_809_104, "浼樻儬绛栫暐涓嶅瓨鍦�");
+    ErrorCode PD_TIERED_PRICE_NOT_EXISTS = new ErrorCode(1_040_809_105, "闃舵鐢典环涓嶅瓨鍦�");
+    ErrorCode PD_PRICE_CALC_OBJECT_NOT_CONFIGURED = new ErrorCode(1_040_809_200, "闃舵鐢典环鏈厤缃紝鏃犳硶璁$畻");
+
     // ========== MES 浠撳偍鐗╂祦-瀹℃壒閰嶇疆锛�1-040-900-000锛� ==========
     ErrorCode WMS_APPROVAL_CONFIG_NOT_EXISTS = new ErrorCode(1_040_900_000, "浠撳簱瀹℃壒閰嶇疆涓嶅瓨鍦�");
     ErrorCode WMS_APPROVAL_ORDER_NOT_EXISTS = new ErrorCode(1_040_900_001, "瀹℃壒鍗曟嵁涓嶅瓨鍦�");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
index 0e2a210..6ce49b6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
@@ -56,7 +56,9 @@
     PD_DOCUMENT_AUDIT_CODE("PD_DOCUMENT_AUDIT_CODE", "璧勬枡瀹℃牳鍗曠紪鐮�"),
     PD_ARCHIVE_CODE("PD_ARCHIVE_CODE", "褰掓。鍙拌处缂栫爜"),
     PD_PROCESS_PARAM_CODE("PD_PROCESS_PARAM_CODE", "宸ヨ壓鍙傛暟缂栫爜"),
-    PD_PROCESS_PARAM_TEMPLATE_CODE("PD_PROCESS_PARAM_TEMPLATE_CODE", "宸ヨ壓鍙傛暟妯℃澘缂栫爜");
+    PD_PROCESS_PARAM_TEMPLATE_CODE("PD_PROCESS_PARAM_TEMPLATE_CODE", "宸ヨ壓鍙傛暟妯℃澘缂栫爜"),
+    PD_PRODUCT_CODE("PD_PRODUCT_CODE", "浜у搧妗f缂栫爜"),
+    PD_PACKAGE_CODE("PD_PACKAGE_CODE", "鏈嶅姟濂楅缂栫爜");
 
     /**
      * 瑙勫垯浠g爜
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdAuditStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdAuditStatusEnum.java
new file mode 100644
index 0000000..77266f1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdAuditStatusEnum.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mes.enums.pd;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 浜у搧璁捐-瀹℃壒鐘舵��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum MesPdAuditStatusEnum implements ArrayValuable<Integer> {
+
+    DRAFT(0, "鏈彁浜�"),
+    PROCESS(10, "瀹℃壒涓�"),
+    APPROVE(20, "瀹℃牳閫氳繃"),
+    REJECT(30, "瀹℃牳涓嶉�氳繃");
+
+    private final Integer status;
+    private final String name;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesPdAuditStatusEnum::getStatus).toArray(Integer[]::new);
+
+    public static String getNameByStatus(Integer status) {
+        if (status == null) {
+            return null;
+        }
+        for (MesPdAuditStatusEnum e : values()) {
+            if (e.getStatus().equals(status)) {
+                return e.getName();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProductArchiveTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProductArchiveTypeEnum.java
new file mode 100644
index 0000000..7373f4c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProductArchiveTypeEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.mes.enums.pd;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 浜у搧璁捐-浜у搧妗f绫诲瀷
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum MesPdProductArchiveTypeEnum implements ArrayValuable<Integer> {
+
+    ELECTRICITY_SERVICE(1, "鐢靛姏鏈嶅姟"),
+    POWER_SUPPLY_PRODUCT(2, "渚涚數浜у搧"),
+    VALUE_ADDED_ENERGY(3, "澧炲�艰兘婧愪骇鍝�");
+
+    private final Integer type;
+    private final String name;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesPdProductArchiveTypeEnum::getType).toArray(Integer[]::new);
+
+    public static String getNameByType(Integer type) {
+        if (type == null) {
+            return null;
+        }
+        for (MesPdProductArchiveTypeEnum e : values()) {
+            if (e.getType().equals(type)) {
+                return e.getName();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProMaintenancePlanTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProMaintenancePlanTypeEnum.java
new file mode 100644
index 0000000..b49f6ba
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProMaintenancePlanTypeEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.mes.enums.pro;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝绫诲瀷鏋氫妇
+ *
+ * @author xiaoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum MesProMaintenancePlanTypeEnum implements ArrayValuable<Integer> {
+
+    GRID_OPERATION(1, "鐢电綉杩愯"),
+    POWER_GENERATION(2, "鍙戠數"),
+    SUBSTATION_MAINTENANCE(3, "鍙樼數杩愮淮"),
+    LINE_MAINTENANCE(4, "绾胯矾妫�淇�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(MesProMaintenancePlanTypeEnum::getType).toArray(Integer[]::new);
+
+    /**
+     * 绫诲瀷鍊�
+     */
+    private final Integer type;
+    /**
+     * 绫诲瀷鍚�
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
index 83459e7..74e7cfd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
@@ -16,7 +16,8 @@
 public enum MesProWorkOrderSourceTypeEnum implements ArrayValuable<Integer> {
 
     ORDER(1, "瀹㈡埛璁㈠崟"),
-    STORE(2, "搴撳瓨澶囪揣");
+    STORE(2, "搴撳瓨澶囪揣"),
+    MAINTENANCE_PLAN(3, "杩愮淮璁″垝");
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProWorkOrderSourceTypeEnum::getType).toArray(Integer[]::new);
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderTypeEnum.java
index 5e1705d..e5f454b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderTypeEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderTypeEnum.java
@@ -17,7 +17,10 @@
 
     SELF(1, "鑷鐢熶骇"),
     OUTSOURCE(2, "浠e伐"),
-    PURCHASE(3, "閲囪喘");
+    PURCHASE(3, "閲囪喘"),
+    POWER_OPERATION(4, "鐢靛姏杩愮淮"),
+    POWER_GENERATION(5, "鍙戠數"),
+    POWER_MAINTENANCE(6, "妫�淇�");
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProWorkOrderTypeEnum::getType).toArray(Integer[]::new);
 
@@ -30,6 +33,13 @@
      */
     private final String name;
 
+    public static MesProWorkOrderTypeEnum valueOf(Integer type) {
+        if (type == null) {
+            return null;
+        }
+        return Arrays.stream(values()).filter(item -> item.getType().equals(type)).findFirst().orElse(null);
+    }
+
     @Override
     public Integer[] array() {
         return ARRAYS;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueTypeEnum.java
index 5162329..96b88dd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueTypeEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueTypeEnum.java
@@ -11,7 +11,9 @@
 public enum MesWmMiscIssueTypeEnum implements ArrayValuable<Integer> {
 
     ADJUST(1, "搴撳瓨璋冩暣"),
-    SCRAP(2, "鎶ュ簾鍑哄簱");
+    SCRAP(2, "鎶ュ簾鍑哄簱"),
+    WASTE_OUT(3, "搴熸棫鐗╄祫鍑哄簱"),
+    EQUIPMENT_OUT(4, "璁惧鍑哄簱");
 
     private final Integer type;
     private final String name;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductIssueTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductIssueTypeEnum.java
new file mode 100644
index 0000000..581ddc5
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductIssueTypeEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.mes.enums.wm;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES 鐢熶骇棰嗘枡鍗曟嵁绫诲瀷鏋氫妇
+ *
+ * @author xiaoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum MesWmProductIssueTypeEnum implements ArrayValuable<Integer> {
+
+    NORMAL(1, "姝e父棰嗘枡"),
+    URGENT_REPLENISH(2, "绱ф�ヨˉ鏂�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(MesWmProductIssueTypeEnum::getType).toArray(Integer[]::new);
+
+    /**
+     * 绫诲瀷鍊�
+     */
+    private final Integer type;
+    /**
+     * 绫诲瀷鍚�
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTelemetryProperties.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTelemetryProperties.java
new file mode 100644
index 0000000..61927d7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTelemetryProperties.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * MES 鏁伴噰锛堣澶囨暟鎹噰闆嗭級閰嶇疆
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+@ConfigurationProperties(prefix = "yudao.mes.telemetry")
+@Data
+public class MesTelemetryProperties {
+
+    /**
+     * 鏁伴噰鏈嶅姟鍩虹鍦板潃锛堜笉鍚矾寰勶級锛屽 http://192.168.0.10:8080
+     */
+    private String baseUrl;
+
+    /**
+     * 鏁伴噰鎺ュ彛璺緞锛堥粯璁よ繑鍥炶繎 5 鍒嗛挓璁惧鏁版嵁锛�
+     */
+    private String path = "/api/jxjs/mesTb/getDeviceDetails";
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/dv/MesDvTelemetryPullJob.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/dv/MesDvTelemetryPullJob.java
new file mode 100644
index 0000000..ad84873
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/dv/MesDvTelemetryPullJob.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.job.dv;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPullRespVO;
+import cn.iocoder.yudao.module.mes.service.dv.telemetry.MesDvTelemetryService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 璁惧鏁伴噰鏁版嵁瀹氭椂鎷夊彇 Job
+ *
+ * 鏁伴噰鎺ュ彛浠呬繚鐣欒繎 5 鍒嗛挓鏁版嵁锛岄渶瀹氭椂鎷夊彇閬垮厤鏁版嵁涓㈠け銆傞粯璁ゆ瘡 5 鍒嗛挓鎵ц涓�娆°��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+public class MesDvTelemetryPullJob implements JobHandler {
+
+    @Resource
+    private MesDvTelemetryService telemetryService;
+
+    @Override
+    @TenantJob
+    public String execute(String param) {
+        MesDvTelemetryPullRespVO resp = telemetryService.pullTelemetry();
+        return String.format("鎷夊彇鏁伴噰鏁版嵁鎴愬姛锛屾柊澧� %s 鏉★紝娑夊強璁惧 %s 鍙�", resp.getRecordCount(), resp.getDeviceCount());
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java
new file mode 100644
index 0000000..74e1ec7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.service.dv.telemetry;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryDeviceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPullRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.telemetry.MesDvTelemetryDO;
+
+import java.util.List;
+
+/**
+ * MES 璁惧鏁伴噰閬ユ祴鏁版嵁 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesDvTelemetryService {
+
+    /**
+     * 鍒嗛〉鏌ヨ閬ユ祴鏁版嵁
+     */
+    PageResult<MesDvTelemetryDO> getTelemetryPage(MesDvTelemetryPageReqVO pageReqVO);
+
+    /**
+     * 鏌ヨ鍚勮澶囨渶鏂伴仴娴嬫暟鎹紙鎸夎澶�+淇″彿鍘婚噸锛屼繚鐣欐渶鏂帮級
+     */
+    List<MesDvTelemetryDO> getLatestTelemetry(MesDvTelemetryLatestReqVO reqVO);
+
+    /**
+     * 鏌ヨ鏁伴噰璁惧涓嬫媺鍒楄〃锛堟潵鑷仴娴嬫暟鎹幓閲嶏級
+     */
+    List<MesDvTelemetryDeviceRespVO> getDeviceList();
+
+    /**
+     * 璋冪敤澶栭儴鏁伴噰鎺ュ彛鎷夊彇杩� 5 鍒嗛挓璁惧鏁版嵁骞跺叆搴�
+     */
+    MesDvTelemetryPullRespVO pullTelemetry();
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java
new file mode 100644
index 0000000..b5678e4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java
@@ -0,0 +1,169 @@
+package cn.iocoder.yudao.module.mes.service.dv.telemetry;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryDeviceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPullRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.telemetry.MesDvTelemetryDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.telemetry.MesDvTelemetryMapper;
+import cn.iocoder.yudao.module.mes.framework.config.MesTelemetryProperties;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_TELEMETRY_PULL_ERROR;
+
+/**
+ * MES 璁惧鏁伴噰閬ユ祴鏁版嵁 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Slf4j
+public class MesDvTelemetryServiceImpl implements MesDvTelemetryService {
+
+    @Resource
+    private MesDvTelemetryMapper telemetryMapper;
+    @Resource
+    private MesTelemetryProperties telemetryProperties;
+
+    @Override
+    public PageResult<MesDvTelemetryDO> getTelemetryPage(MesDvTelemetryPageReqVO pageReqVO) {
+        return telemetryMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<MesDvTelemetryDO> getLatestTelemetry(MesDvTelemetryLatestReqVO reqVO) {
+        // 鍙栨渶杩戜竴鎵规暟鎹紝鎸夎澶�+淇″彿鍘婚噸淇濈暀鏈�鏂帮紝鐢ㄤ簬瀹炴椂鐩戞帶瑙嗗浘
+        List<MesDvTelemetryDO> list = telemetryMapper.selectLatest(reqVO, 5000);
+        Map<String, MesDvTelemetryDO> latestMap = new LinkedHashMap<>();
+        for (MesDvTelemetryDO row : list) {
+            String key = StrUtil.blankToDefault(row.getTbDeviceId(), "")
+                    + ":" + StrUtil.blankToDefault(row.getParamKeyName(), "");
+            latestMap.putIfAbsent(key, row);
+        }
+        return new ArrayList<>(latestMap.values());
+    }
+
+    @Override
+    public List<MesDvTelemetryDeviceRespVO> getDeviceList() {
+        List<MesDvTelemetryDeviceRespVO> result = new ArrayList<>();
+        for (MesDvTelemetryDO row : telemetryMapper.selectDeviceList()) {
+            MesDvTelemetryDeviceRespVO vo = new MesDvTelemetryDeviceRespVO();
+            vo.setTbDeviceId(row.getTbDeviceId());
+            vo.setDeviceName(row.getDeviceName());
+            result.add(vo);
+        }
+        return result;
+    }
+
+    @Override
+    public MesDvTelemetryPullRespVO pullTelemetry() {
+        // 1. 鏍¢獙閰嶇疆
+        if (StrUtil.isBlank(telemetryProperties.getBaseUrl())) {
+            throw exception(DV_TELEMETRY_PULL_ERROR, "鏈厤缃暟閲囨湇鍔″湴鍧� yudao.mes.telemetry.base-url");
+        }
+        String url = telemetryProperties.getBaseUrl() + telemetryProperties.getPath();
+        // 2. 璋冪敤澶栭儴鏁伴噰鎺ュ彛
+        String responseText;
+        try {
+            responseText = HttpUtils.get(url, null);
+        } catch (Exception e) {
+            log.error("[pullTelemetry][璋冪敤鏁伴噰鎺ュ彛澶辫触锛寀rl({})]", url, e);
+            throw exception(DV_TELEMETRY_PULL_ERROR, e.getMessage());
+        }
+        if (StrUtil.isBlank(responseText)) {
+            throw exception(DV_TELEMETRY_PULL_ERROR, "鏁伴噰鎺ュ彛杩斿洖涓虹┖");
+        }
+        // 3. 瑙f瀽鍝嶅簲
+        Map<?, ?> response = JsonUtils.parseObject(responseText, Map.class);
+        if (response == null || !"200".equals(String.valueOf(response.get("code")))) {
+            log.error("[pullTelemetry][鏁伴噰鎺ュ彛杩斿洖寮傚父锛寀rl({})锛屽搷搴�({})]", url, responseText);
+            throw exception(DV_TELEMETRY_PULL_ERROR, "鏁伴噰鎺ュ彛杩斿洖寮傚父");
+        }
+        List<?> dataList = (List<?>) response.get("data");
+        // 4. 鏃犳暟鎹椂杩斿洖绌虹粨鏋滐紝涓嶈涓洪敊璇�
+        MesDvTelemetryPullRespVO result = new MesDvTelemetryPullRespVO();
+        LocalDateTime pullTime = LocalDateTime.now();
+        result.setPullTime(pullTime);
+        if (CollUtil.isEmpty(dataList)) {
+            result.setRecordCount(0);
+            result.setDeviceCount(0);
+            return result;
+        }
+        // 5. 杞崲骞舵壒閲忓叆搴�
+        List<MesDvTelemetryDO> saveList = new ArrayList<>(dataList.size());
+        for (Object rowObj : dataList) {
+            if (!(rowObj instanceof Map)) {
+                continue;
+            }
+            MesDvTelemetryDO data = convert((Map<?, ?>) rowObj);
+            data.setPullTime(pullTime);
+            saveList.add(data);
+        }
+        telemetryMapper.insertBatch(saveList);
+        // 6. 杩斿洖缁熻
+        result.setRecordCount(saveList.size());
+        result.setDeviceCount((int) saveList.stream()
+                .map(MesDvTelemetryDO::getTbDeviceId).distinct().count());
+        return result;
+    }
+
+    private MesDvTelemetryDO convert(Map<?, ?> row) {
+        MesDvTelemetryDO data = new MesDvTelemetryDO();
+        data.setTbDeviceId(StrUtil.toStringOrNull(row.get("tbDeviceId")));
+        data.setDeviceName(StrUtil.toStringOrNull(row.get("deviceName")));
+        data.setParamName(StrUtil.toStringOrNull(row.get("paramName")));
+        data.setParamKeyName(StrUtil.toStringOrNull(row.get("paramKeyName")));
+        data.setStandardValue(StrUtil.toStringOrNull(row.get("standardValue")));
+        data.setTimelyValue(StrUtil.toStringOrNull(row.get("timelyValue")));
+        data.setAvgValue(StrUtil.toStringOrNull(row.get("avgValue")));
+        data.setMaxValue(StrUtil.toStringOrNull(row.get("maxValue")));
+        data.setMinValue(StrUtil.toStringOrNull(row.get("minValue")));
+        data.setWhetherAnomaly(parseBoolean(row.get("whetherAnomaly")));
+        data.setTelemetryDataTime(parseTime(row.get("telemetryDataTime")));
+        data.setBillNo(StrUtil.toStringOrNull(row.get("billNo")));
+        data.setShiftName(StrUtil.toStringOrNull(row.get("shiftName")));
+        return data;
+    }
+
+    private Boolean parseBoolean(Object value) {
+        if (value == null) {
+            return null;
+        }
+        String str = String.valueOf(value).trim();
+        if ("1".equals(str) || "true".equalsIgnoreCase(str) || "Y".equalsIgnoreCase(str)) {
+            return Boolean.TRUE;
+        }
+        if ("0".equals(str) || "false".equalsIgnoreCase(str) || "N".equalsIgnoreCase(str)) {
+            return Boolean.FALSE;
+        }
+        return null;
+    }
+
+    private LocalDateTime parseTime(Object value) {
+        if (value == null || StrUtil.isBlank(String.valueOf(value))) {
+            return null;
+        }
+        try {
+            return DateUtil.parse(String.valueOf(value)).toLocalDateTime();
+        } catch (Exception e) {
+            log.warn("[parseTime][閬ユ祴鏃堕棿瑙f瀽澶辫触锛寁alue({})]", value);
+            return null;
+        }
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyService.java
new file mode 100644
index 0000000..c1b024d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicyPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicyRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicySaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdDiscountPolicyDO;
+
+/**
+ * MES 浼樻儬绛栫暐 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdDiscountPolicyService {
+
+    Long createDiscountPolicy(MesPdDiscountPolicySaveReqVO createReqVO);
+
+    void updateDiscountPolicy(MesPdDiscountPolicySaveReqVO updateReqVO);
+
+    void deleteDiscountPolicy(Long id);
+
+    MesPdDiscountPolicyRespVO getDiscountPolicy(Long id);
+
+    PageResult<MesPdDiscountPolicyRespVO> getDiscountPolicyPage(MesPdDiscountPolicyPageReqVO pageReqVO);
+
+    MesPdDiscountPolicyDO validateDiscountPolicyExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyServiceImpl.java
new file mode 100644
index 0000000..c198edd
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdDiscountPolicyServiceImpl.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicyPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicyRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdDiscountPolicySaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdDiscountPolicyDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdDiscountPolicyMapper;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_DISCOUNT_POLICY_NOT_EXISTS;
+
+/**
+ * MES 浼樻儬绛栫暐 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesPdDiscountPolicyServiceImpl implements MesPdDiscountPolicyService {
+
+    @Resource
+    private MesPdDiscountPolicyMapper discountPolicyMapper;
+
+    @Override
+    public Long createDiscountPolicy(MesPdDiscountPolicySaveReqVO createReqVO) {
+        MesPdDiscountPolicyDO entity = BeanUtils.toBean(createReqVO, MesPdDiscountPolicyDO.class);
+        discountPolicyMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateDiscountPolicy(MesPdDiscountPolicySaveReqVO updateReqVO) {
+        validateDiscountPolicyExists(updateReqVO.getId());
+        MesPdDiscountPolicyDO entity = BeanUtils.toBean(updateReqVO, MesPdDiscountPolicyDO.class);
+        discountPolicyMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteDiscountPolicy(Long id) {
+        validateDiscountPolicyExists(id);
+        discountPolicyMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdDiscountPolicyRespVO getDiscountPolicy(Long id) {
+        MesPdDiscountPolicyDO entity = discountPolicyMapper.selectById(id);
+        if (entity == null) {
+            return null;
+        }
+        MesPdDiscountPolicyRespVO respVO = BeanUtils.toBean(entity, MesPdDiscountPolicyRespVO.class);
+        fillDictName(respVO);
+        return respVO;
+    }
+
+    @Override
+    public PageResult<MesPdDiscountPolicyRespVO> getDiscountPolicyPage(MesPdDiscountPolicyPageReqVO pageReqVO) {
+        PageResult<MesPdDiscountPolicyDO> page = discountPolicyMapper.selectPage(pageReqVO);
+        PageResult<MesPdDiscountPolicyRespVO> result = BeanUtils.toBean(page, MesPdDiscountPolicyRespVO.class);
+        result.getList().forEach(this::fillDictName);
+        return result;
+    }
+
+    @Override
+    public MesPdDiscountPolicyDO validateDiscountPolicyExists(Long id) {
+        MesPdDiscountPolicyDO entity = discountPolicyMapper.selectById(id);
+        if (entity == null) {
+            throw exception(PD_DISCOUNT_POLICY_NOT_EXISTS);
+        }
+        return entity;
+    }
+
+    private void fillDictName(MesPdDiscountPolicyRespVO respVO) {
+        respVO.setPolicyTypeName(DictFrameworkUtils.parseDictDataLabel(
+                DictTypeConstants.MES_PD_DISCOUNT_POLICY_TYPE, respVO.getPolicyType()));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeService.java
new file mode 100644
index 0000000..dbc324a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeService.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypeRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypeSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdElectricityTypeDO;
+
+import java.util.List;
+
+/**
+ * MES 鐢ㄧ數绫诲瀷鍩虹淇℃伅 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdElectricityTypeService {
+
+    Long createElectricityType(MesPdElectricityTypeSaveReqVO createReqVO);
+
+    void updateElectricityType(MesPdElectricityTypeSaveReqVO updateReqVO);
+
+    void deleteElectricityType(Long id);
+
+    MesPdElectricityTypeRespVO getElectricityType(Long id);
+
+    PageResult<MesPdElectricityTypeRespVO> getElectricityTypePage(MesPdElectricityTypePageReqVO pageReqVO);
+
+    List<MesPdElectricityTypeDO> getElectricityTypeList();
+
+    MesPdElectricityTypeDO validateElectricityTypeExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeServiceImpl.java
new file mode 100644
index 0000000..9f4058c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdElectricityTypeServiceImpl.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypeRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdElectricityTypeSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdElectricityTypeDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdElectricityTypeMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_ELECTRICITY_TYPE_NOT_EXISTS;
+
+/**
+ * MES 鐢ㄧ數绫诲瀷鍩虹淇℃伅 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesPdElectricityTypeServiceImpl implements MesPdElectricityTypeService {
+
+    @Resource
+    private MesPdElectricityTypeMapper electricityTypeMapper;
+
+    @Override
+    public Long createElectricityType(MesPdElectricityTypeSaveReqVO createReqVO) {
+        MesPdElectricityTypeDO entity = BeanUtils.toBean(createReqVO, MesPdElectricityTypeDO.class);
+        electricityTypeMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateElectricityType(MesPdElectricityTypeSaveReqVO updateReqVO) {
+        validateElectricityTypeExists(updateReqVO.getId());
+        MesPdElectricityTypeDO entity = BeanUtils.toBean(updateReqVO, MesPdElectricityTypeDO.class);
+        electricityTypeMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteElectricityType(Long id) {
+        validateElectricityTypeExists(id);
+        electricityTypeMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdElectricityTypeRespVO getElectricityType(Long id) {
+        MesPdElectricityTypeDO entity = electricityTypeMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, MesPdElectricityTypeRespVO.class);
+    }
+
+    @Override
+    public PageResult<MesPdElectricityTypeRespVO> getElectricityTypePage(MesPdElectricityTypePageReqVO pageReqVO) {
+        return BeanUtils.toBean(electricityTypeMapper.selectPage(pageReqVO), MesPdElectricityTypeRespVO.class);
+    }
+
+    @Override
+    public List<MesPdElectricityTypeDO> getElectricityTypeList() {
+        return electricityTypeMapper.selectList();
+    }
+
+    @Override
+    public MesPdElectricityTypeDO validateElectricityTypeExists(Long id) {
+        MesPdElectricityTypeDO entity = electricityTypeMapper.selectById(id);
+        if (entity == null) {
+            throw exception(PD_ELECTRICITY_TYPE_NOT_EXISTS);
+        }
+        return entity;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardService.java
new file mode 100644
index 0000000..62903a1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdPriceStandardDO;
+
+/**
+ * MES 瀹氫环鏍囧噯 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdPriceStandardService {
+
+    Long createPriceStandard(MesPdPriceStandardSaveReqVO createReqVO);
+
+    void updatePriceStandard(MesPdPriceStandardSaveReqVO updateReqVO);
+
+    void deletePriceStandard(Long id);
+
+    MesPdPriceStandardRespVO getPriceStandard(Long id);
+
+    PageResult<MesPdPriceStandardRespVO> getPriceStandardPage(MesPdPriceStandardPageReqVO pageReqVO);
+
+    MesPdPriceStandardDO validatePriceStandardExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardServiceImpl.java
new file mode 100644
index 0000000..f32e364
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdPriceStandardServiceImpl.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+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.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdPriceStandardSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdPriceStandardDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdPriceStandardMapper;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_PRICE_STANDARD_NOT_EXISTS;
+
+/**
+ * MES 瀹氫环鏍囧噯 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesPdPriceStandardServiceImpl implements MesPdPriceStandardService {
+
+    @Resource
+    private MesPdPriceStandardMapper priceStandardMapper;
+
+    @Override
+    public Long createPriceStandard(MesPdPriceStandardSaveReqVO createReqVO) {
+        MesPdPriceStandardDO entity = BeanUtils.toBean(createReqVO, MesPdPriceStandardDO.class);
+        priceStandardMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updatePriceStandard(MesPdPriceStandardSaveReqVO updateReqVO) {
+        validatePriceStandardExists(updateReqVO.getId());
+        MesPdPriceStandardDO entity = BeanUtils.toBean(updateReqVO, MesPdPriceStandardDO.class);
+        priceStandardMapper.updateById(entity);
+    }
+
+    @Override
+    public void deletePriceStandard(Long id) {
+        validatePriceStandardExists(id);
+        priceStandardMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdPriceStandardRespVO getPriceStandard(Long id) {
+        MesPdPriceStandardDO entity = priceStandardMapper.selectById(id);
+        if (entity == null) {
+            return null;
+        }
+        MesPdPriceStandardRespVO respVO = BeanUtils.toBean(entity, MesPdPriceStandardRespVO.class);
+        fillDictName(respVO);
+        return respVO;
+    }
+
+    @Override
+    public PageResult<MesPdPriceStandardRespVO> getPriceStandardPage(MesPdPriceStandardPageReqVO pageReqVO) {
+        PageResult<MesPdPriceStandardDO> page = priceStandardMapper.selectPage(pageReqVO);
+        PageResult<MesPdPriceStandardRespVO> result = BeanUtils.toBean(page, MesPdPriceStandardRespVO.class);
+        result.getList().forEach(this::fillDictName);
+        return result;
+    }
+
+    @Override
+    public MesPdPriceStandardDO validatePriceStandardExists(Long id) {
+        MesPdPriceStandardDO entity = priceStandardMapper.selectById(id);
+        if (entity == null) {
+            throw exception(PD_PRICE_STANDARD_NOT_EXISTS);
+        }
+        return entity;
+    }
+
+    private void fillDictName(MesPdPriceStandardRespVO respVO) {
+        respVO.setObjectTypeName(DictFrameworkUtils.parseDictDataLabel(
+                DictTypeConstants.MES_PD_PRICE_OBJECT_TYPE, respVO.getObjectType()));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageService.java
new file mode 100644
index 0000000..f6bbcab
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageService.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdServicePackagePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdServicePackageRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdServicePackageSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdServicePackageDO;
+
+import java.util.List;
+
+/**
+ * MES 鏈嶅姟濂楅鍩虹淇℃伅 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdServicePackageService {
+
+    Long createServicePackage(MesPdServicePackageSaveReqVO createReqVO);
+
+    void updateServicePackage(MesPdServicePackageSaveReqVO updateReqVO);
+
+    void deleteServicePackage(Long id);
+
+    MesPdServicePackageRespVO getServicePackage(Long id);
+
+    PageResult<MesPdServicePackageRespVO> getServicePackagePage(MesPdServicePackagePageReqVO pageReqVO);
+
+    List<MesPdServicePackageDO> getServicePackageList();
+
+    MesPdServicePackageDO validateServicePackageExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageServiceImpl.java
new file mode 100644
index 0000000..f4d8cc7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdServicePackageServiceImpl.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdServicePackagePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdServicePackageRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdServicePackageSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdServicePackageDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdServicePackageMapper;
+import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
+import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
+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.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_SERVICE_PACKAGE_NOT_EXISTS;
+
+/**
+ * MES 鏈嶅姟濂楅鍩虹淇℃伅 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesPdServicePackageServiceImpl implements MesPdServicePackageService {
+
+    @Resource
+    private MesPdServicePackageMapper servicePackageMapper;
+
+    @Resource
+    @Lazy
+    private MesMdAutoCodeRecordService autoCodeRecordService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createServicePackage(MesPdServicePackageSaveReqVO createReqVO) {
+        MesPdServicePackageDO entity = BeanUtils.toBean(createReqVO, MesPdServicePackageDO.class);
+        entity.setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PD_PACKAGE_CODE.getCode()));
+        servicePackageMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateServicePackage(MesPdServicePackageSaveReqVO updateReqVO) {
+        MesPdServicePackageDO packageDO = validateServicePackageExists(updateReqVO.getId());
+        MesPdServicePackageDO entity = BeanUtils.toBean(updateReqVO, MesPdServicePackageDO.class);
+        entity.setCode(packageDO.getCode());
+        servicePackageMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteServicePackage(Long id) {
+        validateServicePackageExists(id);
+        servicePackageMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdServicePackageRespVO getServicePackage(Long id) {
+        MesPdServicePackageDO entity = servicePackageMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, MesPdServicePackageRespVO.class);
+    }
+
+    @Override
+    public PageResult<MesPdServicePackageRespVO> getServicePackagePage(MesPdServicePackagePageReqVO pageReqVO) {
+        return BeanUtils.toBean(servicePackageMapper.selectPage(pageReqVO), MesPdServicePackageRespVO.class);
+    }
+
+    @Override
+    public List<MesPdServicePackageDO> getServicePackageList() {
+        return servicePackageMapper.selectList();
+    }
+
+    @Override
+    public MesPdServicePackageDO validateServicePackageExists(Long id) {
+        MesPdServicePackageDO entity = servicePackageMapper.selectById(id);
+        if (entity == null) {
+            throw exception(PD_SERVICE_PACKAGE_NOT_EXISTS);
+        }
+        return entity;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceService.java
new file mode 100644
index 0000000..004ca96
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdTieredPricePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdTieredPriceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdTieredPriceSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdTieredPriceDO;
+
+/**
+ * MES 闃舵鐢典环 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdTieredPriceService {
+
+    Long createTieredPrice(MesPdTieredPriceSaveReqVO createReqVO);
+
+    void updateTieredPrice(MesPdTieredPriceSaveReqVO updateReqVO);
+
+    void deleteTieredPrice(Long id);
+
+    MesPdTieredPriceRespVO getTieredPrice(Long id);
+
+    PageResult<MesPdTieredPriceRespVO> getTieredPricePage(MesPdTieredPricePageReqVO pageReqVO);
+
+    MesPdTieredPriceDO validateTieredPriceExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceServiceImpl.java
new file mode 100644
index 0000000..8653bf1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdTieredPriceServiceImpl.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdTieredPricePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdTieredPriceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdTieredPriceSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdTieredPriceDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdTieredPriceMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TIERED_PRICE_NOT_EXISTS;
+
+/**
+ * MES 闃舵鐢典环 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesPdTieredPriceServiceImpl implements MesPdTieredPriceService {
+
+    @Resource
+    private MesPdTieredPriceMapper tieredPriceMapper;
+
+    @Override
+    public Long createTieredPrice(MesPdTieredPriceSaveReqVO createReqVO) {
+        MesPdTieredPriceDO entity = BeanUtils.toBean(createReqVO, MesPdTieredPriceDO.class);
+        tieredPriceMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateTieredPrice(MesPdTieredPriceSaveReqVO updateReqVO) {
+        validateTieredPriceExists(updateReqVO.getId());
+        MesPdTieredPriceDO entity = BeanUtils.toBean(updateReqVO, MesPdTieredPriceDO.class);
+        tieredPriceMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteTieredPrice(Long id) {
+        validateTieredPriceExists(id);
+        tieredPriceMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdTieredPriceRespVO getTieredPrice(Long id) {
+        MesPdTieredPriceDO entity = tieredPriceMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, MesPdTieredPriceRespVO.class);
+    }
+
+    @Override
+    public PageResult<MesPdTieredPriceRespVO> getTieredPricePage(MesPdTieredPricePageReqVO pageReqVO) {
+        return BeanUtils.toBean(tieredPriceMapper.selectPage(pageReqVO), MesPdTieredPriceRespVO.class);
+    }
+
+    @Override
+    public MesPdTieredPriceDO validateTieredPriceExists(Long id) {
+        MesPdTieredPriceDO entity = tieredPriceMapper.selectById(id);
+        if (entity == null) {
+            throw exception(PD_TIERED_PRICE_NOT_EXISTS);
+        }
+        return entity;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelService.java
new file mode 100644
index 0000000..ce85459
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelService.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdVoltageLevelDO;
+
+import java.util.List;
+
+/**
+ * MES 鐢靛帇绛夌骇鍩虹淇℃伅 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdVoltageLevelService {
+
+    Long createVoltageLevel(MesPdVoltageLevelSaveReqVO createReqVO);
+
+    void updateVoltageLevel(MesPdVoltageLevelSaveReqVO updateReqVO);
+
+    void deleteVoltageLevel(Long id);
+
+    MesPdVoltageLevelRespVO getVoltageLevel(Long id);
+
+    PageResult<MesPdVoltageLevelRespVO> getVoltageLevelPage(MesPdVoltageLevelPageReqVO pageReqVO);
+
+    List<MesPdVoltageLevelDO> getVoltageLevelList();
+
+    MesPdVoltageLevelDO validateVoltageLevelExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelServiceImpl.java
new file mode 100644
index 0000000..58e8d8f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/basedata/MesPdVoltageLevelServiceImpl.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.mes.service.pd.basedata;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.basedata.vo.MesPdVoltageLevelSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdVoltageLevelDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdVoltageLevelMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_VOLTAGE_LEVEL_NOT_EXISTS;
+
+/**
+ * MES 鐢靛帇绛夌骇鍩虹淇℃伅 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesPdVoltageLevelServiceImpl implements MesPdVoltageLevelService {
+
+    @Resource
+    private MesPdVoltageLevelMapper voltageLevelMapper;
+
+    @Override
+    public Long createVoltageLevel(MesPdVoltageLevelSaveReqVO createReqVO) {
+        MesPdVoltageLevelDO entity = BeanUtils.toBean(createReqVO, MesPdVoltageLevelDO.class);
+        voltageLevelMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateVoltageLevel(MesPdVoltageLevelSaveReqVO updateReqVO) {
+        validateVoltageLevelExists(updateReqVO.getId());
+        MesPdVoltageLevelDO entity = BeanUtils.toBean(updateReqVO, MesPdVoltageLevelDO.class);
+        voltageLevelMapper.updateById(entity);
+    }
+
+    @Override
+    public void deleteVoltageLevel(Long id) {
+        validateVoltageLevelExists(id);
+        voltageLevelMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdVoltageLevelRespVO getVoltageLevel(Long id) {
+        MesPdVoltageLevelDO entity = voltageLevelMapper.selectById(id);
+        return entity == null ? null : BeanUtils.toBean(entity, MesPdVoltageLevelRespVO.class);
+    }
+
+    @Override
+    public PageResult<MesPdVoltageLevelRespVO> getVoltageLevelPage(MesPdVoltageLevelPageReqVO pageReqVO) {
+        return BeanUtils.toBean(voltageLevelMapper.selectPage(pageReqVO), MesPdVoltageLevelRespVO.class);
+    }
+
+    @Override
+    public List<MesPdVoltageLevelDO> getVoltageLevelList() {
+        return voltageLevelMapper.selectList();
+    }
+
+    @Override
+    public MesPdVoltageLevelDO validateVoltageLevelExists(Long id) {
+        MesPdVoltageLevelDO entity = voltageLevelMapper.selectById(id);
+        if (entity == null) {
+            throw exception(PD_VOLTAGE_LEVEL_NOT_EXISTS);
+        }
+        return entity;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcService.java
new file mode 100644
index 0000000..7668a05
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcService.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.service.pd.ctc;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.ctc.vo.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc.ProductCtcDO;
+
+/**
+ * MES 鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface ProductCtcService {
+
+    Long createCtc(ProductCtcSaveReqVO createReqVO);
+
+    void updateCtc(ProductCtcSaveReqVO updateReqVO);
+
+    void deleteCtc(Long id);
+
+    ProductCtcDO getCtc(Long id);
+
+    PageResult<ProductCtcDO> getCtcPage(ProductCtcPageReqVO pageReqVO);
+
+    /**
+     * 缁曞寘瑙掑害璁$畻锛堝凡鐭ョ焊甯﹀搴︼紝璁$畻鏈�澶栧眰缁曞寘瑙掑害锛�
+     */
+    ProductCtcAngleCalcRespVO calcAngle(ProductCtcAngleCalcReqVO reqVO);
+
+    /**
+     * 鐩存祦鐢甸樆璁$畻锛堟寜娓╁害鎹㈢畻锛�
+     */
+    ProductCtcResistanceCalcRespVO calcResistance(ProductCtcResistanceCalcReqVO reqVO);
+
+    /**
+     * 鐩樺叿瀹归噺璁$畻锛堟瘡鐩樻渶澶ч暱搴︼級
+     */
+    ProductCtcReelCalcRespVO calcReelCapacity(ProductCtcReelCalcReqVO reqVO);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcServiceImpl.java
new file mode 100644
index 0000000..6b268e7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/ctc/ProductCtcServiceImpl.java
@@ -0,0 +1,175 @@
+package cn.iocoder.yudao.module.mes.service.pd.ctc;
+
+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.mes.controller.admin.pd.ctc.vo.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc.ProductCtcDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.ctc.ProductCtcMapper;
+import cn.iocoder.yudao.module.mes.service.pd.reel.ProductCtcReelService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_CTC_CALC_PARAM_INVALID;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_CTC_ITEM_DUPLICATE;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_CTC_NOT_EXISTS;
+
+/**
+ * MES 鎹綅瀵肩嚎浜у搧鎶�鏈弬鏁� Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class ProductCtcServiceImpl implements ProductCtcService {
+
+    @Resource
+    private ProductCtcMapper ctcMapper;
+
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private ProductCtcReelService reelService;
+
+    @Override
+    public Long createCtc(ProductCtcSaveReqVO createReqVO) {
+        // 鏍¢獙鐗╂枡瀛樺湪
+        mdmItemApi.validateItemExists(createReqVO.getItemId());
+        // 鏍¢獙鐗╂枡鍞竴
+        validateItemUnique(createReqVO.getItemId(), null);
+        // 鎻掑叆
+        ProductCtcDO ctc = BeanUtils.toBean(createReqVO, ProductCtcDO.class);
+        ctcMapper.insert(ctc);
+        return ctc.getId();
+    }
+
+    @Override
+    public void updateCtc(ProductCtcSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        ProductCtcDO ctc = validateCtcExists(updateReqVO.getId());
+        // 鏍¢獙鐗╂枡瀛樺湪
+        mdmItemApi.validateItemExists(updateReqVO.getItemId());
+        // 鏍¢獙鐗╂枡鍞竴锛堟帓闄よ嚜韬級
+        validateItemUnique(updateReqVO.getItemId(), updateReqVO.getId());
+        // 鏇存柊
+        ProductCtcDO updateObj = BeanUtils.toBean(updateReqVO, ProductCtcDO.class);
+        ctcMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteCtc(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateCtcExists(id);
+        // 鍒犻櫎鍏宠仈鐩樺叿
+        reelService.deleteByCtcId(id);
+        // 鍒犻櫎
+        ctcMapper.deleteById(id);
+    }
+
+    @Override
+    public ProductCtcDO getCtc(Long id) {
+        return ctcMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<ProductCtcDO> getCtcPage(ProductCtcPageReqVO pageReqVO) {
+        return ctcMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public ProductCtcAngleCalcRespVO calcAngle(ProductCtcAngleCalcReqVO reqVO) {
+        double a = reqVO.getConductorThickness().doubleValue();
+        double b = reqVO.getConductorWidth().doubleValue();
+        double r = reqVO.getCornerRadius().doubleValue();
+        double t = reqVO.getInsulationThickness().doubleValue();
+        double w = reqVO.getTapeWidth().doubleValue();
+        double overlap = reqVO.getOverlapWidth().doubleValue();
+        // 瀵肩嚎鍛ㄩ暱锛氬渾瑙掔煩褰紝鍘氬害鏂瑰悜鍚粷缂橈紝瀹藉害鏂瑰悜涓嶅惈
+        double circumference = 2 * (a + b + 2 * t) - (8 - 2 * Math.PI) * r;
+        // 鏈夋晥绾稿甫瀹藉害 = 绾稿甫瀹藉害 - 閲嶅彔瀹藉害
+        double effectiveWidth = w - overlap;
+        if (circumference <= 0 || effectiveWidth <= 0 || effectiveWidth > circumference) {
+            throw exception(PD_CTC_CALC_PARAM_INVALID);
+        }
+        double angle = Math.asin(effectiveWidth / circumference);
+        double angleDeg = Math.toDegrees(angle);
+        double pitch = effectiveWidth / Math.cos(angle);
+        ProductCtcAngleCalcRespVO respVO = new ProductCtcAngleCalcRespVO();
+        respVO.setCircumference(BigDecimal.valueOf(circumference).setScale(6, RoundingMode.HALF_UP));
+        respVO.setWrappingAngle(BigDecimal.valueOf(angleDeg).setScale(6, RoundingMode.HALF_UP));
+        respVO.setWrappingPitch(BigDecimal.valueOf(pitch).setScale(6, RoundingMode.HALF_UP));
+        return respVO;
+    }
+
+    @Override
+    public ProductCtcResistanceCalcRespVO calcResistance(ProductCtcResistanceCalcReqVO reqVO) {
+        double thickness = reqVO.getConductorThickness().doubleValue();
+        double width = reqVO.getConductorWidth().doubleValue();
+        double temp = reqVO.getTemperature().doubleValue();
+        // 鏉愯川鍙傛暟锛氶摐/閾濓紙蟻20 鍗曚綅 惟路mm虏/m锛屛� 鍗曚綅 1/鈩冿級
+        double rho20;
+        double alpha;
+        if ("aluminum".equalsIgnoreCase(reqVO.getMaterial())) {
+            rho20 = 0.0283;
+            alpha = 0.00403;
+        } else {
+            rho20 = 0.0175;
+            alpha = 0.00393;
+        }
+        double area = thickness * width;
+        if (area <= 0) {
+            throw exception(PD_CTC_CALC_PARAM_INVALID);
+        }
+        // R20 = 蟻20 / A锛埼�/m锛�
+        double r20 = rho20 / area;
+        // Rt = R20 脳 (1 + 伪 脳 (t - 20))
+        double rt = r20 * (1 + alpha * (temp - 20));
+        ProductCtcResistanceCalcRespVO respVO = new ProductCtcResistanceCalcRespVO();
+        respVO.setCrossSection(BigDecimal.valueOf(area).setScale(6, RoundingMode.HALF_UP));
+        respVO.setResistance20cKm(BigDecimal.valueOf(r20 * 1000).setScale(6, RoundingMode.HALF_UP));
+        respVO.setResistanceAtTemp(BigDecimal.valueOf(rt).setScale(6, RoundingMode.HALF_UP));
+        respVO.setResistanceAtTempKm(BigDecimal.valueOf(rt * 1000).setScale(6, RoundingMode.HALF_UP));
+        return respVO;
+    }
+
+    @Override
+    public ProductCtcReelCalcRespVO calcReelCapacity(ProductCtcReelCalcReqVO reqVO) {
+        double d1 = reqVO.getReelOuterDiameter().doubleValue();
+        double d2 = reqVO.getReelInnerDiameter().doubleValue();
+        double l2 = reqVO.getReelWidth().doubleValue();
+        double ct = reqVO.getCtcThickness().doubleValue();
+        double cw = reqVO.getCtcWidth().doubleValue();
+        if (d1 <= d2 || l2 <= 0 || ct <= 0 || cw <= 0) {
+            throw exception(PD_CTC_CALC_PARAM_INVALID);
+        }
+        // 鐩樺叿瀹归噺鍏紡锛堝弬鐓ч渶姹傛枃妗g洏鍏疯绠� sheet锛夛細
+        // ((d1-60)+(d2+10)) 脳 INT(l2/(CTC瀹�+1)) 脳 (d1-d2-70) / (CTC楂�+0.2) / 1274
+        double capacityM = ((d1 - 60) + (d2 + 10)) * Math.floor(l2 / (cw + 1))
+                * (d1 - d2 - 70) / (ct + 0.2) / 1274;
+        ProductCtcReelCalcRespVO respVO = new ProductCtcReelCalcRespVO();
+        respVO.setCapacityMeter(BigDecimal.valueOf(capacityM).setScale(2, RoundingMode.HALF_UP));
+        respVO.setCapacityKm(BigDecimal.valueOf(capacityM / 1000).setScale(4, RoundingMode.HALF_UP));
+        return respVO;
+    }
+
+    private void validateItemUnique(Long itemId, Long excludeId) {
+        ProductCtcDO ctc = ctcMapper.selectByItemId(itemId);
+        if (ctc != null && !ctc.getId().equals(excludeId)) {
+            throw exception(PD_CTC_ITEM_DUPLICATE);
+        }
+    }
+
+    private ProductCtcDO validateCtcExists(Long id) {
+        ProductCtcDO ctc = ctcMapper.selectById(id);
+        if (ctc == null) {
+            throw exception(PD_CTC_NOT_EXISTS);
+        }
+        return ctc;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceService.java
new file mode 100644
index 0000000..544ca36
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceService.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.service.pd.price;
+
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcRespVO;
+
+/**
+ * MES 闃舵鐢典环娴嬬畻 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdPriceService {
+
+    /**
+     * 闃舵鐢典环娴嬬畻锛氭寜鐢ㄧ數閲忓垎妗g疮璁★紝鍙犲姞浼樻儬绛栫暐
+     *
+     * @param reqVO 娴嬬畻璇锋眰
+     * @return 娴嬬畻缁撴灉
+     */
+    MesPdPriceCalcRespVO calcPrice(MesPdPriceCalcReqVO reqVO);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceServiceImpl.java
new file mode 100644
index 0000000..2ea9cce
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/price/MesPdPriceServiceImpl.java
@@ -0,0 +1,140 @@
+package cn.iocoder.yudao.module.mes.service.pd.price;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcDetailRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.price.vo.MesPdPriceCalcRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdDiscountPolicyDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdServicePackageDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdTieredPriceDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.basedata.MesPdVoltageLevelDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdDiscountPolicyMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.basedata.MesPdTieredPriceMapper;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdServicePackageService;
+import cn.iocoder.yudao.module.mes.service.pd.basedata.MesPdVoltageLevelService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_PRICE_CALC_OBJECT_NOT_CONFIGURED;
+
+/**
+ * MES 闃舵鐢典环娴嬬畻 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+@Slf4j
+public class MesPdPriceServiceImpl implements MesPdPriceService {
+
+    private static final Integer CALC_TYPE_PACKAGE = 1;
+    private static final Integer CALC_TYPE_VOLTAGE = 2;
+
+    @Resource
+    private MesPdTieredPriceMapper tieredPriceMapper;
+
+    @Resource
+    private MesPdDiscountPolicyMapper discountPolicyMapper;
+
+    @Resource
+    private MesPdServicePackageService servicePackageService;
+
+    @Resource
+    private MesPdVoltageLevelService voltageLevelService;
+
+    @Override
+    public MesPdPriceCalcRespVO calcPrice(MesPdPriceCalcReqVO reqVO) {
+        // 1. 瀹氫綅闃舵鐢典环妗d綅
+        List<MesPdTieredPriceDO> tiers = getTieredPrices(reqVO);
+        if (CollUtil.isEmpty(tiers)) {
+            throw exception(PD_PRICE_CALC_OBJECT_NOT_CONFIGURED);
+        }
+        // 2. 鍒嗘。绱璁$畻鍘熶环
+        BigDecimal quantity = reqVO.getQuantity();
+        BigDecimal originalPrice = BigDecimal.ZERO;
+        List<MesPdPriceCalcDetailRespVO> details = new ArrayList<>();
+        for (MesPdTieredPriceDO tier : tiers) {
+            BigDecimal lower = tier.getStartValue();
+            BigDecimal upper = tier.getEndValue();
+            if (quantity.compareTo(lower) <= 0) {
+                break;
+            }
+            BigDecimal upperBound = upper == null ? quantity : upper.min(quantity);
+            BigDecimal tierQuantity = upperBound.subtract(lower);
+            if (tierQuantity.signum() <= 0) {
+                continue;
+            }
+            BigDecimal subtotal = tier.getPricePerUnit().multiply(tierQuantity);
+            originalPrice = originalPrice.add(subtotal);
+            MesPdPriceCalcDetailRespVO detail = new MesPdPriceCalcDetailRespVO();
+            detail.setStartValue(lower);
+            detail.setEndValue(upper);
+            detail.setTierQuantity(tierQuantity);
+            detail.setPricePerUnit(tier.getPricePerUnit());
+            detail.setSubtotal(subtotal);
+            details.add(detail);
+            if (upper != null && quantity.compareTo(upper) <= 0) {
+                break;
+            }
+        }
+        // 3. 鍙犲姞浼樻儬绛栫暐锛堢洰鏍囦笓灞� + 鍏ㄥ眬锛屽彇鏈�浼橈級
+        BigDecimal finalPrice = originalPrice;
+        Integer targetType = Objects.equals(CALC_TYPE_PACKAGE, reqVO.getCalcType()) ? 1 : 2;
+        Long targetId = Objects.equals(CALC_TYPE_PACKAGE, reqVO.getCalcType()) ? reqVO.getPackageId() : reqVO.getVoltageLevelId();
+        List<MesPdDiscountPolicyDO> policies = discountPolicyMapper.selectListByTarget(targetType, targetId);
+        LocalDateTime now = LocalDateTime.now();
+        for (MesPdDiscountPolicyDO policy : policies) {
+            if (policy.getStartDate() != null && now.isBefore(policy.getStartDate())) {
+                continue;
+            }
+            if (policy.getEndDate() != null && now.isAfter(policy.getEndDate())) {
+                continue;
+            }
+            if (policy.getPolicyValue() == null) {
+                continue;
+            }
+            BigDecimal candidate;
+            if (Objects.equals(policy.getPolicyType(), 1)) { // 鎶樻墸
+                candidate = originalPrice.multiply(policy.getPolicyValue());
+            } else { // 绔嬪噺
+                candidate = originalPrice.subtract(policy.getPolicyValue()).max(BigDecimal.ZERO);
+            }
+            finalPrice = finalPrice.min(candidate);
+        }
+        // 4. 缁勮杩斿洖
+        MesPdPriceCalcRespVO respVO = new MesPdPriceCalcRespVO();
+        respVO.setOriginalPrice(originalPrice);
+        respVO.setFinalPrice(finalPrice);
+        respVO.setDiscountAmount(originalPrice.subtract(finalPrice));
+        respVO.setDetails(details);
+        if (reqVO.getPackageId() != null) {
+            MesPdServicePackageDO pkg = servicePackageService.validateServicePackageExists(reqVO.getPackageId());
+            respVO.setPackageName(pkg.getName());
+        }
+        if (reqVO.getVoltageLevelId() != null) {
+            MesPdVoltageLevelDO level = voltageLevelService.validateVoltageLevelExists(reqVO.getVoltageLevelId());
+            respVO.setVoltageLevelName(level.getName());
+        }
+        return respVO;
+    }
+
+    private List<MesPdTieredPriceDO> getTieredPrices(MesPdPriceCalcReqVO reqVO) {
+        if (Objects.equals(CALC_TYPE_PACKAGE, reqVO.getCalcType()) && reqVO.getPackageId() != null) {
+            return tieredPriceMapper.selectListByPackageId(reqVO.getPackageId());
+        }
+        if (Objects.equals(CALC_TYPE_VOLTAGE, reqVO.getCalcType()) && reqVO.getVoltageLevelId() != null) {
+            return tieredPriceMapper.selectListByVoltageLevelId(reqVO.getVoltageLevelId());
+        }
+        return null;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductService.java
new file mode 100644
index 0000000..66a4a0f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductService.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.mes.service.pd.product;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.product.MesPdProductDO;
+
+/**
+ * MES 浜у搧妗f Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesPdProductService {
+
+    Long createProduct(MesPdProductSaveReqVO createReqVO);
+
+    void updateProduct(MesPdProductSaveReqVO updateReqVO);
+
+    void deleteProduct(Long id);
+
+    MesPdProductRespVO getProduct(Long id);
+
+    PageResult<MesPdProductRespVO> getProductPage(MesPdProductPageReqVO pageReqVO);
+
+    /**
+     * 鎻愪氦瀹℃壒锛堟湭鎻愪氦/椹冲洖 -> 瀹℃壒涓級
+     */
+    void submitProduct(Long id);
+
+    /**
+     * 瀹℃壒锛堝鎵逛腑 -> 閫氳繃/椹冲洖锛�
+     *
+     * @param id 浜у搧妗f缂栧彿
+     * @param pass 鏄惁閫氳繃
+     * @param auditRemark 瀹℃壒鎰忚
+     */
+    void auditProduct(Long id, Boolean pass, String auditRemark);
+
+    MesPdProductDO validateProductExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductServiceImpl.java
new file mode 100644
index 0000000..28c8094
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductServiceImpl.java
@@ -0,0 +1,151 @@
+package cn.iocoder.yudao.module.mes.service.pd.product;
+
+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.mes.controller.admin.pd.product.vo.MesPdProductPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.product.vo.MesPdProductSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.product.MesPdProductDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.product.MesPdProductMapper;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
+import cn.iocoder.yudao.module.mes.enums.pd.MesPdAuditStatusEnum;
+import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
+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;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_PRODUCT_AUDIT_STATUS_INVALID;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_PRODUCT_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_PRODUCT_SUBMIT_DUPLICATE;
+
+/**
+ * MES 浜у搧妗f Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+@Slf4j
+public class MesPdProductServiceImpl implements MesPdProductService {
+
+    @Resource
+    private MesPdProductMapper productMapper;
+
+    @Resource
+    @Lazy
+    private MesMdAutoCodeRecordService autoCodeRecordService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createProduct(MesPdProductSaveReqVO createReqVO) {
+        MesPdProductDO product = BeanUtils.toBean(createReqVO, MesPdProductDO.class);
+        product.setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PD_PRODUCT_CODE.getCode()));
+        product.setAuditStatus(MesPdAuditStatusEnum.DRAFT.getStatus());
+        productMapper.insert(product);
+        return product.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateProduct(MesPdProductSaveReqVO updateReqVO) {
+        MesPdProductDO product = validateProductExists(updateReqVO.getId());
+        validateEditable(product);
+        MesPdProductDO updateObj = BeanUtils.toBean(updateReqVO, MesPdProductDO.class);
+        updateObj.setCode(product.getCode());
+        productMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteProduct(Long id) {
+        MesPdProductDO product = validateProductExists(id);
+        validateEditable(product);
+        productMapper.deleteById(id);
+    }
+
+    @Override
+    public MesPdProductRespVO getProduct(Long id) {
+        MesPdProductDO product = productMapper.selectById(id);
+        return product == null ? null : convertToRespVO(product);
+    }
+
+    @Override
+    public PageResult<MesPdProductRespVO> getProductPage(MesPdProductPageReqVO pageReqVO) {
+        PageResult<MesPdProductDO> page = productMapper.selectPage(pageReqVO);
+        PageResult<MesPdProductRespVO> result = BeanUtils.toBean(page, MesPdProductRespVO.class);
+        result.getList().forEach(this::fillDictName);
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitProduct(Long id) {
+        MesPdProductDO product = validateProductExists(id);
+        // 浠呮湭鎻愪氦/椹冲洖鍙彁浜ゅ鎵�
+        if (!MesPdAuditStatusEnum.DRAFT.getStatus().equals(product.getAuditStatus())
+                && !MesPdAuditStatusEnum.REJECT.getStatus().equals(product.getAuditStatus())) {
+            throw exception(PD_PRODUCT_SUBMIT_DUPLICATE);
+        }
+        productMapper.updateById(MesPdProductDO.builder()
+                .id(id)
+                .auditStatus(MesPdAuditStatusEnum.PROCESS.getStatus())
+                .auditRemark(null)
+                .auditTime(null)
+                .build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void auditProduct(Long id, Boolean pass, String auditRemark) {
+        MesPdProductDO product = validateProductExists(id);
+        // 浠呭鎵逛腑鍙鏍�
+        if (!MesPdAuditStatusEnum.PROCESS.getStatus().equals(product.getAuditStatus())) {
+            throw exception(PD_PRODUCT_AUDIT_STATUS_INVALID);
+        }
+        productMapper.updateById(MesPdProductDO.builder()
+                .id(id)
+                .auditStatus(pass ? MesPdAuditStatusEnum.APPROVE.getStatus() : MesPdAuditStatusEnum.REJECT.getStatus())
+                .auditRemark(auditRemark)
+                .auditTime(LocalDateTime.now())
+                .build());
+    }
+
+    @Override
+    public MesPdProductDO validateProductExists(Long id) {
+        MesPdProductDO product = productMapper.selectById(id);
+        if (product == null) {
+            throw exception(PD_PRODUCT_NOT_EXISTS);
+        }
+        return product;
+    }
+
+    private void validateEditable(MesPdProductDO product) {
+        // 瀹℃壒涓�/宸查�氳繃涓嶅彲淇敼鎴栧垹闄�
+        if (!MesPdAuditStatusEnum.DRAFT.getStatus().equals(product.getAuditStatus())
+                && !MesPdAuditStatusEnum.REJECT.getStatus().equals(product.getAuditStatus())) {
+            throw exception(PD_PRODUCT_AUDIT_STATUS_INVALID);
+        }
+    }
+
+    private MesPdProductRespVO convertToRespVO(MesPdProductDO product) {
+        MesPdProductRespVO respVO = BeanUtils.toBean(product, MesPdProductRespVO.class);
+        fillDictName(respVO);
+        return respVO;
+    }
+
+    private void fillDictName(MesPdProductRespVO respVO) {
+        respVO.setArchiveTypeName(DictFrameworkUtils.parseDictDataLabel(
+                DictTypeConstants.MES_PD_PRODUCT_ARCHIVE_TYPE, respVO.getArchiveType()));
+        respVO.setAuditStatusName(DictFrameworkUtils.parseDictDataLabel(
+                DictTypeConstants.MES_PD_PRODUCT_AUDIT_STATUS, respVO.getAuditStatus()));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelService.java
new file mode 100644
index 0000000..d31655d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelService.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.service.pd.reel;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.reel.ProductCtcReelDO;
+
+import java.util.List;
+
+/**
+ * MES 鎹綅瀵肩嚎鐩樺叿璁$畻 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface ProductCtcReelService {
+
+    Long createReel(ProductCtcReelSaveReqVO createReqVO);
+
+    void updateReel(ProductCtcReelSaveReqVO updateReqVO);
+
+    void deleteReel(Long id);
+
+    ProductCtcReelDO getReel(Long id);
+
+    PageResult<ProductCtcReelDO> getReelPage(ProductCtcReelPageReqVO pageReqVO);
+
+    List<ProductCtcReelDO> getReelListByCtcId(Long ctcId);
+
+    void deleteByCtcId(Long ctcId);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelServiceImpl.java
new file mode 100644
index 0000000..25dee43
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/reel/ProductCtcReelServiceImpl.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.mes.service.pd.reel;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.reel.vo.ProductCtcReelSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.ctc.ProductCtcDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.reel.ProductCtcReelDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.reel.ProductCtcReelMapper;
+import cn.iocoder.yudao.module.mes.service.pd.ctc.ProductCtcService;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_CTC_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_CTC_REEL_NOT_EXISTS;
+
+/**
+ * MES 鎹綅瀵肩嚎鐩樺叿璁$畻 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class ProductCtcReelServiceImpl implements ProductCtcReelService {
+
+    @Resource
+    private ProductCtcReelMapper reelMapper;
+
+    @Resource
+    @Lazy
+    private ProductCtcService ctcService;
+
+    @Override
+    public Long createReel(ProductCtcReelSaveReqVO createReqVO) {
+        // 鏍¢獙鎹綅瀵肩嚎浜у搧瀛樺湪
+        validateCtcExists(createReqVO.getCtcId());
+        // 鎻掑叆
+        ProductCtcReelDO reel = BeanUtils.toBean(createReqVO, ProductCtcReelDO.class);
+        reelMapper.insert(reel);
+        return reel.getId();
+    }
+
+    @Override
+    public void updateReel(ProductCtcReelSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateReelExists(updateReqVO.getId());
+        // 鏍¢獙鎹綅瀵肩嚎浜у搧瀛樺湪
+        validateCtcExists(updateReqVO.getCtcId());
+        // 鏇存柊
+        ProductCtcReelDO updateObj = BeanUtils.toBean(updateReqVO, ProductCtcReelDO.class);
+        reelMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteReel(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateReelExists(id);
+        // 鍒犻櫎
+        reelMapper.deleteById(id);
+    }
+
+    @Override
+    public ProductCtcReelDO getReel(Long id) {
+        return reelMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<ProductCtcReelDO> getReelPage(ProductCtcReelPageReqVO pageReqVO) {
+        return reelMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<ProductCtcReelDO> getReelListByCtcId(Long ctcId) {
+        return reelMapper.selectListByCtcId(ctcId);
+    }
+
+    @Override
+    public void deleteByCtcId(Long ctcId) {
+        reelMapper.deleteByCtcId(ctcId);
+    }
+
+    private void validateCtcExists(Long ctcId) {
+        ProductCtcDO ctc = ctcService.getCtc(ctcId);
+        if (ctc == null) {
+            throw exception(PD_CTC_NOT_EXISTS);
+        }
+    }
+
+    private ProductCtcReelDO validateReelExists(Long id) {
+        ProductCtcReelDO reel = reelMapper.selectById(id);
+        if (reel == null) {
+            throw exception(PD_CTC_REEL_NOT_EXISTS);
+        }
+        return reel;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmService.java
new file mode 100644
index 0000000..8192ac3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmService.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mes.service.pd.techconfirm;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
+
+/**
+ * MES 浜у搧鎶�鏈‘璁ゅ嚱 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface ProductTechConfirmService {
+
+    Long createTechConfirm(ProductTechConfirmSaveReqVO createReqVO);
+
+    void updateTechConfirm(ProductTechConfirmSaveReqVO updateReqVO);
+
+    void deleteTechConfirm(Long id);
+
+    ProductTechConfirmDO getTechConfirm(Long id);
+
+    PageResult<ProductTechConfirmDO> getTechConfirmPage(ProductTechConfirmPageReqVO pageReqVO);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java
new file mode 100644
index 0000000..155c10e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.mes.service.pd.techconfirm;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pd.techconfirm.ProductTechConfirmMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TECH_CONFIRM_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TECH_CONFIRM_NO_DUPLICATE;
+
+/**
+ * MES 浜у搧鎶�鏈‘璁ゅ嚱 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class ProductTechConfirmServiceImpl implements ProductTechConfirmService {
+
+    @Resource
+    private ProductTechConfirmMapper techConfirmMapper;
+
+    @Override
+    public Long createTechConfirm(ProductTechConfirmSaveReqVO createReqVO) {
+        // 鏍¢獙纭鍑界紪鍙峰敮涓�
+        validateConfirmNoUnique(createReqVO.getConfirmNo(), null);
+        // 鎻掑叆
+        ProductTechConfirmDO techConfirm = BeanUtils.toBean(createReqVO, ProductTechConfirmDO.class);
+        techConfirmMapper.insert(techConfirm);
+        return techConfirm.getId();
+    }
+
+    @Override
+    public void updateTechConfirm(ProductTechConfirmSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateTechConfirmExists(updateReqVO.getId());
+        // 鏍¢獙纭鍑界紪鍙峰敮涓�锛堟帓闄よ嚜韬級
+        validateConfirmNoUnique(updateReqVO.getConfirmNo(), updateReqVO.getId());
+        // 鏇存柊
+        ProductTechConfirmDO updateObj = BeanUtils.toBean(updateReqVO, ProductTechConfirmDO.class);
+        techConfirmMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteTechConfirm(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateTechConfirmExists(id);
+        // 鍒犻櫎
+        techConfirmMapper.deleteById(id);
+    }
+
+    @Override
+    public ProductTechConfirmDO getTechConfirm(Long id) {
+        return techConfirmMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<ProductTechConfirmDO> getTechConfirmPage(ProductTechConfirmPageReqVO pageReqVO) {
+        return techConfirmMapper.selectPage(pageReqVO);
+    }
+
+    private void validateConfirmNoUnique(String confirmNo, Long excludeId) {
+        if (confirmNo == null) {
+            return;
+        }
+        ProductTechConfirmDO techConfirm = techConfirmMapper.selectOne(
+                ProductTechConfirmDO::getConfirmNo, confirmNo);
+        if (techConfirm != null && !techConfirm.getId().equals(excludeId)) {
+            throw exception(PD_TECH_CONFIRM_NO_DUPLICATE);
+        }
+    }
+
+    private ProductTechConfirmDO validateTechConfirmExists(Long id) {
+        ProductTechConfirmDO techConfirm = techConfirmMapper.selectById(id);
+        if (techConfirm == null) {
+            throw exception(PD_TECH_CONFIRM_NOT_EXISTS);
+        }
+        return techConfirm;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanService.java
new file mode 100644
index 0000000..4ca87cb
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.service.pro.emergencyplan;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
+
+import java.util.Collection;
+
+/**
+ * MES 搴旀�ュ缃妗� Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface MesProEmergencyPlanService {
+
+    Long createPlan(MesProEmergencyPlanSaveReqVO createReqVO);
+
+    void updatePlan(MesProEmergencyPlanSaveReqVO updateReqVO);
+
+    void deletePlan(Collection<Long> ids);
+
+    MesProEmergencyPlanDO getPlan(Long id);
+
+    PageResult<MesProEmergencyPlanDO> getPlanPage(MesProEmergencyPlanPageReqVO pageReqVO);
+
+    void publishPlan(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanServiceImpl.java
new file mode 100644
index 0000000..0a579d6
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanServiceImpl.java
@@ -0,0 +1,115 @@
+package cn.iocoder.yudao.module.mes.service.pro.emergencyplan;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.emergencyplan.MesProEmergencyPlanMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_NOT_PREPARE;
+
+/**
+ * MES 搴旀�ュ缃妗� Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+public class MesProEmergencyPlanServiceImpl implements MesProEmergencyPlanService {
+
+    private static final Integer STATUS_DRAFT = 0;
+    private static final Integer STATUS_PUBLISHED = 10;
+
+    @Resource
+    private MesProEmergencyPlanMapper planMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createPlan(MesProEmergencyPlanSaveReqVO createReqVO) {
+        String code = generateCode();
+        if (planMapper.selectByCode(code) != null) {
+            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+        }
+        MesProEmergencyPlanDO plan = BeanUtils.toBean(createReqVO, MesProEmergencyPlanDO.class, req -> req
+                .setCode(code)
+                .setStatus(STATUS_DRAFT));
+        planMapper.insert(plan);
+        return plan.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePlan(MesProEmergencyPlanSaveReqVO updateReqVO) {
+        MesProEmergencyPlanDO oldPlan = validatePlanCanModify(updateReqVO.getId());
+        MesProEmergencyPlanDO updateObj = BeanUtils.toBean(updateReqVO, MesProEmergencyPlanDO.class, req -> req
+                .setCode(oldPlan.getCode())
+                .setStatus(oldPlan.getStatus()));
+        planMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deletePlan(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validatePlanCanModify(id);
+            planMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public MesProEmergencyPlanDO getPlan(Long id) {
+        return planMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesProEmergencyPlanDO> getPlanPage(MesProEmergencyPlanPageReqVO pageReqVO) {
+        return planMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void publishPlan(Long id) {
+        MesProEmergencyPlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
+            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
+        }
+        planMapper.updateById(new MesProEmergencyPlanDO().setId(id).setStatus(STATUS_PUBLISHED));
+    }
+
+    private String generateCode() {
+        return "YJ" + DateUtil.format(java.time.LocalDateTime.now(), "yyyyMMddHHmmss")
+                + String.format("%03d", (int) (Math.random() * 1000));
+    }
+
+    private MesProEmergencyPlanDO validatePlanExists(Long id) {
+        MesProEmergencyPlanDO plan = planMapper.selectById(id);
+        if (plan == null) {
+            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+        }
+        return plan;
+    }
+
+    private MesProEmergencyPlanDO validatePlanCanModify(Long id) {
+        MesProEmergencyPlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
+            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
+        }
+        return plan;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
index 05d2a48..dedeaef 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
@@ -6,6 +6,7 @@
 import jakarta.validation.Valid;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.util.Collection;
 import java.util.List;
 
@@ -155,5 +156,18 @@
      */
     MesProFeedbackBatchCreateRespVO batchCreateFeedback(@Valid MesProFeedbackBatchCreateReqVO reqVO);
 
+    /**
+     * 浣滀笟鎴愭灉姹囨�荤粺璁�
+     *
+     * <p>姹囨�绘寚瀹氭椂闂磋寖鍥村唴宸叉壒鍑嗘姤宸ョ殑鎶ュ伐鏁伴噺/鍚堟牸鏁伴噺锛屾寜宸ュ崟绫诲瀷銆佹棩鏈熷垎缁勶紝
+     * 鐢ㄤ簬鍙戠數銆佷緵鐢点�佹淇瓑鐢靛姏浣滀笟鎴愭灉缁熻銆�
+     *
+     * @param beginDate     寮�濮嬫棩鏈燂紙鍚紝鍙┖锛�
+     * @param endDate       缁撴潫鏃ユ湡锛堝惈锛屽彲绌猴級
+     * @param workOrderType 宸ュ崟绫诲瀷杩囨护锛堝彲绌猴級
+     * @return 姹囨�荤粨鏋�
+     */
+    MesProFeedbackSummaryRespVO getFeedbackSummary(LocalDate beginDate, LocalDate endDate, Integer workOrderType);
+
 }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
index d5401e9..0692667 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -6,9 +6,11 @@
 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.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.*;
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
@@ -46,11 +48,17 @@
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -580,4 +588,105 @@
         return respVO;
     }
 
+    @Override
+    public MesProFeedbackSummaryRespVO getFeedbackSummary(LocalDate beginDate, LocalDate endDate,
+                                                          Integer workOrderType) {
+        // 1. 鏌ヨ鏈夋晥鎶ュ伐锛堝凡鎵瑰噯锛氬緟妫�楠� + 宸插畬鎴愶級
+        LambdaQueryWrapperX<MesProFeedbackDO> query = new LambdaQueryWrapperX<MesProFeedbackDO>()
+                .in(MesProFeedbackDO::getStatus, List.of(
+                        MesProFeedbackStatusEnum.UNCHECK.getStatus(),
+                        MesProFeedbackStatusEnum.FINISHED.getStatus()))
+                .betweenIfPresent(MesProFeedbackDO::getFeedbackTime,
+                        beginDate != null ? beginDate.atStartOfDay() : null,
+                        endDate != null ? endDate.plusDays(1).atStartOfDay() : null);
+        List<MesProFeedbackDO> feedbackList = feedbackMapper.selectList(query);
+        if (CollUtil.isEmpty(feedbackList)) {
+            MesProFeedbackSummaryRespVO empty = new MesProFeedbackSummaryRespVO();
+            empty.setTotalFeedbackQuantity(BigDecimal.ZERO);
+            empty.setTotalQualifiedQuantity(BigDecimal.ZERO);
+            empty.setTotalFeedbackCount(0);
+            empty.setByType(Collections.emptyList());
+            empty.setByDate(Collections.emptyList());
+            return empty;
+        }
+
+        // 2. 鍔犺浇宸ュ崟淇℃伅锛屾槧灏� 宸ュ崟ID -> 宸ュ崟绫诲瀷
+        Set<Long> workOrderIds = feedbackList.stream()
+                .map(MesProFeedbackDO::getWorkOrderId).filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(workOrderIds);
+
+        // 3. 杩囨护宸ュ崟绫诲瀷
+        List<MesProFeedbackDO> filteredList = feedbackList.stream()
+                .filter(feedback -> {
+                    if (workOrderType == null) {
+                        return true;
+                    }
+                    MesProWorkOrderDO workOrder = workOrderMap.get(feedback.getWorkOrderId());
+                    return workOrder != null && Objects.equals(workOrder.getType(), workOrderType);
+                })
+                .collect(Collectors.toList());
+
+        // 4. 姹囨��
+        BigDecimal totalFeedbackQuantity = BigDecimal.ZERO;
+        BigDecimal totalQualifiedQuantity = BigDecimal.ZERO;
+        int totalCount = 0;
+        for (MesProFeedbackDO feedback : filteredList) {
+            totalFeedbackQuantity = totalFeedbackQuantity.add(nullToZero(feedback.getFeedbackQuantity()));
+            totalQualifiedQuantity = totalQualifiedQuantity.add(nullToZero(feedback.getQualifiedQuantity()));
+            totalCount++;
+        }
+
+        // 5. 鎸夊伐鍗曠被鍨嬪垎缁�
+        Map<Integer, MesProFeedbackSummaryRespVO.TypeSummaryItem> typeMap = new LinkedHashMap<>();
+        // 6. 鎸夋棩鏈熷垎缁�
+        Map<LocalDate, MesProFeedbackSummaryRespVO.DateSummaryItem> dateMap = new LinkedHashMap<>();
+        for (MesProFeedbackDO feedback : filteredList) {
+            MesProWorkOrderDO workOrder = workOrderMap.get(feedback.getWorkOrderId());
+            Integer type = workOrder != null ? workOrder.getType() : null;
+            if (type != null) {
+                MesProFeedbackSummaryRespVO.TypeSummaryItem item = typeMap.computeIfAbsent(type, key -> {
+                    MesProFeedbackSummaryRespVO.TypeSummaryItem t = new MesProFeedbackSummaryRespVO.TypeSummaryItem();
+                    t.setWorkOrderType(key);
+                    t.setWorkOrderTypeName(MesProWorkOrderTypeEnum.valueOf(key) != null
+                            ? MesProWorkOrderTypeEnum.valueOf(key).getName() : String.valueOf(key));
+                    t.setFeedbackQuantity(BigDecimal.ZERO);
+                    t.setQualifiedQuantity(BigDecimal.ZERO);
+                    t.setFeedbackCount(0);
+                    return t;
+                });
+                item.setFeedbackQuantity(item.getFeedbackQuantity().add(nullToZero(feedback.getFeedbackQuantity())));
+                item.setQualifiedQuantity(item.getQualifiedQuantity().add(nullToZero(feedback.getQualifiedQuantity())));
+                item.setFeedbackCount(item.getFeedbackCount() + 1);
+            }
+            if (feedback.getFeedbackTime() != null) {
+                LocalDate date = feedback.getFeedbackTime().toLocalDate();
+                MesProFeedbackSummaryRespVO.DateSummaryItem item = dateMap.computeIfAbsent(date, key -> {
+                    MesProFeedbackSummaryRespVO.DateSummaryItem d = new MesProFeedbackSummaryRespVO.DateSummaryItem();
+                    d.setDate(key);
+                    d.setFeedbackQuantity(BigDecimal.ZERO);
+                    d.setQualifiedQuantity(BigDecimal.ZERO);
+                    d.setFeedbackCount(0);
+                    return d;
+                });
+                item.setFeedbackQuantity(item.getFeedbackQuantity().add(nullToZero(feedback.getFeedbackQuantity())));
+                item.setQualifiedQuantity(item.getQualifiedQuantity().add(nullToZero(feedback.getQualifiedQuantity())));
+                item.setFeedbackCount(item.getFeedbackCount() + 1);
+            }
+        }
+
+        // 7. 缁勮鍝嶅簲
+        MesProFeedbackSummaryRespVO respVO = new MesProFeedbackSummaryRespVO();
+        respVO.setTotalFeedbackQuantity(totalFeedbackQuantity);
+        respVO.setTotalQualifiedQuantity(totalQualifiedQuantity);
+        respVO.setTotalFeedbackCount(totalCount);
+        respVO.setByType(new ArrayList<>(typeMap.values()));
+        respVO.setByDate(new ArrayList<>(dateMap.values()));
+        return respVO;
+    }
+
+    private static BigDecimal nullToZero(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanService.java
new file mode 100644
index 0000000..11f05f8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanService.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.mes.service.pro.maintenanceplan;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanItemDO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝 Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface MesProMaintenancePlanService {
+
+    /**
+     * 鍒涘缓鐢靛姏浣滀笟璁″垝
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createPlan(MesProMaintenancePlanSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊鐢靛姏浣滀笟璁″垝
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updatePlan(MesProMaintenancePlanSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎鐢靛姏浣滀笟璁″垝
+     *
+     * @param ids 缂栧彿鏁扮粍
+     */
+    void deletePlan(Collection<Long> ids);
+
+    /**
+     * 鑾峰緱鐢靛姏浣滀笟璁″垝
+     *
+     * @param id 缂栧彿
+     * @return 鐢靛姏浣滀笟璁″垝
+     */
+    MesProMaintenancePlanDO getPlan(Long id);
+
+    /**
+     * 鑾峰緱鐢靛姏浣滀笟璁″垝鏄庣粏鍒楄〃
+     *
+     * @param planId 璁″垝缂栧彿
+     * @return 鏄庣粏鍒楄〃
+     */
+    List<MesProMaintenancePlanItemDO> getPlanItemListByPlanId(Long planId);
+
+    /**
+     * 鎸夎鍒掔紪鍙烽泦鍚堣幏寰楁槑缁嗗垪琛�
+     *
+     * @param planIds 璁″垝缂栧彿闆嗗悎
+     * @return 鏄庣粏鍒楄〃
+     */
+    List<MesProMaintenancePlanItemDO> getPlanItemListByPlanIds(Collection<Long> planIds);
+
+    /**
+     * 鑾峰緱鐢靛姏浣滀笟璁″垝鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<MesProMaintenancePlanDO> getPlanPage(MesProMaintenancePlanPageReqVO pageReqVO);
+
+    /**
+     * 涓嬪彂鐢靛姏浣滀笟璁″垝锛堣崏绋� -> 宸蹭笅鍙戯級
+     *
+     * @param id 缂栧彿
+     */
+    void issuePlan(Long id);
+
+    /**
+     * 瀹屾垚鐢靛姏浣滀笟璁″垝锛堝凡涓嬪彂 -> 宸插畬鎴愶級
+     *
+     * @param id 缂栧彿
+     */
+    void finishPlan(Long id);
+
+    /**
+     * 鏍规嵁璁″垝澶囦欢/鐗╄祫闇�姹傜敓鎴� ERP 閲囪喘璁″垝
+     *
+     * @param id 鐢靛姏浣滀笟璁″垝缂栧彿
+     * @return 閲囪喘璁″垝缂栧彿
+     */
+    Long generatePurchasePlan(Long id);
+
+    /**
+     * 鐢熸垚鐢熶骇宸ュ崟锛堣繍缁�/鍙戠數/妫�淇被锛�
+     *
+     * @param id 鐢靛姏浣滀笟璁″垝缂栧彿
+     * @return 宸ュ崟缂栧彿
+     */
+    Long generateWorkOrder(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanServiceImpl.java
new file mode 100644
index 0000000..ef5b5a1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanServiceImpl.java
@@ -0,0 +1,288 @@
+package cn.iocoder.yudao.module.mes.service.pro.maintenanceplan;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchasePlanApi;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchasePlanCreateReqDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanItemDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenanceplan.MesProMaintenancePlanItemMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenanceplan.MesProMaintenancePlanMapper;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum;
+import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+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.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+
+/**
+ * MES 鐢靛姏浣滀笟璁″垝 Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+@Slf4j
+public class MesProMaintenancePlanServiceImpl implements MesProMaintenancePlanService {
+
+    /**
+     * 鐘舵�侊細鑽夌
+     */
+    private static final Integer STATUS_DRAFT = 0;
+    /**
+     * 鐘舵�侊細宸蹭笅鍙�
+     */
+    private static final Integer STATUS_ISSUED = 10;
+    /**
+     * 鐘舵�侊細宸插畬鎴�
+     */
+    private static final Integer STATUS_FINISHED = 20;
+    /**
+     * 鐘舵�侊細宸插彇娑�
+     */
+    private static final Integer STATUS_CANCELLED = 30;
+
+    @Resource
+    private MesProMaintenancePlanMapper planMapper;
+    @Resource
+    private MesProMaintenancePlanItemMapper planItemMapper;
+
+    @Resource
+    private MesCalTeamService teamService;
+    @Resource
+    private MesMdItemService itemService;
+
+    @Resource
+    private ErpPurchasePlanApi erpPurchasePlanApi;
+    @Resource
+    private MesProWorkOrderService workOrderService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createPlan(MesProMaintenancePlanSaveReqVO createReqVO) {
+        // 1. 鐢熸垚璁″垝缂栫爜
+        String code = generatePlanCode();
+        if (planMapper.selectByCode(code) != null) {
+            throw exception(MD_ITEM_CODE_DUPLICATE);
+        }
+        // 2. 鏍¢獙鐝粍骞跺洖濉�
+        fillTeamInfo(createReqVO);
+        // 3. 鎻掑叆璁″垝
+        MesProMaintenancePlanDO plan = BeanUtils.toBean(createReqVO, MesProMaintenancePlanDO.class, req -> req
+                .setCode(code)
+                .setStatus(STATUS_DRAFT));
+        planMapper.insert(plan);
+        // 4. 鎻掑叆鏄庣粏
+        insertPlanItems(plan.getId(), createReqVO.getItems());
+        return plan.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePlan(MesProMaintenancePlanSaveReqVO updateReqVO) {
+        // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        MesProMaintenancePlanDO oldPlan = validatePlanCanModify(updateReqVO.getId());
+        fillTeamInfo(updateReqVO);
+        // 2. 鏇存柊涓昏〃
+        MesProMaintenancePlanDO updateObj = BeanUtils.toBean(updateReqVO, MesProMaintenancePlanDO.class, req -> req
+                .setCode(oldPlan.getCode())
+                .setStatus(oldPlan.getStatus()));
+        planMapper.updateById(updateObj);
+        // 3. 鏇存柊鏄庣粏锛氬厛鍒犲悗鎻�
+        planItemMapper.deleteByPlanId(updateReqVO.getId());
+        insertPlanItems(updateReqVO.getId(), updateReqVO.getItems());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deletePlan(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validatePlanCanModify(id);
+            planItemMapper.deleteByPlanId(id);
+            planMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public MesProMaintenancePlanDO getPlan(Long id) {
+        return planMapper.selectById(id);
+    }
+
+    @Override
+    public List<MesProMaintenancePlanItemDO> getPlanItemListByPlanId(Long planId) {
+        return planItemMapper.selectListByPlanId(planId);
+    }
+
+    @Override
+    public List<MesProMaintenancePlanItemDO> getPlanItemListByPlanIds(Collection<Long> planIds) {
+        return planItemMapper.selectListByPlanIds(planIds);
+    }
+
+    @Override
+    public PageResult<MesProMaintenancePlanDO> getPlanPage(MesProMaintenancePlanPageReqVO pageReqVO) {
+        return planMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void issuePlan(Long id) {
+        MesProMaintenancePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
+            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
+        }
+        planMapper.updateById(new MesProMaintenancePlanDO().setId(id).setStatus(STATUS_ISSUED));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void finishPlan(Long id) {
+        MesProMaintenancePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_ISSUED)) {
+            throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
+        }
+        planMapper.updateById(new MesProMaintenancePlanDO().setId(id).setStatus(STATUS_FINISHED));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long generatePurchasePlan(Long id) {
+        // 1. 鏍¢獙璁″垝瀛樺湪涓斿凡涓嬪彂
+        MesProMaintenancePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_ISSUED)) {
+            throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
+        }
+        // 2. 鑾峰彇鏄庣粏
+        List<MesProMaintenancePlanItemDO> planItems = planItemMapper.selectListByPlanId(id);
+        if (CollUtil.isEmpty(planItems)) {
+            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+        }
+        // 3. 缁勮閲囪喘璁″垝 DTO锛圡ES 鐗╂枡 -> MDM 鐗╂枡锛�
+        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
+                planItems.stream().map(MesProMaintenancePlanItemDO::getItemId).collect(Collectors.toSet()));
+        List<ErpPurchasePlanCreateReqDTO.Item> items = planItems.stream().map(item -> {
+            ErpPurchasePlanCreateReqDTO.Item dtoItem = new ErpPurchasePlanCreateReqDTO.Item();
+            MesMdItemDO mesItem = itemMap.get(item.getItemId());
+            dtoItem.setProductId(mesItem != null ? mesItem.getMdmItemId() : item.getItemId());
+            dtoItem.setProductUnitId(mesItem != null ? mesItem.getUnitMeasureId() : null);
+            dtoItem.setCount(item.getQuantity());
+            dtoItem.setRemark(item.getRemark());
+            return dtoItem;
+        }).collect(Collectors.toList());
+        // 4. 璋冪敤 ERP 鐢熸垚閲囪喘璁″垝
+        ErpPurchasePlanCreateReqDTO createReqDTO = new ErpPurchasePlanCreateReqDTO();
+        createReqDTO.setName("浣滀笟璁″垝閲囪喘-" + plan.getName());
+        createReqDTO.setDemandSource(2); // 杩愮淮/鐢熶骇璁″垝
+        createReqDTO.setPlanDate(plan.getPlanDate());
+        createReqDTO.setRemark("鏉ユ簮璁″垝: " + plan.getCode());
+        createReqDTO.setItems(items);
+        return erpPurchasePlanApi.createPurchasePlan(createReqDTO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long generateWorkOrder(Long id) {
+        // 1. 鏍¢獙璁″垝瀛樺湪涓斿凡涓嬪彂
+        MesProMaintenancePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_ISSUED)) {
+            throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
+        }
+        // 2. 宸茬敓鎴愯繃宸ュ崟鍒欑洿鎺ヨ繑鍥�
+        if (plan.getWorkOrderId() != null) {
+            return plan.getWorkOrderId();
+        }
+        // 3. 缁勮宸ュ崟
+        MesProWorkOrderSaveReqVO workOrderReq = new MesProWorkOrderSaveReqVO();
+        workOrderReq.setName(plan.getName());
+        workOrderReq.setType(convertPlanTypeToWorkOrderType(plan.getPlanType()));
+        workOrderReq.setOrderSourceType(MesProWorkOrderSourceTypeEnum.MAINTENANCE_PLAN.getType());
+        workOrderReq.setOrderSourceCode(plan.getCode());
+        workOrderReq.setQuantity(BigDecimal.ONE);
+        workOrderReq.setRequestDate(plan.getPlanDate() != null ? plan.getPlanDate() : java.time.LocalDateTime.now());
+        workOrderReq.setRemark("鏉ヨ嚜鐢靛姏浣滀笟璁″垝: " + plan.getCode());
+        Long workOrderId = workOrderService.createWorkOrder(workOrderReq);
+        // 4. 鍥炲啓璁″垝
+        MesProMaintenancePlanDO updateObj = new MesProMaintenancePlanDO();
+        updateObj.setId(id);
+        updateObj.setWorkOrderId(workOrderId);
+        planMapper.updateById(updateObj);
+        return workOrderId;
+    }
+
+    /**
+     * 璁″垝绫诲瀷 -> 宸ュ崟绫诲瀷鏄犲皠
+     */
+    private Integer convertPlanTypeToWorkOrderType(Integer planType) {
+        return switch (planType) {
+            case 1 -> MesProWorkOrderTypeEnum.POWER_OPERATION.getType(); // 鐢电綉杩愯 -> 鐢靛姏杩愮淮
+            case 2 -> MesProWorkOrderTypeEnum.POWER_GENERATION.getType(); // 鍙戠數
+            case 3 -> MesProWorkOrderTypeEnum.POWER_OPERATION.getType(); // 鍙樼數杩愮淮 -> 鐢靛姏杩愮淮
+            case 4 -> MesProWorkOrderTypeEnum.POWER_MAINTENANCE.getType(); // 绾胯矾妫�淇� -> 妫�淇�
+            default -> MesProWorkOrderTypeEnum.POWER_OPERATION.getType();
+        };
+    }
+
+    private void insertPlanItems(Long planId, List<MesProMaintenancePlanSaveReqVO.Item> items) {
+        if (CollUtil.isEmpty(items)) {
+            return;
+        }
+        planItemMapper.insertBatch(items.stream().map(item ->
+                BeanUtils.toBean(item, MesProMaintenancePlanItemDO.class).setPlanId(planId))
+                .collect(Collectors.toList()));
+    }
+
+    private void fillTeamInfo(MesProMaintenancePlanSaveReqVO reqVO) {
+        if (reqVO.getTeamId() == null) {
+            return;
+        }
+        MesCalTeamDO team = teamService.validateTeamExists(reqVO.getTeamId());
+        if (team != null) {
+            reqVO.setTeamName(team.getName());
+        }
+    }
+
+    private String generatePlanCode() {
+        return "MP" + DateUtil.format(java.time.LocalDateTime.now(), "yyyyMMddHHmmss")
+                + String.format("%03d", (int) (Math.random() * 1000));
+    }
+
+    private MesProMaintenancePlanDO validatePlanExists(Long id) {
+        MesProMaintenancePlanDO plan = planMapper.selectById(id);
+        if (plan == null) {
+            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+        }
+        return plan;
+    }
+
+    private MesProMaintenancePlanDO validatePlanCanModify(Long id) {
+        MesProMaintenancePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
+            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
+        }
+        return plan;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
index d6ebc52..542955c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -18,6 +18,8 @@
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
 import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
+import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
@@ -108,6 +110,8 @@
     @Resource
     @Lazy
     private MesDvMachineryService machineryService;
+    @Resource
+    private MesCalTeamService teamService;
 
     @Override
     public Long createTask(MesProTaskSaveReqVO createReqVO) {
@@ -116,6 +120,7 @@
         workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
         routeService.validateRouteExists(createReqVO.getRouteId());
         processService.validateProcessExistsAndEnable(createReqVO.getProcessId());
+        fillTeamInfo(createReqVO);
 
         // 2. 鑷姩纭畾 itemId锛氫紭鍏堝墠绔紶鍏� 鈫� 宸ヨ壓璺嚎宸ュ簭鐨勪骇鍑轰骇鍝� 鈫� 宸ュ崟鎴愬搧
         Long itemId = createReqVO.getItemId();
@@ -161,6 +166,7 @@
         if (updateReqVO.getProcessId() != null) {
             processService.validateProcessExistsAndEnable(updateReqVO.getProcessId());
         }
+        fillTeamInfo(updateReqVO);
         MesMdItemDO item;
         if (updateReqVO.getItemId() != null) {
             item = itemService.validateItemExists(updateReqVO.getItemId());
@@ -176,6 +182,19 @@
     }
 
     /**
+     * 鏍¢獙鐝粍瀛樺湪骞跺洖濉彮缁勫悕绉�
+     */
+    private void fillTeamInfo(MesProTaskSaveReqVO reqVO) {
+        if (reqVO.getTeamId() == null) {
+            return;
+        }
+        MesCalTeamDO team = teamService.validateTeamExists(reqVO.getTeamId());
+        if (team != null) {
+            reqVO.setTeamName(team.getName());
+        }
+    }
+
+    /**
      * 鏋勫缓浠诲姟鍚嶇О锛屾牸寮忥細"浜у搧鍚嶃�愭暟閲忋�戝崟浣�"
      */
     private String buildTaskName(MesMdItemDO item, BigDecimal quantity) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
index c4cb972..ad02742 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
@@ -24,7 +24,10 @@
 import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
 import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
 import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
 import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -69,11 +72,17 @@
     @Resource
     private CrmCustomerApi customerApi;
 
+    @Resource
+    @Lazy // 閬垮厤 erpSaleOrderApiImpl -> ... -> mesProWorkOrderServiceImpl 鐨勫惊鐜緷璧�
+    private ErpSaleOrderApi saleOrderApi;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createWorkOrder(MesProWorkOrderSaveReqVO createReqVO) {
         // 1. 鏍¢獙鏁版嵁
         validateWorkOrderSaveData(null, createReqVO);
+        // 1.1 鍏宠仈钀ラ攢璁㈠崟淇℃伅鍥炲~
+        fillSaleOrderInfo(createReqVO);
 
         // 2.1 璁剧疆榛樿鍊�
         if (createReqVO.getParentId() == null) {
@@ -84,8 +93,10 @@
         workOrder.setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus());
         workOrderMapper.insert(workOrder);
 
-        // 3. 鑷姩鐢熸垚 BOM锛氭牴鎹骇鍝� BOM 鐢熸垚宸ュ崟 BOM
-        workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false);
+        // 3. 鑷姩鐢熸垚 BOM锛氭牴鎹骇鍝� BOM 鐢熸垚宸ュ崟 BOM锛堟棤浜у搧鐨勮繍缁�/鍙戠數/妫�淇被宸ュ崟璺宠繃锛�
+        if (createReqVO.getProductId() != null) {
+            workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false);
+        }
 
         // 4. 宸ュ簭鐢卞墠绔�夋嫨浜у搧鍚庡甫鍏ワ紝涓嶅啀鑷姩鐢熸垚
 
@@ -105,11 +116,13 @@
         }
         // 1.2 鏍¢獙鏁版嵁
         validateWorkOrderSaveData(updateReqVO.getId(), updateReqVO);
+        // 1.3 鍏宠仈钀ラ攢璁㈠崟淇℃伅鍥炲~
+        fillSaleOrderInfo(updateReqVO);
 
-        // 2. 鍒ゆ柇浜у搧鎴栨暟閲忔槸鍚﹀彉鏇达紝濡傛灉鍙樻洿鍒欓噸鏂扮敓鎴� BOM锛坲pdated=true 浼氬厛娓呯悊鏃ф暟鎹級
+        // 2. 鍒ゆ柇浜у搧鎴栨暟閲忔槸鍚﹀彉鏇达紝濡傛灉鍙樻洿鍒欓噸鏂扮敓鎴� BOM锛坲pdated=true 浼氬厛娓呯悊鏃ф暟鎹紱鏃犱骇鍝佺殑杩愮淮绫诲伐鍗曡烦杩囷級
         boolean productChanged = ObjUtil.notEqual(oldWorkOrder.getProductId(), updateReqVO.getProductId());
         boolean quantityChanged = oldWorkOrder.getQuantity().compareTo(updateReqVO.getQuantity()) != 0;
-        if (productChanged || quantityChanged) {
+        if ((productChanged || quantityChanged) && updateReqVO.getProductId() != null) {
             workOrderBomService.generateWorkOrderBom(updateReqVO.getId(), updateReqVO, true);
         }
         // 2.1 宸ュ簭鐢卞墠绔鐞嗭紝浜у搧鍙樻洿鏃跺墠绔細閲嶆柊鑾峰彇宸ヨ壓璺嚎宸ュ簭
@@ -245,12 +258,14 @@
     private void validateWorkOrderSaveData(Long id, MesProWorkOrderSaveReqVO reqVO) {
         // 1. 鏍¢獙缂栫爜鍞竴
         validateWorkOrderCodeUnique(id, reqVO.getCode());
-        // 2. 鏍¢獙浜у搧瀛樺湪
-        itemService.validateItemExists(reqVO.getProductId());
-        // 3. 鏍¢獙鎵规閰嶇疆锛堝鏋滀骇鍝佹湁 clientFlag=true锛屽垯 clientId 蹇呭~锛�
-        MesMdItemBatchConfigDO batchConfig = itemBatchConfigService.getItemBatchConfigByItemId(reqVO.getProductId());
-        if (batchConfig != null && Boolean.TRUE.equals(batchConfig.getClientFlag()) && reqVO.getClientId() == null) {
-            throw exception(MD_CLIENT_NOT_EXISTS);
+        // 2. 浜у搧闈炵┖鏃舵牎楠屼骇鍝佸瓨鍦紙杩愮淮/鍙戠數/妫�淇被宸ュ崟鍙棤浜у搧锛�
+        if (reqVO.getProductId() != null) {
+            itemService.validateItemExists(reqVO.getProductId());
+            // 3. 鏍¢獙鎵规閰嶇疆锛堝鏋滀骇鍝佹湁 clientFlag=true锛屽垯 clientId 蹇呭~锛�
+            MesMdItemBatchConfigDO batchConfig = itemBatchConfigService.getItemBatchConfigByItemId(reqVO.getProductId());
+            if (batchConfig != null && Boolean.TRUE.equals(batchConfig.getClientFlag()) && reqVO.getClientId() == null) {
+                throw exception(MD_CLIENT_NOT_EXISTS);
+            }
         }
     }
 
@@ -267,6 +282,23 @@
         }
     }
 
+    /**
+     * 鍏宠仈钀ラ攢璁㈠崟锛圗RP 閿�鍞鍗曪級锛氭牎楠屽瓨鍦ㄦ�у苟鍥炲~鍗曞彿銆佸鎴�
+     */
+    private void fillSaleOrderInfo(MesProWorkOrderSaveReqVO reqVO) {
+        if (reqVO.getSaleOrderId() == null) {
+            return;
+        }
+        ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(reqVO.getSaleOrderId()).getCheckedData();
+        if (saleOrder == null) {
+            throw exception(PRO_WORK_ORDER_SALE_ORDER_NOT_EXISTS);
+        }
+        reqVO.setSaleOrderNo(saleOrder.getNo());
+        if (reqVO.getClientId() == null && saleOrder.getCustomerId() != null) {
+            reqVO.setClientId(saleOrder.getCustomerId());
+        }
+    }
+
     @Override
     public void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) {
         // 鏍¢獙宸ュ崟瀛樺湪
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestService.java
new file mode 100644
index 0000000..56d8acc
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestService.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.mes.service.qc.outsourcetest;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.outsourcetest.MesQcOutsourceTestDO;
+
+import java.util.Collection;
+
+/**
+ * MES 濮斿妫�娴�/璇曢獙鍗� Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface MesQcOutsourceTestService {
+
+    /**
+     * 鍒涘缓濮斿妫�娴�/璇曢獙鍗�
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createTest(MesQcOutsourceTestSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊濮斿妫�娴�/璇曢獙鍗�
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateTest(MesQcOutsourceTestSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎濮斿妫�娴�/璇曢獙鍗�
+     *
+     * @param ids 缂栧彿鏁扮粍
+     */
+    void deleteTest(Collection<Long> ids);
+
+    /**
+     * 鑾峰緱濮斿妫�娴�/璇曢獙鍗�
+     *
+     * @param id 缂栧彿
+     * @return 濮斿妫�娴�/璇曢獙鍗�
+     */
+    MesQcOutsourceTestDO getTest(Long id);
+
+    /**
+     * 鑾峰緱濮斿妫�娴�/璇曢獙鍗曞垎椤�
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<MesQcOutsourceTestDO> getTestPage(MesQcOutsourceTestPageReqVO pageReqVO);
+
+    /**
+     * 鎻愪氦濮斿妫�娴�/璇曢獙鍗曪紙鑽夌 -> 宸叉彁浜わ級
+     *
+     * @param id 缂栧彿
+     */
+    void submitTest(Long id);
+
+    /**
+     * 瀹屾垚濮斿妫�娴�/璇曢獙鍗曪紙宸叉彁浜� -> 宸插畬鎴愶級
+     *
+     * @param id 缂栧彿
+     */
+    void finishTest(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestServiceImpl.java
new file mode 100644
index 0000000..6911ad6
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/outsourcetest/MesQcOutsourceTestServiceImpl.java
@@ -0,0 +1,158 @@
+package cn.iocoder.yudao.module.mes.service.qc.outsourcetest;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.outsourcetest.vo.MesQcOutsourceTestSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.outsourcetest.MesQcOutsourceTestDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.qc.outsourcetest.MesQcOutsourceTestMapper;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
+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.util.Collection;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+
+/**
+ * MES 濮斿妫�娴�/璇曢獙鍗� Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+@Slf4j
+public class MesQcOutsourceTestServiceImpl implements MesQcOutsourceTestService {
+
+    /**
+     * 闄勪欢鍏宠仈涓氬姟绫诲瀷
+     */
+    private static final String ATTACHMENT_BIZ_TYPE = "file";
+    /**
+     * 闄勪欢鍏宠仈涓氬姟璁板綍绫诲瀷
+     */
+    private static final String ATTACHMENT_RECORD_TYPE = "mes_qc_outsource_test";
+
+    /**
+     * 鐘舵�侊細鑽夌
+     */
+    private static final Integer STATUS_DRAFT = 0;
+    /**
+     * 鐘舵�侊細宸叉彁浜�
+     */
+    private static final Integer STATUS_SUBMITTED = 10;
+    /**
+     * 鐘舵�侊細宸插畬鎴�
+     */
+    private static final Integer STATUS_FINISHED = 20;
+
+    @Resource
+    private MesQcOutsourceTestMapper testMapper;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createTest(MesQcOutsourceTestSaveReqVO createReqVO) {
+        // 1. 鐢熸垚鍗曞彿
+        String code = generateCode();
+        if (testMapper.selectByCode(code) != null) {
+            throw exception(PRO_WORK_ORDER_CODE_DUPLICATE);
+        }
+        // 2. 鎻掑叆
+        MesQcOutsourceTestDO test = BeanUtils.toBean(createReqVO, MesQcOutsourceTestDO.class, req -> req
+                .setCode(code)
+                .setStatus(STATUS_DRAFT));
+        testMapper.insert(test);
+        // 3. 缁戝畾闄勪欢
+        storageAttachmentApi.bindAttachments(ATTACHMENT_BIZ_TYPE, ATTACHMENT_RECORD_TYPE,
+                test.getId(), createReqVO.getBlobIds());
+        return test.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateTest(MesQcOutsourceTestSaveReqVO updateReqVO) {
+        // 1. 鏍¢獙瀛樺湪 + 鑽夌
+        MesQcOutsourceTestDO oldTest = validateTestCanModify(updateReqVO.getId());
+        // 2. 鏇存柊
+        MesQcOutsourceTestDO updateObj = BeanUtils.toBean(updateReqVO, MesQcOutsourceTestDO.class, req -> req
+                .setCode(oldTest.getCode())
+                .setStatus(oldTest.getStatus()));
+        testMapper.updateById(updateObj);
+        // 3. 閲嶆柊缁戝畾闄勪欢
+        storageAttachmentApi.bindAttachments(ATTACHMENT_BIZ_TYPE, ATTACHMENT_RECORD_TYPE,
+                updateReqVO.getId(), updateReqVO.getBlobIds());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteTest(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validateTestCanModify(id);
+            testMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public MesQcOutsourceTestDO getTest(Long id) {
+        return testMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesQcOutsourceTestDO> getTestPage(MesQcOutsourceTestPageReqVO pageReqVO) {
+        return testMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitTest(Long id) {
+        MesQcOutsourceTestDO test = validateTestExists(id);
+        if (!Objects.equals(test.getStatus(), STATUS_DRAFT)) {
+            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
+        }
+        testMapper.updateById(new MesQcOutsourceTestDO().setId(id).setStatus(STATUS_SUBMITTED));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void finishTest(Long id) {
+        MesQcOutsourceTestDO test = validateTestExists(id);
+        if (!Objects.equals(test.getStatus(), STATUS_SUBMITTED)) {
+            throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
+        }
+        testMapper.updateById(new MesQcOutsourceTestDO().setId(id).setStatus(STATUS_FINISHED));
+    }
+
+    private String generateCode() {
+        return "WT" + DateUtil.format(java.time.LocalDateTime.now(), "yyyyMMddHHmmss")
+                + String.format("%03d", (int) (Math.random() * 1000));
+    }
+
+    private MesQcOutsourceTestDO validateTestExists(Long id) {
+        MesQcOutsourceTestDO test = testMapper.selectById(id);
+        if (test == null) {
+            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
+        }
+        return test;
+    }
+
+    private MesQcOutsourceTestDO validateTestCanModify(Long id) {
+        MesQcOutsourceTestDO test = validateTestExists(id);
+        if (!Objects.equals(test.getStatus(), STATUS_DRAFT)) {
+            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
+        }
+        return test;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
index ee24926..2715d91 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
@@ -18,6 +18,7 @@
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueTypeEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
@@ -102,6 +103,9 @@
         // 3. 鎻掑叆涓昏〃
         MesWmProductIssueDO issue = BeanUtils.toBean(createReqVO, MesWmProductIssueDO.class);
         issue.setStatus(MesWmProductIssueStatusEnum.PREPARE.getStatus());
+        if (issue.getIssueType() == null) {
+            issue.setIssueType(MesWmProductIssueTypeEnum.NORMAL.getType());
+        }
         issueMapper.insert(issue);
         return issue.getId();
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineService.java
index 4e87db0..4708c4d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineService.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.mes.service.wm.stocktaking.task;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLinePageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineSaveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO;
@@ -114,4 +116,16 @@
      */
     void updateStockTakingTaskLineTakingQuantity(Long id, BigDecimal takingQuantity);
 
+    /**
+     * Excel 瀵煎叆瀹炵洏鏁伴噺
+     *
+     * <p>鎸� 鐗╂枡缂栫爜 + 鎵规鍙� 鍖归厤鐩樼偣浠诲姟琛岋紝鍐欏叆瀹炵洏鏁伴噺銆�
+     *
+     * @param taskId 鐩樼偣浠诲姟缂栧彿
+     * @param list   瀵煎叆鏁版嵁
+     * @return 瀵煎叆缁撴灉
+     */
+    MesWmStockTakingTaskLineImportRespVO importTakingQuantity(Long taskId,
+                                                              List<MesWmStockTakingTaskLineImportExcelVO> list);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java
index 5f68604..1fed457 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java
@@ -7,6 +7,8 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLinePageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.line.MesWmStockTakingTaskLineImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.plan.MesWmStockTakingPlanParamDO;
@@ -50,6 +52,8 @@
     private MesWmMaterialStockService materialStockService;
     @Resource
     private MesWmStockTakingTaskService stockTakingTaskService;
+    @Resource
+    private cn.iocoder.yudao.module.mdm.api.item.MdmItemApi mdmItemApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -230,4 +234,58 @@
         return param != null ? param.getValueId() : null;
     }
 
+    @Override
+    public MesWmStockTakingTaskLineImportRespVO importTakingQuantity(Long taskId,
+                                                                    List<MesWmStockTakingTaskLineImportExcelVO> list) {
+        MesWmStockTakingTaskLineImportRespVO respVO = new MesWmStockTakingTaskLineImportRespVO();
+        respVO.setSuccessCount(0);
+        respVO.setFailureCount(0);
+        respVO.setFailureMessages(new java.util.ArrayList<>());
+        if (CollUtil.isEmpty(list)) {
+            return respVO;
+        }
+        // 1. 鑾峰彇鐩樼偣浠诲姟琛�
+        List<MesWmStockTakingTaskLineDO> lines = stockTakingTaskLineMapper.selectListByTaskId(taskId);
+        if (CollUtil.isEmpty(lines)) {
+            throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_STOCK_TAKING_TASK_LINE_NOT_EXISTS);
+        }
+        // 2. 鏋勫缓 鐗╂枡缂栫爜 -> 鐗╂枡ID 鏄犲皠
+        java.util.Set<Long> itemIds = lines.stream().map(MesWmStockTakingTaskLineDO::getItemId)
+                .filter(Objects::nonNull).collect(java.util.stream.Collectors.toSet());
+        java.util.Map<Long, cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO> itemMap =
+                mdmItemApi.getItemMap(itemIds);
+        java.util.Map<String, Long> codeToItemIdMap = new java.util.HashMap<>();
+        itemMap.values().forEach(item -> {
+            if (item.getCode() != null) {
+                codeToItemIdMap.put(item.getCode(), item.getId());
+            }
+        });
+        // 3. 閫愯鍖归厤骞跺啓鍏ュ疄鐩樻暟閲�
+        for (MesWmStockTakingTaskLineImportExcelVO row : list) {
+            Long itemId = codeToItemIdMap.get(row.getItemCode());
+            if (itemId == null) {
+                respVO.setFailureCount(respVO.getFailureCount() + 1);
+                respVO.getFailureMessages().add("鐗╂枡缂栫爜涓嶅瓨鍦�: " + row.getItemCode());
+                continue;
+            }
+            MesWmStockTakingTaskLineDO matched = lines.stream()
+                    .filter(line -> Objects.equals(line.getItemId(), itemId)
+                            && Objects.equals(line.getBatchCode(), row.getBatchCode()))
+                    .findFirst().orElse(null);
+            if (matched == null) {
+                respVO.setFailureCount(respVO.getFailureCount() + 1);
+                respVO.getFailureMessages().add("鏈壘鍒板搴旂洏鐐硅: 鐗╂枡=" + row.getItemCode() + " 鎵规=" + row.getBatchCode());
+                continue;
+            }
+            if (row.getTakingQuantity() == null) {
+                respVO.setFailureCount(respVO.getFailureCount() + 1);
+                respVO.getFailureMessages().add("瀹炵洏鏁伴噺涓虹┖: 鐗╂枡=" + row.getItemCode());
+                continue;
+            }
+            updateStockTakingTaskLineTakingQuantity(matched.getId(), row.getTakingQuantity());
+            respVO.setSuccessCount(respVO.getSuccessCount() + 1);
+        }
+        return respVO;
+    }
+
 }
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index eaf06ab..ea2a296 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -75,7 +75,7 @@
       host: localhost # 鍦板潃
       port: 6379 # 绔彛
       database: 0 # 鏁版嵁搴撶储寮�
-#      password: # 瀵嗙爜锛屽缓璁敓浜х幆澧冨紑鍚�
+      password: 123456
 
 --- #################### 瀹氭椂浠诲姟鐩稿叧閰嶇疆 ####################
 
@@ -198,3 +198,10 @@
     compress: true # 鏄惁鍘嬬缉鍥剧墖
     need-compress-size: 10MB # 瑙﹀彂鍘嬬缉鐨勬枃浠跺ぇ灏忛槇鍊�
     compress-quality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
+
+# MES 鏁伴噰锛堣澶囨暟鎹噰闆嗭級閰嶇疆锛屽閮ㄦ暟閲囨湇鍔″湴鍧�鎸夊疄闄呴儴缃插~鍐�
+yudao:
+  mes:
+    telemetry:
+      base-url: http://106.227.91.252:3100 # 鏁伴噰鏈嶅姟鍦板潃
+      path: /api/jxjs/mesTb/getDeviceDetails

--
Gitblit v1.9.3