Переглянути джерело

Merge branch 'feature/service-vpp-20260701' of http://47.111.81.118:3000/uskycloud/usky-modules into feature/service-vpp-20260701

james 6 днів тому
батько
коміт
0d584f3694
62 змінених файлів з 3152 додано та 150 видалено
  1. 8 7
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ArchiveController.java
  2. 119 21
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ContractController.java
  3. 64 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/DrController.java
  4. 43 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppContract.java
  5. 1 34
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppContractAuditLog.java
  6. 17 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppContractTemplate.java
  7. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppCustomer.java
  8. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppCustomerAccess.java
  9. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppCustomerContact.java
  10. 2 2
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDevice.java
  11. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDeviceControlLog.java
  12. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrEvaluation.java
  13. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrEvent.java
  14. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrExecution.java
  15. 76 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrInvitation.java
  16. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrParticipation.java
  17. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrStrategy.java
  18. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrStrategyResource.java
  19. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppEnergyReadingMonthly.java
  20. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppEnergySummaryDaily.java
  21. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppFileArchive.java
  22. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppPaymentRecord.java
  23. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppRegistration.java
  24. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportAuditLog.java
  25. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportData.java
  26. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportLog.java
  27. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportRecord.java
  28. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportTask.java
  29. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppResourcePoint.java
  30. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSettlementBill.java
  31. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSettlementBillDetail.java
  32. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSite.java
  33. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSiteConfig.java
  34. 2 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppContractTemplateMapper.java
  35. 12 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppDrInvitationMapper.java
  36. 2 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppFileArchiveMapper.java
  37. 1 2
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppArchiveService.java
  38. 40 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppContractService.java
  39. 50 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppContractTemplateService.java
  40. 29 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrInvitationService.java
  41. 7 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrService.java
  42. 110 27
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppArchiveServiceImpl.java
  43. 325 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppContractServiceImpl.java
  44. 296 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppContractTemplateServiceImpl.java
  45. 486 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrInvitationServiceImpl.java
  46. 133 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrServiceImpl.java
  47. 26 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrEventRequest.java
  48. 13 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationNotifyRequest.java
  49. 16 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationReplyRequest.java
  50. 28 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationRequest.java
  51. 43 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationVO.java
  52. 17 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrStrategyRequest.java
  53. 86 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractRequestVO.java
  54. 124 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractResponseVO.java
  55. 53 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractTemplateRequestVO.java
  56. 83 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractTemplateResponseVO.java
  57. 11 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppAuditHelper.java
  58. 93 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppFieldValidator.java
  59. 41 0
      service-vpp/service-vpp-biz/src/main/resources/sql/vpp_dr_invitation_migration.sql
  60. 246 0
      service-vpp/service-vpp-biz/src/main/resources/sql/vpp_id_auto_increment_migration.sql
  61. 362 0
      service-vpp/service-vpp-biz/src/main/resources/sql/vpp_id_reset_from_one_migration.sql
  62. 62 28
      service-vpp/service-vpp-biz/src/main/resources/sql/vpp_schema.sql

+ 8 - 7
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ArchiveController.java

@@ -6,7 +6,6 @@ import com.usky.vpp.service.VppArchiveService;
 import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
 import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
