|
|
@@ -1,11 +1,738 @@
|
|
|
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.VppDrEvaluation;
|
|
|
+import com.usky.vpp.domain.VppDrEvent;
|
|
|
+import com.usky.vpp.domain.VppDrExecution;
|
|
|
+import com.usky.vpp.domain.VppDrParticipation;
|
|
|
+import com.usky.vpp.domain.VppDrStrategy;
|
|
|
+import com.usky.vpp.domain.VppDrStrategyResource;
|
|
|
+import com.usky.vpp.domain.VppResourcePoint;
|
|
|
+import com.usky.vpp.mapper.VppDrEvaluationMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrEventMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrExecutionMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrParticipationMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrStrategyMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrStrategyResourceMapper;
|
|
|
+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.DrInterveneRequest;
|
|
|
+import com.usky.vpp.service.vo.DrParticipateRequest;
|
|
|
+import com.usky.vpp.service.vo.DrStrategyRequest;
|
|
|
+import com.usky.vpp.util.VppAuditHelper;
|
|
|
+import com.usky.vpp.util.VppPageHelper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
-/**
|
|
|
- * VppDrService 默认实现(待按业务完善)
|
|
|
- */
|
|
|
@Service
|
|
|
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 PARTICIPATE_STATUS_ACCEPT = 1;
|
|
|
+
|
|
|
+ private static final int EXECUTE_STATUS_PENDING = 0;
|
|
|
+ private static final int EXECUTE_STATUS_RUNNING = 1;
|
|
|
+ private static final int EXECUTE_STATUS_FINISHED = 2;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private VppDrEventMapper eventMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppDrParticipationMapper participationMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppDrStrategyMapper strategyMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppDrStrategyResourceMapper strategyResourceMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppDrExecutionMapper executionMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppDrEvaluationMapper evaluationMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppResourcePointMapper resourcePointMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppUnIntegrationService unIntegrationService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommonPage<VppDrEvent> pageEvent(Map<String, Object> params) {
|
|
|
+ Page<VppDrEvent> page = VppPageHelper.of(params);
|
|
|
+ LambdaQueryWrapper<VppDrEvent> wrapper = new LambdaQueryWrapper<VppDrEvent>()
|
|
|
+ .isNull(VppDrEvent::getDeletedAt)
|
|
|
+ .orderByDesc(VppDrEvent::getStartTime);
|
|
|
+
|
|
|
+ if (params != null) {
|
|
|
+ if (params.get("eventStatus") != null) {
|
|
|
+ wrapper.eq(VppDrEvent::getEventStatus, Integer.parseInt(params.get("eventStatus").toString()));
|
|
|
+ }
|
|
|
+ if (params.get("responseType") != null) {
|
|
|
+ wrapper.eq(VppDrEvent::getResponseType, Integer.parseInt(params.get("responseType").toString()));
|
|
|
+ }
|
|
|
+ if (params.get("eventType") != null) {
|
|
|
+ wrapper.eq(VppDrEvent::getEventType, Integer.parseInt(params.get("eventType").toString()));
|
|
|
+ }
|
|
|
+ if (params.get("startTime") != null) {
|
|
|
+ wrapper.ge(VppDrEvent::getStartTime, params.get("startTime").toString());
|
|
|
+ }
|
|
|
+ if (params.get("endTime") != null) {
|
|
|
+ wrapper.le(VppDrEvent::getStartTime, params.get("endTime").toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Page<VppDrEvent> result = eventMapper.selectPage(page, wrapper);
|
|
|
+ return new CommonPage<>(result.getRecords(), result.getTotal(), result.getCurrent(), result.getSize());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public VppDrEvent getEvent(Long id) {
|
|
|
+ VppDrEvent event = eventMapper.selectById(id);
|
|
|
+ if (event == null || event.getDeletedAt() != null) {
|
|
|
+ throw new BusinessException("需求响应事件不存在");
|
|
|
+ }
|
|
|
+ return event;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object assessCapability(Long eventId) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<VppResourcePoint> resourceWrapper = new LambdaQueryWrapper<VppResourcePoint>()
|
|
|
+ .isNull(VppResourcePoint::getDeletedAt)
|
|
|
+ .eq(VppResourcePoint::getRunStatus, 1);
|
|
|
+
|
|
|
+ List<VppResourcePoint> resources = resourcePointMapper.selectList(resourceWrapper);
|
|
|
+
|
|
|
+ BigDecimal totalUpCapacity = BigDecimal.ZERO;
|
|
|
+ BigDecimal totalDownCapacity = BigDecimal.ZERO;
|
|
|
+ int avgResponseTime = 120;
|
|
|
+ BigDecimal avgRampSpeed = BigDecimal.valueOf(50);
|
|
|
+
|
|
|
+ List<Map<String, Object>> resourceList = new ArrayList<>();
|
|
|
+ for (VppResourcePoint resource : resources) {
|
|
|
+ Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("resourceId", resource.getId());
|
|
|
+ item.put("resourceName", resource.getResourceName());
|
|
|
+ item.put("upCapacityKw", resource.getAdjustableKw() != null ? resource.getAdjustableKw() : BigDecimal.ZERO);
|
|
|
+ item.put("downCapacityKw", resource.getAdjustableKw() != null ? resource.getAdjustableKw() : BigDecimal.ZERO);
|
|
|
+ item.put("responsePriority", resource.getResponsePriority());
|
|
|
+ item.put("available", resource.getRunStatus() == 1);
|
|
|
+ resourceList.add(item);
|
|
|
+
|
|
|
+ BigDecimal adj = resource.getAdjustableKw() != null ? resource.getAdjustableKw() : BigDecimal.ZERO;
|
|
|
+ totalUpCapacity = totalUpCapacity.add(adj);
|
|
|
+ totalDownCapacity = totalDownCapacity.add(adj);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("eventId", event.getEventId());
|
|
|
+ result.put("targetCapacityKw", event.getTargetCapacityKw());
|
|
|
+ result.put("totalUpCapacityKw", totalUpCapacity);
|
|
|
+ result.put("totalDownCapacityKw", totalDownCapacity);
|
|
|
+ result.put("avgResponseTimeSec", avgResponseTime);
|
|
|
+ result.put("avgRampSpeedKwMin", avgRampSpeed);
|
|
|
+ result.put("sufficient", event.getTargetCapacityKw() == null
|
|
|
+ || totalUpCapacity.compareTo(event.getTargetCapacityKw()) >= 0);
|
|
|
+ result.put("resources", resourceList);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void participate(Long eventId, DrParticipateRequest request) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+
|
|
|
+ if (event.getEventStatus() != EVENT_STATUS_PENDING) {
|
|
|
+ throw new BusinessException("当前状态不允许参与");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request == null || request.getParticipate() == null) {
|
|
|
+ throw new BusinessException("参与状态不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<VppDrParticipation> existingParticipations = participationMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<VppDrParticipation>()
|
|
|
+ .eq(VppDrParticipation::getEventId, eventId)
|
|
|
+ .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_STATUS_ACCEPT)
|
|
|
+ .isNull(VppDrParticipation::getDeletedAt)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!existingParticipations.isEmpty()) {
|
|
|
+ throw new BusinessException("已参与该事件");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Boolean.TRUE.equals(request.getParticipate())) {
|
|
|
+ if (request.getDeclaredCapacityKw() == null || request.getDeclaredCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new BusinessException("申报容量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getResources() == null || request.getResources().isEmpty()) {
|
|
|
+ throw new BusinessException("必须选择参与资源");
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal resourceDeclaredTotal = BigDecimal.ZERO;
|
|
|
+ for (DrParticipateRequest.DrResourceDeclare resource : request.getResources()) {
|
|
|
+ if (resource.getResourceId() == null) {
|
|
|
+ throw new BusinessException("资源ID不能为空");
|
|
|
+ }
|
|
|
+ if (resource.getDeclaredCapacityKw() == null || resource.getDeclaredCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new BusinessException("资源申报容量必须大于0");
|
|
|
+ }
|
|
|
+ VppResourcePoint rp = resourcePointMapper.selectById(resource.getResourceId());
|
|
|
+ if (rp == null || rp.getDeletedAt() != null) {
|
|
|
+ throw new BusinessException("资源点不存在");
|
|
|
+ }
|
|
|
+ if (rp.getRunStatus() != 1) {
|
|
|
+ throw new BusinessException("资源点不在线,无法参与");
|
|
|
+ }
|
|
|
+ Long customerId = rp.getCustomerId();
|
|
|
+ if (customerId == null) {
|
|
|
+ throw new BusinessException("资源点未关联客户");
|
|
|
+ }
|
|
|
+ resourceDeclaredTotal = resourceDeclaredTotal.add(resource.getDeclaredCapacityKw());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (resourceDeclaredTotal.compareTo(request.getDeclaredCapacityKw()) != 0) {
|
|
|
+ throw new BusinessException("各资源申报容量之和与总申报容量不一致");
|
|
|
+ }
|
|
|
+
|
|
|
+ for (DrParticipateRequest.DrResourceDeclare resource : request.getResources()) {
|
|
|
+ VppResourcePoint rp = resourcePointMapper.selectById(resource.getResourceId());
|
|
|
+ VppDrParticipation participation = new VppDrParticipation();
|
|
|
+ participation.setEventId(eventId);
|
|
|
+ participation.setCustomerId(rp.getCustomerId());
|
|
|
+ participation.setResourceId(resource.getResourceId());
|
|
|
+ participation.setParticipateStatus(PARTICIPATE_STATUS_ACCEPT);
|
|
|
+ participation.setDeclaredCapacityKw(resource.getDeclaredCapacityKw());
|
|
|
+ participation.setDeclaredAt(LocalDateTime.now());
|
|
|
+ VppAuditHelper.fillCreate(participation);
|
|
|
+ participationMapper.insert(participation);
|
|
|
+ }
|
|
|
+
|
|
|
+ event.setEventStatus(EVENT_STATUS_DECLARED);
|
|
|
+ VppAuditHelper.fillUpdate(event);
|
|
|
+ eventMapper.updateById(event);
|
|
|
+ } else {
|
|
|
+ event.setEventStatus(EVENT_STATUS_CANCELLED);
|
|
|
+ VppAuditHelper.fillUpdate(event);
|
|
|
+ eventMapper.updateById(event);
|
|
|
+ }
|
|
|
+
|
|
|
+ syncParticipationToUn(eventId, request.getParticipate());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void syncParticipationToUn(Long eventId, Boolean participate) {
|
|
|
+ try {
|
|
|
+ unIntegrationService.submitParticipation(eventId, Boolean.TRUE.equals(participate));
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.warn("同步 UN 参与申报失败 eventId={}: {}", eventId, ex.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void clearing(Long eventId, DrClearingRequest request) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+
|
|
|
+ if (event.getEventStatus() != EVENT_STATUS_DECLARED) {
|
|
|
+ throw new BusinessException("当前状态不允许出清分拆");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request == null || request.getTotalClearedCapacityKw() == null) {
|
|
|
+ throw new BusinessException("出清总容量不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getResources() == null || request.getResources().isEmpty()) {
|
|
|
+ throw new BusinessException("资源分拆列表不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ long existingExecCount = executionMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<VppDrExecution>()
|
|
|
+ .eq(VppDrExecution::getEventId, eventId)
|
|
|
+ .isNull(VppDrExecution::getDeletedAt)
|
|
|
+ );
|
|
|
+ if (existingExecCount > 0) {
|
|
|
+ throw new BusinessException("已出清,请勿重复操作");
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal totalCleared = BigDecimal.ZERO;
|
|
|
+ for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
|
|
|
+ if (resource.getResourceId() == null) {
|
|
|
+ throw new BusinessException("资源ID不能为空");
|
|
|
+ }
|
|
|
+ if (resource.getClearedCapacityKw() == null || resource.getClearedCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new BusinessException("出清容量必须大于0");
|
|
|
+ }
|
|
|
+ totalCleared = totalCleared.add(resource.getClearedCapacityKw());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (totalCleared.compareTo(request.getTotalClearedCapacityKw()) != 0) {
|
|
|
+ throw new BusinessException("分拆容量之和与总出清容量不一致");
|
|
|
+ }
|
|
|
+
|
|
|
+ for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
|
|
|
+ LambdaQueryWrapper<VppDrParticipation> wrapper = new LambdaQueryWrapper<VppDrParticipation>()
|
|
|
+ .eq(VppDrParticipation::getEventId, eventId)
|
|
|
+ .eq(VppDrParticipation::getResourceId, resource.getResourceId())
|
|
|
+ .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_STATUS_ACCEPT)
|
|
|
+ .isNull(VppDrParticipation::getDeletedAt);
|
|
|
+
|
|
|
+ VppDrParticipation participation = participationMapper.selectOne(wrapper);
|
|
|
+ if (participation == null) {
|
|
|
+ throw new BusinessException("资源未参与申报,无法出清: " + resource.getResourceId());
|
|
|
+ }
|
|
|
+ if (participation.getDeclaredCapacityKw() != null
|
|
|
+ && resource.getClearedCapacityKw().compareTo(participation.getDeclaredCapacityKw()) > 0) {
|
|
|
+ throw new BusinessException("出清容量不能超过申报容量");
|
|
|
+ }
|
|
|
+ participation.setClearedCapacityKw(resource.getClearedCapacityKw());
|
|
|
+ VppAuditHelper.fillUpdate(participation);
|
|
|
+ participationMapper.updateById(participation);
|
|
|
+ }
|
|
|
+
|
|
|
+ event.setClearedCapacityKw(request.getTotalClearedCapacityKw());
|
|
|
+ event.setEventStatus(EVENT_STATUS_EXECUTING);
|
|
|
+ VppAuditHelper.fillUpdate(event);
|
|
|
+ eventMapper.updateById(event);
|
|
|
+
|
|
|
+ for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
|
|
|
+ VppDrExecution execution = new VppDrExecution();
|
|
|
+ execution.setEventId(eventId);
|
|
|
+ execution.setResourceId(resource.getResourceId());
|
|
|
+ execution.setTargetKw(resource.getClearedCapacityKw());
|
|
|
+ execution.setActualKw(BigDecimal.ZERO);
|
|
|
+ execution.setExecuteStatus(EXECUTE_STATUS_RUNNING);
|
|
|
+ execution.setStartedAt(LocalDateTime.now());
|
|
|
+ VppAuditHelper.fillCreate(execution);
|
|
|
+ executionMapper.insert(execution);
|
|
|
+ }
|
|
|
+
|
|
|
+ syncClearingToUn(eventId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void syncClearingToUn(Long eventId) {
|
|
|
+ try {
|
|
|
+ unIntegrationService.submitClearing(eventId);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.warn("同步 UN 出清分拆失败 eventId={}: {}", eventId, ex.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object monitorExecution(Long eventId) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+
|
|
|
+ List<VppDrExecution> executions = executionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<VppDrExecution>()
|
|
|
+ .eq(VppDrExecution::getEventId, eventId)
|
|
|
+ .isNull(VppDrExecution::getDeletedAt)
|
|
|
+ );
|
|
|
+
|
|
|
+ BigDecimal totalTarget = BigDecimal.ZERO;
|
|
|
+ BigDecimal totalActual = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ List<Map<String, Object>> resources = new ArrayList<>();
|
|
|
+ for (VppDrExecution exe : executions) {
|
|
|
+ VppResourcePoint rp = resourcePointMapper.selectById(exe.getResourceId());
|
|
|
+
|
|
|
+ Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("resourceId", exe.getResourceId());
|
|
|
+ item.put("resourceName", rp != null ? rp.getResourceName() : "");
|
|
|
+ item.put("targetKw", exe.getTargetKw());
|
|
|
+ item.put("actualKw", exe.getActualKw());
|
|
|
+ item.put("executeStatus", exe.getExecuteStatus());
|
|
|
+ resources.add(item);
|
|
|
+
|
|
|
+ totalTarget = totalTarget.add(exe.getTargetKw() != null ? exe.getTargetKw() : BigDecimal.ZERO);
|
|
|
+ totalActual = totalActual.add(exe.getActualKw() != null ? exe.getActualKw() : BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ int remainingMinutes = 0;
|
|
|
+ if (event.getEndTime() != null) {
|
|
|
+ remainingMinutes = (int) java.time.Duration.between(LocalDateTime.now(), event.getEndTime()).toMinutes();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("eventId", event.getEventId());
|
|
|
+ result.put("targetCapacityKw", totalTarget.compareTo(BigDecimal.ZERO) > 0 ? totalTarget : event.getClearedCapacityKw());
|
|
|
+ result.put("actualCapacityKw", totalActual);
|
|
|
+ result.put("progressPercent", totalTarget.compareTo(BigDecimal.ZERO) > 0 ?
|
|
|
+ totalActual.multiply(BigDecimal.valueOf(100)).divide(totalTarget, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
|
|
|
+ result.put("remainingMinutes", remainingMinutes > 0 ? remainingMinutes : 0);
|
|
|
+ result.put("resources", resources);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void intervene(Long eventId, DrInterveneRequest request) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+
|
|
|
+ if (event.getEventStatus() != EVENT_STATUS_EXECUTING) {
|
|
|
+ throw new BusinessException("仅执行中的事件允许干预");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request == null || !StringUtils.hasText(request.getAction())) {
|
|
|
+ throw new BusinessException("干预动作不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ String action = request.getAction();
|
|
|
+ List<VppDrExecution> executions = executionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<VppDrExecution>()
|
|
|
+ .eq(VppDrExecution::getEventId, eventId)
|
|
|
+ .isNull(VppDrExecution::getDeletedAt)
|
|
|
+ );
|
|
|
+ if (executions.isEmpty()) {
|
|
|
+ throw new BusinessException("暂无执行明细,无法干预");
|
|
|
+ }
|
|
|
+
|
|
|
+ if ("ADJUST".equals(action)) {
|
|
|
+ if (request.getStrategyId() == null) {
|
|
|
+ throw new BusinessException("切换策略时必须指定策略ID");
|
|
|
+ }
|
|
|
+ VppDrStrategy strategy = strategyMapper.selectById(request.getStrategyId());
|
|
|
+ if (strategy == null || strategy.getDeletedAt() != null) {
|
|
|
+ throw new BusinessException("策略不存在");
|
|
|
+ }
|
|
|
+ if (strategy.getIsEnabled() != null && strategy.getIsEnabled() == 0) {
|
|
|
+ throw new BusinessException("策略未启用");
|
|
|
+ }
|
|
|
+ applyStrategyTargets(event, executions, request.getStrategyId());
|
|
|
+ } else if ("PAUSE".equals(action)) {
|
|
|
+ for (VppDrExecution execution : executions) {
|
|
|
+ if (execution.getExecuteStatus() == EXECUTE_STATUS_RUNNING) {
|
|
|
+ execution.setExecuteStatus(EXECUTE_STATUS_PENDING);
|
|
|
+ VppAuditHelper.fillUpdate(execution);
|
|
|
+ executionMapper.updateById(execution);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if ("RESUME".equals(action)) {
|
|
|
+ for (VppDrExecution execution : executions) {
|
|
|
+ if (execution.getExecuteStatus() == EXECUTE_STATUS_PENDING) {
|
|
|
+ execution.setExecuteStatus(EXECUTE_STATUS_RUNNING);
|
|
|
+ if (execution.getStartedAt() == null) {
|
|
|
+ execution.setStartedAt(LocalDateTime.now());
|
|
|
+ }
|
|
|
+ VppAuditHelper.fillUpdate(execution);
|
|
|
+ executionMapper.updateById(execution);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new BusinessException("无效的干预动作");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void completeEvent(Long eventId) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+ if (event.getEventStatus() != EVENT_STATUS_EXECUTING) {
|
|
|
+ throw new BusinessException("仅执行中的事件可结束");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<VppDrExecution> executions = executionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<VppDrExecution>()
|
|
|
+ .eq(VppDrExecution::getEventId, eventId)
|
|
|
+ .isNull(VppDrExecution::getDeletedAt)
|
|
|
+ );
|
|
|
+ if (executions.isEmpty()) {
|
|
|
+ throw new BusinessException("暂无执行明细,无法结束事件");
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal totalActual = BigDecimal.ZERO;
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ for (VppDrExecution execution : executions) {
|
|
|
+ BigDecimal actual = execution.getActualKw();
|
|
|
+ if (actual == null || actual.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
+ actual = execution.getTargetKw() != null ? execution.getTargetKw() : BigDecimal.ZERO;
|
|
|
+ execution.setActualKw(actual);
|
|
|
+ }
|
|
|
+ totalActual = totalActual.add(actual);
|
|
|
+ execution.setExecuteStatus(EXECUTE_STATUS_FINISHED);
|
|
|
+ execution.setFinishedAt(now);
|
|
|
+ VppAuditHelper.fillUpdate(execution);
|
|
|
+ executionMapper.updateById(execution);
|
|
|
+ }
|
|
|
+
|
|
|
+ event.setEventStatus(EVENT_STATUS_ENDED);
|
|
|
+ VppAuditHelper.fillUpdate(event);
|
|
|
+ eventMapper.updateById(event);
|
|
|
+
|
|
|
+ VppDrEvaluation evaluation = evaluationMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<VppDrEvaluation>()
|
|
|
+ .eq(VppDrEvaluation::getEventId, eventId)
|
|
|
+ .isNull(VppDrEvaluation::getDeletedAt)
|
|
|
+ );
|
|
|
+ if (evaluation == null) {
|
|
|
+ evaluation = new VppDrEvaluation();
|
|
|
+ evaluation.setEventId(eventId);
|
|
|
+ evaluation.setActualCapacityKw(totalActual);
|
|
|
+ if (event.getStartTime() != null && event.getEndTime() != null) {
|
|
|
+ evaluation.setResponseDurationMin(
|
|
|
+ (int) java.time.Duration.between(event.getStartTime(), event.getEndTime()).toMinutes());
|
|
|
+ }
|
|
|
+ if (event.getSubsidyPrice() != null) {
|
|
|
+ evaluation.setSubsidyAmount(totalActual.multiply(event.getSubsidyPrice()));
|
|
|
+ }
|
|
|
+ BigDecimal cleared = event.getClearedCapacityKw();
|
|
|
+ if (cleared != null && cleared.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ evaluation.setQualifiedRate(totalActual.multiply(BigDecimal.valueOf(100))
|
|
|
+ .divide(cleared, 2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ evaluation.setEvaluatedAt(now);
|
|
|
+ VppAuditHelper.fillCreate(evaluation);
|
|
|
+ evaluationMapper.insert(evaluation);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void acknowledgeClearing(Long eventId) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+ if (event.getEventStatus() != EVENT_STATUS_DECLARED) {
|
|
|
+ throw new BusinessException("当前状态不允许确认出清公示");
|
|
|
+ }
|
|
|
+ unIntegrationService.acknowledgeClearing(eventId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public VppDrEvaluation getEvaluation(Long eventId) {
|
|
|
+ VppDrEvent event = getEvent(eventId);
|
|
|
+
|
|
|
+ if (event.getEventStatus() != EVENT_STATUS_ENDED) {
|
|
|
+ throw new BusinessException("事件未结束,暂无评估结果");
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<VppDrEvaluation> wrapper = new LambdaQueryWrapper<VppDrEvaluation>()
|
|
|
+ .eq(VppDrEvaluation::getEventId, eventId)
|
|
|
+ .isNull(VppDrEvaluation::getDeletedAt);
|
|
|
+
|
|
|
+ VppDrEvaluation evaluation = evaluationMapper.selectOne(wrapper);
|
|
|
+ if (evaluation == null) {
|
|
|
+ throw new BusinessException("评估结果不存在");
|
|
|
+ }
|
|
|
+ return evaluation;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommonPage<VppDrStrategy> pageStrategy(Map<String, Object> params) {
|
|
|
+ Page<VppDrStrategy> page = VppPageHelper.of(params);
|
|
|
+ LambdaQueryWrapper<VppDrStrategy> wrapper = new LambdaQueryWrapper<VppDrStrategy>()
|
|
|
+ .isNull(VppDrStrategy::getDeletedAt)
|
|
|
+ .orderByDesc(VppDrStrategy::getCreateTime);
|
|
|
+
|
|
|
+ if (params != null) {
|
|
|
+ if (params.get("responseType") != null) {
|
|
|
+ wrapper.eq(VppDrStrategy::getResponseType, Integer.parseInt(params.get("responseType").toString()));
|
|
|
+ }
|
|
|
+ if (params.get("isEnabled") != null) {
|
|
|
+ wrapper.eq(VppDrStrategy::getIsEnabled, Integer.parseInt(params.get("isEnabled").toString()));
|
|
|
+ }
|
|
|
+ if (params.get("strategyName") != null) {
|
|
|
+ wrapper.like(VppDrStrategy::getStrategyName, params.get("strategyName").toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Page<VppDrStrategy> result = strategyMapper.selectPage(page, wrapper);
|
|
|
+ return new CommonPage<>(result.getRecords(), result.getTotal(), result.getCurrent(), result.getSize());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public VppDrStrategy createStrategy(DrStrategyRequest request) {
|
|
|
+ validateStrategyRequest(request);
|
|
|
+
|
|
|
+ VppDrStrategy strategy = new VppDrStrategy();
|
|
|
+ strategy.setStrategyCode(request.getStrategyCode());
|
|
|
+ strategy.setStrategyName(request.getStrategyName());
|
|
|
+ strategy.setResponseType(request.getResponseType());
|
|
|
+ strategy.setAdjustStepKw(request.getAdjustStepKw());
|
|
|
+ strategy.setExecuteMode(request.getExecuteMode() != null ? request.getExecuteMode() : 1);
|
|
|
+ strategy.setStrategyConfig(request.getStrategyConfig());
|
|
|
+ strategy.setIsDefault(request.getIsDefault() != null ? request.getIsDefault() : 0);
|
|
|
+ strategy.setIsEnabled(request.getIsEnabled() != null ? request.getIsEnabled() : 1);
|
|
|
+ VppAuditHelper.fillCreate(strategy);
|
|
|
+ strategyMapper.insert(strategy);
|
|
|
+
|
|
|
+ if (request.getIsDefault() != null && request.getIsDefault() == 1) {
|
|
|
+ LambdaQueryWrapper<VppDrStrategy> wrapper = new LambdaQueryWrapper<VppDrStrategy>()
|
|
|
+ .ne(VppDrStrategy::getId, strategy.getId())
|
|
|
+ .eq(VppDrStrategy::getIsDefault, 1)
|
|
|
+ .isNull(VppDrStrategy::getDeletedAt);
|
|
|
+
|
|
|
+ List<VppDrStrategy> defaults = strategyMapper.selectList(wrapper);
|
|
|
+ for (VppDrStrategy def : defaults) {
|
|
|
+ def.setIsDefault(0);
|
|
|
+ VppAuditHelper.fillUpdate(def);
|
|
|
+ strategyMapper.updateById(def);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getResources() != null) {
|
|
|
+ for (DrStrategyRequest.DrStrategyResource resource : request.getResources()) {
|
|
|
+ VppDrStrategyResource sr = new VppDrStrategyResource();
|
|
|
+ sr.setStrategyId(strategy.getId());
|
|
|
+ sr.setResourceId(resource.getResourceId());
|
|
|
+ sr.setPriority(resource.getPriority() != null ? resource.getPriority() : 0);
|
|
|
+ sr.setMaxAdjustKw(resource.getMaxAdjustKw());
|
|
|
+ strategyResourceMapper.insert(sr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return strategy;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void updateStrategy(Long id, DrStrategyRequest request) {
|
|
|
+ VppDrStrategy strategy = strategyMapper.selectById(id);
|
|
|
+ if (strategy == null || strategy.getDeletedAt() != null) {
|
|
|
+ throw new BusinessException("策略不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ validateStrategyRequest(request);
|
|
|
+
|
|
|
+ strategy.setStrategyCode(request.getStrategyCode());
|
|
|
+ strategy.setStrategyName(request.getStrategyName());
|
|
|
+ strategy.setResponseType(request.getResponseType());
|
|
|
+ strategy.setAdjustStepKw(request.getAdjustStepKw());
|
|
|
+ if (request.getExecuteMode() != null) {
|
|
|
+ strategy.setExecuteMode(request.getExecuteMode());
|
|
|
+ }
|
|
|
+ strategy.setStrategyConfig(request.getStrategyConfig());
|
|
|
+ if (request.getIsDefault() != null) {
|
|
|
+ strategy.setIsDefault(request.getIsDefault());
|
|
|
+ }
|
|
|
+ if (request.getIsEnabled() != null) {
|
|
|
+ strategy.setIsEnabled(request.getIsEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getIsDefault() != null && request.getIsDefault() == 1) {
|
|
|
+ LambdaQueryWrapper<VppDrStrategy> wrapper = new LambdaQueryWrapper<VppDrStrategy>()
|
|
|
+ .ne(VppDrStrategy::getId, id)
|
|
|
+ .eq(VppDrStrategy::getIsDefault, 1)
|
|
|
+ .isNull(VppDrStrategy::getDeletedAt);
|
|
|
+
|
|
|
+ List<VppDrStrategy> defaults = strategyMapper.selectList(wrapper);
|
|
|
+ for (VppDrStrategy def : defaults) {
|
|
|
+ def.setIsDefault(0);
|
|
|
+ VppAuditHelper.fillUpdate(def);
|
|
|
+ strategyMapper.updateById(def);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ VppAuditHelper.fillUpdate(strategy);
|
|
|
+ strategyMapper.updateById(strategy);
|
|
|
+
|
|
|
+ strategyResourceMapper.delete(new LambdaQueryWrapper<VppDrStrategyResource>()
|
|
|
+ .eq(VppDrStrategyResource::getStrategyId, id));
|
|
|
+
|
|
|
+ if (request.getResources() != null) {
|
|
|
+ for (DrStrategyRequest.DrStrategyResource resource : request.getResources()) {
|
|
|
+ VppDrStrategyResource sr = new VppDrStrategyResource();
|
|
|
+ sr.setStrategyId(id);
|
|
|
+ sr.setResourceId(resource.getResourceId());
|
|
|
+ sr.setPriority(resource.getPriority() != null ? resource.getPriority() : 0);
|
|
|
+ sr.setMaxAdjustKw(resource.getMaxAdjustKw());
|
|
|
+ strategyResourceMapper.insert(sr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void deleteStrategy(Long id) {
|
|
|
+ VppDrStrategy strategy = strategyMapper.selectById(id);
|
|
|
+ if (strategy == null || strategy.getDeletedAt() != null) {
|
|
|
+ throw new BusinessException("策略不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ strategy.setDeletedAt(LocalDateTime.now());
|
|
|
+ VppAuditHelper.fillUpdate(strategy);
|
|
|
+ strategyMapper.updateById(strategy);
|
|
|
+
|
|
|
+ strategyResourceMapper.delete(new LambdaQueryWrapper<VppDrStrategyResource>()
|
|
|
+ .eq(VppDrStrategyResource::getStrategyId, id));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateStrategyRequest(DrStrategyRequest request) {
|
|
|
+ if (request == null) {
|
|
|
+ throw new BusinessException("请求不能为空");
|
|
|
+ }
|
|
|
+ if (!StringUtils.hasText(request.getStrategyCode())) {
|
|
|
+ throw new BusinessException("策略编码不能为空");
|
|
|
+ }
|
|
|
+ if (!StringUtils.hasText(request.getStrategyName())) {
|
|
|
+ throw new BusinessException("策略名称不能为空");
|
|
|
+ }
|
|
|
+ if (request.getResponseType() == null) {
|
|
|
+ throw new BusinessException("响应类型不能为空");
|
|
|
+ }
|
|
|
+ if (request.getResources() != null) {
|
|
|
+ for (DrStrategyRequest.DrStrategyResource resource : request.getResources()) {
|
|
|
+ if (resource.getResourceId() == null) {
|
|
|
+ throw new BusinessException("策略资源ID不能为空");
|
|
|
+ }
|
|
|
+ VppResourcePoint rp = resourcePointMapper.selectById(resource.getResourceId());
|
|
|
+ if (rp == null || rp.getDeletedAt() != null) {
|
|
|
+ throw new BusinessException("策略关联的资源点不存在");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void applyStrategyTargets(VppDrEvent event, List<VppDrExecution> executions, Long strategyId) {
|
|
|
+ List<VppDrStrategyResource> strategyResources = strategyResourceMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<VppDrStrategyResource>()
|
|
|
+ .eq(VppDrStrategyResource::getStrategyId, strategyId)
|
|
|
+ .orderByAsc(VppDrStrategyResource::getPriority)
|
|
|
+ );
|
|
|
+ if (strategyResources.isEmpty()) {
|
|
|
+ throw new BusinessException("策略未配置资源");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<Long, VppDrExecution> executionByResource = executions.stream()
|
|
|
+ .collect(Collectors.toMap(VppDrExecution::getResourceId, e -> e, (a, b) -> a));
|
|
|
+
|
|
|
+ BigDecimal totalMaxAdjust = strategyResources.stream()
|
|
|
+ .map(sr -> sr.getMaxAdjustKw() != null ? sr.getMaxAdjustKw() : BigDecimal.ZERO)
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ BigDecimal totalTarget = event.getClearedCapacityKw() != null ? event.getClearedCapacityKw() : BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (VppDrStrategyResource strategyResource : strategyResources) {
|
|
|
+ VppDrExecution execution = executionByResource.get(strategyResource.getResourceId());
|
|
|
+ if (execution == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ BigDecimal maxAdjust = strategyResource.getMaxAdjustKw() != null ? strategyResource.getMaxAdjustKw() : BigDecimal.ZERO;
|
|
|
+ BigDecimal newTarget = totalMaxAdjust.compareTo(BigDecimal.ZERO) > 0
|
|
|
+ ? totalTarget.multiply(maxAdjust).divide(totalMaxAdjust, 4, RoundingMode.HALF_UP)
|
|
|
+ : BigDecimal.ZERO;
|
|
|
+ execution.setTargetKw(newTarget);
|
|
|
+ execution.setExecuteStatus(EXECUTE_STATUS_RUNNING);
|
|
|
+ if (execution.getStartedAt() == null) {
|
|
|
+ execution.setStartedAt(LocalDateTime.now());
|
|
|
+ }
|
|
|
+ VppAuditHelper.fillUpdate(execution);
|
|
|
+ executionMapper.updateById(execution);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|