Quellcode durchsuchen

Merge branch 'fyc-vpp' of uskycloud/usky-modules into feature/service-vpp-20260701

fuyuchuan vor 2 Tagen
Ursprung
Commit
51539e7666
24 geänderte Dateien mit 499 neuen und 325 gelöschten Zeilen
  1. 18 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/constant/VppDrEventStatus.java
  2. 15 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/DrController.java
  3. 17 9
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/DrMonitorController.java
  4. 1 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrEvent.java
  5. 1 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrInvitation.java
  6. 14 8
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrMonitorService.java
  7. 6 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrService.java
  8. 2 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppBaselineServiceImpl.java
  9. 2 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDashboardServiceImpl.java
  10. 6 5
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrEventIngestServiceImpl.java
  11. 49 10
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrInvitationServiceImpl.java
  12. 246 266
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrMonitorServiceImpl.java
  13. 35 5
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrServiceImpl.java
  14. 2 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppResponseMonitorServiceImpl.java
  15. 2 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppSiteCompletionRateTaskServiceImpl.java
  16. 1 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrEventDetailVO.java
  17. 19 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrEventStatusRequest.java
  18. 3 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrInvitationVO.java
  19. 13 5
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrMonitorRecordVO.java
  20. 33 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrMonitorSiteResourceVO.java
  21. 4 6
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrMonitorSummaryVO.java
  22. 4 3
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnEventParser.java
  23. 4 2
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnPayloadHelper.java
  24. 2 2
      service-vpp/service-vpp-biz/src/main/resources/sql/vpp_schema.sql

+ 18 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/constant/VppDrEventStatus.java

@@ -0,0 +1,18 @@
+package com.usky.vpp.constant;
+
+/**
+ * 需求响应事件 / 邀约响应状态:0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消
+ * <p>vpp_dr_event.event_status 与 vpp_dr_invitation.response_status 共用同一套取值。</p>
+ */
+public final class VppDrEventStatus {
+
+    public static final int PENDING = 0;
+    public static final int DECLARED = 1;
+    public static final int DECLARE_COMPLETED = 2;
+    public static final int EXECUTING = 3;
+    public static final int ENDED = 4;
+    public static final int CANCELLED = 5;
+
+    private VppDrEventStatus() {
+    }
+}

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

@@ -12,6 +12,7 @@ import com.usky.vpp.service.VppResponseDeviationPriceService;
 import com.usky.vpp.service.vo.DrClearingRequest;
 import com.usky.vpp.service.vo.DrEventDetailVO;
 import com.usky.vpp.service.vo.DrEventRequest;
+import com.usky.vpp.service.vo.DrEventStatusRequest;
 import com.usky.vpp.service.vo.DrInterveneRequest;
 import com.usky.vpp.service.vo.DrInvitationNotifyRequest;
 import com.usky.vpp.service.vo.DrInvitationReplyRequest;
@@ -53,6 +54,17 @@ public class DrController {
         return ApiResult.success(vppDrService.pageEvent(params));
     }
 
