|
|
@@ -5,12 +5,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.usky.common.core.bean.CommonPage;
|
|
|
import com.usky.common.core.exception.BusinessException;
|
|
|
import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.vpp.domain.VppCustomer;
|
|
|
import com.usky.vpp.domain.VppDrEvent;
|
|
|
import com.usky.vpp.domain.VppDrInvitation;
|
|
|
import com.usky.vpp.domain.VppDrSubsidyPrediction;
|
|
|
import com.usky.vpp.domain.VppResponseDeviationPrice;
|
|
|
import com.usky.vpp.domain.VppSite;
|
|
|
import com.usky.vpp.mapper.*;
|
|
|
+import com.usky.vpp.mapper.VppCustomerMapper;
|
|
|
import com.usky.vpp.mapper.VppResponseDeviationPriceMapper;
|
|
|
import com.usky.vpp.service.VppBaselineService;
|
|
|
import com.usky.vpp.service.VppDrSubsidyPredictionService;
|
|
|
@@ -59,12 +61,15 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
@Autowired
|
|
|
private VppSiteMapper siteMapper;
|
|
|
@Autowired
|
|
|
+ private VppCustomerMapper customerMapper;
|
|
|
+ @Autowired
|
|
|
private VppResponseDeviationPriceMapper priceAdjustmentMapper;
|
|
|
@Autowired
|
|
|
private VppBaselineService vppBaselineService;
|
|
|
|
|
|
@Override
|
|
|
- public CommonPage<DrSubsidyPredictionVO> page(String siteName, String startTime, String endTime,
|
|
|
+ public CommonPage<DrSubsidyPredictionVO> page(String siteName, String customerName,
|
|
|
+ String startTime, String endTime,
|
|
|
Integer current, Integer size) {
|
|
|
Page<VppDrSubsidyPrediction> page = new Page<>(
|
|
|
current != null ? current : 1,
|
|
|
@@ -76,6 +81,9 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
if (StringUtils.hasText(siteName)) {
|
|
|
wrapper.like(VppDrSubsidyPrediction::getSiteName, siteName);
|
|
|
}
|
|
|
+ if (StringUtils.hasText(customerName)) {
|
|
|
+ wrapper.like(VppDrSubsidyPrediction::getCustomerName, customerName);
|
|
|
+ }
|
|
|
if (StringUtils.hasText(startTime)) {
|
|
|
wrapper.ge(VppDrSubsidyPrediction::getEventStartTime, startTime);
|
|
|
}
|
|
|
@@ -116,6 +124,8 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
entity.setDrEventId(event.getId());
|
|
|
entity.setSiteId(invitation.getSiteId());
|
|
|
fillSiteName(entity);
|
|
|
+ entity.setCustomerId(invitation.getCustomerId());
|
|
|
+ fillCustomerName(entity);
|
|
|
entity.setEventStartTime(event.getStartTime());
|
|
|
entity.setEventEndTime(event.getEndTime());
|
|
|
entity.setSubsidyPrice(event.getSubsidyPrice());
|
|
|
@@ -147,9 +157,13 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
if (request == null) {
|
|
|
throw new BusinessException("请求参数不能为空");
|
|
|
}
|
|
|
+ assertInvitationNotDuplicated(request.getInvitationId(), null);
|
|
|
+
|
|
|
VppDrSubsidyPrediction entity = new VppDrSubsidyPrediction();
|
|
|
applyRequest(entity, request);
|
|
|
+ fillCustomerFromInvitationIfAbsent(entity);
|
|
|
fillSiteName(entity);
|
|
|
+ fillCustomerName(entity);
|
|
|
entity.setTenantId(SecurityUtils.getTenantId());
|
|
|
entity.setCreateTime(LocalDateTime.now());
|
|
|
entity.setUpdateTime(LocalDateTime.now());
|
|
|
@@ -170,15 +184,40 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
if (tenantId != null && existing.getTenantId() != null && !tenantId.equals(existing.getTenantId())) {
|
|
|
throw new BusinessException("补贴预测记录不存在");
|
|
|
}
|
|
|
+ Long invitationId = request.getInvitationId() != null
|
|
|
+ ? request.getInvitationId()
|
|
|
+ : existing.getInvitationId();
|
|
|
+ assertInvitationNotDuplicated(invitationId, existing.getId());
|
|
|
|
|
|
applyRequest(existing, request);
|
|
|
if (request.getSiteId() != null) {
|
|
|
fillSiteName(existing);
|
|
|
}
|
|
|
+ if (request.getCustomerId() != null) {
|
|
|
+ fillCustomerName(existing);
|
|
|
+ }
|
|
|
existing.setUpdateTime(LocalDateTime.now());
|
|
|
return subsidyPredictionMapper.updateById(existing) > 0;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 同一邀约不可重复新增补贴预测。
|
|
|
+ */
|
|
|
+ private void assertInvitationNotDuplicated(Long invitationId, Long excludeId) {
|
|
|
+ if (invitationId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+ Integer count = subsidyPredictionMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<VppDrSubsidyPrediction>()
|
|
|
+ .eq(VppDrSubsidyPrediction::getInvitationId, invitationId)
|
|
|
+ .eq(tenantId != null, VppDrSubsidyPrediction::getTenantId, tenantId)
|
|
|
+ .ne(excludeId != null, VppDrSubsidyPrediction::getId, excludeId));
|
|
|
+ if (count != null && count > 0) {
|
|
|
+ throw new BusinessException("该邀约事件已添加补贴金额预测,不能重复添加!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Boolean delete(Long id) {
|
|
|
@@ -227,10 +266,17 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
: siteMapper.selectBatchIds(siteIds).stream()
|
|
|
.collect(Collectors.toMap(VppSite::getId, VppSite::getSiteName, (a, b) -> a));
|
|
|
|
|
|
+ Set<Long> customerIds = invitations.stream()
|
|
|
+ .map(VppDrInvitation::getCustomerId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ Map<Long, String> customerNameMap = loadCustomerNameMap(customerIds);
|
|
|
+
|
|
|
List<VppResponseDeviationPrice> adjustments = loadActivePriceAdjustments(event.getTenantId());
|
|
|
|
|
|
for (VppDrInvitation invitation : invitations) {
|
|
|
- VppDrSubsidyPrediction prediction = buildPrediction(event, invitation, siteNameMap, adjustments);
|
|
|
+ VppDrSubsidyPrediction prediction = buildPrediction(
|
|
|
+ event, invitation, siteNameMap, customerNameMap, adjustments);
|
|
|
|
|
|
VppDrSubsidyPrediction existing = subsidyPredictionMapper.selectOne(
|
|
|
new LambdaQueryWrapper<VppDrSubsidyPrediction>()
|
|
|
@@ -253,12 +299,15 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
|
|
|
private VppDrSubsidyPrediction buildPrediction(VppDrEvent event, VppDrInvitation invitation,
|
|
|
Map<Long, String> siteNameMap,
|
|
|
+ Map<Long, String> customerNameMap,
|
|
|
List<VppResponseDeviationPrice> adjustments) {
|
|
|
VppDrSubsidyPrediction p = new VppDrSubsidyPrediction();
|
|
|
p.setInvitationId(invitation.getId());
|
|
|
p.setDrEventId(event.getId());
|
|
|
p.setSiteId(invitation.getSiteId());
|
|
|
p.setSiteName(invitation.getSiteId() == null ? null : siteNameMap.get(invitation.getSiteId()));
|
|
|
+ p.setCustomerId(invitation.getCustomerId());
|
|
|
+ p.setCustomerName(invitation.getCustomerId() == null ? null : customerNameMap.get(invitation.getCustomerId()));
|
|
|
p.setEventStartTime(event.getStartTime());
|
|
|
p.setEventEndTime(event.getEndTime());
|
|
|
p.setSubsidyPrice(event.getSubsidyPrice());
|
|
|
@@ -331,6 +380,12 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
if (request.getSiteId() != null) {
|
|
|
entity.setSiteId(request.getSiteId());
|
|
|
}
|
|
|
+ if (request.getCustomerId() != null) {
|
|
|
+ entity.setCustomerId(request.getCustomerId());
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(request.getCustomerName())) {
|
|
|
+ entity.setCustomerName(request.getCustomerName().trim());
|
|
|
+ }
|
|
|
if (request.getEventStartTime() != null) {
|
|
|
entity.setEventStartTime(request.getEventStartTime());
|
|
|
}
|
|
|
@@ -371,6 +426,16 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void fillCustomerFromInvitationIfAbsent(VppDrSubsidyPrediction entity) {
|
|
|
+ if (entity.getCustomerId() != null || entity.getInvitationId() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ VppDrInvitation invitation = invitationMapper.selectById(entity.getInvitationId());
|
|
|
+ if (invitation != null) {
|
|
|
+ entity.setCustomerId(invitation.getCustomerId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void fillSiteName(VppDrSubsidyPrediction entity) {
|
|
|
if (entity.getSiteId() == null) {
|
|
|
entity.setSiteName(null);
|
|
|
@@ -380,6 +445,28 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
entity.setSiteName(site != null ? site.getSiteName() : null);
|
|
|
}
|
|
|
|
|
|
+ private void fillCustomerName(VppDrSubsidyPrediction entity) {
|
|
|
+ if (entity.getCustomerId() == null) {
|
|
|
+ if (!StringUtils.hasText(entity.getCustomerName())) {
|
|
|
+ entity.setCustomerName(null);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(entity.getCustomerName())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ VppCustomer customer = customerMapper.selectById(entity.getCustomerId());
|
|
|
+ entity.setCustomerName(customer != null ? customer.getCustomerName() : null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Long, String> loadCustomerNameMap(Set<Long> customerIds) {
|
|
|
+ if (customerIds == null || customerIds.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ return customerMapper.selectBatchIds(customerIds).stream()
|
|
|
+ .collect(Collectors.toMap(VppCustomer::getId, VppCustomer::getCustomerName, (a, b) -> a));
|
|
|
+ }
|
|
|
+
|
|
|
private List<VppResponseDeviationPrice> loadActivePriceAdjustments(Integer tenantId) {
|
|
|
return priceAdjustmentMapper.selectList(
|
|
|
new LambdaQueryWrapper<VppResponseDeviationPrice>()
|
|
|
@@ -423,7 +510,8 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
* <ul>
|
|
|
* <li>完成率(%)= 平台实际响应量 / 申报出清量 × 100(≥0,正常 0~100,超发可 >100)</li>
|
|
|
* <li>偏差率(%)= (电网实际响应量 − 平台实际响应量) / 申报出清量 × 100(可正可负)</li>
|
|
|
- * <li>补贴金额 = 平台实际响应量 × 响应时长(h) × 价格调整系数 × 补贴标准</li>
|
|
|
+ * <li>价格调整系数:按完成率比例匹配响应量偏差价格调整配置</li>
|
|
|
+ * <li>补贴金额 = 电网实际响应量 × 响应时长(h) × 价格调整系数 × 补贴标准</li>
|
|
|
* </ul>
|
|
|
*/
|
|
|
private void recalculate(VppDrSubsidyPrediction entity,
|
|
|
@@ -452,7 +540,7 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 比例:完成率用于匹配响应量偏差价格调整配置(区间按 0~1+ 配置)
|
|
|
+ // 完成率用平台量;价格档按完成率比例匹配
|
|
|
BigDecimal completionRatio = platformKw.divide(declaredKw, 6, RoundingMode.HALF_UP);
|
|
|
if (completionRatio.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
completionRatio = BigDecimal.ZERO;
|
|
|
@@ -461,15 +549,15 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
.divide(declaredKw, 6, RoundingMode.HALF_UP);
|
|
|
BigDecimal priceAdjustmentCoefficient = resolvePriceAdjustmentCoefficient(completionRatio, adjustments);
|
|
|
|
|
|
- // 对外/入库为百分比
|
|
|
entity.setCompletionRate(completionRatio.multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP));
|
|
|
entity.setDeviationRate(deviationRatio.multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP));
|
|
|
entity.setPriceAdjustmentCoefficient(priceAdjustmentCoefficient);
|
|
|
|
|
|
+ // 补贴按电网核定实际响应量结算
|
|
|
BigDecimal subsidyAmount = null;
|
|
|
if (entity.getSubsidyPrice() != null && durationMin > 0) {
|
|
|
BigDecimal hours = BigDecimal.valueOf(durationMin).divide(MINUTES_PER_HOUR, 6, RoundingMode.HALF_UP);
|
|
|
- subsidyAmount = platformKw
|
|
|
+ subsidyAmount = gridKw
|
|
|
.multiply(hours)
|
|
|
.multiply(priceAdjustmentCoefficient)
|
|
|
.multiply(entity.getSubsidyPrice())
|
|
|
@@ -497,6 +585,8 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
vo.setDrEventId(entity.getDrEventId());
|
|
|
vo.setSiteId(entity.getSiteId());
|
|
|
vo.setSiteName(entity.getSiteName());
|
|
|
+ vo.setCustomerId(entity.getCustomerId());
|
|
|
+ vo.setCustomerName(entity.getCustomerName());
|
|
|
vo.setEventStartTime(entity.getEventStartTime());
|
|
|
vo.setEventEndTime(entity.getEventEndTime());
|
|
|
vo.setResponseDurationMin(entity.getResponseDurationMin());
|