From cd4e26608b01cbc376760d96e2eab5d0a796f0b0 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期二, 28 七月 2026 10:14:04 +0800
Subject: [PATCH] 合并监控
---
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java | 40 ++++
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/support/FranchiseeUserStatistics.java | 111 +++++++++++++
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplRefundableAmountTest.java | 41 +++++
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java | 2
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java | 2
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TStationCollectController.java | 2
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/feignClient/PartnerClient.java | 4
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java | 6
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TPartnerAppUserServiceImpl.java | 34 +++
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java | 22 ++
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TPartnerAppUserService.java | 2
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/support/FranchiseeUserStatisticsTest.java | 74 +++++++++
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/factory/PartnerFallbackFactory.java | 6
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java | 2
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderInfoDto.java | 2
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/StationCollectQuery.java | 1
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java | 72 ++++-----
17 files changed, 371 insertions(+), 52 deletions(-)
diff --git a/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/factory/PartnerFallbackFactory.java b/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/factory/PartnerFallbackFactory.java
index a05fa3f..d2cbd3a 100644
--- a/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/factory/PartnerFallbackFactory.java
+++ b/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/factory/PartnerFallbackFactory.java
@@ -2,6 +2,7 @@
import com.ruoyi.chargingPile.api.feignClient.PartnerClient;
import com.ruoyi.chargingPile.api.model.Partner;
+import com.ruoyi.chargingPile.api.model.TPartnerAppUser;
import com.ruoyi.common.core.domain.R;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,6 +36,11 @@
public R<Partner> getPartnerByAppUserId(Long appUserId) {
return R.fail("根据小程序用户获取合作商信息失败:" + throwable.getMessage());
}
+
+ @Override
+ public R<List<TPartnerAppUser>> getBoundAppUserRelations(Integer partnerId) {
+ return R.fail("获取合作商绑定用户失败:" + throwable.getMessage());
+ }
@Override
public R<Set<Integer>> authSite(Integer partnerId, String siteMenu) {
diff --git a/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/feignClient/PartnerClient.java b/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/feignClient/PartnerClient.java
index d8612e9..58cc55b 100644
--- a/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/feignClient/PartnerClient.java
+++ b/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/feignClient/PartnerClient.java
@@ -2,6 +2,7 @@
import com.ruoyi.chargingPile.api.factory.PartnerFallbackFactory;
import com.ruoyi.chargingPile.api.model.Partner;
+import com.ruoyi.chargingPile.api.model.TPartnerAppUser;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.web.domain.AjaxResult;
@@ -31,6 +32,9 @@
@PostMapping("/partner/getPartnerByAppUserId/{appUserId}")
R<Partner> getPartnerByAppUserId(@PathVariable("appUserId") Long appUserId);
+
+ @PostMapping("/partner/appUser/relations/{partnerId}")
+ R<List<TPartnerAppUser>> getBoundAppUserRelations(@PathVariable("partnerId") Integer partnerId);
/**
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
index 1aea58e..9788e50 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
@@ -638,8 +638,16 @@
@ApiOperation(value = "重置操作密码", tags = {"管理后台-系统用户管理"})
public AjaxResult resetOperationPwd(@RequestBody Map<String, Object> params) {
Long userId = parseLong(params.get("userId"));
+ String operationPassword = parseString(params.get("operationPassword"));
+ String code = parseString(params.get("code"));
if (userId == null) {
return AjaxResult.error("用户不存在");
+ }
+ if (StringUtils.isEmpty(operationPassword)) {
+ return AjaxResult.error("请输入新操作密码");
+ }
+ if (StringUtils.isEmpty(code)) {
+ return AjaxResult.error("请输入验证码");
}
SysUser user = userService.selectUserById(userId);
@@ -649,9 +657,19 @@
userService.checkUserAllowed(user);
userService.checkUserDataScope(userId);
- user.setOperationPassword(SecurityUtils.encryptPassword("123456"));
+ String cacheCode = redisService.getCacheObject(RESET_PWD_CODE_KEY + userId);
+ if (StringUtils.isEmpty(cacheCode)) {
+ return AjaxResult.error("验证码已过期,请重新获取验证码");
+ }
+ if (!code.equals(cacheCode)) {
+ return AjaxResult.error("验证码错误");
+ }
+
+ user.setOperationPassword(SecurityUtils.encryptPassword(operationPassword));
user.setUpdateBy(SecurityUtils.getUsername());
- return toAjax(userService.updateUser(user));
+ int result = userService.updateUser(user);
+ redisService.deleteObject(RESET_PWD_CODE_KEY + userId);
+ return toAjax(result);
}
private Long parseLong(Object value) {
if (value == null || StringUtils.isEmpty(String.valueOf(value))) {
diff --git a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java
index f5e665e..3ee7e42 100644
--- a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java
+++ b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java
@@ -4,6 +4,7 @@
import com.ruoyi.chargingPile.api.dto.GetPermissionConfigurationDTO;
import com.ruoyi.chargingPile.api.dto.PartnerListDTO;
import com.ruoyi.chargingPile.api.model.Partner;
+import com.ruoyi.chargingPile.api.model.TPartnerAppUser;
import com.ruoyi.chargingPile.api.model.TPartnerSite;
import com.ruoyi.chargingPile.api.model.TSiteMenu;
import com.ruoyi.chargingPile.api.query.GetPartnerList;
@@ -202,6 +203,11 @@
return AjaxResult.success(partnerAppUserService.getBoundUsers(partnerId));
}
+ @PostMapping("/appUser/relations/{partnerId}")
+ public R<List<TPartnerAppUser>> getBoundAppUserRelations(@PathVariable Integer partnerId) {
+ return R.ok(partnerAppUserService.getRelations(partnerId));
+ }
+
@RequiresPermissions(value = {"/partners/update"}, logical = Logical.OR)
@PostMapping("/appUser/bind")
@ApiOperation(value = "保存合作商绑定的小程序用户", tags = {"管理后台-合作商管理"})
diff --git a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TPartnerAppUserService.java b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TPartnerAppUserService.java
index 302e067..8a0f467 100644
--- a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TPartnerAppUserService.java
+++ b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TPartnerAppUserService.java
@@ -15,6 +15,8 @@
List<PartnerAppUserVO> getBoundUsers(Integer partnerId);
+ List<TPartnerAppUser> getRelations(Integer partnerId);
+
void saveBindings(PartnerAppUserBindDTO bindDTO);
Partner getPartnerByAppUserId(Long appUserId);
diff --git a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TPartnerAppUserServiceImpl.java b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TPartnerAppUserServiceImpl.java
index 010aa09..54e84c0 100644
--- a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TPartnerAppUserServiceImpl.java
+++ b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TPartnerAppUserServiceImpl.java
@@ -96,6 +96,14 @@
}
@Override
+ public List<TPartnerAppUser> getRelations(Integer partnerId) {
+ assertPartnerExists(partnerId);
+ return list(new LambdaQueryWrapper<TPartnerAppUser>()
+ .eq(TPartnerAppUser::getPartnerId, partnerId)
+ .orderByAsc(TPartnerAppUser::getCreateTime));
+ }
+
+ @Override
@Transactional(rollbackFor = Exception.class)
public void saveBindings(PartnerAppUserBindDTO bindDTO) {
Integer partnerId = bindDTO.getPartnerId();
@@ -115,19 +123,35 @@
}
}
- remove(new LambdaQueryWrapper<TPartnerAppUser>().eq(TPartnerAppUser::getPartnerId, partnerId));
- if (selectedIds.isEmpty()) {
- return;
+ List<TPartnerAppUser> existingRelations = list(new LambdaQueryWrapper<TPartnerAppUser>()
+ .eq(TPartnerAppUser::getPartnerId, partnerId));
+ Set<Long> existingIds = existingRelations.stream()
+ .map(TPartnerAppUser::getAppUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ Set<Long> removedIds = existingIds.stream()
+ .filter(appUserId -> !selectedIds.contains(appUserId))
+ .collect(Collectors.toSet());
+ if (!removedIds.isEmpty()) {
+ remove(new LambdaQueryWrapper<TPartnerAppUser>()
+ .eq(TPartnerAppUser::getPartnerId, partnerId)
+ .in(TPartnerAppUser::getAppUserId, removedIds));
}
- List<TPartnerAppUser> relations = new ArrayList<>(selectedIds.size());
+ List<TPartnerAppUser> relations = new ArrayList<>();
for (Long appUserId : selectedIds) {
+ if (existingIds.contains(appUserId)) {
+ continue;
+ }
TPartnerAppUser relation = new TPartnerAppUser();
relation.setPartnerId(partnerId);
relation.setAppUserId(appUserId);
relations.add(relation);
}
- saveBatch(relations);
+ if (!relations.isEmpty()) {
+ saveBatch(relations);
+ }
}
@Override
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
index 9314981..5ca26f5 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -20,10 +20,12 @@
import com.ruoyi.chargingPile.api.domain.SiteMenu;
import com.ruoyi.chargingPile.api.dto.GetSiteListDTO;
import com.ruoyi.chargingPile.api.feignClient.*;
+import com.ruoyi.chargingPile.api.model.Partner;
import com.ruoyi.chargingPile.api.model.Site;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.chargingPile.api.model.TChargingPile;
import com.ruoyi.chargingPile.api.model.TParkingRecord;
+import com.ruoyi.chargingPile.api.model.TPartnerAppUser;
import com.ruoyi.chargingPile.api.vo.GetChargingGunByChargingPileIdsVo;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.dto.ChargingOrderGroup;
@@ -57,6 +59,7 @@
import com.ruoyi.order.export.PayOrderExport;
import com.ruoyi.order.export.EnterpriseChargingDetailExport;
import com.ruoyi.order.service.*;
+import com.ruoyi.order.support.FranchiseeUserStatistics;
import com.ruoyi.order.util.PreviousSixMonths;
import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService;
import com.ruoyi.order.vo.EndOfChargePageInfo;
@@ -230,20 +233,22 @@
@PostMapping("/app/franchisee/user")
public R<Map<String, Object>> franchiseeUser(@RequestBody(required = false) Map<String, Object> params) {
- List<TChargingOrder> orders = franchiseeOrders(params);
- Set<Long> users = orders.stream().map(TChargingOrder::getAppUserId).filter(Objects::nonNull).collect(Collectors.toSet());
- List<Integer> siteIds = franchiseeSiteIds(params);
+ Partner partner = franchiseePartner();
+ List<Integer> siteIds = partner == null ? Collections.emptyList() : franchiseeSiteIds(params, partner.getId());
LocalDateTime[] range = franchiseeRange(params);
- Set<Long> newUsers = new HashSet<>();
- if (!siteIds.isEmpty() && range[0] != null && range[1] != null) {
- List<TChargingOrder> allOrders = franchiseeOrders(siteIds, null, range[1]);
- Map<Long, LocalDateTime> firstOrder = new HashMap<>();
- for (TChargingOrder order : allOrders) {
- if (order.getAppUserId() == null || order.getEndTime() == null) continue;
- firstOrder.merge(order.getAppUserId(), order.getEndTime(), (oldTime, newTime) -> oldTime.isBefore(newTime) ? oldTime : newTime);
+ List<TChargingOrder> orders = franchiseeOrders(siteIds, range[0], range[1]).stream()
+ .filter(order -> Objects.equals(order.getStatus(), 5))
+ .collect(Collectors.toList());
+ Set<Long> users = orders.stream().map(TChargingOrder::getAppUserId).filter(Objects::nonNull).collect(Collectors.toSet());
+ List<TPartnerAppUser> relations = Collections.emptyList();
+ if (partner != null && partner.getId() != null) {
+ R<List<TPartnerAppUser>> relationResult = partnerClient.getBoundAppUserRelations(partner.getId());
+ if (relationResult != null && relationResult.getData() != null) {
+ relations = relationResult.getData();
}
- firstOrder.forEach((userId, firstTime) -> { if (!firstTime.isBefore(range[0])) newUsers.add(userId); });
}
+ FranchiseeUserStatistics.Result userStatistics = FranchiseeUserStatistics.calculate(
+ orders, relations, range[0], range[1]);
List<TAppUser> appUsers = Collections.emptyList();
if (!users.isEmpty()) {
@@ -262,9 +267,8 @@
Map<String, Object> evaluation = franchiseeEvaluation(siteIds);
Map<String, Object> result = new LinkedHashMap<>();
- Map<String, Object> summary = new LinkedHashMap<>(); summary.put("chargeUserCount", users.size()); summary.put("newUserCount", newUsers.size());
- result.put("summary", summary);
- result.put("userStats", franchiseeUserTrend(orders, newUsers, range));
+ result.put("summary", userStatistics.getSummary());
+ result.put("userStats", userStatistics.getTrend());
result.put("userTags", franchiseeUserTags(orders));
Map<Integer, String> vipNames = new HashMap<>();
R<List<TVip>> vipResult = vipClient.getAllVip();
@@ -319,40 +323,27 @@
}
private List<Integer> franchiseeSiteIds(Map<String, Object> params) {
- if (tokenService.getLoginUserApplet() == null || tokenService.getLoginUserApplet().getUserId() == null) return Collections.emptyList();
- Long appUserId = tokenService.getLoginUserApplet().getUserId();
- R<com.ruoyi.chargingPile.api.model.Partner> partnerResult = partnerClient.getPartnerByAppUserId(appUserId);
- com.ruoyi.chargingPile.api.model.Partner partner = partnerResult == null ? null : partnerResult.getData();
+ Partner partner = franchiseePartner();
if (partner == null || partner.getId() == null) return Collections.emptyList();
+ return franchiseeSiteIds(params, partner.getId());
+ }
+
+ private Partner franchiseePartner() {
+ if (tokenService.getLoginUserApplet() == null || tokenService.getLoginUserApplet().getUserId() == null) return null;
+ Long appUserId = tokenService.getLoginUserApplet().getUserId();
+ R<Partner> partnerResult = partnerClient.getPartnerByAppUserId(appUserId);
+ return partnerResult == null ? null : partnerResult.getData();
+ }
+
+ private List<Integer> franchiseeSiteIds(Map<String, Object> params, Integer partnerId) {
R<List<Site>> siteResult = siteClient.getSiteAll();
List<Site> sites = siteResult == null ? null : siteResult.getData();
- List<Integer> siteIds = sites == null ? new ArrayList<>() : sites.stream().filter(s -> Objects.equals(s.getPartnerId(), partner.getId())).map(Site::getId).collect(Collectors.toList());
+ List<Integer> siteIds = sites == null ? new ArrayList<>() : sites.stream().filter(s -> Objects.equals(s.getPartnerId(), partnerId)).map(Site::getId).collect(Collectors.toList());
Object requestedSite = params == null ? null : params.get("siteId");
if (requestedSite != null && !String.valueOf(requestedSite).isEmpty()) {
Integer siteId = Integer.valueOf(String.valueOf(requestedSite)); siteIds = siteIds.contains(siteId) ? Collections.singletonList(siteId) : Collections.singletonList(-1);
}
return siteIds;
- }
-
- private Map<String, Object> franchiseeUserTrend(List<TChargingOrder> orders, Set<Long> newUsers, LocalDateTime[] range) {
- LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate();
- LocalDate end = range[1] == null ? start.plusDays(1) : range[1].toLocalDate();
- List<String> categories = new ArrayList<>();
- List<BigDecimal> chargeUsers = new ArrayList<>(), newUserCounts = new ArrayList<>();
- for (LocalDate day = start; day.isBefore(end); day = day.plusDays(1)) {
- LocalDateTime dayStart = day.atStartOfDay(), dayEnd = day.plusDays(1).atStartOfDay();
- Set<Long> dayUsers = orders.stream().filter(order -> order.getAppUserId() != null && order.getEndTime() != null
- && !order.getEndTime().isBefore(dayStart) && order.getEndTime().isBefore(dayEnd))
- .map(TChargingOrder::getAppUserId).collect(Collectors.toSet());
- long newCount = dayUsers.stream().filter(newUsers::contains).count();
- categories.add(day.toString().substring(5));
- chargeUsers.add(BigDecimal.valueOf(dayUsers.size()));
- newUserCounts.add(BigDecimal.valueOf(newCount));
- }
- Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", categories);
- Map<String, Object> chargeSeries = new LinkedHashMap<>(); chargeSeries.put("name", "充电用户"); chargeSeries.put("data", chargeUsers);
- Map<String, Object> newSeries = new LinkedHashMap<>(); newSeries.put("name", "新增用户"); newSeries.put("data", newUserCounts);
- chart.put("series", Arrays.asList(chargeSeries, newSeries)); return chart;
}
private List<Map<String, Object>> franchiseeUserTags(List<TChargingOrder> orders) {
@@ -892,6 +883,7 @@
payOrderInfoDto.setThirdPayAmount(byId.getThirdPayAmount());
payOrderInfoDto.setBalanceRefundAmount(byId.getBalanceRefundAmount());
payOrderInfoDto.setThirdRefundAmount(byId.getThirdRefundAmount());
+ payOrderInfoDto.setRefundableAmount(chargingOrderService.getManualRefundableAmount(byId.getId()));
return R.ok(payOrderInfoDto);
case 2:
TShoppingOrder byId1 = shoppingOrderService.getById(orderId);
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderInfoDto.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderInfoDto.java
index 6f38492..a017a52 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderInfoDto.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderInfoDto.java
@@ -37,6 +37,8 @@
private BigDecimal balanceRefundAmount;
@ApiModelProperty("第三方退款金额")
private BigDecimal thirdRefundAmount;
+ @ApiModelProperty("剩余可人工退款金额(不扣减充电完成自动退款)")
+ private BigDecimal refundableAmount;
}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java
index 0fa01ea..976550e 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java
@@ -215,6 +215,8 @@
R specialRefund(PayOrderRefundDto payOrderQueryDto);
+ BigDecimal getManualRefundableAmount(Long chargingOrderId);
+
ChargingOrderListInfoVO chargingInfo(String uid);
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index e728c7b..ae56473 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -4885,7 +4885,42 @@
dto.setThirdPayAmount(order.getThirdPayAmount());
dto.setBalanceRefundAmount(order.getBalanceRefundAmount());
dto.setThirdRefundAmount(order.getThirdRefundAmount());
+ dto.setRefundableAmount(getManualRefundableAmount(order));
return R.ok(dto);
+ }
+
+ @Override
+ public BigDecimal getManualRefundableAmount(Long chargingOrderId) {
+ if (chargingOrderId == null) {
+ return BigDecimal.ZERO;
+ }
+ return getManualRefundableAmount(this.getById(chargingOrderId));
+ }
+
+ private BigDecimal getManualRefundableAmount(TChargingOrder order) {
+ if (order == null || order.getId() == null) {
+ return BigDecimal.ZERO;
+ }
+ List<TChargingOrderRefund> successfulRefunds = chargingOrderRefundService.list(
+ new LambdaQueryWrapper<TChargingOrderRefund>()
+ .eq(TChargingOrderRefund::getChargingOrderId, order.getId())
+ .eq(TChargingOrderRefund::getRefundStatus, 2));
+ return calculateManualRefundableAmount(order.getPaymentAmount(), successfulRefunds);
+ }
+
+ static BigDecimal calculateManualRefundableAmount(BigDecimal paymentAmount, List<TChargingOrderRefund> successfulRefunds) {
+ BigDecimal successfulManualRefundAmount = Optional.ofNullable(successfulRefunds)
+ .orElse(Collections.emptyList())
+ .stream()
+ .filter(refund -> "后台退款".equals(refund.getRefundTitle())
+ || SPECIAL_REFUND_TITLE.equals(refund.getRefundTitle()))
+ .map(TChargingOrderRefund::getRefundAmount)
+ .map(amount -> amount == null ? BigDecimal.ZERO : amount)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ return (paymentAmount == null ? BigDecimal.ZERO : paymentAmount)
+ .subtract(successfulManualRefundAmount)
+ .max(BigDecimal.ZERO)
+ .setScale(2, RoundingMode.HALF_UP);
}
@Override
@@ -4913,7 +4948,7 @@
}
mergeSuccessfulRefundAmounts(order);
BigDecimal refundAmount = money(payOrderQueryDto.getRefundAmount());
- BigDecimal refundableAmount = money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP);
+ BigDecimal refundableAmount = getManualRefundableAmount(order);
if (refundAmount.compareTo(refundableAmount) > 0) {
return R.fail("退款金额不能超过剩余可退金额");
}
@@ -5192,8 +5227,7 @@
return R.fail("充电订单不存在");
}
BigDecimal manualRefundAmount = money(payOrderQueryDto.getRefundAmount());
- BigDecimal remainingRefundAmount = money(tChargingOrder.getPaymentAmount())
- .subtract(money(tChargingOrder.getRefundAmount())).max(BigDecimal.ZERO);
+ BigDecimal remainingRefundAmount = getManualRefundableAmount(tChargingOrder);
if (manualRefundAmount.compareTo(remainingRefundAmount) > 0) {
return R.fail("退款金额不能超过剩余可退金额" + remainingRefundAmount.toPlainString() + "元");
}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/support/FranchiseeUserStatistics.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/support/FranchiseeUserStatistics.java
new file mode 100644
index 0000000..77d8bcb
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/support/FranchiseeUserStatistics.java
@@ -0,0 +1,111 @@
+package com.ruoyi.order.support;
+
+import com.ruoyi.chargingPile.api.model.TPartnerAppUser;
+import com.ruoyi.order.api.model.TChargingOrder;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** 加盟商看板用户统计的纯计算逻辑。 */
+public final class FranchiseeUserStatistics {
+
+ private FranchiseeUserStatistics() {
+ }
+
+ public static Result calculate(List<TChargingOrder> orders,
+ List<TPartnerAppUser> relations,
+ LocalDateTime start,
+ LocalDateTime end) {
+ List<TChargingOrder> safeOrders = orders == null ? Collections.emptyList() : orders;
+ List<TPartnerAppUser> safeRelations = relations == null ? Collections.emptyList() : relations;
+
+ Map<LocalDate, Set<Long>> chargingUsersByDay = new HashMap<>();
+ Set<Long> chargingUsers = new HashSet<>();
+ for (TChargingOrder order : safeOrders) {
+ if (order == null || order.getAppUserId() == null || !inRange(order.getEndTime(), start, end)) {
+ continue;
+ }
+ chargingUsers.add(order.getAppUserId());
+ chargingUsersByDay.computeIfAbsent(order.getEndTime().toLocalDate(), key -> new HashSet<>())
+ .add(order.getAppUserId());
+ }
+
+ Map<Long, LocalDateTime> firstBindingByUser = new HashMap<>();
+ for (TPartnerAppUser relation : safeRelations) {
+ if (relation == null || relation.getAppUserId() == null || relation.getCreateTime() == null) {
+ continue;
+ }
+ firstBindingByUser.merge(relation.getAppUserId(), relation.getCreateTime(),
+ (left, right) -> left.isBefore(right) ? left : right);
+ }
+ Map<LocalDate, Set<Long>> newUsersByDay = new HashMap<>();
+ Set<Long> newUsers = new HashSet<>();
+ firstBindingByUser.forEach((appUserId, createTime) -> {
+ if (inRange(createTime, start, end)) {
+ newUsers.add(appUserId);
+ newUsersByDay.computeIfAbsent(createTime.toLocalDate(), key -> new HashSet<>()).add(appUserId);
+ }
+ });
+
+ Map<String, Object> summary = new LinkedHashMap<>();
+ summary.put("chargeUserCount", (long) chargingUsers.size());
+ summary.put("newUserCount", (long) newUsers.size());
+
+ LocalDate firstDay = start == null ? LocalDate.now() : start.toLocalDate();
+ LocalDate exclusiveEndDay = end == null ? firstDay.plusDays(1) : end.toLocalDate();
+ List<String> categories = new ArrayList<>();
+ List<Long> chargingSeries = new ArrayList<>();
+ List<Long> newSeries = new ArrayList<>();
+ for (LocalDate day = firstDay; day.isBefore(exclusiveEndDay); day = day.plusDays(1)) {
+ categories.add(String.format("%02d-%02d", day.getMonthValue(), day.getDayOfMonth()));
+ chargingSeries.add((long) chargingUsersByDay.getOrDefault(day, Collections.emptySet()).size());
+ newSeries.add((long) newUsersByDay.getOrDefault(day, Collections.emptySet()).size());
+ }
+
+ Map<String, Object> trend = new LinkedHashMap<>();
+ trend.put("categories", categories);
+ trend.put("series", Arrays.asList(series("充电用户", chargingSeries), series("新增用户", newSeries)));
+ return new Result(summary, trend);
+ }
+
+ private static boolean inRange(LocalDateTime time, LocalDateTime start, LocalDateTime end) {
+ return time != null
+ && (start == null || !time.isBefore(start))
+ && (end == null || time.isBefore(end));
+ }
+
+ private static Map<String, Object> series(String name, List<Long> data) {
+ Map<String, Object> series = new LinkedHashMap<>();
+ series.put("name", name);
+ series.put("data", data);
+ return series;
+ }
+
+ public static final class Result {
+ private final Map<String, Object> summary;
+ private final Map<String, Object> trend;
+
+ private Result(Map<String, Object> summary, Map<String, Object> trend) {
+ this.summary = Objects.requireNonNull(summary);
+ this.trend = Objects.requireNonNull(trend);
+ }
+
+ public Map<String, Object> getSummary() {
+ return summary;
+ }
+
+ public Map<String, Object> getTrend() {
+ return trend;
+ }
+ }
+}
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplRefundableAmountTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplRefundableAmountTest.java
new file mode 100644
index 0000000..65a00ad
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplRefundableAmountTest.java
@@ -0,0 +1,41 @@
+package com.ruoyi.order.service.impl;
+
+import com.ruoyi.order.api.model.TChargingOrderRefund;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class TChargingOrderServiceImplRefundableAmountTest {
+
+ @Test
+ void completionRefundDoesNotReduceManualRefundableAmount() {
+ TChargingOrderRefund completionRefund = refund("充电完成退款", "2.25");
+
+ BigDecimal refundableAmount = TChargingOrderServiceImpl.calculateManualRefundableAmount(
+ new BigDecimal("26.75"), Collections.singletonList(completionRefund));
+
+ assertEquals(new BigDecimal("26.75"), refundableAmount);
+ }
+
+ @Test
+ void onlySuccessfulManualRefundReducesManualRefundableAmount() {
+ TChargingOrderRefund completionRefund = refund("充电完成退款", "2.25");
+ TChargingOrderRefund manualRefund = refund("后台退款", "5.00");
+
+ BigDecimal refundableAmount = TChargingOrderServiceImpl.calculateManualRefundableAmount(
+ new BigDecimal("26.75"), Arrays.asList(completionRefund, manualRefund));
+
+ assertEquals(new BigDecimal("21.75"), refundableAmount);
+ }
+
+ private TChargingOrderRefund refund(String title, String amount) {
+ TChargingOrderRefund refund = new TChargingOrderRefund();
+ refund.setRefundTitle(title);
+ refund.setRefundAmount(new BigDecimal(amount));
+ return refund;
+ }
+}
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/support/FranchiseeUserStatisticsTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/support/FranchiseeUserStatisticsTest.java
new file mode 100644
index 0000000..3f9568e
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/support/FranchiseeUserStatisticsTest.java
@@ -0,0 +1,74 @@
+package com.ruoyi.order.support;
+
+import com.ruoyi.chargingPile.api.model.TPartnerAppUser;
+import com.ruoyi.order.api.model.TChargingOrder;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class FranchiseeUserStatisticsTest {
+
+ private static final LocalDateTime START = LocalDateTime.of(2026, 7, 1, 0, 0);
+ private static final LocalDateTime END = LocalDateTime.of(2026, 7, 4, 0, 0);
+
+ @Test
+ void countsDistinctChargingUsersAndBucketsBindingsOnTheirExactDate() {
+ List<TChargingOrder> orders = Arrays.asList(
+ order(1L, LocalDateTime.of(2026, 7, 1, 8, 0)),
+ order(1L, LocalDateTime.of(2026, 7, 1, 18, 0)),
+ order(1L, LocalDateTime.of(2026, 7, 2, 8, 0)),
+ order(2L, LocalDateTime.of(2026, 7, 2, 9, 0))
+ );
+ List<TPartnerAppUser> relations = Arrays.asList(
+ relation(1L, LocalDateTime.of(2026, 6, 30, 23, 59)),
+ relation(2L, LocalDateTime.of(2026, 7, 2, 10, 0)),
+ relation(3L, LocalDateTime.of(2026, 7, 2, 11, 0)),
+ relation(3L, LocalDateTime.of(2026, 7, 2, 12, 0))
+ );
+
+ FranchiseeUserStatistics.Result result = FranchiseeUserStatistics.calculate(orders, relations, START, END);
+
+ assertEquals(2L, result.getSummary().get("chargeUserCount"));
+ assertEquals(2L, result.getSummary().get("newUserCount"));
+ assertEquals(Arrays.asList("07-01", "07-02", "07-03"), result.getTrend().get("categories"));
+ assertEquals(Arrays.asList(1L, 2L, 0L), seriesData(result.getTrend(), 0));
+ assertEquals(Arrays.asList(0L, 2L, 0L), seriesData(result.getTrend(), 1));
+ }
+
+ @Test
+ void returnsZeroFilledSeriesWhenThereIsNoActivity() {
+ FranchiseeUserStatistics.Result result = FranchiseeUserStatistics.calculate(
+ Collections.emptyList(), Collections.emptyList(), START, END);
+
+ assertEquals(0L, result.getSummary().get("chargeUserCount"));
+ assertEquals(0L, result.getSummary().get("newUserCount"));
+ assertEquals(Arrays.asList(0L, 0L, 0L), seriesData(result.getTrend(), 0));
+ assertEquals(Arrays.asList(0L, 0L, 0L), seriesData(result.getTrend(), 1));
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<Long> seriesData(Map<String, Object> trend, int index) {
+ List<Map<String, Object>> series = (List<Map<String, Object>>) trend.get("series");
+ return (List<Long>) series.get(index).get("data");
+ }
+
+ private TChargingOrder order(Long appUserId, LocalDateTime endTime) {
+ TChargingOrder order = new TChargingOrder();
+ order.setAppUserId(appUserId);
+ order.setEndTime(endTime);
+ return order;
+ }
+
+ private TPartnerAppUser relation(Long appUserId, LocalDateTime createTime) {
+ TPartnerAppUser relation = new TPartnerAppUser();
+ relation.setAppUserId(appUserId);
+ relation.setCreateTime(createTime);
+ return relation;
+ }
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java
index aa310b0..a0c3e58 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java
@@ -28,7 +28,7 @@
@Resource
private TDeviceService deviceService;
- @RequiresPermissions(value = {"/deviceManagement/list"}, logical = Logical.OR)
+ @RequiresPermissions(value = {"/deviceManagement/list", "/faultInformationList"}, logical = Logical.OR)
@ResponseBody
@GetMapping("/pageList")
@ApiOperation(value = "获取设备列表", tags = {"管理后台-设备管理"})
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TStationCollectController.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TStationCollectController.java
index 8a6b71c..b40bcbd 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TStationCollectController.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TStationCollectController.java
@@ -22,7 +22,7 @@
public class TStationCollectController {
@Resource private TStationCollectService stationCollectService;
- @RequiresPermissions(value = {"/stationCollect/list"}, logical = Logical.OR)
+ @RequiresPermissions(value = {"/stationCollect/list", "/faultInformationList"}, logical = Logical.OR)
@GetMapping("/pageList") @ApiOperation("分页查询逆变器")
public AjaxResult<PageInfo<TStationCollect>> pageList(StationCollectQuery query) {
return AjaxResult.success(stationCollectService.pageList(query));
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/StationCollectQuery.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/StationCollectQuery.java
index 133cedf..2210a5b 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/StationCollectQuery.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/StationCollectQuery.java
@@ -14,4 +14,5 @@
@ApiModelProperty("机型型号") private String deviceModel;
@ApiModelProperty("逆变器SN") private String inverterId;
@ApiModelProperty("状态") private String status;
+ @ApiModelProperty("是否只查询异常状态(离线、故障)") private Boolean abnormalOnly;
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java
index e83841a..78bb1d8 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java
@@ -61,6 +61,8 @@
.like(StringUtils.isNotBlank(query.getDeviceModel()), TStationCollect::getDeviceModel, query.getDeviceModel())
.like(StringUtils.isNotBlank(query.getInverterId()), TStationCollect::getInverterId, query.getInverterId())
.eq(StringUtils.isNotBlank(query.getStatus()), TStationCollect::getStatus, query.getStatus())
+ .in(StringUtils.isBlank(query.getStatus()) && Boolean.TRUE.equals(query.getAbnormalOnly()),
+ TStationCollect::getStatus, "2", "3")
.orderByAsc(TStationCollect::getSortOrder).orderByDesc(TStationCollect::getCreateTime).page(page);
fillStationAddress(page.getRecords());
return page;
--
Gitblit v1.7.1