Przeglądaj źródła

优化出清通知和出清结果的逻辑,如果接收到平台返回的用户和量和我们上报的用户和量一致,那就申报成功,否则状态继续为末位分拆

james 1 dzień temu
rodzic
commit
82e8453e5b

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

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

+ 2 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/enums/VppUnEventPhase.java

@@ -9,9 +9,9 @@ public enum VppUnEventPhase {
     INVITATION,
     /** 申报结果反馈:filing=true,target.resources 含申报量 */
     DECLARE_FEEDBACK,
-    /** 末位分拆通知:filing=false,lastFiling=true,resources 为系统出清量 */
+    /** 末位分拆通知:系统出清量与本地出清容量不一致 */
     SPLIT_NOTICE,
-    /** 末位分拆申报结果反馈 */
+    /** 末位分拆申报结果反馈:各 account 上报量与站点资源点出清容量之和一致 */
     SPLIT_RESULT,
     /** 出清公示:target.resources 含最终出清量 */
     CLEARING_PUBLICITY,

+ 17 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppUnEventPhaseDetector.java

@@ -0,0 +1,17 @@
+package com.usky.vpp.service;
+
+import com.usky.vpp.enums.VppUnEventPhase;
+
+import java.util.Map;
+
+/**
+ * 运管平台事件阶段识别(含需数据库比对的末位分拆阶段)
+ */
+public interface VppUnEventPhaseDetector {
+
+    /**
+     * @param event     UN Event 报文节点
+     * @param drEventId 本地 vpp_dr_event.id,末位分拆比对出清容量时使用;可为 null
+     */
+    VppUnEventPhase detectPhase(Map<String, Object> event, Long drEventId);
+}

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

@@ -14,6 +14,7 @@ import com.usky.vpp.service.VppDrEventIngestService;
 import com.usky.vpp.service.VppDrExecutionBootstrapService;
 import com.usky.vpp.service.VppDrUnInvitationIngestService;
 import com.usky.vpp.service.VppUnDrSyncService;
+import com.usky.vpp.service.VppUnEventPhaseDetector;
 import com.usky.vpp.util.VppAuditHelper;
 import com.usky.vpp.util.VppUnEventParser;
 import com.usky.vpp.util.VppUnPayloadHelper;
@@ -52,6 +53,8 @@ public class VppDrEventIngestServiceImpl implements VppDrEventIngestService {
     @Autowired
     private VppDrUnInvitationIngestService unInvitationIngestService;
     @Autowired
+    private VppUnEventPhaseDetector eventPhaseDetector;
+    @Autowired
     private ObjectMapper objectMapper;
 
     @Override
@@ -166,8 +169,8 @@ public class VppDrEventIngestServiceImpl implements VppDrEventIngestService {
             return cancelEvent(findByPlatformEventId(platformEventId), platformEventId, rawBody, source);
         }
 
-        VppUnEventPhase phase = VppUnEventParser.detectPhase(eventMap);
         VppDrEvent event = findByPlatformEventId(platformEventId);
+        VppUnEventPhase phase = eventPhaseDetector.detectPhase(eventMap, event != null ? event.getId() : null);
         if (event == null) {
             event = new VppDrEvent();
             event.setEventId(platformEventId);
@@ -211,7 +214,7 @@ public class VppDrEventIngestServiceImpl implements VppDrEventIngestService {
             case SPLIT_NOTICE:
                 event.setEventStatus(EVENT_STATUS_SPLITTING);
                 event.setTargetCapacityKw(VppUnEventParser.sumResourceLoadKw(resources, true));
-                deleteEventParticipations(event);
+//                deleteEventParticipations(event);
                 break;
             case SPLIT_RESULT:
                 event.setEventStatus(EVENT_STATUS_DECLARED);

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

@@ -491,9 +491,9 @@ public class VppDrServiceImpl implements VppDrService {
     public void clearing(Long eventId, DrClearingRequest request) {
         VppDrEvent event = requireEvent(eventId);
 
-        if (event.getEventStatus() != EVENT_STATUS_DECLARED) {
-            throw new BusinessException("当前状态不允许出清分拆");
-        }
+//        if (event.getEventStatus() != EVENT_STATUS_DECLARED) {
+//            throw new BusinessException("当前状态不允许出清分拆");
+//        }
 
         if (request == null || request.getTotalClearedCapacityKw() == null) {
             throw new BusinessException("出清总容量不能为空");
@@ -503,14 +503,14 @@ public class VppDrServiceImpl implements VppDrService {
             throw new BusinessException("资源分拆列表不能为空");
         }
 
-        long existingExecCount = executionMapper.selectCount(
-                new LambdaQueryWrapper<VppDrExecution>()
-                        .eq(VppDrExecution::getEventId, eventId)
-                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED)
-        );
-        if (existingExecCount > 0) {
-            throw new BusinessException("已出清,请勿重复操作");
-        }
+//        long existingExecCount = executionMapper.selectCount(
+//                new LambdaQueryWrapper<VppDrExecution>()
+//                        .eq(VppDrExecution::getEventId, eventId)
+//                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+//        );
+//        if (existingExecCount > 0) {
+//            throw new BusinessException("已出清,请勿重复操作");
+//        }
 
         BigDecimal totalCleared = BigDecimal.ZERO;
         for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
@@ -549,7 +549,7 @@ public class VppDrServiceImpl implements VppDrService {
         }
 
         event.setClearedCapacityKw(request.getTotalClearedCapacityKw());
-        event.setEventStatus(EVENT_STATUS_EXECUTING);
+//        event.setEventStatus(EVENT_STATUS_EXECUTING);
         VppAuditHelper.fillUpdate(event);
         eventMapper.updateById(event);
 

+ 135 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppUnEventPhaseDetectorImpl.java

@@ -0,0 +1,135 @@
+package com.usky.vpp.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.vpp.domain.VppDrParticipation;
+import com.usky.vpp.domain.VppResourcePoint;
+import com.usky.vpp.domain.VppSite;
+import com.usky.vpp.enums.VppUnEventPhase;
+import com.usky.vpp.mapper.VppDrParticipationMapper;
+import com.usky.vpp.mapper.VppResourcePointMapper;
+import com.usky.vpp.mapper.VppSiteMapper;
+import com.usky.vpp.service.VppUnEventPhaseDetector;
+import com.usky.vpp.util.VppAuditHelper;
+import com.usky.vpp.util.VppUnEventParser;
+import com.usky.vpp.util.VppUnPayloadHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Service
+public class VppUnEventPhaseDetectorImpl implements VppUnEventPhaseDetector {
+
+    private static final int CAPACITY_SCALE = 4;
+
+    @Autowired
+    private VppSiteMapper siteMapper;
+    @Autowired
+    private VppResourcePointMapper resourcePointMapper;
+    @Autowired
+    private VppDrParticipationMapper participationMapper;
+
+    @Override
+    public VppUnEventPhase detectPhase(Map<String, Object> event, Long drEventId) {
+        Map<String, Object> descriptor = VppUnEventParser.getDescriptor(event);
+        Boolean filing = VppUnPayloadHelper.getBoolean(descriptor, "filing");
+        Boolean lastFiling = VppUnPayloadHelper.getBoolean(descriptor, "lastFiling");
+        List<Map<String, Object>> resources = VppUnEventParser.getTargetResources(event);
+        boolean hasValues = VppUnEventParser.hasResourceValues(resources);
+
+        if (Boolean.TRUE.equals(filing) && !hasValues) {
+            return VppUnEventPhase.INVITATION;
+        }
+        if (Boolean.TRUE.equals(filing) && hasValues) {
+            return VppUnEventPhase.DECLARE_FEEDBACK;
+        }
+        if (Boolean.FALSE.equals(filing) && Boolean.TRUE.equals(lastFiling)) {
+            return resolveSplitPhase(drEventId, resources);
+        }
+        if (hasValues) {
+            return VppUnEventPhase.CLEARING_PUBLICITY;
+        }
+        return VppUnEventPhase.UNKNOWN;
+    }
+
+    private VppUnEventPhase resolveSplitPhase(Long drEventId, List<Map<String, Object>> resources) {
+        if (drEventId == null || CollectionUtils.isEmpty(resources)) {
+            return VppUnEventPhase.SPLIT_NOTICE;
+        }
+        for (Map<String, Object> resource : resources) {
+            String account = VppUnPayloadHelper.getString(resource, "account");
+            if (!StringUtils.hasText(account)) {
+                return VppUnEventPhase.SPLIT_NOTICE;
+            }
+            BigDecimal reportedKw = VppUnEventParser.resolveResourceReportedKw(resource);
+            if (reportedKw == null) {
+                return VppUnEventPhase.SPLIT_NOTICE;
+            }
+            BigDecimal clearedSumKw = sumClearedCapacityKwByAccount(drEventId, account.trim());
+            if (!capacityEquals(reportedKw, clearedSumKw)) {
+                return VppUnEventPhase.SPLIT_NOTICE;
+            }
+        }
+        return VppUnEventPhase.SPLIT_RESULT;
+    }
+
+    private BigDecimal sumClearedCapacityKwByAccount(Long drEventId, String accountNo) {
+        VppSite site = siteMapper.selectOne(new LambdaQueryWrapper<VppSite>()
+                .eq(VppSite::getAccountNo, accountNo)
+                .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .last("LIMIT 1"));
+        if (site == null || site.getId() == null) {
+            return BigDecimal.ZERO;
+        }
+
+        List<VppResourcePoint> resourcePoints = resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
+                .eq(VppResourcePoint::getSiteId, site.getId())
+                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+        if (CollectionUtils.isEmpty(resourcePoints)) {
+            return BigDecimal.ZERO;
+        }
+
+        List<Long> resourceIds = resourcePoints.stream()
+                .map(VppResourcePoint::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (resourceIds.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+
+        List<VppDrParticipation> participations = participationMapper.selectList(new LambdaQueryWrapper<VppDrParticipation>()
+                .eq(VppDrParticipation::getEventId, drEventId)
+                .in(VppDrParticipation::getResourceId, resourceIds)
+                .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+        BigDecimal sum = BigDecimal.ZERO;
+        for (VppDrParticipation participation : participations) {
+            if (participation.getClearedCapacityKw() != null) {
+                sum = sum.add(participation.getClearedCapacityKw());
+            }
+        }
+        return sum;
+    }
+
+    private static boolean capacityEquals(BigDecimal reportedKw, BigDecimal clearedSumKw) {
+        BigDecimal reported = normalizeCapacity(reportedKw);
+        BigDecimal cleared = normalizeCapacity(clearedSumKw);
+        if (reported == null || cleared == null) {
+            return reported == null && cleared == null;
+        }
+        return reported.compareTo(cleared) == 0;
+    }
+
+    private static BigDecimal normalizeCapacity(BigDecimal value) {
+        if (value == null) {
+            return null;
+        }
+        return value.abs().setScale(CAPACITY_SCALE, RoundingMode.HALF_UP);
+    }
+}

+ 12 - 27
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnEventParser.java

@@ -1,7 +1,6 @@
 package com.usky.vpp.util;
 
 import com.usky.vpp.constant.VppDrEventStatus;
-import com.usky.vpp.enums.VppUnEventPhase;
 import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
@@ -95,31 +94,6 @@ public final class VppUnEventParser {
         return list;
     }
 
-    public static VppUnEventPhase detectPhase(Map<String, Object> event) {
-        Map<String, Object> descriptor = getDescriptor(event);
-        Boolean filing = VppUnPayloadHelper.getBoolean(descriptor, "filing");
-        Boolean lastFiling = VppUnPayloadHelper.getBoolean(descriptor, "lastFiling");
-        List<Map<String, Object>> resources = getTargetResources(event);
-        boolean hasValues = hasResourceValues(resources);
-
-        if (Boolean.TRUE.equals(filing) && !hasValues) {
-            return VppUnEventPhase.INVITATION;
-        }
-        if (Boolean.TRUE.equals(filing) && hasValues) {
-            return VppUnEventPhase.DECLARE_FEEDBACK;
-        }
-//        if (Boolean.FALSE.equals(filing) && Boolean.TRUE.equals(lastFiling) && hasValues) {
-//            return VppUnEventPhase.SPLIT_RESULT;
-//        }
-        if (Boolean.FALSE.equals(filing) && Boolean.TRUE.equals(lastFiling)) {
-            return VppUnEventPhase.SPLIT_NOTICE;
-        }
-        if (hasValues) {
-            return VppUnEventPhase.CLEARING_PUBLICITY;
-        }
-        return VppUnEventPhase.UNKNOWN;
-    }
-
     public static String getEventId(Map<String, Object> event) {
         Map<String, Object> descriptor = getDescriptor(event);
         return VppUnPayloadHelper.getString(descriptor, "eventID", "eventId", "event_id");
@@ -301,6 +275,17 @@ public final class VppUnEventParser {
     }
 
     @SuppressWarnings("unchecked")
+    /**
+     * 解析 target.resources 单条节点上报容量(values 最大值或 load 字段)
+     */
+    public static BigDecimal resolveResourceReportedKw(Map<String, Object> resource) {
+        BigDecimal load = maxAbsFromResourceValues(resource);
+        if (load == null) {
+            load = VppUnPayloadHelper.getDecimal(resource, "load");
+        }
+        return load;
+    }
+
     private static BigDecimal maxAbsFromResourceValues(Map<String, Object> resource) {
         Object values = resource.get("values");
         if (!(values instanceof List)) {
@@ -318,7 +303,7 @@ public final class VppUnEventParser {
         return max;
     }
 
-    private static boolean hasResourceValues(List<Map<String, Object>> resources) {
+    public static boolean hasResourceValues(List<Map<String, Object>> resources) {
         for (Map<String, Object> resource : resources) {
             if (maxAbsFromResourceValues(resource) != null) {
                 return true;