@@ -33,23 +32,25 @@ public class ArchiveController {
 
     /**
      * 查询电子档案
+     * id          主键ID 精确匹配
      * archiveName 档案名称 模糊匹配
      * archiveType 档案类型 1图纸 2文档 3设计稿 4验收报告 5其他
      * siteId 站点id
      * bizType 业务类型
      * bizId 业务主键
-     * pageNum 页码
-     * pageSize 页大小
+     * current 页码
+     * size 页大小
      */
     @GetMapping
-    public ApiResult<CommonPage<VppFileArchiveResponseVO>> page(@RequestParam(value = "archiveName", required = false) String archiveName,
+    public ApiResult<CommonPage<VppFileArchiveResponseVO>> page(@RequestParam(value = "id", required = false) Long id,
+                                                                @RequestParam(value = "archiveName", required = false) String archiveName,
                                                                 @RequestParam(value = "archiveType", required = false) Integer archiveType,
                                                                 @RequestParam(value = "siteId", required = false) Long siteId,
                                                                 @RequestParam(value = "bizType", required = false) String bizType,
                                                                 @RequestParam(value = "bizId", required = false) Long bizId,
-                                                                @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
-                                                                @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize) {
-        return ApiResult.success(vppArchiveService.page(archiveName, archiveType, siteId, bizType, bizId, pageNum, pageSize));
+                                                                @RequestParam(value = "current", required = false, defaultValue = "1") Integer current,
+                                                                @RequestParam(value = "size", required = false, defaultValue = "20") Integer size) {
+        return ApiResult.success(vppArchiveService.page(id, archiveName, archiveType, siteId, bizType, bizId, current, size));
     }
 
     /**

+ 119 - 21
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ContractController.java

@@ -1,10 +1,20 @@
 package com.usky.vpp.controller.web;
 
 import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.service.VppContractService;
+import com.usky.vpp.service.VppContractTemplateService;
+import com.usky.vpp.service.vo.VppContractRequestVO;
+import com.usky.vpp.service.vo.VppContractResponseVO;
+import com.usky.vpp.service.vo.VppContractTemplateRequestVO;
+import com.usky.vpp.service.vo.VppContractTemplateResponseVO;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDate;
+
 /**
  * 虚拟电厂 - Contract 接口
  * 网关前缀: /prod-api/service-vpp
@@ -13,35 +23,123 @@ import org.springframework.web.bind.annotation.*;
 @RequestMapping("/contract")
 public class ContractController {
 
+    @Autowired
+    private VppContractTemplateService vppContractTemplateService;
     @Autowired
     private VppContractService vppContractService;
 
-    @GetMapping(value = "/template")
-    public ApiResult<Object> listTemplate(@RequestParam(required = false) java.util.Map<String, Object> params) {
-        return ApiResult.success(null);
+    // ==================== 合同模板 ====================
+
+    /**
+     * 新增合同模板
+     */
+    @PostMapping("/template")
+    public ApiResult<Boolean> createTemplate(@RequestBody VppContractTemplateRequestVO vo) {
+        return vppContractTemplateService.create(vo) ? ApiResult.success(true) : ApiResult.error("新增合同模板失败!请重试!");
     }
-    @PostMapping
-    public ApiResult<Object> createContract() {
-        return ApiResult.success(null);
+
+    /**
+     * 修改合同模板
+     */
+    @PutMapping("/template")
+    public ApiResult<Boolean> updateTemplate(@RequestBody VppContractTemplateRequestVO vo) {
+        return vppContractTemplateService.update(vo) ? ApiResult.success(true) : ApiResult.error("修改或启用合同模板失败!请重试!");
     }
-    @GetMapping
-    public ApiResult<Object> pageContract() {
-        return ApiResult.success(null);
+
+    /**
+     * 分页查询合同模板
+     * id           主键ID 精确匹配
+     * templateName 模板名称 模糊匹配
+     * contractType 合同类型
+     * isEnabled    是否启用 0-禁用 1-启用
+     * current      页码
+     * size         页大小
+     */
+    @GetMapping("/template")
+    public ApiResult<CommonPage<VppContractTemplateResponseVO>> pageTemplate(
+            @RequestParam(value = "id", required = false) Long id,
+            @RequestParam(value = "templateName", required = false) String templateName,
+            @RequestParam(value = "contractType", required = false) Integer contractType,
+            @RequestParam(value = "isEnabled", required = false) Integer isEnabled,
+            @RequestParam(value = "current", required = false, defaultValue = "1") Integer current,
+            @RequestParam(value = "size", required = false, defaultValue = "20") Integer size) {
+        return ApiResult.success(vppContractTemplateService.page(id, templateName, contractType, isEnabled, current, size));
+    }
+
+    /**
+     * 删除合同模板(软删除)
+     */
+    @DeleteMapping("/template/{id}")
+    public ApiResult<Boolean> deleteTemplate(@PathVariable("id") Long id) {
+        return vppContractTemplateService.delete(id) ? ApiResult.success(true) : ApiResult.error("删除合同模板失败!请重试!");
+    }
+
+    /**
+     * 下载合同模板文件(单个文件流下载,不打包)
+     */
+    @GetMapping("/template/{id}/download")
+    public void downloadTemplate(@PathVariable("id") Long id, HttpServletResponse response) {
+        vppContractTemplateService.download(id, response);
+    }
+
+    // ==================== 合同 ====================
+
+    /**
+     * 新增合同
+     */
+    @PostMapping
+    public ApiResult<Boolean> create(@RequestBody VppContractRequestVO vo) {
+        return vppContractService.create(vo) ? ApiResult.success(true) : ApiResult.error("新增合同失败!请重试!");
     }
-    @GetMapping(value = "/{id}")
-    public ApiResult<Object> getContract(@PathVariable("id") Long id, @RequestParam(required = false) java.util.Map<String, Object> params) {
-        return ApiResult.success(null);
+
+    /**
+     * 修改合同
+     */
+    @PutMapping
+    public ApiResult<Boolean> update(@RequestBody VppContractRequestVO vo) {
+        return vppContractService.update(vo) ? ApiResult.success(true) : ApiResult.error("修改合同失败!请重试!");
     }
-    @PostMapping(value = "/{id}/submit")
-    public ApiResult<Void> submitContract(@PathVariable("id") Long id, @RequestBody(required = false) Object body) {
-        return ApiResult.success();
+
+    /**
+     * 分页查询合同
+     * id             主键ID 精确匹配
+     * signDateStart  签约日期起始
+     * signDateEnd    签约日期截止
+     * templateId     模板ID
+     * contractType   合同类型
+     * customerId     客户ID
+     * contractStatus 合同状态
+     * current        页码
+     * size           页大小
+     */
+    @GetMapping
+    public ApiResult<CommonPage<VppContractResponseVO>> page(
+            @RequestParam(value = "id", required = false) Long id,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
+            @RequestParam(value = "templateId", required = false) Long templateId,
+            @RequestParam(value = "contractType", required = false) Integer contractType,
+            @RequestParam(value = "customerId", required = false) Long customerId,
+            @RequestParam(value = "contractStatus", required = false) Integer contractStatus,
+            @RequestParam(value = "current", required = false, defaultValue = "1") Integer current,
+            @RequestParam(value = "size", required = false, defaultValue = "20") Integer size) {
+        return ApiResult.success(vppContractService.page(id, startDate, endDate,
+                templateId, contractType, customerId, contractStatus, current, size));
     }
-    @PutMapping(value = "/{id}/audit")
-    public ApiResult<Void> auditContract(@PathVariable("id") Long id, @RequestBody(required = false) Object body) {
-        return ApiResult.success();
+
+    /**
+     * 删除合同(软删除)
+     */
+    @DeleteMapping("/{id}")
+    public ApiResult<Boolean> delete(@PathVariable("id") Long id) {
+        return vppContractService.delete(id) ? ApiResult.success(true) : ApiResult.error("删除合同失败!请重试!");
     }
-    @PostMapping(value = "/{id}/archive")
-    public ApiResult<Void> archiveContract(@PathVariable("id") Long id, @RequestBody(required = false) Object body) {
-        return ApiResult.success();
+
+    /**
+     * 下载合同文件(单个文件流下载,不打包)
+     */
+    @GetMapping("/{id}/download")
+    public void download(@PathVariable("id") Long id, HttpServletResponse response) {
+        vppContractService.download(id, response);
     }
 }

+ 64 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/DrController.java

@@ -5,9 +5,15 @@ import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.domain.VppDrEvent;
 import com.usky.vpp.domain.VppDrEvaluation;
 import com.usky.vpp.domain.VppDrStrategy;
+import com.usky.vpp.service.VppDrInvitationService;
 import com.usky.vpp.service.VppDrService;
 import com.usky.vpp.service.vo.DrClearingRequest;
+import com.usky.vpp.service.vo.DrEventRequest;
 import com.usky.vpp.service.vo.DrInterveneRequest;
+import com.usky.vpp.service.vo.DrInvitationNotifyRequest;
+import com.usky.vpp.service.vo.DrInvitationReplyRequest;
+import com.usky.vpp.service.vo.DrInvitationRequest;
+import com.usky.vpp.service.vo.DrInvitationVO;
 import com.usky.vpp.service.vo.DrParticipateRequest;
 import com.usky.vpp.service.vo.DrStrategyRequest;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +31,8 @@ public class DrController {
 
     @Autowired
     private VppDrService vppDrService;
+    @Autowired
+    private VppDrInvitationService vppDrInvitationService;
 
     @GetMapping(value = "/event")
     public ApiResult<CommonPage<VppDrEvent>> pageEvent(@RequestParam(required = false) Map<String, Object> params) {
@@ -36,6 +44,23 @@ public class DrController {
         return ApiResult.success(vppDrService.getEvent(id));
     }
 
+    @PostMapping(value = "/event")
+    public ApiResult<VppDrEvent> createEvent(@RequestBody DrEventRequest body) {
+        return ApiResult.success(vppDrService.createEvent(body));
+    }
+
+    @PutMapping(value = "/event/{id}")
+    public ApiResult<Void> updateEvent(@PathVariable("id") Long id, @RequestBody DrEventRequest body) {
+        vppDrService.updateEvent(id, body);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping(value = "/event/{id}")
+    public ApiResult<Void> deleteEvent(@PathVariable("id") Long id) {
+        vppDrService.deleteEvent(id);
+        return ApiResult.success();
+    }
+
     @GetMapping(value = "/event/{id}/capability")
     public ApiResult<Object> assessCapability(@PathVariable("id") Long id) {
         return ApiResult.success(vppDrService.assessCapability(id));
@@ -102,4 +127,43 @@ public class DrController {
         vppDrService.deleteStrategy(id);
         return ApiResult.success();
     }
+
+    @GetMapping(value = "/invitation")
+    public ApiResult<CommonPage<DrInvitationVO>> pageInvitation(@RequestParam(required = false) Map<String, Object> params) {
+        return ApiResult.success(vppDrInvitationService.pageInvitation(params));
+    }
+
+    @GetMapping(value = "/invitation/{id}")
+    public ApiResult<DrInvitationVO> getInvitation(@PathVariable("id") Long id) {
+        return ApiResult.success(vppDrInvitationService.getInvitation(id));
+    }
+
+    @PostMapping(value = "/invitation")
+    public ApiResult<DrInvitationVO> createInvitation(@RequestBody DrInvitationRequest body) {
+        return ApiResult.success(vppDrInvitationService.createInvitation(body));
+    }
+
+    @PutMapping(value = "/invitation/{id}")
+    public ApiResult<Void> updateInvitation(@PathVariable("id") Long id, @RequestBody DrInvitationRequest body) {
+        vppDrInvitationService.updateInvitation(id, body);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping(value = "/invitation/{id}")
+    public ApiResult<Void> deleteInvitation(@PathVariable("id") Long id) {
+        vppDrInvitationService.deleteInvitation(id);
+        return ApiResult.success();
+    }
+
+    @PostMapping(value = "/invitation/{id}/notify-sms")
+    public ApiResult<DrInvitationVO> notifyInvitationSms(@PathVariable("id") Long id,
+                                                         @RequestBody(required = false) DrInvitationNotifyRequest body) {
+        return ApiResult.success(vppDrInvitationService.notifySms(id, body));
+    }
+
+    @PostMapping(value = "/invitation/{id}/reply")
+    public ApiResult<DrInvitationVO> replyInvitation(@PathVariable("id") Long id,
+                                                       @RequestBody DrInvitationReplyRequest body) {
+        return ApiResult.success(vppDrInvitationService.replyInvitation(id, body));
+    }
 }

+ 43 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppContract.java

@@ -4,8 +4,12 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDate;
@@ -21,47 +25,85 @@ public class VppContract implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
+
     @TableField("contract_no")
+    @NotBlank(message = "合同编号不能为空!")
     private String contractNo;
+
     @TableField("customer_id")
+    @NotNull(message = "客户ID不能为空!")
     private Long customerId;
+
     @TableField("template_id")
+    @NotNull(message = "合同模板ID不能为空!")
     private Long templateId;
+
+    /**
+     * 1购售电 2需求响应合作 3聚合代理 4服务代理 5居民充电桩 6自有资产
+     */
     @TableField("contract_type")
+    @NotNull(message = "合同类型不能为空!")
     private Integer contractType;
+
     @TableField("contract_name")
+    @NotNull(message = "合同名称不能为空!")
     private String contractName;
+
+    /**
+     * 0草稿 1审核中 2已生效 3已到期 4已终止
+     */
+    @NotNull(message = "合同状态不能为空!")
     @TableField("contract_status")
     private Integer contractStatus;
+
     @TableField("sign_date")
     private LocalDate signDate;
+
     @TableField("effective_date")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private LocalDate effectiveDate;
+
     @TableField("expire_date")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private LocalDate expireDate;
+
     @TableField("file_url")
     private String fileUrl;
+
     @TableField("share_ratio")
     private BigDecimal shareRatio;
+
     @TableField("price_json")
     private String priceJson;
+
     @TableField("account_info_json")
     private String accountInfoJson;
+
+    @TableField("remark")
     private String remark;
+
     @TableField("tenant_id")
     private Integer tenantId;
+
     @TableField("create_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
+
     @TableField("update_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
+
     @TableField("created_by")
     private String createdBy;
+
     @TableField("updated_by")
     private String updatedBy;
+
     @TableField("delete_flag")
     private Integer deleteFlag;
+
     @TableField("deleted_at")
     private LocalDateTime deletedAt;
 }

+ 1 - 34
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppContractAuditLog.java

@@ -1,34 +1 @@
-package com.usky.vpp.domain;

-

-import com.baomidou.mybatisplus.annotation.IdType;

-import com.baomidou.mybatisplus.annotation.TableField;

-import com.baomidou.mybatisplus.annotation.TableId;

-import com.baomidou.mybatisplus.annotation.TableName;

-import lombok.Data;

-import lombok.EqualsAndHashCode;

-import java.io.Serializable;

-import java.time.LocalDateTime;

-

-/**

- * vpp_contract_audit_log

- */

-@Data

-@EqualsAndHashCode(callSuper = false)

-@TableName("vpp_contract_audit_log")

-public class VppContractAuditLog implements Serializable {

-

-    private static final long serialVersionUID = 1L;

-

-    @TableId(value = "id", type = IdType.ASSIGN_ID)

-    private Long id;

-    @TableField("contract_id")

-    private Long contractId;

-    private Integer action;

-    private String opinion;

-    @TableField("tenant_id")

-    private Integer tenantId;

-    @TableField("create_time")

-    private LocalDateTime createTime;

-    @TableField("created_by")

-    private String createdBy;

-}

+package com.usky.vpp.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.time.LocalDateTime;


/**
 * vpp_contract_audit_log

 */

@Data

@EqualsAndHashCode(callSuper = false)

@TableName("vpp_contract_audit_log")

public class VppContractAuditLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)

    private Long id;

    @TableField("contract_id")

    private Long contractId;

    private Integer action;

    private String opinion;

    @TableField("tenant_id")

    private Integer tenantId;

    @TableField("create_time")

    private LocalDateTime createTime;

    @TableField("created_by")

    private String createdBy;

}

+ 17 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppContractTemplate.java

@@ -4,8 +4,12 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
@@ -19,26 +23,37 @@ public class VppContractTemplate implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("template_code")
+    @NotBlank(message = "模板编码不能为空!")
     private String templateCode;
     @TableField("template_name")
+    @NotBlank(message = "模板名称不能为空!")
     private String templateName;
+
+    /**
+     * 1、购售电合同 2、需求响应合作协议 3、聚合代理协议(2024 版) 4、虚拟电厂服务代理协议 5、居民个人充电桩协议
+     */
     @TableField("contract_type")
+    @NotNull(message = "合同类型不能为空!")
     private Integer contractType;
     private String version;
     @TableField("file_url")
+    @NotBlank(message = "文件路径不能为空!")
     private String fileUrl;
     @TableField("variables_json")
     private String variablesJson;
     @TableField("is_enabled")
+    @NotNull(message = "是否启用不能为空!")
     private Integer isEnabled;
     @TableField("tenant_id")
     private Integer tenantId;
     @TableField("create_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
     @TableField("update_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
     @TableField("created_by")
     private String createdBy;
@@ -47,5 +62,6 @@ public class VppContractTemplate implements Serializable {
     @TableField("delete_flag")
     private Integer deleteFlag;
     @TableField("deleted_at")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime deletedAt;
 }

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppCustomer.java

@@ -20,7 +20,7 @@ public class VppCustomer implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("account_no")
     private String accountNo;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppCustomerAccess.java

@@ -20,7 +20,7 @@ public class VppCustomerAccess implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("apply_no")
     private String applyNo;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppCustomerContact.java

@@ -19,7 +19,7 @@ public class VppCustomerContact implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("customer_id")
     private Long customerId;

+ 2 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDevice.java

@@ -20,7 +20,7 @@ public class VppDevice implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("device_code")
     private String deviceCode;
@@ -28,7 +28,7 @@ public class VppDevice implements Serializable {
     private String deviceUuid;
     @TableField("device_name")
     private String deviceName;
-    /** 所属站点ID,关è�vpp_site.id */
+    /** 所属站点ID,关�vpp_site.id */
     @TableField("site_id")
     private Long siteId;
     @TableField("device_type")

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDeviceControlLog.java

@@ -19,7 +19,7 @@ public class VppDeviceControlLog implements Serializable {
 

     private static final long serialVersionUID = 1L;

 

-    @TableId(value = "id", type = IdType.ASSIGN_ID)

+    @TableId(value = "id", type = IdType.AUTO)

     private Long id;

     @TableField("device_id")

     private Long deviceId;


+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrEvaluation.java

@@ -20,7 +20,7 @@ public class VppDrEvaluation implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("event_id")
     private Long eventId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrEvent.java

@@ -20,7 +20,7 @@ public class VppDrEvent implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("event_id")
     private String eventId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrExecution.java

@@ -20,7 +20,7 @@ public class VppDrExecution implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("event_id")
     private Long eventId;

+ 76 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrInvitation.java

@@ -0,0 +1,76 @@
+package com.usky.vpp.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * vpp_dr_invitation
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("vpp_dr_invitation")
+public class VppDrInvitation implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    @TableField("invitation_no")
+    private String invitationNo;
+    @TableField("dr_event_id")
+    private Long drEventId;
+    @TableField("customer_id")
+    private Long customerId;
+    @TableField("execute_start_date")
+    private LocalDate executeStartDate;
+    @TableField("execute_end_date")
+    private LocalDate executeEndDate;
+    @TableField("reply_deadline")
+    private LocalDateTime replyDeadline;
+    @TableField("issue_time")
+    private LocalDateTime issueTime;
+    @TableField("transaction_type")
+    private Integer transactionType;
+    @TableField("response_type")
+    private Integer responseType;
+    @TableField("demand_capacity_kw")
+    private BigDecimal demandCapacityKw;
+    @TableField("declared_capacity_kw")
+    private BigDecimal declaredCapacityKw;
+    @TableField("reply_status")
+    private Integer replyStatus;
+    @TableField("response_status")
+    private Integer responseStatus;
+    @TableField("sms_notify_status")
+    private Integer smsNotifyStatus;
+    @TableField("sms_notify_at")
+    private LocalDateTime smsNotifyAt;
+    @TableField("sms_contact_id")
+    private Long smsContactId;
+    @TableField("participation_id")
+    private Long participationId;
+    private String remark;
+    @TableField("tenant_id")
+    private Integer tenantId;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("updated_by")
+    private String updatedBy;
+    @TableField("delete_flag")
+    private Integer deleteFlag;
+    @TableField("deleted_at")
+    private LocalDateTime deletedAt;
+}

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrParticipation.java

@@ -20,7 +20,7 @@ public class VppDrParticipation implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("event_id")
     private Long eventId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrStrategy.java

@@ -20,7 +20,7 @@ public class VppDrStrategy implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("strategy_code")
     private String strategyCode;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrStrategyResource.java

@@ -19,7 +19,7 @@ public class VppDrStrategyResource implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("strategy_id")
     private Long strategyId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppEnergyReadingMonthly.java

@@ -20,7 +20,7 @@ public class VppEnergyReadingMonthly implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("customer_id")
     private Long customerId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppEnergySummaryDaily.java

@@ -21,7 +21,7 @@ public class VppEnergySummaryDaily implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("summary_date")
     private LocalDate summaryDate;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppFileArchive.java

@@ -129,4 +129,4 @@ public class VppFileArchive implements Serializable {
      */
     @TableField("tenant_id")
     private Integer tenantId;
-}
+}

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppPaymentRecord.java

@@ -21,7 +21,7 @@ public class VppPaymentRecord implements Serializable {
 

     private static final long serialVersionUID = 1L;

 

-    @TableId(value = "id", type = IdType.ASSIGN_ID)

+    @TableId(value = "id", type = IdType.AUTO)

     private Long id;

     @TableField("bill_id")

     private Long billId;


+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppRegistration.java

@@ -19,7 +19,7 @@ public class VppRegistration implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("dn_id")
     private String dnId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportAuditLog.java

@@ -19,7 +19,7 @@ public class VppReportAuditLog implements Serializable {
 

     private static final long serialVersionUID = 1L;

 

-    @TableId(value = "id", type = IdType.ASSIGN_ID)

+    @TableId(value = "id", type = IdType.AUTO)

     private Long id;

     @TableField("record_id")

     private Long recordId;


+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportData.java

@@ -19,7 +19,7 @@ public class VppReportData implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("record_id")
     private Long recordId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportLog.java

@@ -19,7 +19,7 @@ public class VppReportLog implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("report_type")
     private String reportType;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportRecord.java

@@ -19,7 +19,7 @@ public class VppReportRecord implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("task_id")
     private Long taskId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppReportTask.java

@@ -19,7 +19,7 @@ public class VppReportTask implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("task_no")
     private String taskNo;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppResourcePoint.java

@@ -21,7 +21,7 @@ public class VppResourcePoint implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("resource_code")
     private String resourceCode;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSettlementBill.java

@@ -21,7 +21,7 @@ public class VppSettlementBill implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("bill_no")
     private String billNo;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSettlementBillDetail.java

@@ -19,7 +19,7 @@ public class VppSettlementBillDetail implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("bill_id")
     private Long billId;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSite.java

@@ -21,7 +21,7 @@ public class VppSite implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("site_code")
     private String siteCode;

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppSiteConfig.java

@@ -21,7 +21,7 @@ public class VppSiteConfig implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     @TableField("site_id")
     private Long siteId;

+ 2 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppContractTemplateMapper.java

@@ -2,9 +2,11 @@ package com.usky.vpp.mapper;
 
 import com.usky.common.mybatis.core.CrudMapper;
 import com.usky.vpp.domain.VppContractTemplate;
+import org.springframework.stereotype.Repository;
 
 /**
  * vpp_contract_template Mapper
  */
+@Repository
 public interface VppContractTemplateMapper extends CrudMapper<VppContractTemplate> {
 }

+ 12 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppDrInvitationMapper.java

@@ -0,0 +1,12 @@
+package com.usky.vpp.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.vpp.domain.VppDrInvitation;
+import org.springframework.stereotype.Repository;
+
+/**
+ * vpp_dr_invitation Mapper
+ */
+@Repository
+public interface VppDrInvitationMapper extends CrudMapper<VppDrInvitation> {
+}

+ 2 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppFileArchiveMapper.java

@@ -2,9 +2,11 @@ package com.usky.vpp.mapper;
 
 import com.usky.common.mybatis.core.CrudMapper;
 import com.usky.vpp.domain.VppFileArchive;
+import org.springframework.stereotype.Repository;
 
 /**
  * vpp_file_archive Mapper
  */
+@Repository
 public interface VppFileArchiveMapper extends CrudMapper<VppFileArchive> {
 }

+ 1 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppArchiveService.java

@@ -3,7 +3,6 @@ package com.usky.vpp.service;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
 import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
-import org.springframework.http.ResponseEntity;
 
 import javax.servlet.http.HttpServletResponse;
 import java.util.List;
@@ -20,7 +19,7 @@ public interface VppArchiveService {
 
     Boolean create(VppFileArchiveRequestVO vo);
 
-    CommonPage<VppFileArchiveResponseVO> page(String archiveName, Integer archiveType, Long siteId, String bizType, Long bizId, Integer pageNum, Integer pageSize);
+    CommonPage<VppFileArchiveResponseVO> page(Long id, String archiveName, Integer archiveType, Long siteId, String bizType, Long bizId, Integer pageNum, Integer pageSize);
 
     Boolean upload(VppFileArchiveRequestVO vo);
 

+ 40 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppContractService.java

@@ -1,7 +1,11 @@
 package com.usky.vpp.service;
 
 import com.usky.common.core.bean.CommonPage;
+import com.usky.vpp.service.vo.VppContractRequestVO;
+import com.usky.vpp.service.vo.VppContractResponseVO;
 
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDate;
 import java.util.Map;
 
 /**
@@ -12,4 +16,40 @@ public interface VppContractService {
     default Object stub(String action, Map<String, Object> params) {
         return null;
     }
+
+    /**
+     * 新增合同
+     */
+    Boolean create(VppContractRequestVO vo);
+
+    /**
+     * 修改合同
+     */
+    Boolean update(VppContractRequestVO vo);
+
+    /**
+     * 分页查询合同
+     * @param id             主键ID(精确匹配)
+     * @param signDateStart  签约日期起始
+     * @param signDateEnd    签约日期截止
+     * @param templateId     模板ID
+     * @param contractType   合同类型
+     * @param customerId     客户ID
+     * @param contractStatus 合同状态
+     * @param pageNum        页码
+     * @param pageSize       页大小
+     */
+    CommonPage<VppContractResponseVO> page(Long id, LocalDate signDateStart, LocalDate signDateEnd,
+                                           Long templateId, Integer contractType, Long customerId,
+                                           Integer contractStatus, Integer pageNum, Integer pageSize);
+
+    /**
+     * 删除合同(软删除)
+     */
+    Boolean delete(Long id);
+
+    /**
+     * 下载合同文件
+     */
+    void download(Long id, HttpServletResponse response);
 }

+ 50 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppContractTemplateService.java

@@ -0,0 +1,50 @@
+package com.usky.vpp.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.vpp.service.vo.VppContractTemplateRequestVO;
+import com.usky.vpp.service.vo.VppContractTemplateResponseVO;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * VppContractTemplateService 业务接口
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+public interface VppContractTemplateService {
+
+    /**
+     * 新增合同模板
+     */
+    Boolean create(VppContractTemplateRequestVO vo);
+
+    /**
+     * 修改合同模板
+     */
+    Boolean update(VppContractTemplateRequestVO vo);
+
+    /**
+     * 分页查询合同模板
+     * @param id           主键ID(精确匹配)
+     * @param templateName 模板名称(模糊匹配)
+     * @param contractType 合同类型
+     * @param isEnabled    是否启用
+     * @param pageNum      页码
+     * @param pageSize     页大小
+     */
+    CommonPage<VppContractTemplateResponseVO> page(Long id, String templateName, Integer contractType, Integer isEnabled,
+                                                     Integer pageNum, Integer pageSize);
+
+    /**
+     * 删除合同模板(软删除)
+     */
+    Boolean delete(Long id);
+
+    /**
+     * 下载合同模板文件
+     * @param id       主键ID
+     * @param response HTTP响应
+     */
+    void download(Long id, HttpServletResponse response);
+}

+ 29 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrInvitationService.java

@@ -0,0 +1,29 @@
+package com.usky.vpp.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.vpp.service.vo.DrInvitationNotifyRequest;
+import com.usky.vpp.service.vo.DrInvitationReplyRequest;
+import com.usky.vpp.service.vo.DrInvitationRequest;
+import com.usky.vpp.service.vo.DrInvitationVO;
+
+import java.util.Map;
+
+/**
+ * 需求响应邀约业务接口
+ */
+public interface VppDrInvitationService {
+
+    CommonPage<DrInvitationVO> pageInvitation(Map<String, Object> params);
+
+    DrInvitationVO getInvitation(Long id);
+
+    DrInvitationVO createInvitation(DrInvitationRequest request);
+
+    void updateInvitation(Long id, DrInvitationRequest request);
+
+    void deleteInvitation(Long id);
+
+    DrInvitationVO notifySms(Long id, DrInvitationNotifyRequest request);
+
+    DrInvitationVO replyInvitation(Long id, DrInvitationReplyRequest request);
+}

+ 7 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrService.java

@@ -5,6 +5,7 @@ import com.usky.vpp.domain.VppDrEvent;
 import com.usky.vpp.domain.VppDrEvaluation;
 import com.usky.vpp.domain.VppDrStrategy;
 import com.usky.vpp.service.vo.DrClearingRequest;
+import com.usky.vpp.service.vo.DrEventRequest;
 import com.usky.vpp.service.vo.DrInterveneRequest;
 import com.usky.vpp.service.vo.DrParticipateRequest;
 import com.usky.vpp.service.vo.DrStrategyRequest;
@@ -20,6 +21,12 @@ public interface VppDrService {
 
     VppDrEvent getEvent(Long id);
 
+    VppDrEvent createEvent(DrEventRequest request);
+
+    void updateEvent(Long id, DrEventRequest request);
+
+    void deleteEvent(Long id);
+
     Object assessCapability(Long eventId);
 
     void participate(Long eventId, DrParticipateRequest request);

+ 110 - 27
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppArchiveServiceImpl.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.file.domain.FilesUpload;
 import com.ruoyi.file.mapper.FilesMapper;
 import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.vpp.domain.VppFileArchive;
 import com.usky.vpp.mapper.VppFileArchiveMapper;
@@ -13,17 +14,19 @@ import com.usky.vpp.service.VppArchiveService;
 import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
 import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.RestTemplate;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
-import java.io.ByteArrayOutputStream;
+import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
@@ -31,9 +34,16 @@ import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -48,6 +58,37 @@ public class VppArchiveServiceImpl implements VppArchiveService {
     @Autowired
     private VppFileArchiveMapper vppFileArchiveMapper;
 
+    /**
+     * 连接池化的 RestTemplate,避免每次创建新连接
+     */
+    private final RestTemplate restTemplate;
+
+    /**
+     * 并行下载线程池
+     */
+    private static final ExecutorService DOWNLOAD_EXECUTOR = new ThreadPoolExecutor(
+            4, 8, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(200),
+            r -> {
+                Thread t = new Thread(r, "archive-download-");
+                t.setDaemon(true);
+                return t;
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy()
+    );
+
+    {
+        // 配置 HttpClient 连接池(最大连接数、超时时间)
+        CloseableHttpClient httpClient = HttpClientBuilder.create()
+                .setMaxConnTotal(50)
+                .setMaxConnPerRoute(20)
+                .build();
+        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
+        factory.setConnectTimeout(10_000);
+        factory.setReadTimeout(30_000);
+        this.restTemplate = new RestTemplate(factory);
+    }
+
     @Override
     public Boolean create(VppFileArchiveRequestVO vo) {
         String username = SecurityUtils.getUsername();
@@ -82,12 +123,13 @@ public class VppArchiveServiceImpl implements VppArchiveService {
     }
 
     @Override
-    public CommonPage<VppFileArchiveResponseVO> page(String archiveName, Integer archiveType, Long siteId, String bizType, Long bizId, Integer pageNum, Integer pageSize) {
+    public CommonPage<VppFileArchiveResponseVO> page(Long id, String archiveName, Integer archiveType, Long siteId, String bizType, Long bizId, Integer pageNum, Integer pageSize) {
         IPage<VppFileArchive> page = new Page<>(pageNum, pageSize);
 
         LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(VppFileArchive::getDeleteFlag, 0)
                 .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId())
+                .eq(id != null, VppFileArchive::getId, id)
                 .eq(siteId != null, VppFileArchive::getSiteId, siteId)
                 .eq(archiveType != null, VppFileArchive::getArchiveType, archiveType)
                 .eq(StringUtils.isNotBlank(bizType), VppFileArchive::getBizType, bizType)
@@ -126,7 +168,7 @@ public class VppArchiveServiceImpl implements VppArchiveService {
     @Override
     public Boolean upload(VppFileArchiveRequestVO vo) {
         if (vo.getId() == null) {
-            throw new RuntimeException("主键ID不能为空!");
+            throw new BusinessException("主键ID不能为空!");
         }
 
         LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
@@ -136,7 +178,7 @@ public class VppArchiveServiceImpl implements VppArchiveService {
         VppFileArchive existingRecord = vppFileArchiveMapper.selectOne(queryWrapper);
 
         if (existingRecord == null) {
-            throw new RuntimeException("档案记录不存在或已被删除!");
+            throw new BusinessException("档案记录不存在或已被删除!");
         }
 
         // 根据 name + url 关联 FilesUpload 表,同步更新文件信息
@@ -153,7 +195,7 @@ public class VppArchiveServiceImpl implements VppArchiveService {
                 existingRecord.setFileType(filesUpload.getType());
                 existingRecord.setFileSize(filesUpload.getSize());
             } else {
-                throw new RuntimeException("未找到匹配的文件记录,请确认文件名与路径是否正确!");
+                throw new BusinessException("未找到匹配的文件记录,请确认文件名与路径是否正确!");
             }
         }
 
@@ -223,6 +265,25 @@ public class VppArchiveServiceImpl implements VppArchiveService {
             return;
         }
 
+        // ========== 预解析文件名(去重处理,必须在并行下载之前完成)==========
+        List<DownloadTask> tasks = new ArrayList<>();
+        Set<String> usedNames = new HashSet<>();
+        for (VppFileArchive archive : archives) {
+            String fileUrl = archive.getFileUrl();
+            String fileName = archive.getFileName();
+            if (StringUtils.isBlank(fileUrl) || StringUtils.isBlank(fileName)) {
+                continue;
+            }
+            String zipEntryName = resolveDuplicateName(fileName, usedNames);
+            usedNames.add(zipEntryName);
+            tasks.add(new DownloadTask(zipEntryName, fileUrl));
+        }
+
+        if (tasks.isEmpty()) {
+            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "没有可下载的文件!");
+            return;
+        }
+
         // ========== 构造 ZIP 文件名 ==========
         String zipFileName = "电子档案_"
                 + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
@@ -233,36 +294,43 @@ public class VppArchiveServiceImpl implements VppArchiveService {
         response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                 "attachment; filename=" + encodeFileName(zipFileName));
 
-        // ========== 生成 ZIP 并写入响应流 ==========
-        RestTemplate restTemplate = new RestTemplate();
-        Set<String> usedNames = new HashSet<>();
+        // ========== 并行下载所有文件 ==========
+        List<CompletableFuture<DownloadTask>> futures = tasks.stream()
+                .map(task -> CompletableFuture.supplyAsync(() -> {
+                    try {
+                        byte[] bytes = restTemplate.getForObject(task.fileUrl, byte[].class);
+                        task.fileBytes = bytes;
+                    } catch (Exception e) {
+                        task.error = e.getMessage();
+                    }
+                    return task;
+                }, DOWNLOAD_EXECUTOR))
+                .collect(Collectors.toList());
 
+        // ========== 生成 ZIP 并写入响应流 ==========
         try (ServletOutputStream sos = response.getOutputStream();
-             ZipOutputStream zos = new ZipOutputStream(sos)) {
-
-            for (VppFileArchive archive : archives) {
-                String fileUrl = archive.getFileUrl();
-                String fileName = archive.getFileName();
-
-                if (StringUtils.isBlank(fileUrl) || StringUtils.isBlank(fileName)) {
-                    continue;
-                }
-
-                String zipEntryName = resolveDuplicateName(fileName, usedNames);
-                usedNames.add(zipEntryName);
+             BufferedOutputStream bos = new BufferedOutputStream(sos, 8192);
+             ZipOutputStream zos = new ZipOutputStream(bos)) {
 
+            for (CompletableFuture<DownloadTask> future : futures) {
+                DownloadTask task = future.join(); // 阻塞等待当前文件下载完成
                 try {
-                    byte[] fileBytes = restTemplate.getForObject(fileUrl, byte[].class);
-                    if (fileBytes != null && fileBytes.length > 0) {
-                        ZipEntry zipEntry = new ZipEntry(zipEntryName);
+                    if (task.error != null) {
+                        // 下载失败,写入错误信息
+                        ZipEntry errorEntry = new ZipEntry(task.zipEntryName + ".error.txt");
+                        zos.putNextEntry(errorEntry);
+                        zos.write(("下载失败: " + task.error).getBytes(StandardCharsets.UTF_8));
+                        zos.closeEntry();
+                    } else if (task.fileBytes != null && task.fileBytes.length > 0) {
+                        ZipEntry zipEntry = new ZipEntry(task.zipEntryName);
                         zos.putNextEntry(zipEntry);
-                        zos.write(fileBytes);
+                        zos.write(task.fileBytes);
                         zos.closeEntry();
                     }
                 } catch (Exception e) {
-                    ZipEntry errorEntry = new ZipEntry(zipEntryName + ".error.txt");
+                    ZipEntry errorEntry = new ZipEntry(task.zipEntryName + ".error.txt");
                     zos.putNextEntry(errorEntry);
-                    zos.write(("下载失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
+                    zos.write(("写入ZIP失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
                     zos.closeEntry();
                 }
             }
@@ -277,6 +345,21 @@ public class VppArchiveServiceImpl implements VppArchiveService {
         }
     }
 
+    /**
+     * 下载任务内部类,承载文件名、URL、下载结果
+     */
+    private static class DownloadTask {
+        final String zipEntryName;
+        final String fileUrl;
+        volatile byte[] fileBytes;
+        volatile String error;
+
+        DownloadTask(String zipEntryName, String fileUrl) {
+            this.zipEntryName = zipEntryName;
+            this.fileUrl = fileUrl;
+        }
+    }
+
     /**
      * 处理 ZIP 内重名文件,自动添加编号后缀
      * @param fileName  原始文件名

+ 325 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppContractServiceImpl.java

@@ -1,11 +1,335 @@
 package com.usky.vpp.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.vpp.domain.VppContract;
+import com.usky.vpp.domain.VppContractTemplate;
+import com.usky.vpp.domain.VppCustomer;
+import com.usky.vpp.mapper.VppContractMapper;
+import com.usky.vpp.mapper.VppContractTemplateMapper;
+import com.usky.vpp.mapper.VppCustomerMapper;
 import com.usky.vpp.service.VppContractService;
+import com.usky.vpp.service.vo.VppContractRequestVO;
+import com.usky.vpp.service.vo.VppContractResponseVO;
+import com.usky.vpp.util.VppAuditHelper;
+import com.usky.vpp.util.VppFieldValidator;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
- * VppContractService 默认实现(待按业务完善)
+ * VppContractService 默认实现
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
  */
 @Service
 public class VppContractServiceImpl implements VppContractService {
+
+    @Autowired
+    private VppContractMapper vppContractMapper;
+    @Autowired
+    private VppContractTemplateMapper vppContractTemplateMapper;
+    @Autowired
+    private VppCustomerMapper vppCustomerMapper;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    @Override
+    public Boolean create(VppContractRequestVO vo) {
+        // ========== 根据 templateId 查询模板编码并生成合同编号 ==========
+        VppContractTemplate template = getTemplateById(vo.getTemplateId());
+        String contractNo = VppFieldValidator.generateContractNo(template.getTemplateCode());
+
+        // ========== 合同编号全租户唯一性校验(极小概率冲突) ==========
+        checkContractNoUnique(contractNo, null);
+
+        VppContract contract = new VppContract();
+        contract.setContractNo(contractNo);
+        contract.setCustomerId(vo.getCustomerId());
+        contract.setTemplateId(vo.getTemplateId());
+        contract.setContractType(vo.getContractType());
+        contract.setContractName(vo.getContractName());
+        contract.setContractStatus(vo.getContractStatus());
+        contract.setSignDate(vo.getSignDate());
+        contract.setEffectiveDate(vo.getEffectiveDate());
+        contract.setExpireDate(vo.getExpireDate());
+        contract.setFileUrl(vo.getFileUrl());
+        contract.setShareRatio(vo.getShareRatio());
+        contract.setPriceJson(VppFieldValidator.validateJson(vo.getPriceJson(), "价格定义"));
+        contract.setAccountInfoJson(VppFieldValidator.validateJson(vo.getAccountInfoJson(), "账户信息"));
+        contract.setRemark(vo.getRemark());
+
+        VppAuditHelper.fillCreate(contract);
+
+        int insert = vppContractMapper.insert(contract);
+        return insert > 0;
+    }
+
+    @Override
+    public Boolean update(VppContractRequestVO vo) {
+        if (vo.getId() == null) {
+            throw new BusinessException("主键ID不能为空!");
+        }
+
+        LambdaQueryWrapper<VppContract> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContract::getId, vo.getId())
+                .eq(VppContract::getDeleteFlag, 0);
+        VppContract existingRecord = vppContractMapper.selectOne(queryWrapper);
+
+        if (existingRecord == null) {
+            throw new BusinessException("合同不存在或已被删除!");
+        }
+
+        // 仅更新非空字段
+        if (vo.getCustomerId() != null) {
+            existingRecord.setCustomerId(vo.getCustomerId());
+        }
+        if (vo.getTemplateId() != null) {
+            existingRecord.setTemplateId(vo.getTemplateId());
+        }
+        if (vo.getContractType() != null) {
+            existingRecord.setContractType(vo.getContractType());
+        }
+        if (StringUtils.isNotBlank(vo.getContractName())) {
+            existingRecord.setContractName(vo.getContractName());
+        }
+        if (vo.getContractStatus() != null) {
+            existingRecord.setContractStatus(vo.getContractStatus());
+        }
+        if (vo.getSignDate() != null) {
+            existingRecord.setSignDate(vo.getSignDate());
+        }
+        if (vo.getEffectiveDate() != null) {
+            existingRecord.setEffectiveDate(vo.getEffectiveDate());
+        }
+        if (vo.getExpireDate() != null) {
+            existingRecord.setExpireDate(vo.getExpireDate());
+        }
+        if (StringUtils.isNotBlank(vo.getFileUrl())) {
+            existingRecord.setFileUrl(vo.getFileUrl());
+        }
+        if (vo.getShareRatio() != null) {
+            existingRecord.setShareRatio(vo.getShareRatio());
+        }
+        if (vo.getPriceJson() != null) {
+            existingRecord.setPriceJson(VppFieldValidator.validateJson(vo.getPriceJson(), "价格定义"));
+        }
+        if (vo.getAccountInfoJson() != null) {
+            existingRecord.setAccountInfoJson(VppFieldValidator.validateJson(vo.getAccountInfoJson(), "账户信息"));
+        }
+        if (vo.getRemark() != null) {
+            existingRecord.setRemark(vo.getRemark());
+        }
+
+        VppAuditHelper.fillUpdate(existingRecord);
+
+        int result = vppContractMapper.updateById(existingRecord);
+        return result > 0;
+    }
+
+    @Override
+    public CommonPage<VppContractResponseVO> page(Long id, LocalDate signDateStart, LocalDate signDateEnd,
+                                                   Long templateId, Integer contractType, Long customerId,
+                                                   Integer contractStatus, Integer pageNum, Integer pageSize) {
+        IPage<VppContract> page = new Page<>(pageNum, pageSize);
+
+        LambdaQueryWrapper<VppContract> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContract::getDeleteFlag, 0)
+                .eq(id != null, VppContract::getId, id)
+                .ge(signDateStart != null, VppContract::getSignDate, signDateStart)
+                .le(signDateEnd != null, VppContract::getSignDate, signDateEnd)
+                .eq(templateId != null, VppContract::getTemplateId, templateId)
+                .eq(contractType != null, VppContract::getContractType, contractType)
+                .eq(customerId != null, VppContract::getCustomerId, customerId)
+                .eq(contractStatus != null, VppContract::getContractStatus, contractStatus)
+                .orderByDesc(VppContract::getCreateTime);
+        page = vppContractMapper.selectPage(page, queryWrapper);
+
+        List<VppContract> records = page.getRecords();
+        if (records == null || records.isEmpty()) {
+            return new CommonPage<>(Collections.emptyList(), page.getTotal(), pageSize, pageNum);
+        }
+
+        // ========== 批量反查客户名称和模板名称 ==========
+        Set<Long> customerIds = records.stream().map(VppContract::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
+        Set<Long> templateIds = records.stream().map(VppContract::getTemplateId).filter(Objects::nonNull).collect(Collectors.toSet());
+
+        final Map<Long, String> customerNameMap = customerIds.isEmpty() ? Collections.emptyMap()
+                : vppCustomerMapper.selectBatchIds(customerIds).stream()
+                        .collect(Collectors.toMap(VppCustomer::getId, VppCustomer::getCustomerName, (a, b) -> a));
+
+        final Map<Long, String> templateNameMap = templateIds.isEmpty() ? Collections.emptyMap()
+                : vppContractTemplateMapper.selectBatchIds(templateIds).stream()
+                        .collect(Collectors.toMap(VppContractTemplate::getId, VppContractTemplate::getTemplateName, (a, b) -> a));
+
+        // ========== 组装响应VO ==========
+        List<VppContractResponseVO> list = records.stream().map(entity -> {
+            VppContractResponseVO vo = new VppContractResponseVO();
+            vo.setId(entity.getId());
+            vo.setContractNo(entity.getContractNo());
+            vo.setCustomerId(entity.getCustomerId());
+            vo.setCustomerName(customerNameMap.get(entity.getCustomerId()));
+            vo.setTemplateId(entity.getTemplateId());
+            vo.setTemplateName(templateNameMap.get(entity.getTemplateId()));
+            vo.setContractType(entity.getContractType());
+            vo.setContractName(entity.getContractName());
+            vo.setContractStatus(entity.getContractStatus());
+            vo.setSignDate(entity.getSignDate());
+            vo.setEffectiveDate(entity.getEffectiveDate());
+            vo.setExpireDate(entity.getExpireDate());
+            vo.setFileUrl(entity.getFileUrl());
+            vo.setShareRatio(entity.getShareRatio());
+            vo.setPriceJson(entity.getPriceJson());
+            vo.setAccountInfoJson(entity.getAccountInfoJson());
+            vo.setRemark(entity.getRemark());
+            vo.setTenantId(entity.getTenantId());
+            vo.setCreateTime(entity.getCreateTime());
+            vo.setUpdateTime(entity.getUpdateTime());
+            vo.setCreatedBy(entity.getCreatedBy());
+            vo.setUpdatedBy(entity.getUpdatedBy());
+            return vo;
+        }).collect(Collectors.toList());
+
+        return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
+    }
+
+    @Override
+    public Boolean delete(Long id) {
+        LambdaQueryWrapper<VppContract> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContract::getId, id)
+                .eq(VppContract::getDeleteFlag, 0);
+        VppContract contract = vppContractMapper.selectOne(queryWrapper);
+
+        if (contract == null) {
+            throw new BusinessException("合同不存在或已被删除!");
+        }
+
+        VppAuditHelper.fillSoftDelete(contract);
+
+        int i = vppContractMapper.updateById(contract);
+        return i > 0;
+    }
+
+    @Override
+    public void download(Long id, HttpServletResponse response) {
+        LambdaQueryWrapper<VppContract> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContract::getId, id)
+                .eq(VppContract::getDeleteFlag, 0);
+        VppContract contract = vppContractMapper.selectOne(queryWrapper);
+
+        if (contract == null) {
+            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "合同不存在或已被删除!");
+            return;
+        }
+
+        String fileUrl = contract.getFileUrl();
+        String fileName = contract.getContractName();
+        if (StringUtils.isBlank(fileUrl)) {
+            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "合同文件URL为空,无法下载!");
+            return;
+        }
+
+        // ========== 设置响应头 ==========
+        String downloadFileName = fileName;
+        int dotIndex = fileUrl.lastIndexOf('.');
+        if (dotIndex > 0 && !fileName.contains(".")) {
+            downloadFileName = fileName + fileUrl.substring(dotIndex);
+        }
+        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+        response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
+                "attachment; filename=" + encodeFileName(downloadFileName));
+
+        // ========== 下载文件并写入响应流 ==========
+        try {
+            byte[] fileBytes = restTemplate.getForObject(fileUrl, byte[].class);
+            if (fileBytes == null || fileBytes.length == 0) {
+                writeErrorResponse(response, HttpStatus.NOT_FOUND.value(), "合同文件内容为空!");
+                return;
+            }
+            ServletOutputStream sos = response.getOutputStream();
+            sos.write(fileBytes);
+            sos.flush();
+        } catch (Exception e) {
+            if (!response.isCommitted()) {
+                writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR.value(),
+                        "下载合同文件失败: " + e.getMessage());
+            }
+        }
+    }
+
+    // ========== 内部辅助方法 ==========
+
+    /**
+     * 根据模板ID查询模板信息
+     */
+    private VppContractTemplate getTemplateById(Long templateId) {
+        if (templateId == null) {
+            throw new BusinessException("模板ID不能为空!");
+        }
+        LambdaQueryWrapper<VppContractTemplate> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(VppContractTemplate::getId, templateId)
+                .eq(VppContractTemplate::getDeleteFlag, 0);
+        VppContractTemplate template = vppContractTemplateMapper.selectOne(wrapper);
+        if (template == null) {
+            throw new BusinessException("合同模板不存在或已被删除!");
+        }
+        return template;
+    }
+
+    /**
+     * 合同编号全租户唯一性校验
+     */
+    private void checkContractNoUnique(String contractNo, Long excludeId) {
+        LambdaQueryWrapper<VppContract> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(VppContract::getContractNo, contractNo)
+                .eq(VppContract::getDeleteFlag, 0)
+                .ne(excludeId != null, VppContract::getId, excludeId);
+        Integer count = vppContractMapper.selectCount(wrapper);
+        if (count != null && count > 0) {
+            throw new BusinessException("合同编号【" + contractNo + "】已存在,请重新提交!");
+        }
+    }
+
+    private void writeErrorResponse(HttpServletResponse response, int status, String message) {
+        response.setStatus(status);
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+        try {
+            response.getWriter().write("{\"code\":\"" + status + "\",\"msg\":\"" + message + "\"}");
+        } catch (IOException e) {
+            // 忽略写入异常
+        }
+    }
+
+    private String encodeFileName(String fileName) {
+        try {
+            return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
+        } catch (UnsupportedEncodingException e) {
+            return fileName;
+        }
+    }
 }

+ 296 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppContractTemplateServiceImpl.java

@@ -0,0 +1,296 @@
+package com.usky.vpp.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.vpp.domain.VppContractTemplate;
+import com.usky.vpp.mapper.VppContractTemplateMapper;
+import com.usky.vpp.service.VppContractTemplateService;
+import com.usky.vpp.service.vo.VppContractTemplateRequestVO;
+import com.usky.vpp.service.vo.VppContractTemplateResponseVO;
+import com.usky.vpp.util.VppFieldValidator;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * VppContractTemplateService 默认实现
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+@Service
+public class VppContractTemplateServiceImpl implements VppContractTemplateService {
+
+    @Autowired
+    private VppContractTemplateMapper vppContractTemplateMapper;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    @Override
+    public Boolean create(VppContractTemplateRequestVO vo) {
+        String username = SecurityUtils.getUsername();
+        Integer tenantId = SecurityUtils.getTenantId();
+
+        // ========== templateCode 格式校验 ==========
+        VppFieldValidator.validateTemplateCode(vo.getTemplateCode());
+
+        // ========== templateCode 全租户唯一性校验 ==========
+        checkTemplateCodeUnique(vo.getTemplateCode(), null, tenantId);
+
+        VppContractTemplate template = new VppContractTemplate();
+        template.setTemplateCode(vo.getTemplateCode());
+        template.setTemplateName(vo.getTemplateName());
+        template.setContractType(vo.getContractType());
+        template.setVersion("V1.0");
+        template.setFileUrl(vo.getFileUrl());
+        template.setVariablesJson(VppFieldValidator.validateJson(vo.getVariablesJson(), "占位符变量定义"));
+        template.setIsEnabled(vo.getIsEnabled() != null ? vo.getIsEnabled() : 1);
+        template.setCreateTime(LocalDateTime.now());
+        template.setCreatedBy(username);
+        template.setTenantId(tenantId);
+        template.setDeleteFlag(0);
+
+        int insert = vppContractTemplateMapper.insert(template);
+        return insert > 0;
+    }
+
+    @Override
+    public Boolean update(VppContractTemplateRequestVO vo) {
+        if (vo.getId() == null) {
+            throw new BusinessException("主键ID不能为空!");
+        }
+
+        LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContractTemplate::getId, vo.getId())
+                .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId())
+                .eq(VppContractTemplate::getDeleteFlag, 0);
+        VppContractTemplate existingRecord = vppContractTemplateMapper.selectOne(queryWrapper);
+
+        if (existingRecord == null) {
+            throw new BusinessException("合同模板不存在或已被删除!");
+        }
+
+        // 仅更新非空字段,追踪是否仅变更 isEnabled
+        boolean hasNonEnabledChange = false;
+
+        if (StringUtils.isNotBlank(vo.getTemplateCode())) {
+            // ========== templateCode 格式校验 ==========
+            VppFieldValidator.validateTemplateCode(vo.getTemplateCode());
+
+            // ========== 仅当与现有值不同时做唯一性校验 ==========
+            if (!vo.getTemplateCode().equals(existingRecord.getTemplateCode())) {
+                checkTemplateCodeUnique(vo.getTemplateCode(), vo.getId(), SecurityUtils.getTenantId());
+            }
+
+            existingRecord.setTemplateCode(vo.getTemplateCode());
+            hasNonEnabledChange = true;
+        }
+        if (StringUtils.isNotBlank(vo.getTemplateName())) {
+            existingRecord.setTemplateName(vo.getTemplateName());
+            hasNonEnabledChange = true;
+        }
+        if (vo.getContractType() != null) {
+            existingRecord.setContractType(vo.getContractType());
+            hasNonEnabledChange = true;
+        }
+        if (StringUtils.isNotBlank(vo.getFileUrl())) {
+            existingRecord.setFileUrl(vo.getFileUrl());
+            hasNonEnabledChange = true;
+        }
+        if (vo.getVariablesJson() != null) {
+            existingRecord.setVariablesJson(VppFieldValidator.validateJson(vo.getVariablesJson(), "占位符变量定义"));
+            hasNonEnabledChange = true;
+        }
+        // isEnabled 单独变更不触发版本号累加
+        if (vo.getIsEnabled() != null) {
+            existingRecord.setIsEnabled(vo.getIsEnabled());
+        }
+
+        // 版本号自动累加:仅当非 isEnabled 字段有变更时 +0.1
+        if (hasNonEnabledChange) {
+            String currentVersion = existingRecord.getVersion();
+            if (StringUtils.isNotBlank(currentVersion) && currentVersion.startsWith("V")) {
+                try {
+                    double versionNum = Double.parseDouble(currentVersion.substring(1));
+                    versionNum += 0.1;
+                    existingRecord.setVersion("V" + String.format("%.1f", versionNum));
+                } catch (NumberFormatException e) {
+                    existingRecord.setVersion(currentVersion + ".1");
+                }
+            }
+        }
+
+        existingRecord.setUpdateTime(LocalDateTime.now());
+        existingRecord.setUpdatedBy(SecurityUtils.getUsername());
+
+        int result = vppContractTemplateMapper.updateById(existingRecord);
+        return result > 0;
+    }
+
+    @Override
+    public CommonPage<VppContractTemplateResponseVO> page(Long id, String templateName, Integer contractType,
+                                                          Integer isEnabled, Integer pageNum, Integer pageSize) {
+        IPage<VppContractTemplate> page = new Page<>(pageNum, pageSize);
+
+        LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContractTemplate::getDeleteFlag, 0)
+                .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId())
+                .eq(id != null, VppContractTemplate::getId, id)
+                .eq(contractType != null, VppContractTemplate::getContractType, contractType)
+                .eq(isEnabled != null, VppContractTemplate::getIsEnabled, isEnabled)
+                .like(StringUtils.isNotBlank(templateName), VppContractTemplate::getTemplateName, templateName)
+                .orderByDesc(VppContractTemplate::getCreateTime);
+        page = vppContractTemplateMapper.selectPage(page, queryWrapper);
+
+        List<VppContractTemplateResponseVO> list = page.getRecords().stream().map(entity -> {
+            VppContractTemplateResponseVO responseVO = new VppContractTemplateResponseVO();
+            responseVO.setId(entity.getId());
+            responseVO.setTemplateCode(entity.getTemplateCode());
+            responseVO.setTemplateName(entity.getTemplateName());
+            responseVO.setContractType(entity.getContractType());
+            responseVO.setVersion(entity.getVersion());
+            responseVO.setFileUrl(entity.getFileUrl());
+            responseVO.setVariablesJson(entity.getVariablesJson());
+            responseVO.setIsEnabled(entity.getIsEnabled());
+            responseVO.setCreatedBy(entity.getCreatedBy());
+            responseVO.setCreateTime(entity.getCreateTime());
+            responseVO.setUpdatedBy(entity.getUpdatedBy());
+            responseVO.setUpdateTime(entity.getUpdateTime());
+            responseVO.setTenantId(entity.getTenantId());
+            return responseVO;
+        }).collect(Collectors.toList());
+
+        return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
+    }
+
+    @Override
+    public Boolean delete(Long id) {
+        LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContractTemplate::getId, id)
+                .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId());
+        VppContractTemplate template = vppContractTemplateMapper.selectOne(queryWrapper);
+
+        if (template == null) {
+            throw new BusinessException("合同模板不存在!");
+        }
+
+        template.setDeleteFlag(1);
+        template.setIsEnabled(0);
+        template.setDeletedAt(LocalDateTime.now());
+        template.setUpdatedBy(SecurityUtils.getUsername());
+        int i = vppContractTemplateMapper.updateById(template);
+        return i > 0;
+    }
+
+    @Override
+    public void download(Long id, HttpServletResponse response) {
+        // ========== 查询模板记录 ==========
+        LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContractTemplate::getId, id)
+                .eq(VppContractTemplate::getDeleteFlag, 0)
+                .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId());
+        VppContractTemplate template = vppContractTemplateMapper.selectOne(queryWrapper);
+
+        if (template == null) {
+            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "合同模板不存在或已被删除!");
+            return;
+        }
+
+        String fileUrl = template.getFileUrl();
+        String fileName = template.getTemplateName();
+        if (StringUtils.isBlank(fileUrl)) {
+            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "模板文件URL为空,无法下载!");
+            return;
+        }
+
+        // ========== 设置响应头 ==========
+        // 根据 fileUrl 推断原始文件名(优先使用模板名称 + 扩展名)
+        String downloadFileName = fileName;
+        int dotIndex = fileUrl.lastIndexOf('.');
+        if (dotIndex > 0 && !fileName.contains(".")) {
+            downloadFileName = fileName + fileUrl.substring(dotIndex);
+        }
+        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+        response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
+                "attachment; filename=" + encodeFileName(downloadFileName));
+
+        // ========== 下载文件并写入响应流 ==========
+        try {
+            byte[] fileBytes = restTemplate.getForObject(fileUrl, byte[].class);
+            if (fileBytes == null || fileBytes.length == 0) {
+                writeErrorResponse(response, HttpStatus.NOT_FOUND.value(), "模板文件内容为空!");
+                return;
+            }
+            ServletOutputStream sos = response.getOutputStream();
+            sos.write(fileBytes);
+            sos.flush();
+        } catch (Exception e) {
+            if (!response.isCommitted()) {
+                writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR.value(),
+                        "下载合同模板失败: " + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 向响应中写入 JSON 错误信息
+     */
+    private void writeErrorResponse(HttpServletResponse response, int status, String message) {
+        response.setStatus(status);
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+        try {
+            response.getWriter().write("{\"code\":\"" + status + "\",\"msg\":\"" + message + "\"}");
+        } catch (IOException e) {
+            // 忽略写入异常
+        }
+    }
+
+    /**
+     * 编码文件名,兼容各浏览器
+     */
+    private String encodeFileName(String fileName) {
+        try {
+            return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
+        } catch (UnsupportedEncodingException e) {
+            return fileName;
+        }
+    }
+
+    /**
+     * 全租户唯一性校验:检查 templateCode 是否已被占用
+     *
+     * @param templateCode 模板编码
+     * @param excludeId    排除的记录ID(更新时传入当前记录ID,创建时传 null)
+     * @throws BusinessException 编码已被占用时抛出
+     */
+    private void checkTemplateCodeUnique(String templateCode, Long excludeId, Integer tenantId) {
+        LambdaQueryWrapper<VppContractTemplate> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(VppContractTemplate::getTemplateCode, templateCode)
+                .eq(VppContractTemplate::getDeleteFlag, 0)
+                .eq(VppContractTemplate::getTenantId, tenantId)
+                .ne(excludeId != null, VppContractTemplate::getId, excludeId);
+        Integer count = vppContractTemplateMapper.selectCount(wrapper);
+        if (count != null && count > 0) {
+            throw new BusinessException("模板编码【" + templateCode + "】已存在,请使用其他编码!");
+        }
+    }
+}

+ 486 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrInvitationServiceImpl.java

@@ -0,0 +1,486 @@
+package com.usky.vpp.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.vpp.domain.VppCustomer;
+import com.usky.vpp.domain.VppCustomerContact;
+import com.usky.vpp.domain.VppDrEvent;
+import com.usky.vpp.domain.VppDrInvitation;
+import com.usky.vpp.domain.VppDrParticipation;
+import com.usky.vpp.mapper.VppCustomerContactMapper;
+import com.usky.vpp.mapper.VppCustomerMapper;
+import com.usky.vpp.mapper.VppDrEventMapper;
+import com.usky.vpp.mapper.VppDrInvitationMapper;
+import com.usky.vpp.mapper.VppDrParticipationMapper;
+import com.usky.vpp.service.VppDrInvitationService;
+import com.usky.vpp.service.VppUnIntegrationService;
+import com.usky.vpp.service.vo.DrInvitationNotifyRequest;
+import com.usky.vpp.service.vo.DrInvitationReplyRequest;
+import com.usky.vpp.service.vo.DrInvitationRequest;
+import com.usky.vpp.service.vo.DrInvitationVO;
+import com.usky.vpp.util.VppAuditHelper;
+import com.usky.vpp.util.VppPageHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Service
+public class VppDrInvitationServiceImpl implements VppDrInvitationService {
+
+    private static final Logger log = LoggerFactory.getLogger(VppDrInvitationServiceImpl.class);
+
+    private static final int REPLY_PENDING = 0;
+    private static final int REPLY_ACCEPT = 1;
+    private static final int REPLY_REJECT = 2;
+    private static final int REPLY_TIMEOUT = 3;
+
+    private static final int RESPONSE_PENDING = 0;
+    private static final int RESPONSE_DECLARED = 1;
+    private static final int RESPONSE_CANCELLED = 4;
+
+    private static final int SMS_NOT_SENT = 0;
+    private static final int SMS_SENT = 1;
+    private static final int SMS_FAILED = 2;
+
+    private static final int PARTICIPATE_ACCEPT = 1;
+
+    @Autowired
+    private VppDrInvitationMapper invitationMapper;
+    @Autowired
+    private VppDrEventMapper eventMapper;
+    @Autowired
+    private VppCustomerMapper customerMapper;
+    @Autowired
+    private VppCustomerContactMapper contactMapper;
+    @Autowired
+    private VppDrParticipationMapper participationMapper;
+    @Autowired
+    private VppUnIntegrationService unIntegrationService;
+
+    @Override
+    public CommonPage<DrInvitationVO> pageInvitation(Map<String, Object> params) {
+        Page<VppDrInvitation> page = VppPageHelper.of(params);
+        LambdaQueryWrapper<VppDrInvitation> wrapper = new LambdaQueryWrapper<VppDrInvitation>()
+                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .orderByDesc(VppDrInvitation::getIssueTime);
+
+        applyListFilters(wrapper, params);
+
+        Page<VppDrInvitation> result = invitationMapper.selectPage(page, wrapper);
+        List<DrInvitationVO> records = toVoList(result.getRecords());
+        return new CommonPage<>(records, result.getTotal(), result.getCurrent(), result.getSize());
+    }
+
+    @Override
+    public DrInvitationVO getInvitation(Long id) {
+        return toVo(requireInvitation(id));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public DrInvitationVO createInvitation(DrInvitationRequest request) {
+        validateCreateRequest(request);
+
+        VppDrEvent event = requireEvent(request.getDrEventId());
+        VppCustomer customer = requireCustomer(request.getCustomerId());
+        ensureInvitationUnique(request.getDrEventId(), request.getCustomerId(), null);
+
+        VppDrInvitation invitation = new VppDrInvitation();
+        invitation.setInvitationNo(VppAuditHelper.nextInvitationNo());
+        invitation.setDrEventId(request.getDrEventId());
+        invitation.setCustomerId(request.getCustomerId());
+        invitation.setExecuteStartDate(request.getExecuteStartDate());
+        invitation.setExecuteEndDate(request.getExecuteEndDate());
+        invitation.setReplyDeadline(request.getReplyDeadline());
+        invitation.setIssueTime(LocalDateTime.now());
+        invitation.setTransactionType(request.getTransactionType() != null ? request.getTransactionType() : event.getEventType());
+        invitation.setResponseType(request.getResponseType() != null ? request.getResponseType() : event.getResponseType());
+        invitation.setDemandCapacityKw(request.getDemandCapacityKw());
+        invitation.setDeclaredCapacityKw(request.getDeclaredCapacityKw() != null ? request.getDeclaredCapacityKw() : BigDecimal.ZERO);
+        invitation.setReplyStatus(REPLY_PENDING);
+        invitation.setResponseStatus(RESPONSE_PENDING);
+        invitation.setSmsNotifyStatus(SMS_NOT_SENT);
+        invitation.setSmsContactId(resolveSmsContactId(request.getSmsContactId(), customer.getId()));
+        invitation.setRemark(request.getRemark());
+        VppAuditHelper.fillCreate(invitation);
+        invitationMapper.insert(invitation);
+        return toVo(invitation);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateInvitation(Long id, DrInvitationRequest request) {
+        VppDrInvitation invitation = requireInvitation(id);
+        if (invitation.getReplyStatus() != REPLY_PENDING || invitation.getResponseStatus() != RESPONSE_PENDING) {
+            throw new BusinessException("仅待回复状态的邀约可修改");
+        }
+        if (request == null) {
+            throw new BusinessException("请求不能为空");
+        }
+        if (request.getExecuteStartDate() == null || request.getExecuteEndDate() == null) {
+            throw new BusinessException("执行日期不能为空");
+        }
+        if (request.getReplyDeadline() == null) {
+            throw new BusinessException("邀约回复截止日期不能为空");
+        }
+        if (request.getDemandCapacityKw() == null || request.getDemandCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BusinessException("需求容量必须大于0");
+        }
+        validateDateRange(request.getExecuteStartDate(), request.getExecuteEndDate());
+
+        invitation.setExecuteStartDate(request.getExecuteStartDate());
+        invitation.setExecuteEndDate(request.getExecuteEndDate());
+        invitation.setReplyDeadline(request.getReplyDeadline());
+        invitation.setDemandCapacityKw(request.getDemandCapacityKw());
+        if (request.getDeclaredCapacityKw() != null) {
+            invitation.setDeclaredCapacityKw(request.getDeclaredCapacityKw());
+        }
+        if (request.getTransactionType() != null) {
+            invitation.setTransactionType(request.getTransactionType());
+        }
+        if (request.getResponseType() != null) {
+            invitation.setResponseType(request.getResponseType());
+        }
+        if (request.getSmsContactId() != null) {
+            requireContact(request.getSmsContactId(), invitation.getCustomerId());
+            invitation.setSmsContactId(request.getSmsContactId());
+        }
+        if (request.getRemark() != null) {
+            invitation.setRemark(request.getRemark());
+        }
+        VppAuditHelper.fillUpdate(invitation);
+        invitationMapper.updateById(invitation);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteInvitation(Long id) {
+        VppDrInvitation invitation = requireInvitation(id);
+        if (invitation.getReplyStatus() == REPLY_ACCEPT) {
+            throw new BusinessException("已参与的邀约不可删除");
+        }
+        if (invitation.getResponseStatus() != RESPONSE_PENDING) {
+            throw new BusinessException("仅待响应状态的邀约可删除");
+        }
+        if (invitation.getParticipationId() != null) {
+            throw new BusinessException("邀约已关联参与记录,不可删除");
+        }
+
+        VppAuditHelper.fillSoftDelete(invitation);
+        invitationMapper.updateById(invitation);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public DrInvitationVO notifySms(Long id, DrInvitationNotifyRequest request) {
+        VppDrInvitation invitation = requireInvitation(id);
+        Long contactId = request != null && request.getContactId() != null
+                ? request.getContactId()
+                : invitation.getSmsContactId();
+        if (contactId == null) {
+            contactId = resolveSmsContactId(null, invitation.getCustomerId());
+            invitation.setSmsContactId(contactId);
+        }
+        VppCustomerContact contact = requireContact(contactId, invitation.getCustomerId());
+        String templateCode = request != null && StringUtils.hasText(request.getTemplateCode())
+                ? request.getTemplateCode()
+                : "vpp_dr_invitation";
+
+        try {
+            log.info("发送邀约短信 invitationNo={} contact={} phone={} template={}",
+                    invitation.getInvitationNo(), contact.getContactName(), contact.getContactPhone(), templateCode);
+            invitation.setSmsNotifyStatus(SMS_SENT);
+            invitation.setSmsNotifyAt(LocalDateTime.now());
+        } catch (Exception ex) {
+            log.warn("邀约短信发送失败 invitationId={}: {}", id, ex.getMessage());
+            invitation.setSmsNotifyStatus(SMS_FAILED);
+            throw new BusinessException("短信发送失败: " + ex.getMessage());
+        }
+
+        VppAuditHelper.fillUpdate(invitation);
+        invitationMapper.updateById(invitation);
+        return toVo(invitation);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public DrInvitationVO replyInvitation(Long id, DrInvitationReplyRequest request) {
+        VppDrInvitation invitation = requireInvitation(id);
+        if (invitation.getReplyStatus() != REPLY_PENDING) {
+            throw new BusinessException("当前邀约已回复");
+        }
+        if (invitation.getReplyDeadline() != null && LocalDateTime.now().isAfter(invitation.getReplyDeadline())) {
+            throw new BusinessException("已超过邀约回复截止时间");
+        }
+        if (request == null || request.getParticipate() == null) {
+            throw new BusinessException("回复结果不能为空");
+        }
+
+        if (Boolean.TRUE.equals(request.getParticipate())) {
+            BigDecimal declared = request.getDeclaredCapacityKw() != null
+                    ? request.getDeclaredCapacityKw()
+                    : invitation.getDeclaredCapacityKw();
+            if (declared == null || declared.compareTo(BigDecimal.ZERO) <= 0) {
+                throw new BusinessException("申报容量必须大于0");
+            }
+
+            VppDrParticipation participation = findOrCreateParticipation(invitation, declared);
+            invitation.setParticipationId(participation.getId());
+            invitation.setDeclaredCapacityKw(declared);
+            invitation.setReplyStatus(REPLY_ACCEPT);
+            invitation.setResponseStatus(RESPONSE_DECLARED);
+            syncParticipationToUn(invitation.getDrEventId(), true);
+        } else {
+            if (!StringUtils.hasText(request.getReason())) {
+                throw new BusinessException("拒绝原因不能为空");
+            }
+            invitation.setReplyStatus(REPLY_REJECT);
+            invitation.setResponseStatus(RESPONSE_CANCELLED);
+            invitation.setRemark(appendRemark(invitation.getRemark(), "拒绝原因: " + request.getReason()));
+            syncParticipationToUn(invitation.getDrEventId(), false);
+        }
+
+        VppAuditHelper.fillUpdate(invitation);
+        invitationMapper.updateById(invitation);
+        return toVo(invitation);
+    }
+
+    private void applyListFilters(LambdaQueryWrapper<VppDrInvitation> wrapper, Map<String, Object> params) {
+        if (params == null) {
+            return;
+        }
+        if (params.get("drEventId") != null) {
+            wrapper.eq(VppDrInvitation::getDrEventId, Long.parseLong(params.get("drEventId").toString()));
+        }
+        if (params.get("responseType") != null) {
+            wrapper.eq(VppDrInvitation::getResponseType, Integer.parseInt(params.get("responseType").toString()));
+        }
+        if (params.get("replyStatus") != null) {
+            wrapper.eq(VppDrInvitation::getReplyStatus, Integer.parseInt(params.get("replyStatus").toString()));
+        }
+        if (params.get("responseStatus") != null) {
+            wrapper.eq(VppDrInvitation::getResponseStatus, Integer.parseInt(params.get("responseStatus").toString()));
+        }
+        if (params.get("customerId") != null) {
+            wrapper.eq(VppDrInvitation::getCustomerId, Long.parseLong(params.get("customerId").toString()));
+        }
+        if (params.get("customerName") != null && StringUtils.hasText(params.get("customerName").toString())) {
+            List<Long> customerIds = customerMapper.selectList(new LambdaQueryWrapper<VppCustomer>()
+                            .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                            .like(VppCustomer::getCustomerName, params.get("customerName").toString()))
+                    .stream()
+                    .map(VppCustomer::getId)
+                    .collect(Collectors.toList());
+            if (customerIds.isEmpty()) {
+                wrapper.eq(VppDrInvitation::getCustomerId, -1L);
+            } else {
+                wrapper.in(VppDrInvitation::getCustomerId, customerIds);
+            }
+        }
+        if (params.get("issueDateStart") != null && StringUtils.hasText(params.get("issueDateStart").toString())) {
+            LocalDate start = LocalDate.parse(params.get("issueDateStart").toString());
+            wrapper.ge(VppDrInvitation::getIssueTime, start.atStartOfDay());
+        }
+        if (params.get("issueDateEnd") != null && StringUtils.hasText(params.get("issueDateEnd").toString())) {
+            LocalDate end = LocalDate.parse(params.get("issueDateEnd").toString());
+            wrapper.le(VppDrInvitation::getIssueTime, end.atTime(LocalTime.MAX));
+        }
+    }
+
+    private VppDrParticipation findOrCreateParticipation(VppDrInvitation invitation, BigDecimal declaredCapacityKw) {
+        VppDrParticipation existing = participationMapper.selectOne(new LambdaQueryWrapper<VppDrParticipation>()
+                .eq(VppDrParticipation::getEventId, invitation.getDrEventId())
+                .eq(VppDrParticipation::getCustomerId, invitation.getCustomerId())
+                .isNull(VppDrParticipation::getResourceId)
+                .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .last("LIMIT 1"));
+        if (existing != null) {
+            existing.setParticipateStatus(PARTICIPATE_ACCEPT);
+            existing.setDeclaredCapacityKw(declaredCapacityKw);
+            existing.setDeclaredAt(LocalDateTime.now());
+            VppAuditHelper.fillUpdate(existing);
+            participationMapper.updateById(existing);
+            return existing;
+        }
+
+        VppDrParticipation participation = new VppDrParticipation();
+        participation.setEventId(invitation.getDrEventId());
+        participation.setCustomerId(invitation.getCustomerId());
+        participation.setParticipateStatus(PARTICIPATE_ACCEPT);
+        participation.setDeclaredCapacityKw(declaredCapacityKw);
+        participation.setDeclaredAt(LocalDateTime.now());
+        VppAuditHelper.fillCreate(participation);
+        participationMapper.insert(participation);
+        return participation;
+    }
+
+    private void syncParticipationToUn(Long eventId, boolean participate) {
+        try {
+            unIntegrationService.submitParticipation(eventId, participate);
+        } catch (Exception ex) {
+            log.warn("同步 UN 邀约回复失败 eventId={}: {}", eventId, ex.getMessage());
+        }
+    }
+
+    private VppDrInvitation requireInvitation(Long id) {
+        VppDrInvitation invitation = invitationMapper.selectById(id);
+        if (invitation == null || VppAuditHelper.isDeleted(invitation.getDeleteFlag())) {
+            throw new BusinessException("邀约记录不存在");
+        }
+        return invitation;
+    }
+
+    private VppDrEvent requireEvent(Long id) {
+        VppDrEvent event = eventMapper.selectById(id);
+        if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
+            throw new BusinessException("需求响应事件不存在");
+        }
+        return event;
+    }
+
+    private VppCustomer requireCustomer(Long id) {
+        VppCustomer customer = customerMapper.selectById(id);
+        if (customer == null || VppAuditHelper.isDeleted(customer.getDeleteFlag())) {
+            throw new BusinessException("客户不存在");
+        }
+        return customer;
+    }
+
+    private VppCustomerContact requireContact(Long contactId, Long customerId) {
+        VppCustomerContact contact = contactMapper.selectById(contactId);
+        if (contact == null || VppAuditHelper.isDeleted(contact.getDeleteFlag())) {
+            throw new BusinessException("联系人不存在");
+        }
+        if (!Objects.equals(contact.getCustomerId(), customerId)) {
+            throw new BusinessException("联系人不属于该客户");
+        }
+        return contact;
+    }
+
+    private Long resolveSmsContactId(Long contactId, Long customerId) {
+        if (contactId != null) {
+            return requireContact(contactId, customerId).getId();
+        }
+        VppCustomerContact primary = contactMapper.selectOne(new LambdaQueryWrapper<VppCustomerContact>()
+                .eq(VppCustomerContact::getCustomerId, customerId)
+                .eq(VppCustomerContact::getIsPrimary, 1)
+                .eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .last("LIMIT 1"));
+        if (primary != null) {
+            return primary.getId();
+        }
+        VppCustomerContact any = contactMapper.selectOne(new LambdaQueryWrapper<VppCustomerContact>()
+                .eq(VppCustomerContact::getCustomerId, customerId)
+                .eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .last("LIMIT 1"));
+        return any != null ? any.getId() : null;
+    }
+
+    private void ensureInvitationUnique(Long drEventId, Long customerId, Long excludeId) {
+        LambdaQueryWrapper<VppDrInvitation> wrapper = new LambdaQueryWrapper<VppDrInvitation>()
+                .eq(VppDrInvitation::getDrEventId, drEventId)
+                .eq(VppDrInvitation::getCustomerId, customerId)
+                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED);
+        if (excludeId != null) {
+            wrapper.ne(VppDrInvitation::getId, excludeId);
+        }
+        if (invitationMapper.selectCount(wrapper) > 0) {
+            throw new BusinessException("该事件已存在该企业的邀约记录");
+        }
+    }
+
+    private void validateCreateRequest(DrInvitationRequest request) {
+        if (request == null) {
+            throw new BusinessException("请求不能为空");
+        }
+        if (request.getDrEventId() == null) {
+            throw new BusinessException("需求响应事件不能为空");
+        }
+        if (request.getCustomerId() == null) {
+            throw new BusinessException("企业不能为空");
+        }
+        if (request.getExecuteStartDate() == null || request.getExecuteEndDate() == null) {
+            throw new BusinessException("执行日期不能为空");
+        }
+        if (request.getReplyDeadline() == null) {
+            throw new BusinessException("邀约回复截止日期不能为空");
+        }
+        if (request.getDemandCapacityKw() == null || request.getDemandCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BusinessException("需求容量必须大于0");
+        }
+        validateDateRange(request.getExecuteStartDate(), request.getExecuteEndDate());
+    }
+
+    private void validateDateRange(LocalDate start, LocalDate end) {
+        if (start.isAfter(end)) {
+            throw new BusinessException("执行开始日期不能晚于结束日期");
+        }
+    }
+
+    private String appendRemark(String original, String addition) {
+        if (!StringUtils.hasText(original)) {
+            return addition;
+        }
+        return original + "; " + addition;
+    }
+
+    private List<DrInvitationVO> toVoList(List<VppDrInvitation> invitations) {
+        if (CollectionUtils.isEmpty(invitations)) {
+            return Collections.emptyList();
+        }
+        Set<Long> eventIds = invitations.stream().map(VppDrInvitation::getDrEventId).collect(Collectors.toSet());
+        Set<Long> customerIds = invitations.stream().map(VppDrInvitation::getCustomerId).collect(Collectors.toSet());
+
+        Map<Long, VppDrEvent> eventMap = eventMapper.selectBatchIds(eventIds).stream()
+                .collect(Collectors.toMap(VppDrEvent::getId, e -> e, (a, b) -> a));
+        Map<Long, VppCustomer> customerMap = customerMapper.selectBatchIds(customerIds).stream()
+                .collect(Collectors.toMap(VppCustomer::getId, c -> c, (a, b) -> a));
+
+        return invitations.stream()
+                .map(inv -> toVo(inv, eventMap.get(inv.getDrEventId()), customerMap.get(inv.getCustomerId()), null))
+                .collect(Collectors.toList());
+    }
+
+    private DrInvitationVO toVo(VppDrInvitation invitation) {
+        VppDrEvent event = eventMapper.selectById(invitation.getDrEventId());
+        VppCustomer customer = customerMapper.selectById(invitation.getCustomerId());
+        VppCustomerContact contact = invitation.getSmsContactId() != null
+                ? contactMapper.selectById(invitation.getSmsContactId())
+                : null;
+        return toVo(invitation, event, customer, contact);
+    }
+
+    private DrInvitationVO toVo(VppDrInvitation invitation, VppDrEvent event, VppCustomer customer, VppCustomerContact contact) {
+        DrInvitationVO vo = new DrInvitationVO();
+        BeanUtils.copyProperties(invitation, vo);
+        if (event != null) {
+            vo.setDrEventCode(event.getEventId());
+        }
+        if (customer != null) {
+            vo.setCustomerName(customer.getCustomerName());
+        }
+        if (contact != null) {
+            vo.setSmsContactName(contact.getContactName());
+            vo.setSmsContactPhone(contact.getContactPhone());
+        }
+        return vo;
+    }
+}

+ 133 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrServiceImpl.java

@@ -21,6 +21,7 @@ import com.usky.vpp.mapper.VppResourcePointMapper;
 import com.usky.vpp.service.VppDrService;
 import com.usky.vpp.service.VppUnIntegrationService;
 import com.usky.vpp.service.vo.DrClearingRequest;
+import com.usky.vpp.service.vo.DrEventRequest;
 import com.usky.vpp.service.vo.DrInterveneRequest;
 import com.usky.vpp.service.vo.DrParticipateRequest;
 import com.usky.vpp.service.vo.DrStrategyRequest;
@@ -114,6 +115,72 @@ public class VppDrServiceImpl implements VppDrService {
         return event;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public VppDrEvent createEvent(DrEventRequest request) {
+        validateEventRequest(request);
+
+        String eventId = StringUtils.hasText(request.getEventId())
+                ? request.getEventId().trim()
+                : VppAuditHelper.nextEventId();
+        ensureEventIdUnique(eventId, null);
+
+        VppDrEvent event = new VppDrEvent();
+        event.setEventId(eventId);
+        event.setEventName(StringUtils.hasText(request.getEventName()) ? request.getEventName() : eventId);
+        event.setResponseType(request.getResponseType());
+        event.setEventType(request.getEventType());
+        event.setStartTime(request.getStartTime());
+        event.setEndTime(request.getEndTime());
+        event.setTargetCapacityKw(request.getTargetCapacityKw());
+        event.setClearedCapacityKw(request.getClearedCapacityKw());
+        event.setSubsidyPrice(request.getSubsidyPrice());
+        event.setEventStatus(EVENT_STATUS_PENDING);
+        VppAuditHelper.fillCreate(event);
+        eventMapper.insert(event);
+        return event;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateEvent(Long id, DrEventRequest request) {
+        VppDrEvent event = getEvent(id);
+        if (event.getEventStatus() != EVENT_STATUS_PENDING) {
+            throw new BusinessException("仅待参与状态的事件可修改");
+        }
+        if (hasEventRelatedData(id)) {
+            throw new BusinessException("事件已有关联参与记录,不可修改");
+        }
+
+        validateEventRequest(request);
+
+        event.setEventName(StringUtils.hasText(request.getEventName()) ? request.getEventName() : event.getEventId());
+        event.setResponseType(request.getResponseType());
+        event.setEventType(request.getEventType());
+        event.setStartTime(request.getStartTime());
+        event.setEndTime(request.getEndTime());
+        event.setTargetCapacityKw(request.getTargetCapacityKw());
+        event.setClearedCapacityKw(request.getClearedCapacityKw());
+        event.setSubsidyPrice(request.getSubsidyPrice());
+        VppAuditHelper.fillUpdate(event);
+        eventMapper.updateById(event);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteEvent(Long id) {
+        VppDrEvent event = getEvent(id);
+        if (event.getEventStatus() != EVENT_STATUS_PENDING && event.getEventStatus() != EVENT_STATUS_CANCELLED) {
+            throw new BusinessException("仅待参与或已取消状态的事件可删除");
+        }
+        if (hasEventRelatedData(id)) {
+            throw new BusinessException("事件已有关联业务数据,不可删除");
+        }
+
+        VppAuditHelper.fillSoftDelete(event);
+        eventMapper.updateById(event);
+    }
+
     @Override
     public Object assessCapability(Long eventId) {
         VppDrEvent event = getEvent(eventId);
@@ -684,6 +751,72 @@ public class VppDrServiceImpl implements VppDrService {
                 .eq(VppDrStrategyResource::getStrategyId, id));
     }
 
+    private void validateEventRequest(DrEventRequest request) {
+        if (request == null) {
+            throw new BusinessException("请求不能为空");
+        }
+        if (request.getResponseType() == null) {
+            throw new BusinessException("响应类型不能为空");
+        }
+        if (request.getResponseType() < 1 || request.getResponseType() > 3) {
+            throw new BusinessException("响应类型无效,取值 1日前 2日内 3秒级");
+        }
+        if (request.getEventType() != null && (request.getEventType() < 1 || request.getEventType() > 2)) {
+            throw new BusinessException("事件类型无效,取值 1削峰 2填谷");
+        }
+        if (request.getStartTime() == null) {
+            throw new BusinessException("响应开始时间不能为空");
+        }
+        if (request.getEndTime() == null) {
+            throw new BusinessException("响应结束时间不能为空");
+        }
+        if (request.getEndTime().isBefore(request.getStartTime())) {
+            throw new BusinessException("响应结束时间不能早于开始时间");
+        }
+        if (request.getTargetCapacityKw() == null || request.getTargetCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BusinessException("目标容量必须大于0");
+        }
+        if (request.getClearedCapacityKw() != null && request.getClearedCapacityKw().compareTo(BigDecimal.ZERO) < 0) {
+            throw new BusinessException("出清容量不能为负数");
+        }
+        if (request.getSubsidyPrice() != null && request.getSubsidyPrice().compareTo(BigDecimal.ZERO) < 0) {
+            throw new BusinessException("补贴标准不能为负数");
+        }
+    }
+
+    private void ensureEventIdUnique(String eventId, Long excludeId) {
+        LambdaQueryWrapper<VppDrEvent> wrapper = new LambdaQueryWrapper<VppDrEvent>()
+                .eq(VppDrEvent::getEventId, eventId)
+                .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED);
+        if (excludeId != null) {
+            wrapper.ne(VppDrEvent::getId, excludeId);
+        }
+        if (eventMapper.selectCount(wrapper) > 0) {
+            throw new BusinessException("事件ID已存在");
+        }
+    }
+
+    private boolean hasEventRelatedData(Long eventId) {
+        long participationCount = participationMapper.selectCount(
+                new LambdaQueryWrapper<VppDrParticipation>()
+                        .eq(VppDrParticipation::getEventId, eventId)
+                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+        if (participationCount > 0) {
+            return true;
+        }
+        long executionCount = executionMapper.selectCount(
+                new LambdaQueryWrapper<VppDrExecution>()
+                        .eq(VppDrExecution::getEventId, eventId)
+                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+        if (executionCount > 0) {
+            return true;
+        }
+        return evaluationMapper.selectCount(
+                new LambdaQueryWrapper<VppDrEvaluation>()
+                        .eq(VppDrEvaluation::getEventId, eventId)
+                        .eq(VppDrEvaluation::getDeleteFlag, VppAuditHelper.NOT_DELETED)) > 0;
+    }
+
     private void validateStrategyRequest(DrStrategyRequest request) {
         if (request == null) {
             throw new BusinessException("请求不能为空");

+ 26 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrEventRequest.java

@@ -0,0 +1,26 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 需求响应事件创建/更新请求(手工录入,非 UN 同步)
+ */
+@Data
+public class DrEventRequest {
+
+    /** 运管平台事件 ID,创建时可选,不传则自动生成 */
+    private String eventId;
+    private String eventName;
+    /** 1日前 2日内 3秒级 */
+    private Integer responseType;
+    /** 1削峰 2填谷 */
+    private Integer eventType;
+    private LocalDateTime startTime;
+    private LocalDateTime endTime;
+    private BigDecimal targetCapacityKw;
+    private BigDecimal clearedCapacityKw;
+    private BigDecimal subsidyPrice;
+}

+ 13 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationNotifyRequest.java

@@ -0,0 +1,13 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+/**
+ * 邀约短信通知请求
+ */
+@Data
+public class DrInvitationNotifyRequest {
+
+    private Long contactId;
+    private String templateCode;
+}

+ 16 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationReplyRequest.java

@@ -0,0 +1,16 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 企业回复邀约请求
+ */
+@Data
+public class DrInvitationReplyRequest {
+
+    private Boolean participate;
+    private BigDecimal declaredCapacityKw;
+    private String reason;
+}

+ 28 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationRequest.java

@@ -0,0 +1,28 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 需求响应邀约创建/更新请求
+ */
+@Data
+public class DrInvitationRequest {
+
+    private Long drEventId;
+    private Long customerId;
+    private LocalDate executeStartDate;
+    private LocalDate executeEndDate;
+    private LocalDateTime replyDeadline;
+    private BigDecimal demandCapacityKw;
+    private BigDecimal declaredCapacityKw;
+    /** 1削峰 2填谷 */
+    private Integer transactionType;
+    /** 1日前 2日内 3秒级 */
+    private Integer responseType;
+    private Long smsContactId;
+    private String remark;
+}

+ 43 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationVO.java

@@ -0,0 +1,43 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 需求响应邀约展示对象
+ */
+@Data
+public class DrInvitationVO {
+
+    private Long id;
+    private String invitationNo;
+    private Long drEventId;
+    private String drEventCode;
+    private Long customerId;
+    private String customerName;
+    private LocalDate executeStartDate;
+    private LocalDate executeEndDate;
+    private LocalDateTime replyDeadline;
+    private LocalDateTime issueTime;
+    private Integer transactionType;
+    private Integer responseType;
+    private BigDecimal demandCapacityKw;
+    private BigDecimal declaredCapacityKw;
+    private Integer replyStatus;
+    private Integer responseStatus;
+    private Integer smsNotifyStatus;
+    private LocalDateTime smsNotifyAt;
+    private Long smsContactId;
+    private String smsContactName;
+    private String smsContactPhone;
+    private Long participationId;
+    private String remark;
+    private Integer tenantId;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+    private String createdBy;
+    private String updatedBy;
+}

+ 17 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrStrategyRequest.java

@@ -1,5 +1,7 @@
 package com.usky.vpp.service.vo;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -16,11 +18,25 @@ public class DrStrategyRequest {
     private Integer responseType;
     private BigDecimal adjustStepKw;
     private Integer executeMode;
-    private String strategyConfig;
+    @JsonProperty("strategyConfig")
+    private JsonNode strategyConfigJson;
     private Integer isDefault;
     private Integer isEnabled;
     private List<DrStrategyResource> resources;
 
+    /**
+     * 策略扩展配置 JSON 字符串(兼容对象或字符串入参)
+     */
+    public String getStrategyConfig() {
+        if (strategyConfigJson == null || strategyConfigJson.isNull()) {
+            return null;
+        }
+        if (strategyConfigJson.isTextual()) {
+            return strategyConfigJson.asText();
+        }
+        return strategyConfigJson.toString();
+    }
+
     @Data
     public static class DrStrategyResource {
         private Long resourceId;

+ 86 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractRequestVO.java

@@ -0,0 +1,86 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 合同 请求VO
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+@Data
+public class VppContractRequestVO {
+
+    /**
+     * 主键(更新时必传)
+     */
+    private Long id;
+
+    /**
+     * 客户ID
+     */
+    private Long customerId;
+
+    /**
+     * 模板ID
+     */
+    private Long templateId;
+
+    /**
+     * 合同类型
+     */
+    private Integer contractType;
+
+    /**
+     * 合同名称
+     */
+    private String contractName;
+
+    /**
+     * 合同状态
+     */
+    private Integer contractStatus;
+
+    /**
+     * 签约日期
+     */
+    private LocalDate signDate;
+
+    /**
+     * 生效日期
+     */
+    private LocalDate effectiveDate;
+
+    /**
+     * 到期日期
+     */
+    private LocalDate expireDate;
+
+    /**
+     * 合同文件URL
+     */
+    private String fileUrl;
+
+    /**
+     * 分成比例
+     */
+    private BigDecimal shareRatio;
+
+    /**
+     * 价格定义(JSON)
+     */
+    private String priceJson;
+
+    /**
+     * 账户信息(JSON)
+     */
+    private String accountInfoJson;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 124 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractResponseVO.java

@@ -0,0 +1,124 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 合同 响应VO
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+@Data
+public class VppContractResponseVO {
+
+    private Long id;
+
+    /**
+     * 合同编号(自动生成)
+     */
+    private String contractNo;
+
+    /**
+     * 客户ID
+     */
+    private Long customerId;
+
+    /**
+     * 客户名称(关联查询)
+     */
+    private String customerName;
+
+    /**
+     * 模板ID
+     */
+    private Long templateId;
+
+    /**
+     * 模板名称(关联查询)
+     */
+    private String templateName;
+
+    /**
+     * 合同类型
+     */
+    private Integer contractType;
+
+    /**
+     * 合同名称
+     */
+    private String contractName;
+
+    /**
+     * 合同状态
+     */
+    private Integer contractStatus;
+
+    /**
+     * 签约日期
+     */
+    private LocalDate signDate;
+
+    /**
+     * 生效日期
+     */
+    private LocalDate effectiveDate;
+
+    /**
+     * 到期日期
+     */
+    private LocalDate expireDate;
+
+    /**
+     * 合同文件URL
+     */
+    private String fileUrl;
+
+    /**
+     * 分成比例
+     */
+    private BigDecimal shareRatio;
+
+    /**
+     * 价格定义(JSON)
+     */
+    private String priceJson;
+
+    /**
+     * 账户信息(JSON)
+     */
+    private String accountInfoJson;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private String createdBy;
+
+    /**
+     * 更新人
+     */
+    private String updatedBy;
+}

+ 53 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractTemplateRequestVO.java

@@ -0,0 +1,53 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+/**
+ * 合同模板 请求VO
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+@Data
+public class VppContractTemplateRequestVO {
+
+    /**
+     * 主键(更新时必传)
+     */
+    private Long id;
+
+    /**
+     * 模板编码
+     */
+    private String templateCode;
+
+    /**
+     * 模板名称
+     */
+    private String templateName;
+
+    /**
+     * 合同类型
+     */
+    private Integer contractType;
+
+    /**
+     * 版本号
+     */
+    private String version;
+
+    /**
+     * 模板文件URL
+     */
+    private String fileUrl;
+
+    /**
+     * 占位符变量定义(JSON)
+     */
+    private String variablesJson;
+
+    /**
+     * 是否启用 0-禁用 1-启用
+     */
+    private Integer isEnabled;
+}

+ 83 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractTemplateResponseVO.java

@@ -0,0 +1,83 @@
+package com.usky.vpp.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 合同模板 响应VO
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+@Data
+public class VppContractTemplateResponseVO {
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 模板编码
+     */
+    private String templateCode;
+
+    /**
+     * 模板名称
+     */
+    private String templateName;
+
+    /**
+     * 合同类型
+     */
+    private Integer contractType;
+
+    /**
+     * 版本号
+     */
+    private String version;
+
+    /**
+     * 模板文件URL
+     */
+    private String fileUrl;
+
+    /**
+     * 占位符变量定义(JSON)
+     */
+    private String variablesJson;
+
+    /**
+     * 是否启用 0-禁用 1-启用
+     */
+    private Integer isEnabled;
+
+    /**
+     * 创建人
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    private String updatedBy;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+}

+ 11 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppAuditHelper.java

@@ -5,6 +5,7 @@ import org.springframework.util.StringUtils;
 
 import java.lang.reflect.Method;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.UUID;
 
 /**
@@ -68,6 +69,16 @@ public final class VppAuditHelper {
         return "ACC" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 4).toUpperCase();
     }
 
+    public static String nextEventId() {
+        return "EVE_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now())
+                + UUID.randomUUID().toString().substring(0, 4).toUpperCase();
+    }
+
+    public static String nextInvitationNo() {
+        return "INV_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now())
+                + UUID.randomUUID().toString().substring(0, 4).toUpperCase();
+    }
+
     private static String currentUsername() {
         try {
             return SecurityUtils.getUsername();

+ 93 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppFieldValidator.java

@@ -0,0 +1,93 @@
+package com.usky.vpp.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.usky.common.core.exception.BusinessException;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.regex.Pattern;
+
+/**
+ * VPP 字段校验工具类
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/7/4
+ */
+public final class VppFieldValidator {
+
+    /**
+     * templateCode 校验正则:仅允许大写英文字母,长度 1~5
+     */
+    private static final Pattern TEMPLATE_CODE_PATTERN = Pattern.compile("^[A-Z]{1,5}$");
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private VppFieldValidator() {
+        // 工具类禁止实例化
+    }
+
+    /**
+     * 校验并规范化模板编码 templateCode:
+     * <ul>
+     *   <li>不允许为空白</li>
+     *   <li>最大长度不超过 5 个字符</li>
+     *   <li>仅允许大写英文字母(A-Z)</li>
+     * </ul>
+     *
+     * @param templateCode 原始模板编码
+     * @throws BusinessException 校验不通过时抛出
+     */
+    public static void validateTemplateCode(String templateCode) {
+        if (StringUtils.isBlank(templateCode)) {
+            throw new BusinessException("模板编码不能为空!");
+        }
+        if (!TEMPLATE_CODE_PATTERN.matcher(templateCode).matches()) {
+            throw new BusinessException("模板编码格式错误:仅允许大写英文字母(A-Z),且长度不超过5个字符!");
+        }
+    }
+
+    /**
+     * 校验 JSON 格式字符串的合法性:
+     * <ul>
+     *   <li>null:视为合法,字段允许为空</li>
+     *   <li>空字符串:不允许,抛出异常</li>
+     *   <li>非空时:必须能解析为合法的 JSON 对象或数组</li>
+     * </ul>
+     *
+     * @param json      待校验的 JSON 字符串
+     * @param fieldName 字段名称(用于错误提示)
+     * @return 规范化后的值:null → null;合法 JSON → 原值
+     * @throws BusinessException 空字符串或 JSON 格式不合法时抛出
+     */
+    public static String validateJson(String json, String fieldName) {
+        if (json == null) {
+            return null;
+        }
+        if (json.trim().isEmpty()) {
+            throw new BusinessException(fieldName + "不能为空字符串!");
+        }
+        try {
+            JsonNode node = OBJECT_MAPPER.readTree(json);
+            if (!node.isObject() && !node.isArray()) {
+                throw new BusinessException(fieldName + "格式错误:仅支持JSON对象({})或数组([])格式!");
+            }
+        } catch (BusinessException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new BusinessException(fieldName + "格式错误:不是合法的JSON格式,请检查!");
+        }
+        return json;
+    }
+
+    /**
+     * 根据模板编码生成合同编号,格式:{templateCode}-{13位毫秒时间戳}
+     * <p>示例:templateCode=CG → CG-1783154814855</p>
+     *
+     * @param templateCode 模板编码
+     * @return 合同编号
+     */
+    public static String generateContractNo(String templateCode) {
+        return templateCode + "-" + System.currentTimeMillis();
+    }
+}

+ 41 - 0
service-vpp/service-vpp-biz/src/main/resources/sql/vpp_dr_invitation_migration.sql

@@ -0,0 +1,41 @@
+-- VPP 需求响应企业邀约表(已有库执行,可重复执行)
+-- 对应 Web「邀约管理」界面,详见 虚拟电厂平台数据库设计文档 V1.1 §5.23
+
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_invitation') = 0,
+    'CREATE TABLE `vpp_dr_invitation` (
+        `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
+        `invitation_no` VARCHAR(32) NOT NULL COMMENT ''邀约编号'',
+        `dr_event_id` BIGINT NOT NULL COMMENT ''需求响应事件ID'',
+        `customer_id` BIGINT NOT NULL COMMENT ''客户ID'',
+        `execute_start_date` DATE NOT NULL COMMENT ''执行开始日期'',
+        `execute_end_date` DATE NOT NULL COMMENT ''执行结束日期'',
+        `reply_deadline` DATETIME(3) NOT NULL COMMENT ''邀约回复截止日期'',
+        `issue_time` DATETIME(3) NOT NULL COMMENT ''下发时间'',
+        `transaction_type` TINYINT NULL COMMENT ''交易类型 1削峰 2填谷'',
+        `response_type` TINYINT NOT NULL COMMENT ''1日前 2日内 3秒级'',
+        `demand_capacity_kw` DECIMAL(12,4) NOT NULL COMMENT ''需求容量 kW'',
+        `declared_capacity_kw` DECIMAL(12,4) NULL COMMENT ''已申报容量 kW'',
+        `reply_status` TINYINT NOT NULL COMMENT ''0待回复 1参与 2拒绝 3超时'',
+        `response_status` TINYINT NOT NULL COMMENT ''0待响应 1已申报 2执行中 3已结束 4已取消'',
+        `sms_notify_status` TINYINT NOT NULL DEFAULT 0 COMMENT ''0未发送 1已发送 2发送失败'',
+        `sms_notify_at` DATETIME(3) NULL COMMENT ''短信通知时间'',
+        `sms_contact_id` BIGINT NULL COMMENT ''通知联系人ID'',
+        `participation_id` BIGINT NULL COMMENT ''关联参与记录ID'',
+        `remark` VARCHAR(500) NULL COMMENT ''备注'',
+        `tenant_id` INT NULL COMMENT ''租户ID'',
+        `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT ''创建时间'',
+        `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT ''更新时间'',
+        `created_by` VARCHAR(30) NULL COMMENT ''创建人'',
+        `updated_by` VARCHAR(30) NULL COMMENT ''更新人'',
+        `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT ''删除标识 0未删除 1已删除'',
+        `deleted_at` DATETIME(3) NULL COMMENT ''软删除时间'',
+        PRIMARY KEY (`id`),
+        UNIQUE KEY `uk_dr_invitation_no` (`invitation_no`),
+        UNIQUE KEY `uk_dr_invitation_event_customer` (`dr_event_id`, `customer_id`),
+        KEY `idx_dr_invitation_customer` (`customer_id`, `reply_status`),
+        KEY `idx_dr_invitation_issue` (`issue_time`, `response_type`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT=''需求响应企业邀约''',
+    'SELECT ''skip: vpp_dr_invitation already exists'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

+ 246 - 0
service-vpp/service-vpp-biz/src/main/resources/sql/vpp_id_auto_increment_migration.sql

@@ -0,0 +1,246 @@
+-- VPP 主键自增迁移(已有库执行,可重复执行)
+-- 将各表 id 列改为 BIGINT AUTO_INCREMENT,与 MyBatis-Plus IdType.AUTO 对齐
+
+-- vpp_customer
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_customer' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_customer` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_customer.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_customer_access
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_customer_access' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_customer_access` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_customer_access.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_customer_contact
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_customer_contact' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_customer_contact` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_customer_contact.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_contract
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_contract' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_contract` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_contract.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_contract_template
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_contract_template' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_contract_template` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_contract_template.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_contract_audit_log
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_contract_audit_log' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_contract_audit_log` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_contract_audit_log.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_site
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_site') > 0
+    AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_site' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_site` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_site.id already auto_increment or table missing'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_site_config
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_site_config') > 0
+    AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_site_config' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_site_config` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_site_config.id already auto_increment or table missing'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_resource_point
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_resource_point' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_resource_point` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_resource_point.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_resource_point_config
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_resource_point_config' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_resource_point_config` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_resource_point_config.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_device
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_device' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_device` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_device.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_device_control_log
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_device_control_log' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_device_control_log` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_device_control_log.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_energy_reading_monthly
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_energy_reading_monthly' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_energy_reading_monthly` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_energy_reading_monthly.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_energy_summary_daily
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_energy_summary_daily' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_energy_summary_daily` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_energy_summary_daily.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_settlement_bill
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_settlement_bill' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_settlement_bill` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_settlement_bill.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_settlement_bill_detail
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_settlement_bill_detail' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_settlement_bill_detail` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_settlement_bill_detail.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_payment_record
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_payment_record' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_payment_record` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_payment_record.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_report_task
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_report_task' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_report_task` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_report_task.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_report_record
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_report_record' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_report_record` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_report_record.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_report_data
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_report_data' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_report_data` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_report_data.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_report_audit_log
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_report_audit_log' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_report_audit_log` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_report_audit_log.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_dr_event
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_event' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_dr_event` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_dr_event.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_dr_participation
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_participation' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_dr_participation` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_dr_participation.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_dr_strategy
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_strategy' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_dr_strategy` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_dr_strategy.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_dr_strategy_resource
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_strategy_resource' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_dr_strategy_resource` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_dr_strategy_resource.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_dr_execution
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_execution' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_dr_execution` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_dr_execution.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_dr_evaluation
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_evaluation' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_dr_evaluation` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_dr_evaluation.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_registration
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_registration' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_registration` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_registration.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_report_log
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_report_log' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_report_log` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_report_log.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- vpp_file_archive
+SET @sql = IF(
+    (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'vpp_file_archive' AND column_name = 'id' AND EXTRA LIKE '%auto_increment%') = 0,
+    'ALTER TABLE `vpp_file_archive` MODIFY COLUMN `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT ''主键''',
+    'SELECT ''skip: vpp_file_archive.id already auto_increment'''
+);
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 若需将自增计数与数据 ID 均重置为从 1 开始,请执行 vpp_id_reset_from_one_migration.sql

+ 362 - 0
service-vpp/service-vpp-biz/src/main/resources/sql/vpp_id_reset_from_one_migration.sql

@@ -0,0 +1,362 @@
+-- VPP 主键 ID 重置为从 1 开始
+-- 执行前请备份数据库!
+--
+-- 提供两种方案(二选一执行,不要同时执行):
+--   方案一:清空全部 vpp_* 数据,TRUNCATE 后自增从 1 开始(开发/联调推荐)
+--   方案二:保留现有数据,按依赖关系重编号为 1..n(需 MySQL 8.0+,支持 ROW_NUMBER)
+
+-- =============================================================================
+-- 方案一:清空数据并重置自增(会删除所有 vpp_* 业务数据)
+-- 仅 TRUNCATE 当前库中已存在的表,可重复执行
+-- =============================================================================
+
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- vpp_payment_record
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_payment_record') > 0, 'TRUNCATE TABLE `vpp_payment_record`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_settlement_bill_detail
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_settlement_bill_detail') > 0, 'TRUNCATE TABLE `vpp_settlement_bill_detail`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_device_control_log
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_device_control_log') > 0, 'TRUNCATE TABLE `vpp_device_control_log`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_contract_audit_log
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_contract_audit_log') > 0, 'TRUNCATE TABLE `vpp_contract_audit_log`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_customer_contact
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_customer_contact') > 0, 'TRUNCATE TABLE `vpp_customer_contact`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_resource_point_config
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_resource_point_config') > 0, 'TRUNCATE TABLE `vpp_resource_point_config`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_site_config
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_site_config') > 0, 'TRUNCATE TABLE `vpp_site_config`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_participation
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_participation') > 0, 'TRUNCATE TABLE `vpp_dr_participation`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_strategy_resource
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_strategy_resource') > 0, 'TRUNCATE TABLE `vpp_dr_strategy_resource`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_execution
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_execution') > 0, 'TRUNCATE TABLE `vpp_dr_execution`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_evaluation
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_evaluation') > 0, 'TRUNCATE TABLE `vpp_dr_evaluation`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_report_audit_log
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_report_audit_log') > 0, 'TRUNCATE TABLE `vpp_report_audit_log`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_report_data
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_report_data') > 0, 'TRUNCATE TABLE `vpp_report_data`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_energy_reading_monthly
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_energy_reading_monthly') > 0, 'TRUNCATE TABLE `vpp_energy_reading_monthly`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_energy_summary_daily
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_energy_summary_daily') > 0, 'TRUNCATE TABLE `vpp_energy_summary_daily`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_file_archive
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_file_archive') > 0, 'TRUNCATE TABLE `vpp_file_archive`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_device
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_device') > 0, 'TRUNCATE TABLE `vpp_device`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_resource_point
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_resource_point') > 0, 'TRUNCATE TABLE `vpp_resource_point`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_site
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_site') > 0, 'TRUNCATE TABLE `vpp_site`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_contract
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_contract') > 0, 'TRUNCATE TABLE `vpp_contract`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_settlement_bill
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_settlement_bill') > 0, 'TRUNCATE TABLE `vpp_settlement_bill`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_report_record
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_report_record') > 0, 'TRUNCATE TABLE `vpp_report_record`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_customer_access
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_customer_access') > 0, 'TRUNCATE TABLE `vpp_customer_access`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_invitation
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_invitation') > 0, 'TRUNCATE TABLE `vpp_dr_invitation`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_invitation
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_invitation') > 0, 'TRUNCATE TABLE `vpp_dr_invitation`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_event
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_event') > 0, 'TRUNCATE TABLE `vpp_dr_event`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_dr_strategy
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_dr_strategy') > 0, 'TRUNCATE TABLE `vpp_dr_strategy`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_contract_template
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_contract_template') > 0, 'TRUNCATE TABLE `vpp_contract_template`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_report_task
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_report_task') > 0, 'TRUNCATE TABLE `vpp_report_task`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_registration
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_registration') > 0, 'TRUNCATE TABLE `vpp_registration`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_report_log
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_report_log') > 0, 'TRUNCATE TABLE `vpp_report_log`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+-- vpp_customer
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'vpp_customer') > 0, 'TRUNCATE TABLE `vpp_customer`', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- TRUNCATE 后 InnoDB 自增值已自动重置为 1,无需再 ALTER AUTO_INCREMENT
+
+-- =============================================================================
+-- 方案二:保留数据,重编号为 1..n(若需使用,请注释掉上方方案一后执行本段)
+-- 要求:MySQL 8.0+;assignee_id 等指向 sys_user 的字段不在此脚本范围内
+-- =============================================================================
+
+/*
+SET FOREIGN_KEY_CHECKS = 0;
+SET UNIQUE_CHECKS = 0;
+
+-- 1. 生成 ID 映射(old_id -> new_id)
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_contract_template;
+CREATE TEMPORARY TABLE tmp_map_vpp_contract_template AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_contract_template`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_customer;
+CREATE TEMPORARY TABLE tmp_map_vpp_customer AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_customer`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_dr_event;
+CREATE TEMPORARY TABLE tmp_map_vpp_dr_event AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_dr_event`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_dr_strategy;
+CREATE TEMPORARY TABLE tmp_map_vpp_dr_strategy AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_dr_strategy`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_registration;
+CREATE TEMPORARY TABLE tmp_map_vpp_registration AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_registration`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_report_task;
+CREATE TEMPORARY TABLE tmp_map_vpp_report_task AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_report_task`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_report_log;
+CREATE TEMPORARY TABLE tmp_map_vpp_report_log AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_report_log`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_customer_access;
+CREATE TEMPORARY TABLE tmp_map_vpp_customer_access AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_customer_access`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_customer_contact;
+CREATE TEMPORARY TABLE tmp_map_vpp_customer_contact AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_customer_contact`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_contract;
+CREATE TEMPORARY TABLE tmp_map_vpp_contract AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_contract`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_resource_point;
+CREATE TEMPORARY TABLE tmp_map_vpp_resource_point AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_resource_point`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_report_record;
+CREATE TEMPORARY TABLE tmp_map_vpp_report_record AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_report_record`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_contract_audit_log;
+CREATE TEMPORARY TABLE tmp_map_vpp_contract_audit_log AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_contract_audit_log`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_resource_point_config;
+CREATE TEMPORARY TABLE tmp_map_vpp_resource_point_config AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_resource_point_config`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_device;
+CREATE TEMPORARY TABLE tmp_map_vpp_device AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_device`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_energy_reading_monthly;
+CREATE TEMPORARY TABLE tmp_map_vpp_energy_reading_monthly AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_energy_reading_monthly`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_energy_summary_daily;
+CREATE TEMPORARY TABLE tmp_map_vpp_energy_summary_daily AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_energy_summary_daily`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_settlement_bill;
+CREATE TEMPORARY TABLE tmp_map_vpp_settlement_bill AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_settlement_bill`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_dr_participation;
+CREATE TEMPORARY TABLE tmp_map_vpp_dr_participation AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_dr_participation`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_dr_strategy_resource;
+CREATE TEMPORARY TABLE tmp_map_vpp_dr_strategy_resource AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_dr_strategy_resource`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_report_data;
+CREATE TEMPORARY TABLE tmp_map_vpp_report_data AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_report_data`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_report_audit_log;
+CREATE TEMPORARY TABLE tmp_map_vpp_report_audit_log AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_report_audit_log`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_device_control_log;
+CREATE TEMPORARY TABLE tmp_map_vpp_device_control_log AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_device_control_log`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_settlement_bill_detail;
+CREATE TEMPORARY TABLE tmp_map_vpp_settlement_bill_detail AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_settlement_bill_detail`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_payment_record;
+CREATE TEMPORARY TABLE tmp_map_vpp_payment_record AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_payment_record`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_dr_execution;
+CREATE TEMPORARY TABLE tmp_map_vpp_dr_execution AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_dr_execution`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_dr_evaluation;
+CREATE TEMPORARY TABLE tmp_map_vpp_dr_evaluation AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_dr_evaluation`;
+
+DROP TEMPORARY TABLE IF EXISTS tmp_map_vpp_file_archive;
+CREATE TEMPORARY TABLE tmp_map_vpp_file_archive AS
+SELECT `id` AS old_id, ROW_NUMBER() OVER (ORDER BY `id`) AS new_id FROM `vpp_file_archive`;
+
+-- 2. 更新外键引用
+UPDATE `vpp_customer_access` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_customer_contact` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_contract` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_contract` t JOIN tmp_map_vpp_contract_template m ON t.`template_id` = m.old_id SET t.`template_id` = m.new_id;
+UPDATE `vpp_resource_point` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_energy_reading_monthly` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_energy_reading_monthly` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_energy_summary_daily` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_settlement_bill` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_contract_audit_log` t JOIN tmp_map_vpp_contract m ON t.`contract_id` = m.old_id SET t.`contract_id` = m.new_id;
+UPDATE `vpp_resource_point_config` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_device` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_device_control_log` t JOIN tmp_map_vpp_device m ON t.`device_id` = m.old_id SET t.`device_id` = m.new_id;
+UPDATE `vpp_settlement_bill_detail` t JOIN tmp_map_vpp_settlement_bill m ON t.`bill_id` = m.old_id SET t.`bill_id` = m.new_id;
+UPDATE `vpp_payment_record` t JOIN tmp_map_vpp_settlement_bill m ON t.`bill_id` = m.old_id SET t.`bill_id` = m.new_id;
+UPDATE `vpp_report_record` t JOIN tmp_map_vpp_report_task m ON t.`task_id` = m.old_id SET t.`task_id` = m.new_id;
+UPDATE `vpp_report_data` t JOIN tmp_map_vpp_report_record m ON t.`record_id` = m.old_id SET t.`record_id` = m.new_id;
+UPDATE `vpp_report_audit_log` t JOIN tmp_map_vpp_report_record m ON t.`record_id` = m.old_id SET t.`record_id` = m.new_id;
+UPDATE `vpp_dr_participation` t JOIN tmp_map_vpp_dr_event m ON t.`event_id` = m.old_id SET t.`event_id` = m.new_id;
+UPDATE `vpp_dr_participation` t JOIN tmp_map_vpp_customer m ON t.`customer_id` = m.old_id SET t.`customer_id` = m.new_id;
+UPDATE `vpp_dr_participation` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_dr_strategy_resource` t JOIN tmp_map_vpp_dr_strategy m ON t.`strategy_id` = m.old_id SET t.`strategy_id` = m.new_id;
+UPDATE `vpp_dr_strategy_resource` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_dr_execution` t JOIN tmp_map_vpp_dr_event m ON t.`event_id` = m.old_id SET t.`event_id` = m.new_id;
+UPDATE `vpp_dr_execution` t JOIN tmp_map_vpp_resource_point m ON t.`resource_id` = m.old_id SET t.`resource_id` = m.new_id;
+UPDATE `vpp_dr_evaluation` t JOIN tmp_map_vpp_dr_event m ON t.`event_id` = m.old_id SET t.`event_id` = m.new_id;
+
+-- 电子档案 biz_id(按业务类型映射)
+UPDATE `vpp_file_archive` t JOIN tmp_map_vpp_contract m ON t.`biz_type` = 'CONTRACT' AND t.`biz_id` = m.old_id SET t.`biz_id` = m.new_id;
+UPDATE `vpp_file_archive` t JOIN tmp_map_vpp_customer m ON t.`biz_type` = 'CUSTOMER' AND t.`biz_id` = m.old_id SET t.`biz_id` = m.new_id;
+UPDATE `vpp_file_archive` t JOIN tmp_map_vpp_resource_point m ON t.`biz_type` = 'RESOURCE' AND t.`biz_id` = m.old_id SET t.`biz_id` = m.new_id;
+
+-- 3. 重编号主键(先置负避免冲突,再转正)
+UPDATE `vpp_contract_template` t JOIN tmp_map_vpp_contract_template m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_customer` t JOIN tmp_map_vpp_customer m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_dr_event` t JOIN tmp_map_vpp_dr_event m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_dr_strategy` t JOIN tmp_map_vpp_dr_strategy m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_registration` t JOIN tmp_map_vpp_registration m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_report_task` t JOIN tmp_map_vpp_report_task m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_report_log` t JOIN tmp_map_vpp_report_log m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_customer_access` t JOIN tmp_map_vpp_customer_access m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_customer_contact` t JOIN tmp_map_vpp_customer_contact m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_contract` t JOIN tmp_map_vpp_contract m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_resource_point` t JOIN tmp_map_vpp_resource_point m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_report_record` t JOIN tmp_map_vpp_report_record m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_contract_audit_log` t JOIN tmp_map_vpp_contract_audit_log m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_resource_point_config` t JOIN tmp_map_vpp_resource_point_config m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_device` t JOIN tmp_map_vpp_device m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_energy_reading_monthly` t JOIN tmp_map_vpp_energy_reading_monthly m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_energy_summary_daily` t JOIN tmp_map_vpp_energy_summary_daily m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_settlement_bill` t JOIN tmp_map_vpp_settlement_bill m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_dr_participation` t JOIN tmp_map_vpp_dr_participation m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_dr_strategy_resource` t JOIN tmp_map_vpp_dr_strategy_resource m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_report_data` t JOIN tmp_map_vpp_report_data m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_report_audit_log` t JOIN tmp_map_vpp_report_audit_log m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_device_control_log` t JOIN tmp_map_vpp_device_control_log m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_settlement_bill_detail` t JOIN tmp_map_vpp_settlement_bill_detail m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_payment_record` t JOIN tmp_map_vpp_payment_record m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_dr_execution` t JOIN tmp_map_vpp_dr_execution m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_dr_evaluation` t JOIN tmp_map_vpp_dr_evaluation m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+UPDATE `vpp_file_archive` t JOIN tmp_map_vpp_file_archive m ON t.`id` = m.old_id SET t.`id` = -m.new_id;
+
+UPDATE `vpp_contract_template` SET `id` = -`id`;
+UPDATE `vpp_customer` SET `id` = -`id`;
+UPDATE `vpp_dr_event` SET `id` = -`id`;
+UPDATE `vpp_dr_strategy` SET `id` = -`id`;
+UPDATE `vpp_registration` SET `id` = -`id`;
+UPDATE `vpp_report_task` SET `id` = -`id`;
+UPDATE `vpp_report_log` SET `id` = -`id`;
+UPDATE `vpp_customer_access` SET `id` = -`id`;
+UPDATE `vpp_customer_contact` SET `id` = -`id`;
+UPDATE `vpp_contract` SET `id` = -`id`;
+UPDATE `vpp_resource_point` SET `id` = -`id`;
+UPDATE `vpp_report_record` SET `id` = -`id`;
+UPDATE `vpp_contract_audit_log` SET `id` = -`id`;
+UPDATE `vpp_resource_point_config` SET `id` = -`id`;
+UPDATE `vpp_device` SET `id` = -`id`;
+UPDATE `vpp_energy_reading_monthly` SET `id` = -`id`;
+UPDATE `vpp_energy_summary_daily` SET `id` = -`id`;
+UPDATE `vpp_settlement_bill` SET `id` = -`id`;
+UPDATE `vpp_dr_participation` SET `id` = -`id`;
+UPDATE `vpp_dr_strategy_resource` SET `id` = -`id`;
+UPDATE `vpp_report_data` SET `id` = -`id`;
+UPDATE `vpp_report_audit_log` SET `id` = -`id`;
+UPDATE `vpp_device_control_log` SET `id` = -`id`;
+UPDATE `vpp_settlement_bill_detail` SET `id` = -`id`;
+UPDATE `vpp_payment_record` SET `id` = -`id`;
+UPDATE `vpp_dr_execution` SET `id` = -`id`;
+UPDATE `vpp_dr_evaluation` SET `id` = -`id`;
+UPDATE `vpp_file_archive` SET `id` = -`id`;
+
+-- 4. 重置自增计数(下一条插入从 max(id)+1,空表则为 1)
+ALTER TABLE `vpp_contract_template` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_customer` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_dr_event` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_dr_strategy` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_registration` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_report_task` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_report_log` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_customer_access` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_customer_contact` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_contract` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_resource_point` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_report_record` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_contract_audit_log` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_resource_point_config` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_device` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_energy_reading_monthly` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_energy_summary_daily` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_settlement_bill` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_dr_participation` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_dr_strategy_resource` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_report_data` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_report_audit_log` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_device_control_log` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_settlement_bill_detail` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_payment_record` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_dr_execution` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_dr_evaluation` AUTO_INCREMENT = 1;
+ALTER TABLE `vpp_file_archive` AUTO_INCREMENT = 1;
+
+SET UNIQUE_CHECKS = 1;
+SET FOREIGN_KEY_CHECKS = 1;
+*/

+ 62 - 28
service-vpp/service-vpp-biz/src/main/resources/sql/vpp_schema.sql

@@ -2,7 +2,7 @@
 -- 来源: 虚拟电厂平台数据库设计文档 V1.0
 
 CREATE TABLE `vpp_customer` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `account_no` VARCHAR(32) NOT NULL COMMENT '电力户号,全局唯一',
     `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
     `customer_type` TINYINT NOT NULL COMMENT '1高压 2低压商用 3居民充电桩 4自有资产',
@@ -33,7 +33,7 @@ CREATE TABLE `vpp_customer` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户档案';
 
 CREATE TABLE `vpp_customer_access` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `apply_no` VARCHAR(32) NOT NULL COMMENT '申请编号',
     `account_no` VARCHAR(32) NOT NULL COMMENT '电力户号',
     `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
@@ -60,7 +60,7 @@ CREATE TABLE `vpp_customer_access` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='准入申请';
 
 CREATE TABLE `vpp_customer_contact` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `customer_id` BIGINT NOT NULL COMMENT '客户ID',
     `contact_name` VARCHAR(100) NOT NULL COMMENT '联系人姓名',
     `contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
@@ -79,7 +79,7 @@ CREATE TABLE `vpp_customer_contact` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户联系人';
 
 CREATE TABLE `vpp_contract` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `contract_no` VARCHAR(64) NOT NULL COMMENT '合同编号',
     `customer_id` BIGINT NOT NULL COMMENT '客户ID',
     `template_id` BIGINT NULL COMMENT '模板ID',
@@ -108,7 +108,7 @@ CREATE TABLE `vpp_contract` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同';
 
 CREATE TABLE `vpp_contract_template` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `template_code` VARCHAR(32) NOT NULL COMMENT '模板编码',
     `template_name` VARCHAR(200) NOT NULL COMMENT '模板名称',
     `contract_type` TINYINT NOT NULL COMMENT '合同类型',
@@ -128,7 +128,7 @@ CREATE TABLE `vpp_contract_template` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同模板';
 
 CREATE TABLE `vpp_contract_audit_log` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `contract_id` BIGINT NOT NULL COMMENT '合同ID',
     `action` TINYINT NOT NULL COMMENT '1提交 2通过 3驳回 4归档',
     `opinion` VARCHAR(500) NULL COMMENT '意见',
@@ -140,7 +140,7 @@ CREATE TABLE `vpp_contract_audit_log` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同审核流水';
 
 CREATE TABLE `vpp_resource_point` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `resource_code` VARCHAR(64) NOT NULL COMMENT '资源编号',
     `resource_name` VARCHAR(200) NOT NULL COMMENT '资源名称',
     `customer_id` BIGINT NOT NULL COMMENT '所属客户ID',
@@ -174,7 +174,7 @@ CREATE TABLE `vpp_resource_point` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='资源点';
 
 CREATE TABLE `vpp_resource_point_config` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `resource_id` BIGINT NOT NULL COMMENT '资源点ID',
     `collect_interval_sec` INT NOT NULL COMMENT '采集频率秒 60/300/900',
     `power_upper_limit` DECIMAL(12,4) NULL COMMENT '功率上限告警',
@@ -195,7 +195,7 @@ CREATE TABLE `vpp_resource_point_config` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='资源点运行参数';
 
 CREATE TABLE `vpp_device` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `device_code` VARCHAR(64) NOT NULL COMMENT '设备编号',
     `device_name` VARCHAR(200) NOT NULL COMMENT '设备名称',
     `resource_id` BIGINT NOT NULL COMMENT '所属资源点ID',
@@ -223,7 +223,7 @@ CREATE TABLE `vpp_device` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备';
 
 CREATE TABLE `vpp_device_control_log` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `device_id` BIGINT NOT NULL COMMENT '设备ID',
     `control_type` VARCHAR(32) NOT NULL COMMENT 'START/STOP/CONFIG/OTA',
     `control_params` JSON NULL COMMENT '控制参数',
@@ -237,7 +237,7 @@ CREATE TABLE `vpp_device_control_log` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备远程控制日志';
 
 CREATE TABLE `vpp_energy_reading_monthly` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `customer_id` BIGINT NOT NULL COMMENT '客户ID',
     `resource_id` BIGINT NULL COMMENT '资源点ID,可空表示户号级',
     `settle_year` SMALLINT NOT NULL COMMENT '结算年',
@@ -263,7 +263,7 @@ CREATE TABLE `vpp_energy_reading_monthly` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='月度电量核算';
 
 CREATE TABLE `vpp_energy_summary_daily` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `summary_date` DATE NOT NULL COMMENT '汇总日期',
     `resource_id` BIGINT NULL COMMENT '资源点ID,NULL表示全站',
     `resource_type` VARCHAR(16) NULL COMMENT '资源类型',
@@ -285,7 +285,7 @@ CREATE TABLE `vpp_energy_summary_daily` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='日度能源汇总';
 
 CREATE TABLE `vpp_settlement_bill` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `bill_no` VARCHAR(64) NOT NULL COMMENT '账单编号',
     `customer_id` BIGINT NOT NULL COMMENT '客户ID',
     `settle_year` SMALLINT NOT NULL COMMENT '结算年',
@@ -311,7 +311,7 @@ CREATE TABLE `vpp_settlement_bill` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='电费账单';
 
 CREATE TABLE `vpp_settlement_bill_detail` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `bill_id` BIGINT NOT NULL COMMENT '账单ID',
     `time_period` TINYINT NOT NULL COMMENT '1尖 2峰 3平 4谷',
     `energy_kwh` DECIMAL(18,4) NOT NULL COMMENT '时段电量',
@@ -323,7 +323,7 @@ CREATE TABLE `vpp_settlement_bill_detail` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账单分时明细';
 
 CREATE TABLE `vpp_payment_record` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `bill_id` BIGINT NOT NULL COMMENT '账单ID',
     `payment_amount` DECIMAL(18,4) NOT NULL COMMENT '缴费金额',
     `payment_method` TINYINT NOT NULL COMMENT '1线下转账 2电费抵扣 3其他',
@@ -338,7 +338,7 @@ CREATE TABLE `vpp_payment_record` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='缴费记录';
 
 CREATE TABLE `vpp_report_task` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `task_no` VARCHAR(64) NOT NULL COMMENT '任务编号',
     `task_name` VARCHAR(200) NOT NULL COMMENT '任务名称',
     `report_org` TINYINT NOT NULL COMMENT '1交易中心 2能监局 3经信委 4国网上海',
@@ -361,7 +361,7 @@ CREATE TABLE `vpp_report_task` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送任务';
 
 CREATE TABLE `vpp_report_record` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `task_id` BIGINT NOT NULL COMMENT '任务ID',
     `record_no` VARCHAR(64) NOT NULL COMMENT '报送记录编号',
     `report_period` VARCHAR(32) NOT NULL COMMENT '报送周期标识 如2026-05',
@@ -386,7 +386,7 @@ CREATE TABLE `vpp_report_record` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送记录';
 
 CREATE TABLE `vpp_report_data` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `record_id` BIGINT NOT NULL COMMENT '报送记录ID',
     `form_data` JSON NOT NULL COMMENT '填报表单JSON',
     `validation_errors` JSON NULL COMMENT '校验异常项',
@@ -403,7 +403,7 @@ CREATE TABLE `vpp_report_data` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送填报数据';
 
 CREATE TABLE `vpp_report_audit_log` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `record_id` BIGINT NOT NULL COMMENT '报送记录ID',
     `audit_level` TINYINT NOT NULL COMMENT '1填报 2复核 3审批',
     `action` TINYINT NOT NULL COMMENT '1提交 2通过 3驳回',
@@ -417,7 +417,7 @@ CREATE TABLE `vpp_report_audit_log` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送审核流水';
 
 CREATE TABLE `vpp_dr_event` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `event_id` VARCHAR(64) NOT NULL COMMENT '运管平台事件ID',
     `event_name` VARCHAR(200) NULL COMMENT '事件名称',
     `response_type` TINYINT NOT NULL COMMENT '1日前 2日内 3秒级',
@@ -442,7 +442,7 @@ CREATE TABLE `vpp_dr_event` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='需求响应事件';
 
 CREATE TABLE `vpp_dr_participation` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `event_id` BIGINT NOT NULL COMMENT '事件ID',
     `customer_id` BIGINT NOT NULL COMMENT '客户ID',
     `resource_id` BIGINT NULL COMMENT '资源点ID',
@@ -462,8 +462,42 @@ CREATE TABLE `vpp_dr_participation` (
     UNIQUE KEY `uk_dr_part` (event_id, customer_id, resource_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='事件参与记录';
 
+CREATE TABLE `vpp_dr_invitation` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `invitation_no` VARCHAR(32) NOT NULL COMMENT '邀约编号',
+    `dr_event_id` BIGINT NOT NULL COMMENT '需求响应事件ID',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `execute_start_date` DATE NOT NULL COMMENT '执行开始日期',
+    `execute_end_date` DATE NOT NULL COMMENT '执行结束日期',
+    `reply_deadline` DATETIME(3) NOT NULL COMMENT '邀约回复截止日期',
+    `issue_time` DATETIME(3) NOT NULL COMMENT '下发时间',
+    `transaction_type` TINYINT NULL COMMENT '交易类型 1削峰 2填谷',
+    `response_type` TINYINT NOT NULL COMMENT '1日前 2日内 3秒级',
+    `demand_capacity_kw` DECIMAL(12,4) NOT NULL COMMENT '需求容量 kW',
+    `declared_capacity_kw` DECIMAL(12,4) NULL COMMENT '已申报容量 kW',
+    `reply_status` TINYINT NOT NULL COMMENT '0待回复 1参与 2拒绝 3超时',
+    `response_status` TINYINT NOT NULL COMMENT '0待响应 1已申报 2执行中 3已结束 4已取消',
+    `sms_notify_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0未发送 1已发送 2发送失败',
+    `sms_notify_at` DATETIME(3) NULL COMMENT '短信通知时间',
+    `sms_contact_id` BIGINT NULL COMMENT '通知联系人ID',
+    `participation_id` BIGINT NULL COMMENT '关联参与记录ID',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_dr_invitation_no` (invitation_no),
+    UNIQUE KEY `uk_dr_invitation_event_customer` (dr_event_id, customer_id),
+    KEY `idx_dr_invitation_customer` (customer_id, reply_status),
+    KEY `idx_dr_invitation_issue` (issue_time, response_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='需求响应企业邀约';
+
 CREATE TABLE `vpp_dr_strategy` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `strategy_code` VARCHAR(64) NOT NULL COMMENT '策略编码',
     `strategy_name` VARCHAR(200) NOT NULL COMMENT '策略名称',
     `response_type` TINYINT NOT NULL COMMENT '适用响应类型',
@@ -484,7 +518,7 @@ CREATE TABLE `vpp_dr_strategy` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='响应策略';
 
 CREATE TABLE `vpp_dr_strategy_resource` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `strategy_id` BIGINT NOT NULL COMMENT '策略ID',
     `resource_id` BIGINT NOT NULL COMMENT '资源点ID',
     `priority` TINYINT NOT NULL COMMENT '优先级序号',
@@ -496,7 +530,7 @@ CREATE TABLE `vpp_dr_strategy_resource` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='策略-资源关联';
 
 CREATE TABLE `vpp_dr_execution` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `event_id` BIGINT NOT NULL COMMENT '事件ID',
     `resource_id` BIGINT NOT NULL COMMENT '资源点ID',
     `target_kw` DECIMAL(12,4) NOT NULL COMMENT '目标调节量',
@@ -517,7 +551,7 @@ CREATE TABLE `vpp_dr_execution` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='响应执行明细';
 
 CREATE TABLE `vpp_dr_evaluation` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `event_id` BIGINT NOT NULL COMMENT '事件ID',
     `actual_capacity_kw` DECIMAL(12,4) NULL COMMENT '实际响应容量',
     `response_duration_min` INT NULL COMMENT '响应时长分钟',
@@ -537,7 +571,7 @@ CREATE TABLE `vpp_dr_evaluation` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='响应效果评估';
 
 CREATE TABLE `vpp_registration` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `dn_id` VARCHAR(64) NOT NULL COMMENT '虚拟电厂运营商ID',
     `registration_id` VARCHAR(128) NOT NULL COMMENT '运管平台registrationID',
     `reg_status` TINYINT NOT NULL COMMENT '0未注册 1已注册 2需重新注册',
@@ -555,7 +589,7 @@ CREATE TABLE `vpp_registration` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='运管平台注册信息';
 
 CREATE TABLE `vpp_report_log` (
-    `id` BIGINT NOT NULL COMMENT '主键',
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
     `report_type` VARCHAR(64) NOT NULL COMMENT 'MomentData/IntervalData/RegisterReport等',
     `request_id` VARCHAR(64) NULL COMMENT '请求ID',
     `report_status` TINYINT NOT NULL COMMENT '0失败 1成功',
@@ -569,7 +603,7 @@ CREATE TABLE `vpp_report_log` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='数据上报日志';
 
 CREATE TABLE vpp_file_archive (
-    id          BIGINT         NOT NULL COMMENT '主键',
+    id          BIGINT         NOT NULL AUTO_INCREMENT COMMENT '主键',
     file_name   VARCHAR(255)   NOT NULL COMMENT '原始文件名',
     file_url    VARCHAR(500)   NOT NULL COMMENT '存储路径',
     file_type   VARCHAR(32)    NOT NULL COMMENT '文件类型',