+    /**
+     * 修改事件状态(独立接口,不修改事件其它字段)。
+     * <p>校验租户后,仅允许从 0待参与 改为 1已申报。</p>
+     * <p>事件状态:0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消</p>
+     */
+    @PutMapping(value = "/event/status")
+    public ApiResult<Void> updateEventStatus(@RequestBody DrEventStatusRequest body) {
+        vppDrService.updateEventStatus(body);
+        return ApiResult.success();
+    }
+
     @GetMapping(value = "/event/{id}")
     public ApiResult<DrEventDetailVO> getEvent(@PathVariable("id") Long id) {
         return ApiResult.success(vppDrService.getEvent(id));
@@ -147,6 +159,9 @@ public class DrController {
         return ApiResult.success();
     }
 
+    /**
+     * 邀约响应状态:0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消(与事件状态一致)
+     */
     @GetMapping(value = "/invitation")
     public ApiResult<CommonPage<DrInvitationVO>> pageInvitation(@RequestParam(required = false) Map<String, Object> params) {
         return ApiResult.success(vppDrInvitationService.pageInvitation(params));

+ 17 - 9
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/DrMonitorController.java

@@ -5,6 +5,7 @@ import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.service.VppDrMonitorService;
 import com.usky.vpp.service.vo.DrMonitorCurveVO;
 import com.usky.vpp.service.vo.DrMonitorRecordVO;
+import com.usky.vpp.service.vo.DrMonitorSiteResourceVO;
 import com.usky.vpp.service.vo.DrMonitorSummaryVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -28,7 +29,7 @@ public class DrMonitorController {
 
     /**
      * 顶部 KPI:响应次数 / 响应容量(kW) / 历史达标率
-     * <p>必传站点;先查站点下 is_support_peak=1 资源点,再按资源点平均。</p>
+     * <p>时间区间内事件开始、结束均落在区间且状态为执行中/已结束,再关联参与且执行中/已结束的邀约。</p>
      */
     @GetMapping("/summary")
     public ApiResult<DrMonitorSummaryVO> summary(@RequestParam("startDate") String startDate,
@@ -38,8 +39,8 @@ public class DrMonitorController {
     }
 
     /**
-     * 响应记录分页(字段对齐 vpp_dr_event
-     * <p>必传站点;仅统计可调峰资源点参与过的事件。</p>
+     * 响应记录分页(口径与 summary 一致,按邀约展开
+     * <p>在事件字段基础上补充客户、站点;目标容量取申报出清容量,出清容量取实际响应容量。</p>
      */
     @GetMapping("/records")
     public ApiResult<CommonPage<DrMonitorRecordVO>> records(@RequestParam("startDate") String startDate,
@@ -50,13 +51,20 @@ public class DrMonitorController {
     }
 
     /**
-     * 折线图:基线负荷 + 实际响应容量;查询范围为当天
-     * <p>基线负荷复用 getSiteBaseline,实际响应容量复用 getSiteDeclaredCapacity。</p>
-     * <p>baselineDays 传 3 或 5 时按对应周期计算基线负荷;为空则按 5 日基线。</p>
+     * 折线图:基线负荷全天曲线 + 实际响应容量
+     * <p>id 为邀约表主键;按邀约站点与响应时间调用 getSiteBaseline。</p>
      */
     @GetMapping("/records/curve")
-    public ApiResult<DrMonitorCurveVO> curve(@RequestParam("siteId") Long siteId,
-                                             @RequestParam(value = "baselineDays", required = false) Integer baselineDays) {
-        return ApiResult.success(drMonitorService.getCurve(siteId, baselineDays));
+    public ApiResult<DrMonitorCurveVO> curve(@RequestParam("id") Long id) {
+        return ApiResult.success(drMonitorService.getCurve(id));
+    }
+
+    /**
+     * 按响应事件查询邀约站点及其可调峰资源点
+     * <p>id 为 vpp_dr_event 主键;先查事件下全部邀约站点(去重),再查各站点 is_support_peak=1 的资源点,按站点分组返回。</p>
+     */
+    @GetMapping("/site-resources")
+    public ApiResult<List<DrMonitorSiteResourceVO>> siteResources(@RequestParam("id") Long id) {
+        return ApiResult.success(drMonitorService.listEventSitePeakResources(id));
     }
 }

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

@@ -60,6 +60,7 @@ public class VppDrEvent implements Serializable {
     /** 下浮系数(必填) */
     @TableField("floating_coefficient")
     private BigDecimal floatingCoefficient;
+    /** 0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消 */
     @TableField("event_status")
     private Integer eventStatus;
     @TableField("raw_payload")

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

@@ -68,6 +68,7 @@ public class VppDrInvitation implements Serializable {
     private Integer isWinningBid;
     @TableField("reply_status")
     private Integer replyStatus;
+    /** 0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消 */
     @TableField("response_status")
     private Integer responseStatus;
     @TableField("sms_notify_status")

+ 14 - 8
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppDrMonitorService.java

@@ -3,6 +3,7 @@ package com.usky.vpp.service;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.service.vo.DrMonitorCurveVO;
 import com.usky.vpp.service.vo.DrMonitorRecordVO;
+import com.usky.vpp.service.vo.DrMonitorSiteResourceVO;
 import com.usky.vpp.service.vo.DrMonitorSummaryVO;
 
 import java.util.List;
@@ -14,26 +15,31 @@ import java.util.Map;
 public interface VppDrMonitorService {
 
     /**
-     * 顶部 KPI
+     * 顶部 KPI:按时间区间内执行中/已结束事件关联邀约统计
      *
      * @param startDate 开始日期 yyyy-MM-dd
      * @param endDate   结束日期 yyyy-MM-dd
-     * @param siteIds   站点 ID(必填,可多个);先查站点下 is_support_peak=1 资源点再平均
+     * @param siteIds   站点 ID(必填,可多个)
      */
     DrMonitorSummaryVO getSummary(String startDate, String endDate, List<Long> siteIds);
 
     /**
-     * 响应记录分页(按事件表字段;仅可调峰资源点参与过的事件
+     * 响应记录分页(查询口径与 summary 一致,按邀约展开
      */
     CommonPage<DrMonitorRecordVO> pageRecords(String startDate, String endDate,
                                               List<Long> siteIds, Map<String, Object> params);
 
     /**
-     * 折线图:以当天为查询范围,基线负荷复用 getSiteBaseline,实际响应容量复用
-     * getSiteDeclaredCapacity,再按可调峰资源点平均
+     * 折线图:按邀约主键取站点与响应时间,复用 getSiteBaseline 全天曲线,并带上实际响应容量
      *
-     * @param siteId       站点 ID(必填)
-     * @param baselineDays 基线参考天数,仅支持 3 或 5;为空时按 5 日基线
+     * @param invitationId 邀约表主键 id
      */
-    DrMonitorCurveVO getCurve(Long siteId, Integer baselineDays);
+    DrMonitorCurveVO getCurve(Long invitationId);
+
+    /**
+     * 按响应事件查询邀约站点及其可调峰资源点,以站点分组返回。
+     *
+     * @param eventId 响应事件主键 id(vpp_dr_event.id)
+     */
+    List<DrMonitorSiteResourceVO> listEventSitePeakResources(Long eventId);
 }

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

@@ -7,6 +7,7 @@ import com.usky.vpp.domain.VppDrStrategy;
 import com.usky.vpp.service.vo.DrClearingRequest;
 import com.usky.vpp.service.vo.DrEventDetailVO;
 import com.usky.vpp.service.vo.DrEventRequest;
+import com.usky.vpp.service.vo.DrEventStatusRequest;
 import com.usky.vpp.service.vo.DrInterveneRequest;
 import com.usky.vpp.service.vo.DrParticipateRequest;
 import com.usky.vpp.service.vo.DrStrategyRequest;
@@ -27,6 +28,11 @@ public interface VppDrService {
 
     void updateEvent(Long id, DrEventRequest request);
 
+    /**
+     * 修改事件状态(独立接口,不走编辑事件)。校验租户后仅允许 0待参与 → 1已申报。
+     */
+    void updateEventStatus(DrEventStatusRequest request);
+
     void deleteEvent(Long id);
 
     Object assessCapability(Long eventId);

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

@@ -2,6 +2,7 @@ package com.usky.vpp.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.common.core.exception.BusinessException;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.domain.VppDevice;
 import com.usky.vpp.domain.VppDrEvent;
 import com.usky.vpp.domain.VppResourcePoint;
@@ -42,7 +43,7 @@ import java.util.stream.Collectors;
 @Service
 public class VppBaselineServiceImpl implements VppBaselineService {
 
-    private static final int EVENT_STATUS_ENDED = 3;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
     private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     @Autowired

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

@@ -2,6 +2,7 @@ package com.usky.vpp.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.common.security.utils.SecurityUtils;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.constant.VppSocialBenefitConstants;
 import com.usky.vpp.domain.DmpDeviceStatus;
 import com.usky.vpp.domain.VppCustomer;
@@ -61,7 +62,7 @@ import java.util.stream.Collectors;
 @Service
 public class VppDashboardServiceImpl implements VppDashboardService {
 
-    private static final int EVENT_STATUS_ENDED = 3;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
     private static final int EVENT_TYPE_PEAK = 1;
     private static final int EVENT_TYPE_VALLEY = 2;
     private static final int REPLY_ACCEPT = 1;

+ 6 - 5
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrEventIngestServiceImpl.java

@@ -3,6 +3,7 @@ package com.usky.vpp.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.domain.VppDrEvent;
 import com.usky.vpp.enums.VppUnEventPhase;
 import com.usky.vpp.mapper.VppDrEventMapper;
@@ -29,11 +30,11 @@ public class VppDrEventIngestServiceImpl implements VppDrEventIngestService {
 
     private static final Logger log = LoggerFactory.getLogger(VppDrEventIngestServiceImpl.class);
 
-    private static final int EVENT_STATUS_PENDING = 0;
-    private static final int EVENT_STATUS_DECLARED = 1;
-    private static final int EVENT_STATUS_EXECUTING = 2;
-    private static final int EVENT_STATUS_ENDED = 3;
-    private static final int EVENT_STATUS_CANCELLED = 4;
+    private static final int EVENT_STATUS_PENDING = VppDrEventStatus.PENDING;
+    private static final int EVENT_STATUS_DECLARED = VppDrEventStatus.DECLARED;
+    private static final int EVENT_STATUS_EXECUTING = VppDrEventStatus.EXECUTING;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
+    private static final int EVENT_STATUS_CANCELLED = VppDrEventStatus.CANCELLED;
 
     @Autowired
     private VppDrEventMapper eventMapper;

+ 49 - 10
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrInvitationServiceImpl.java

@@ -5,6 +5,7 @@ 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.config.VppSmsProperties;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.domain.VppCustomer;
 import com.usky.vpp.domain.VppCustomerContact;
 import com.usky.vpp.domain.VppDrEvent;
@@ -56,9 +57,10 @@ public class VppDrInvitationServiceImpl implements VppDrInvitationService {
     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;
+    /** 与事件状态一致:0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消 */
+    private static final int RESPONSE_PENDING = VppDrEventStatus.PENDING;
+    private static final int RESPONSE_DECLARED = VppDrEventStatus.DECLARED;
+    private static final int RESPONSE_CANCELLED = VppDrEventStatus.CANCELLED;
 
     private static final int SMS_NOT_SENT = 0;
     private static final int SMS_SENT = 1;
@@ -208,7 +210,7 @@ public class VppDrInvitationServiceImpl implements VppDrInvitationService {
             throw new BusinessException("已参与的邀约不可删除");
         }
         if (invitation.getResponseStatus() != RESPONSE_PENDING) {
-            throw new BusinessException("仅待响应状态的邀约可删除");
+            throw new BusinessException("仅待参与状态的邀约可删除");
         }
         if (invitation.getParticipationId() != null) {
             throw new BusinessException("邀约已关联参与记录,不可删除");
@@ -311,6 +313,19 @@ public class VppDrInvitationServiceImpl implements VppDrInvitationService {
         if (params.get("drEventId") != null) {
             wrapper.eq(VppDrInvitation::getDrEventId, Long.parseLong(params.get("drEventId").toString()));
         }
+        if (params.get("eventId") != null && StringUtils.hasText(params.get("eventId").toString())) {
+            List<Long> eventPks = eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
+                            .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                            .like(VppDrEvent::getEventId, params.get("eventId").toString().trim()))
+                    .stream()
+                    .map(VppDrEvent::getId)
+                    .collect(Collectors.toList());
+            if (eventPks.isEmpty()) {
+                wrapper.eq(VppDrInvitation::getDrEventId, -1L);
+            } else {
+                wrapper.in(VppDrInvitation::getDrEventId, eventPks);
+            }
+        }
         if (params.get("responseType") != null) {
             wrapper.eq(VppDrInvitation::getResponseType, Integer.parseInt(params.get("responseType").toString()));
         }
@@ -489,13 +504,27 @@ public class VppDrInvitationServiceImpl implements VppDrInvitationService {
         if (CollectionUtils.isEmpty(invitations)) {
             return Collections.emptyList();
         }
-        Set<Long> customerIds = invitations.stream().map(VppDrInvitation::getCustomerId).collect(Collectors.toSet());
-
-        Map<Long, VppCustomer> customerMap = customerMapper.selectBatchIds(customerIds).stream()
+        Set<Long> customerIds = invitations.stream()
+                .map(VppDrInvitation::getCustomerId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, VppCustomer> customerMap = customerIds.isEmpty()
+                ? Collections.emptyMap()
+                : customerMapper.selectBatchIds(customerIds).stream()
                 .collect(Collectors.toMap(VppCustomer::getId, c -> c, (a, b) -> a));
 
+        Set<Long> eventPks = invitations.stream()
+                .map(VppDrInvitation::getDrEventId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, VppDrEvent> eventMap = eventPks.isEmpty()
+                ? Collections.emptyMap()
+                : eventMapper.selectBatchIds(eventPks).stream()
+                .filter(e -> !VppAuditHelper.isDeleted(e.getDeleteFlag()))
+                .collect(Collectors.toMap(VppDrEvent::getId, e -> e, (a, b) -> a));
+
         return invitations.stream()
-                .map(inv -> toVo(inv, customerMap.get(inv.getCustomerId()), null))
+                .map(inv -> toVo(inv, customerMap.get(inv.getCustomerId()), null, eventMap.get(inv.getDrEventId())))
                 .collect(Collectors.toList());
     }
 
@@ -504,10 +533,17 @@ public class VppDrInvitationServiceImpl implements VppDrInvitationService {
         VppCustomerContact contact = invitation.getSmsContactId() != null
                 ? contactMapper.selectById(invitation.getSmsContactId())
                 : null;
-        return toVo(invitation, customer, contact);
+        VppDrEvent event = invitation.getDrEventId() != null
+                ? eventMapper.selectById(invitation.getDrEventId())
+                : null;
+        if (event != null && VppAuditHelper.isDeleted(event.getDeleteFlag())) {
+            event = null;
+        }
+        return toVo(invitation, customer, contact, event);
     }
 
-    private DrInvitationVO toVo(VppDrInvitation invitation, VppCustomer customer, VppCustomerContact contact) {
+    private DrInvitationVO toVo(VppDrInvitation invitation, VppCustomer customer, VppCustomerContact contact,
+                                VppDrEvent event) {
         DrInvitationVO vo = new DrInvitationVO();
         BeanUtils.copyProperties(invitation, vo);
         if (customer != null) {
@@ -517,6 +553,9 @@ public class VppDrInvitationServiceImpl implements VppDrInvitationService {
             vo.setSmsContactName(contact.getContactName());
             vo.setSmsContactPhone(contact.getContactPhone());
         }
+        if (event != null) {
+            vo.setEventId(event.getEventId());
+        }
         return vo;
     }
 

+ 246 - 266
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrMonitorServiceImpl.java

@@ -5,26 +5,30 @@ 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.constant.VppDrEventStatus;
+import com.usky.vpp.domain.VppCustomer;
 import com.usky.vpp.domain.VppDrEvent;
 import com.usky.vpp.domain.VppDrInvitation;
-import com.usky.vpp.domain.VppDrParticipation;
 import com.usky.vpp.domain.VppDrSubsidyPrediction;
 import com.usky.vpp.domain.VppResourcePoint;
+import com.usky.vpp.domain.VppSite;
+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.mapper.VppDrSubsidyPredictionMapper;
 import com.usky.vpp.mapper.VppResourcePointMapper;
+import com.usky.vpp.mapper.VppSiteMapper;
 import com.usky.vpp.service.VppBaselineService;
 import com.usky.vpp.service.VppDrMonitorService;
 import com.usky.vpp.service.vo.BaselinePointVO;
 import com.usky.vpp.service.vo.DrMonitorCurveVO;
 import com.usky.vpp.service.vo.DrMonitorRecordVO;
+import com.usky.vpp.service.vo.DrMonitorSiteResourceVO;
 import com.usky.vpp.service.vo.DrMonitorSummaryVO;
 import com.usky.vpp.service.vo.SiteBaselineVO;
-import com.usky.vpp.service.vo.SiteDeclaredCapacityVO;
 import com.usky.vpp.util.VppAuditHelper;
 import com.usky.vpp.util.VppPageHelper;
+import com.usky.vpp.util.VppResourceTypeHelper;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -42,7 +46,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -51,15 +55,15 @@ import java.util.stream.Collectors;
 
 /**
  * 需求响应监测(响应记录)
- * <p>统一口径:站点 → is_support_peak=1 资源点 → 按资源点平均。</p>
+ * <p>统一口径:时间区间内执行中/已结束事件 → 参与且执行中/已结束的未删除邀约。</p>
  */
 @Service
 public class VppDrMonitorServiceImpl implements VppDrMonitorService {
 
-    private static final int EVENT_STATUS_EXECUTING = 2;
-    private static final int EVENT_STATUS_ENDED = 3;
+    private static final int EVENT_STATUS_EXECUTING = VppDrEventStatus.EXECUTING;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
+    private static final int REPLY_ACCEPT = 1;
     private static final int SUPPORT_PEAK = 1;
-    private static final int PARTICIPATE_ACCEPT = 1;
     private static final int SCALE = 4;
     private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
     private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@@ -71,146 +75,75 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
     @Autowired
     private VppDrInvitationMapper invitationMapper;
     @Autowired
-    private VppDrParticipationMapper participationMapper;
+    private VppCustomerMapper customerMapper;
     @Autowired
     private VppResourcePointMapper resourcePointMapper;
     @Autowired
+    private VppSiteMapper siteMapper;
+    @Autowired
     private VppBaselineService baselineService;
     @Autowired
     private VppDrSubsidyPredictionMapper subsidyPredictionMapper;
 
     @Override
     public DrMonitorSummaryVO getSummary(String startDate, String endDate, List<Long> siteIds) {
-        Integer tenantId = SecurityUtils.getTenantId();
-        LocalDateTime rangeStart = parseStart(startDate);
-        LocalDateTime rangeEnd = parseEnd(endDate);
         requireSiteIds(siteIds);
+        Integer tenantId = SecurityUtils.getTenantId();
+        List<VppDrInvitation> invitations = listMonitorInvitations(
+                parseStart(startDate), parseEnd(endDate), siteIds, tenantId);
 
-        List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
         DrMonitorSummaryVO vo = new DrMonitorSummaryVO();
-        vo.setPeakResourceCount((long) peakResources.size());
-        if (peakResources.isEmpty()) {
+        if (invitations.isEmpty()) {
             return vo;
         }
-
-        Set<Long> resourceIds = peakResources.stream().map(VppResourcePoint::getId).collect(Collectors.toSet());
-        Map<Long, Long> resourceSiteMap = peakResources.stream()
-                .collect(Collectors.toMap(VppResourcePoint::getId, VppResourcePoint::getSiteId, (a, b) -> a));
-
-        List<VppDrParticipation> participations = listParticipations(resourceIds, tenantId);
-        Map<Long, VppDrEvent> eventMap = loadEventMap(participations, tenantId);
-        List<VppDrEvent> rangedEvents = filterEventsByTime(eventMap.values(), rangeStart, rangeEnd);
-
-        Set<Long> countEventIds = rangedEvents.stream()
-                .filter(e -> e.getEventStatus() != null
-                        && (e.getEventStatus() == EVENT_STATUS_EXECUTING || e.getEventStatus() == EVENT_STATUS_ENDED))
-                .map(VppDrEvent::getId)
-                .collect(Collectors.toSet());
-        Set<Long> endedEventIds = rangedEvents.stream()
-                .filter(e -> Objects.equals(e.getEventStatus(), EVENT_STATUS_ENDED))
-                .map(VppDrEvent::getId)
-                .collect(Collectors.toSet());
-
-        // 每个资源点:参与的执行中/已结束事件数
-        Map<Long, Set<Long>> resourceCountEvents = new HashMap<>();
-        // 每个资源点:参与的已结束事件
-        Map<Long, Set<Long>> resourceEndedEvents = new HashMap<>();
-        for (VppDrParticipation p : participations) {
-            if (p.getResourceId() == null || p.getEventId() == null) {
-                continue;
-            }
-            if (countEventIds.contains(p.getEventId())) {
-                resourceCountEvents.computeIfAbsent(p.getResourceId(), k -> new HashSet<>()).add(p.getEventId());
-            }
-            if (endedEventIds.contains(p.getEventId())) {
-                resourceEndedEvents.computeIfAbsent(p.getResourceId(), k -> new HashSet<>()).add(p.getEventId());
-            }
-        }
-
-        Long countSum = 0L;
-        for (VppResourcePoint resource : peakResources) {
-            countSum += resourceCountEvents.getOrDefault(resource.getId(), Collections.emptySet()).size();
-        }
-        vo.setResponseCount(countSum);
-
-        // 邀约按站点:容量与达标
-        Set<Long> peakSiteIds = peakResources.stream().map(VppResourcePoint::getSiteId).collect(Collectors.toSet());
-        List<VppDrInvitation> endedInvitations = listInvitations(endedEventIds, peakSiteIds, tenantId);
-        Map<Long, List<VppDrInvitation>> invitationsBySite = endedInvitations.stream()
-                .filter(i -> i.getSiteId() != null)
-                .collect(Collectors.groupingBy(VppDrInvitation::getSiteId));
-
-        BigDecimal capacitySum = BigDecimal.ZERO;
-        BigDecimal rateSum = BigDecimal.ZERO;
-        int rateResourceCount = 0;
-        for (VppResourcePoint resource : peakResources) {
-            Long siteId = resourceSiteMap.get(resource.getId());
-            Set<Long> endedForResource = resourceEndedEvents.getOrDefault(resource.getId(), Collections.emptySet());
-            List<VppDrInvitation> siteInvitations = invitationsBySite.getOrDefault(siteId, Collections.emptyList())
-                    .stream()
-                    .filter(i -> endedForResource.contains(i.getDrEventId()))
-                    .collect(Collectors.toList());
-
-            BigDecimal resourceCapacity = BigDecimal.ZERO;
-            long qualified = 0;
-            for (VppDrInvitation invitation : siteInvitations) {
-                resourceCapacity = resourceCapacity.add(nz(invitation.getActualResponseCapacityKw()));
-                if (isCompletionQualified(invitation.getResponseCompletionRate())) {
-                    qualified++;
-                }
-            }
-            capacitySum = capacitySum.add(resourceCapacity);
-            if (!siteInvitations.isEmpty()) {
-                rateSum = rateSum.add(BigDecimal.valueOf(qualified)
-                        .divide(BigDecimal.valueOf(siteInvitations.size()), 8, RoundingMode.HALF_UP));
-                rateResourceCount++;
+        int total = invitations.size();
+        BigDecimal capacity = BigDecimal.ZERO;
+        long qualified = 0L;
+        for (VppDrInvitation invitation : invitations) {
+            capacity = capacity.add(nz(invitation.getActualResponseCapacityKw()));
+            if (isCompletionQualified(invitation.getResponseCompletionRate())) {
+                qualified++;
             }
         }
-        vo.setResponseCapacityKw(avg(capacitySum, peakResources.size()));
-        if (rateResourceCount > 0) {
-            vo.setHistoricalQualifiedRate(avg(rateSum, rateResourceCount));
-        }
+        vo.setResponseCount((long) total);
+        vo.setResponseCapacityKw(capacity.setScale(SCALE, RoundingMode.HALF_UP));
+        vo.setHistoricalQualifiedRate(BigDecimal.valueOf(qualified)
+                .divide(BigDecimal.valueOf(total), SCALE, RoundingMode.HALF_UP));
         return vo;
     }
 
     @Override
     public CommonPage<DrMonitorRecordVO> pageRecords(String startDate, String endDate,
                                                      List<Long> siteIds, Map<String, Object> params) {
-        Integer tenantId = SecurityUtils.getTenantId();
-        LocalDateTime rangeStart = parseStart(startDate);
-        LocalDateTime rangeEnd = parseEnd(endDate);
         requireSiteIds(siteIds);
-
-        List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
-        if (peakResources.isEmpty()) {
-            Page<?> page = VppPageHelper.of(params);
+        Integer tenantId = SecurityUtils.getTenantId();
+        List<VppDrInvitation> invitations = listMonitorInvitations(
+                parseStart(startDate), parseEnd(endDate), siteIds, tenantId);
+        Page<?> page = VppPageHelper.of(params);
+        if (invitations.isEmpty()) {
             return new CommonPage<>(Collections.emptyList(), 0L, page.getCurrent(), page.getSize());
         }
 
-        Set<Long> resourceIds = peakResources.stream().map(VppResourcePoint::getId).collect(Collectors.toSet());
-        Set<Long> peakSiteIds = peakResources.stream().map(VppResourcePoint::getSiteId).collect(Collectors.toSet());
-        List<VppDrParticipation> participations = listParticipations(resourceIds, tenantId);
-        Map<Long, VppDrEvent> eventMap = loadEventMap(participations, tenantId);
-
-        List<VppDrEvent> events = filterEventsByTime(eventMap.values(), rangeStart, rangeEnd).stream()
-                .filter(e -> e.getEventStatus() != null
-                        && (e.getEventStatus() == EVENT_STATUS_EXECUTING || e.getEventStatus() == EVENT_STATUS_ENDED))
-                .sorted(Comparator.comparing(VppDrEvent::getStartTime, Comparator.nullsLast(Comparator.reverseOrder())))
-                .collect(Collectors.toList());
-
-        Set<Long> eventIds = events.stream().map(VppDrEvent::getId).collect(Collectors.toSet());
-        List<VppDrInvitation> invitations = listInvitations(eventIds, peakSiteIds, tenantId);
-        Map<Long, List<VppDrInvitation>> byEvent = invitations.stream()
-                .filter(i -> i.getDrEventId() != null)
-                .collect(Collectors.groupingBy(VppDrInvitation::getDrEventId));
-        Map<Long, BigDecimal> subsidyByEvent = loadSubsidyAmountByEvent(eventIds, peakSiteIds, tenantId);
-
-        List<DrMonitorRecordVO> records = events.stream()
-                .map(e -> toRecordVo(e, byEvent.getOrDefault(e.getId(), Collections.emptyList()),
-                        subsidyByEvent.get(e.getId())))
+        Set<Long> eventIds = invitations.stream()
+                .map(VppDrInvitation::getDrEventId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, VppDrEvent> eventMap = loadEventMapByIds(eventIds, tenantId);
+        Map<Long, VppCustomer> customerMap = loadCustomerMap(invitations);
+        Map<Long, VppSite> siteMap = loadSiteMap(invitations, tenantId);
+        Map<Long, BigDecimal> subsidyByEvent = loadSubsidyAmountByEvent(
+                eventIds, new LinkedHashSet<>(siteIds), tenantId);
+
+        List<DrMonitorRecordVO> records = invitations.stream()
+                .sorted(Comparator.comparing(
+                        i -> eventMap.get(i.getDrEventId()) == null
+                                ? null : eventMap.get(i.getDrEventId()).getStartTime(),
+                        Comparator.nullsLast(Comparator.reverseOrder())))
+                .map(inv -> toRecordVo(inv, eventMap.get(inv.getDrEventId()),
+                        customerMap.get(inv.getCustomerId()), siteMap.get(inv.getSiteId()),
+                        subsidyByEvent.get(inv.getDrEventId())))
                 .collect(Collectors.toList());
 
-        Page<?> page = VppPageHelper.of(params);
         long current = page.getCurrent();
         long size = page.getSize();
         int from = (int) Math.min((current - 1) * size, records.size());
@@ -222,96 +155,137 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
     }
 
     @Override
-    public DrMonitorCurveVO getCurve(Long siteId, Integer baselineDays) {
-        if (siteId == null) {
-            throw new BusinessException("站点ID不能为空");
-        }
-        if (baselineDays != null && baselineDays != 3 && baselineDays != 5) {
-            throw new BusinessException("基线负荷仅支持3天或5天维度查询!");
+    public DrMonitorCurveVO getCurve(Long invitationId) {
+        if (invitationId == null) {
+            throw new BusinessException("邀约ID不能为空");
         }
         Integer tenantId = SecurityUtils.getTenantId();
-        List<VppResourcePoint> peakResources = listPeakResources(siteId, tenantId);
-        if (peakResources.isEmpty()) {
-            throw new BusinessException("所选站点下无可参与调峰的资源点!");
+        VppDrInvitation invitation = invitationMapper.selectById(invitationId);
+        if (invitation == null || VppAuditHelper.isDeleted(invitation.getDeleteFlag())) {
+            throw new BusinessException("邀约记录不存在");
+        }
+        if (tenantId != null && invitation.getTenantId() != null && !tenantId.equals(invitation.getTenantId())) {
+            throw new BusinessException("无权操作其他租户的邀约");
+        }
+        if (invitation.getSiteId() == null || invitation.getSiteId() <= 0) {
+            throw new BusinessException("邀约未关联站点");
         }
-        int resourceCount = peakResources.size();
-
-        LocalDate today = LocalDate.now();
-        LocalDateTime windowStart = today.atStartOfDay();
-        LocalDateTime windowEnd = today.atTime(LocalTime.MAX);
-        String responseStartTime = windowStart.format(DATE_TIME_FMT);
-        String responseEndTime = windowEnd.format(DATE_TIME_FMT);
-        String actualEndTime = windowEnd.format(DATE_TIME_FMT);
-        int requiredDays = (baselineDays != null && baselineDays == 3) ? 3 : 5;
-
-        BigDecimal actualAvg = avg(resolveSiteActualResponseKw(
-                siteId, responseStartTime, responseEndTime, BigDecimal.ZERO), resourceCount);
 
-        SiteBaselineVO baseline = baselineService.getSiteBaseline(
-                siteId, responseStartTime, requiredDays, actualEndTime);
+        VppDrEvent event = invitation.getDrEventId() != null
+                ? eventMapper.selectById(invitation.getDrEventId()) : null;
+        if (event != null && VppAuditHelper.isDeleted(event.getDeleteFlag())) {
+            event = null;
+        }
+        String responseStartTime = resolveInvitationResponseStartTime(invitation, event);
+        SiteBaselineVO baseline = baselineService.getSiteBaseline(invitation.getSiteId(), responseStartTime);
+        BigDecimal actualKw = nz(invitation.getActualResponseCapacityKw()).setScale(SCALE, RoundingMode.HALF_UP);
 
         DrMonitorCurveVO curve = new DrMonitorCurveVO();
-        curve.setResponseDate(today.format(DATE_FMT));
-        curve.setSiteId(siteId);
-        curve.setActualResponseCapacityKw(actualAvg);
+        curve.setId(invitation.getId());
+        curve.setSiteId(invitation.getSiteId());
+        curve.setActualResponseCapacityKw(actualKw);
+        if (invitation.getExecuteStartDate() != null) {
+            curve.setResponseDate(invitation.getExecuteStartDate().format(DATE_FMT));
+        } else if (event != null && event.getStartTime() != null) {
+            curve.setResponseDate(event.getStartTime().toLocalDate().format(DATE_FMT));
+        }
+        if (event != null) {
+            curve.setEventId(event.getEventId());
+        }
         if (baseline != null && StringUtils.hasText(baseline.getDataSource())) {
             curve.setDataSource(baseline.getDataSource());
         }
-        // if (dataSources.isEmpty()) {
-        //     curve.setDataSource("mock");
-        // }
-
         if (baseline == null || CollectionUtils.isEmpty(baseline.getPoints())) {
             return curve;
         }
         for (BaselinePointVO point : baseline.getPoints()) {
-            if (!StringUtils.hasText(point.getTime()) || !inCurveWindow(point.getTime(), windowStart, windowEnd)) {
+            if (!StringUtils.hasText(point.getTime())) {
                 continue;
             }
             DrMonitorCurveVO.Point curvePoint = new DrMonitorCurveVO.Point();
             curvePoint.setTime(point.getTime());
             curvePoint.setBaselineKw(nz(point.getPredictedBaselineKw()).setScale(SCALE, RoundingMode.HALF_UP));
-            curvePoint.setActualResponseCapacityKw(actualAvg);
+            curvePoint.setActualResponseCapacityKw(actualKw);
             curve.getPoints().add(curvePoint);
         }
         return curve;
     }
 
-    /**
-     * 实际响应容量复用申报容量链路 {@link VppBaselineService#getSiteDeclaredCapacity},
-     * 取预估响应容量;无数据时回退邀约实际响应容量。
-     */
-    private BigDecimal resolveSiteActualResponseKw(Long siteId, String responseStartTime,
-                                                   String responseEndTime, BigDecimal fallbackKw) {
-        if (siteId == null || !StringUtils.hasText(responseStartTime) || !StringUtils.hasText(responseEndTime)) {
-            return nz(fallbackKw);
+    @Override
+    public List<DrMonitorSiteResourceVO> listEventSitePeakResources(Long eventId) {
+        if (eventId == null) {
+            throw new BusinessException("事件ID不能为空");
         }
-        try {
-            SiteDeclaredCapacityVO capacity = baselineService.getSiteDeclaredCapacity(
-                    siteId, responseStartTime, responseEndTime);
-            if (capacity != null && capacity.getEstimatedResponseCapacityKw() != null
-                    && capacity.getEstimatedResponseCapacityKw().compareTo(BigDecimal.ZERO) > 0) {
-                return capacity.getEstimatedResponseCapacityKw();
-            }
-        } catch (Exception ignored) {
-            // 回退邀约实际响应容量,保持曲线可返回
+        Integer tenantId = SecurityUtils.getTenantId();
+        VppDrEvent event = eventMapper.selectById(eventId);
+        if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
+            throw new BusinessException("需求响应事件不存在");
+        }
+        if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
+            throw new BusinessException("无权操作其他租户的事件");
         }
-        return nz(fallbackKw);
-    }
 
-    private boolean inCurveWindow(String timeKey, LocalDateTime windowStart, LocalDateTime windowEnd) {
-        try {
-            LocalTime t = LocalTime.parse(timeKey.length() == 5 ? timeKey : timeKey.substring(0, 5));
-            LocalDate date = windowStart.toLocalDate();
-            LocalDateTime point = LocalDateTime.of(date, t);
-            // 跨日窗口兜底:若点早于 windowStart 且 windowEnd 跨日,放到次日
-            if (point.isBefore(windowStart) && windowEnd.toLocalDate().isAfter(date)) {
-                point = point.plusDays(1);
+        List<VppDrInvitation> invitations = invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
+                .eq(VppDrInvitation::getDrEventId, eventId)
+                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
+        Set<Long> siteIds = invitations.stream()
+                .map(VppDrInvitation::getSiteId)
+                .filter(id -> id != null && id > 0)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (siteIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<Long, VppSite> siteMap = siteMapper.selectList(new LambdaQueryWrapper<VppSite>()
+                        .in(VppSite::getId, siteIds)
+                        .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                        .eq(tenantId != null, VppSite::getTenantId, tenantId))
+                .stream()
+                .collect(Collectors.toMap(VppSite::getId, s -> s, (a, b) -> a));
+
+        List<VppResourcePoint> peakResources = listPeakResources(new ArrayList<>(siteIds), tenantId);
+        Map<Long, List<VppResourcePoint>> resourcesBySite = peakResources.stream()
+                .filter(r -> r.getSiteId() != null)
+                .collect(Collectors.groupingBy(VppResourcePoint::getSiteId));
+
+        List<DrMonitorSiteResourceVO> result = new ArrayList<>();
+        for (Long siteId : siteIds) {
+            VppSite site = siteMap.get(siteId);
+            if (site == null) {
+                continue;
             }
-            return !point.isBefore(windowStart) && !point.isAfter(windowEnd);
-        } catch (Exception ex) {
-            return true;
+            DrMonitorSiteResourceVO vo = new DrMonitorSiteResourceVO();
+            vo.setSiteId(site.getId());
+            vo.setSiteCode(site.getSiteCode());
+            vo.setSiteName(site.getSiteName());
+            List<VppResourcePoint> siteResources = resourcesBySite.getOrDefault(siteId, Collections.emptyList())
+                    .stream()
+                    .sorted(Comparator.comparing(VppResourcePoint::getId, Comparator.nullsLast(Long::compareTo)))
+                    .collect(Collectors.toList());
+            for (VppResourcePoint resource : siteResources) {
+                vo.getResources().add(toResourceItem(resource));
+            }
+            result.add(vo);
         }
+        return result;
+    }
+
+    private static DrMonitorSiteResourceVO.ResourceItem toResourceItem(VppResourcePoint resource) {
+        DrMonitorSiteResourceVO.ResourceItem item = new DrMonitorSiteResourceVO.ResourceItem();
+        item.setResourceId(resource.getId());
+        item.setResourceCode(resource.getResourceCode());
+        item.setResourceName(resource.getResourceName());
+        item.setResourceType(resource.getResourceType());
+        item.setResourceTypeLabel(VppResourceTypeHelper.resourceTypeLabel(resource.getResourceType()));
+        item.setCapacityKw(resource.getCapacityKw());
+        item.setIsControl(resource.getIsControl());
+        item.setIsSupportPeak(resource.getIsSupportPeak());
+        item.setMaxUpKw(resource.getMaxUpKw());
+        item.setMinDownKw(resource.getMinDownKw());
+        item.setIsSupportFm(resource.getIsSupportFm());
+        item.setRemark(resource.getRemark());
+        return item;
     }
 
     private Map<Long, BigDecimal> loadSubsidyAmountByEvent(Set<Long> eventIds, Set<Long> siteIds, Integer tenantId) {
@@ -343,40 +317,34 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
         }
     }
 
-    private List<VppResourcePoint> listPeakResources(Long siteId, Integer tenantId) {
-        return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
-                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
-                .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
-                .eq(VppResourcePoint::getSiteId, siteId)
-                .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId));
-    }
-
-    private List<VppResourcePoint> listPeakResources(List<Long> siteIds, Integer tenantId) {
-        return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
-                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
-                .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
-                .in(VppResourcePoint::getSiteId, siteIds)
-                .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId)
-                .isNotNull(VppResourcePoint::getSiteId));
-    }
-
-    private List<VppDrParticipation> listParticipations(Set<Long> resourceIds, Integer tenantId) {
-        if (CollectionUtils.isEmpty(resourceIds)) {
+    /**
+     * 时间区间内事件开始、结束均落在区间,且状态为执行中/已结束;再关联参与且执行中/已结束的未删除邀约。
+     */
+    private List<VppDrInvitation> listMonitorInvitations(LocalDateTime rangeStart, LocalDateTime rangeEnd,
+                                                         List<Long> siteIds, Integer tenantId) {
+        List<VppDrEvent> events = eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
+                .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .in(VppDrEvent::getEventStatus, EVENT_STATUS_EXECUTING, EVENT_STATUS_ENDED)
+                .ge(VppDrEvent::getStartTime, rangeStart)
+                .le(VppDrEvent::getEndTime, rangeEnd)
+                .isNotNull(VppDrEvent::getStartTime)
+                .isNotNull(VppDrEvent::getEndTime)
+                .eq(tenantId != null, VppDrEvent::getTenantId, tenantId));
+        if (events.isEmpty()) {
             return Collections.emptyList();
         }
-        return participationMapper.selectList(new LambdaQueryWrapper<VppDrParticipation>()
-                .in(VppDrParticipation::getResourceId, resourceIds)
-                .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
-                .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_ACCEPT)
-                .eq(tenantId != null, VppDrParticipation::getTenantId, tenantId));
+        Set<Long> eventIds = events.stream().map(VppDrEvent::getId).collect(Collectors.toSet());
+        return invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
+                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .in(VppDrInvitation::getDrEventId, eventIds)
+                .in(VppDrInvitation::getSiteId, siteIds)
+                .eq(VppDrInvitation::getReplyStatus, REPLY_ACCEPT)
+                .in(VppDrInvitation::getResponseStatus, EVENT_STATUS_EXECUTING, EVENT_STATUS_ENDED)
+                .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
     }
 
-    private Map<Long, VppDrEvent> loadEventMap(List<VppDrParticipation> participations, Integer tenantId) {
-        Set<Long> eventIds = participations.stream()
-                .map(VppDrParticipation::getEventId)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet());
-        if (eventIds.isEmpty()) {
+    private Map<Long, VppDrEvent> loadEventMapByIds(Set<Long> eventIds, Integer tenantId) {
+        if (CollectionUtils.isEmpty(eventIds)) {
             return Collections.emptyMap();
         }
         return eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
@@ -387,63 +355,80 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
                 .collect(Collectors.toMap(VppDrEvent::getId, e -> e, (a, b) -> a));
     }
 
-    private List<VppDrEvent> filterEventsByTime(Iterable<VppDrEvent> events,
-                                                LocalDateTime rangeStart, LocalDateTime rangeEnd) {
-        List<VppDrEvent> result = new ArrayList<>();
-        for (VppDrEvent event : events) {
-            if (event.getStartTime() == null) {
-                continue;
-            }
-            if (rangeStart != null && event.getStartTime().isBefore(rangeStart)) {
-                continue;
-            }
-            if (rangeEnd != null && event.getStartTime().isAfter(rangeEnd)) {
-                continue;
-            }
-            result.add(event);
+    private Map<Long, VppCustomer> loadCustomerMap(List<VppDrInvitation> invitations) {
+        Set<Long> customerIds = invitations.stream()
+                .map(VppDrInvitation::getCustomerId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (customerIds.isEmpty()) {
+            return Collections.emptyMap();
         }
-        return result;
+        return customerMapper.selectList(new LambdaQueryWrapper<VppCustomer>()
+                        .in(VppCustomer::getId, customerIds)
+                        .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED))
+                .stream()
+                .collect(Collectors.toMap(VppCustomer::getId, c -> c, (a, b) -> a));
     }
 
-    private List<VppDrInvitation> listInvitations(Set<Long> eventIds, Set<Long> siteIds, Integer tenantId) {
-        if (CollectionUtils.isEmpty(eventIds) || CollectionUtils.isEmpty(siteIds)) {
-            return Collections.emptyList();
+    private Map<Long, VppSite> loadSiteMap(List<VppDrInvitation> invitations, Integer tenantId) {
+        Set<Long> siteIds = invitations.stream()
+                .map(VppDrInvitation::getSiteId)
+                .filter(id -> id != null && id > 0)
+                .collect(Collectors.toSet());
+        if (siteIds.isEmpty()) {
+            return Collections.emptyMap();
         }
-        return invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
-                .in(VppDrInvitation::getDrEventId, eventIds)
-                .in(VppDrInvitation::getSiteId, siteIds)
-                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
-                .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
+        return siteMapper.selectList(new LambdaQueryWrapper<VppSite>()
+                        .in(VppSite::getId, siteIds)
+                        .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                        .eq(tenantId != null, VppSite::getTenantId, tenantId))
+                .stream()
+                .collect(Collectors.toMap(VppSite::getId, s -> s, (a, b) -> a));
     }
 
-    private DrMonitorRecordVO toRecordVo(VppDrEvent event, List<VppDrInvitation> invitations,
+    private DrMonitorRecordVO toRecordVo(VppDrInvitation invitation, VppDrEvent event,
+                                         VppCustomer customer, VppSite site,
                                          BigDecimal estimatedSubsidyAmount) {
         DrMonitorRecordVO vo = new DrMonitorRecordVO();
-        BeanUtils.copyProperties(event, vo);
+        if (event != null) {
+            BeanUtils.copyProperties(event, vo);
+        }
+        vo.setInvitationId(invitation.getId());
+        vo.setCustomerId(invitation.getCustomerId());
+        if (customer != null) {
+            vo.setCustomerName(customer.getCustomerName());
+        }
+        vo.setSiteId(invitation.getSiteId());
+        if (site != null) {
+            vo.setSiteName(site.getSiteName());
+        }
+        vo.setTargetCapacityKw(invitation.getDeclaredCapacityKw());
+        vo.setClearedCapacityKw(invitation.getActualResponseCapacityKw());
+        vo.setResponseCompletionRate(invitation.getResponseCompletionRate());
+        vo.setQualified(isCompletionQualified(invitation.getResponseCompletionRate()));
         vo.setEstimatedSubsidyAmount(estimatedSubsidyAmount);
-        if (CollectionUtils.isEmpty(invitations)) {
-            vo.setResponseCompletionRate(null);
-            vo.setQualified(false);
-            return vo;
-        }
-        BigDecimal sum = BigDecimal.ZERO;
-        int rateCount = 0;
-        boolean allQualified = true;
-        for (VppDrInvitation invitation : invitations) {
-            BigDecimal rate = invitation.getResponseCompletionRate();
-            if (rate != null) {
-                sum = sum.add(rate);
-                rateCount++;
-            }
-            if (!isCompletionQualified(rate)) {
-                allQualified = false;
-            }
+        return vo;
+    }
+
+    private String resolveInvitationResponseStartTime(VppDrInvitation invitation, VppDrEvent event) {
+        if (invitation.getExecuteStartDate() != null) {
+            LocalTime timeOfDay = (event != null && event.getStartTime() != null)
+                    ? event.getStartTime().toLocalTime() : LocalTime.MIN;
+            return LocalDateTime.of(invitation.getExecuteStartDate(), timeOfDay).format(DATE_TIME_FMT);
         }
-        if (rateCount > 0) {
-            vo.setResponseCompletionRate(sum.divide(BigDecimal.valueOf(rateCount), SCALE, RoundingMode.HALF_UP));
+        if (event != null && event.getStartTime() != null) {
+            return event.getStartTime().format(DATE_TIME_FMT);
         }
-        vo.setQualified(allQualified && rateCount == invitations.size());
-        return vo;
+        throw new BusinessException("邀约缺少响应时间");
+    }
+
+    private List<VppResourcePoint> listPeakResources(List<Long> siteIds, Integer tenantId) {
+        return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
+                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
+                .in(VppResourcePoint::getSiteId, siteIds)
+                .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId)
+                .isNotNull(VppResourcePoint::getSiteId));
     }
 
     static boolean isCompletionQualified(BigDecimal rate) {
@@ -451,15 +436,10 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
             return false;
         }
         BigDecimal scaled = rate.setScale(2, RoundingMode.HALF_UP);
-        return scaled.compareTo(ONE.setScale(2, RoundingMode.HALF_UP)) == 0
-                || scaled.compareTo(HUNDRED.setScale(2, RoundingMode.HALF_UP)) == 0;
-    }
-
-    private static BigDecimal avg(BigDecimal sum, int divisor) {
-        if (divisor <= 0) {
-            return BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
+        if (scaled.compareTo(ONE) > 0) {
+            return scaled.compareTo(HUNDRED) >= 0;
         }
-        return nz(sum).divide(BigDecimal.valueOf(divisor), SCALE, RoundingMode.HALF_UP);
+        return scaled.compareTo(ONE) >= 0;
     }
 
     private static LocalDateTime parseStart(String date) {

+ 35 - 5
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppDrServiceImpl.java

@@ -4,6 +4,8 @@ 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.common.security.utils.SecurityUtils;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.domain.VppCustomer;
 import com.usky.vpp.domain.VppDrEvaluation;
 import com.usky.vpp.domain.VppDrEvent;
@@ -26,6 +28,7 @@ import com.usky.vpp.service.VppUnIntegrationService;
 import com.usky.vpp.service.vo.DrClearingRequest;
 import com.usky.vpp.service.vo.DrEventDetailVO;
 import com.usky.vpp.service.vo.DrEventRequest;
+import com.usky.vpp.service.vo.DrEventStatusRequest;
 import com.usky.vpp.service.vo.DrParticipationVO;
 import com.usky.vpp.service.vo.DrInterveneRequest;
 import com.usky.vpp.service.vo.DrParticipateRequest;
@@ -53,11 +56,11 @@ public class VppDrServiceImpl implements VppDrService {
 
     private static final Logger log = LoggerFactory.getLogger(VppDrServiceImpl.class);
 
-    private static final int EVENT_STATUS_PENDING = 0;
-    private static final int EVENT_STATUS_DECLARED = 1;
-    private static final int EVENT_STATUS_EXECUTING = 2;
-    private static final int EVENT_STATUS_ENDED = 3;
-    private static final int EVENT_STATUS_CANCELLED = 4;
+    private static final int EVENT_STATUS_PENDING = VppDrEventStatus.PENDING;
+    private static final int EVENT_STATUS_DECLARED = VppDrEventStatus.DECLARED;
+    private static final int EVENT_STATUS_EXECUTING = VppDrEventStatus.EXECUTING;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
+    private static final int EVENT_STATUS_CANCELLED = VppDrEventStatus.CANCELLED;
 
     private static final int PARTICIPATE_STATUS_ACCEPT = 1;
 
@@ -194,6 +197,33 @@ public class VppDrServiceImpl implements VppDrService {
         eventMapper.updateById(event);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateEventStatus(DrEventStatusRequest request) {
+        if (request == null || request.getId() == null) {
+            throw new BusinessException("事件ID不能为空");
+        }
+        if (request.getEventStatus() == null) {
+            throw new BusinessException("事件状态不能为空");
+        }
+        if (request.getEventStatus() != EVENT_STATUS_DECLARED) {
+            throw new BusinessException("仅允许将事件从待参与改为已申报");
+        }
+
+        VppDrEvent event = requireEvent(request.getId());
+        Integer tenantId = SecurityUtils.getTenantId();
+        if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
+            throw new BusinessException("无权操作其他租户的事件");
+        }
+        if (event.getEventStatus() == null || event.getEventStatus() != EVENT_STATUS_PENDING) {
+            throw new BusinessException("仅待参与状态的事件可改为已申报");
+        }
+
+        event.setEventStatus(EVENT_STATUS_DECLARED);
+        VppAuditHelper.fillUpdate(event);
+        eventMapper.updateById(event);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void deleteEvent(Long id) {

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

@@ -2,6 +2,7 @@ package com.usky.vpp.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.common.core.exception.BusinessException;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.constant.VppTsdbConstants;
 import com.usky.vpp.domain.VppCustomer;
 import com.usky.vpp.domain.VppDevice;
@@ -57,7 +58,7 @@ import java.util.stream.Collectors;
 public class VppResponseMonitorServiceImpl implements VppResponseMonitorService {
 
     private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-    private static final int EVENT_STATUS_ENDED = 3;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
 
     @Autowired
     private VppSiteMapper siteMapper;

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

@@ -3,6 +3,7 @@ package com.usky.vpp.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.usky.common.security.utils.SecurityUtils;
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.domain.VppDevice;
 import com.usky.vpp.domain.VppDrEvent;
 import com.usky.vpp.domain.VppDrParticipation;
@@ -40,7 +41,7 @@ public class VppSiteCompletionRateTaskServiceImpl implements VppSiteCompletionRa
 
     private static final Logger log = LoggerFactory.getLogger(VppSiteCompletionRateTaskServiceImpl.class);
 
-    private static final int EVENT_STATUS_ENDED = 3;
+    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
     private static final int SITE_STATUS_ONLINE = 0;
     private static final int SITE_STATUS_OFFLINE = 1;
     private static final int SITE_STATUS_FAULT = 2;

+ 1 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrEventDetailVO.java

@@ -36,6 +36,7 @@ public class DrEventDetailVO {
     private BigDecimal subsidyPrice;
     /** 下浮系数 */
     private BigDecimal floatingCoefficient;
+    /** 0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消 */
     private Integer eventStatus;
     private List<DrParticipationVO> participations;
 }

+ 19 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrEventStatusRequest.java

@@ -0,0 +1,19 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+/**
+ * 修改需求响应事件状态
+ */
+@Data
+public class DrEventStatusRequest {
+
+    /** 事件表主键 id */
+    private Long id;
+
+    /**
+     * 目标状态。当前仅允许 1(已申报),且原状态必须为 0(待参与)。
+     * 0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消
+     */
+    private Integer eventStatus;
+}

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

@@ -15,6 +15,8 @@ public class DrInvitationVO {
     private Long id;
     private String invitationNo;
     private Long drEventId;
+    /** 事件业务编号(vpp_dr_event.event_id) */
+    private String eventId;
     private Long customerId;
     private String customerName;
     private LocalDate executeStartDate;
@@ -37,6 +39,7 @@ public class DrInvitationVO {
     /** 是否中标 0否 1是 */
     private Integer isWinningBid;
     private Integer replyStatus;
+    /** 0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消 */
     private Integer responseStatus;
     private Integer smsNotifyStatus;
     private LocalDateTime smsNotifyAt;

+ 13 - 5
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrMonitorRecordVO.java

@@ -7,14 +7,21 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 响应监测 - 响应记录(按 vpp_dr_event 实际字段 + 完成率
+ * 响应监测 - 响应记录(事件字段 + 邀约维度补充
  */
 @Data
 public class DrMonitorRecordVO {
 
+    /** 事件主键 */
     private Long id;
+    /** 邀约主键(曲线查询入参) */
+    private Long invitationId;
     private String eventId;
     private String eventName;
+    private Long customerId;
+    private String customerName;
+    private Long siteId;
+    private String siteName;
     private Integer responseType;
     private Integer eventType;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -28,16 +35,17 @@ public class DrMonitorRecordVO {
     /** 邀约范围 */
     private String inviteScope;
 
-    /** 邀约容量 */
+    /** 目标容量:邀约申报出清容量 declared_capacity_kw */
     private BigDecimal targetCapacityKw;
 
-    /** 出清容量 */
+    /** 出清容量:邀约实际响应容量 actual_response_capacity_kw */
     private BigDecimal clearedCapacityKw;
     private String remark;
     private String attachmentUrl;
     private BigDecimal subsidyPrice;
     /** 下浮系数 */
     private BigDecimal floatingCoefficient;
+    /** 0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消 */
     private Integer eventStatus;
     private String rawPayload;
     private Integer tenantId;
@@ -48,9 +56,9 @@ public class DrMonitorRecordVO {
     private String createdBy;
     private String updatedBy;
 
-    /** 关联邀约完成率均值(0~1) */
+    /** 邀约响应完成率 */
     private BigDecimal responseCompletionRate;
-    /** 是否达标:关联邀约 response_completion_rate = 100% */
+    /** 是否达标:完成率 >= 100% */
     private Boolean qualified;
     /** 补贴金额(预测) */
     private BigDecimal estimatedSubsidyAmount;

+ 33 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrMonitorSiteResourceVO.java

@@ -0,0 +1,33 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 响应监测 - 事件下邀约站点及其可调峰资源点(站点 → 资源点)
+ */
+@Data
+public class DrMonitorSiteResourceVO {
+
+    private Long siteId;
+    private String siteCode;
+    private String siteName;
+    private List<ResourceItem> resources = new ArrayList<>();
+
+    @Data
+    public static class ResourceItem {
+        private Long resourceId;
+        private String resourceCode;
+        private String resourceName;
+        private String resourceType;
+        private String resourceTypeLabel;
+        private BigDecimal capacityKw;
+        private Integer isControl;
+        private Integer isSupportPeak;
+        private BigDecimal maxUpKw;
+        private BigDecimal minDownKw;
+    }
+}

+ 4 - 6
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/DrMonitorSummaryVO.java

@@ -5,17 +5,15 @@ import lombok.Data;
 import java.math.BigDecimal;
 
 /**
- * 响应监测 - 顶部 KPI(按可调峰资源点平均
+ * 响应监测 - 顶部 KPI(按符合条件的邀约条数统计
  */
 @Data
 public class DrMonitorSummaryVO {
 
-    /** 可调峰资源点数(分母) */
-    private Long peakResourceCount = 0L;
-    /** 响应次数(执行中+已结束,按资源点平均) */
+    /** 响应次数(符合条件的邀约总条数) */
     private Long responseCount = 0L;
-    /** 响应容量 kW(仅已结束,按资源点平均) */
+    /** 响应容量 kW(actual_response_capacity_kw 累加) */
     private BigDecimal responseCapacityKw = BigDecimal.ZERO;
-    /** 历史达标率 0~1(已结束,按资源点平均) */
+    /** 历史达标率 0~1(完成率 >= 100% 的条数 / 总条数) */
     private BigDecimal historicalQualifiedRate = BigDecimal.ZERO;
 }

+ 4 - 3
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnEventParser.java

@@ -1,5 +1,6 @@
 package com.usky.vpp.util;
 
+import com.usky.vpp.constant.VppDrEventStatus;
 import com.usky.vpp.enums.VppUnEventPhase;
 import org.springframework.util.StringUtils;
 
@@ -130,11 +131,11 @@ public final class VppUnEventParser {
         }
         switch (status.trim().toLowerCase()) {
             case "cancelled":
-                return 4;
+                return VppDrEventStatus.CANCELLED;
             case "completed":
-                return 3;
+                return VppDrEventStatus.ENDED;
             case "active":
-                return 2;
+                return VppDrEventStatus.EXECUTING;
             case "far":
             case "near":
             case "none":

+ 4 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnPayloadHelper.java

@@ -1,5 +1,6 @@
 package com.usky.vpp.util;
 
+import com.usky.vpp.constant.VppDrEventStatus;
 import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
@@ -205,7 +206,7 @@ public final class VppUnPayloadHelper {
 
     public static boolean isCancelled(Map<String, Object> payload) {
         Integer status = getInteger(payload, "eventStatus", "event_status", "status");
-        if (status != null && status == 4) {
+        if (status != null && (status == 4 || status == VppDrEventStatus.CANCELLED)) {
             return true;
         }
         String text = getString(payload, "eventStatus", "event_status", "status", "eventState");
@@ -213,7 +214,8 @@ public final class VppUnPayloadHelper {
             return false;
         }
         text = text.trim().toUpperCase();
-        return text.contains("取消") || "CANCELLED".equals(text) || "CANCELED".equals(text) || "4".equals(text);
+        return text.contains("取消") || "CANCELLED".equals(text) || "CANCELED".equals(text)
+                || "4".equals(text) || "5".equals(text);
     }
 
     private static Object getValue(Map<String, Object> map, String... keys) {

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

@@ -453,7 +453,7 @@ CREATE TABLE `vpp_dr_event` (
     `attachment_name` VARCHAR(200) NULL COMMENT '附件名称',
     `subsidy_price` DECIMAL(10,4) NULL COMMENT '补贴标准 元/kWh',
     `floating_coefficient` DECIMAL(10,4) NULL COMMENT '下浮系数',
-    `event_status` TINYINT NOT NULL COMMENT '0待参与 1已申报 2执行中 3已结束 4已取消',
+    `event_status` TINYINT NOT NULL COMMENT '0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消',
     `raw_payload` JSON NULL COMMENT '原始邀约报文',
     `tenant_id` INT NULL COMMENT '租户ID',
     `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
@@ -509,7 +509,7 @@ CREATE TABLE `vpp_dr_invitation` (
     `response_completion_rate` DECIMAL(12,4) NULL COMMENT '响应完成率=实际响应容量/邀约规模',
     `is_winning_bid` TINYINT NULL COMMENT '是否中标 0否 1是',
     `reply_status` TINYINT NOT NULL COMMENT '0待回复 1参与 2拒绝 3超时',
-    `response_status` TINYINT NOT NULL COMMENT '0待响应 1已申报 2执行中 3已结束 4已取消',
+    `response_status` TINYINT NOT NULL COMMENT '0待参与 1已申报 2申报完成 3执行中 4已结束 5已取消',
     `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',