From 7858ae894c398f2f9248ac898b2ff200fa36225d Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期三, 29 七月 2026 18:42:15 +0800
Subject: [PATCH] 各类修复
---
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java | 244 ++++----------
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java | 2
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TOrderInvoiceMapper.xml | 17
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/PartnerAppUserAccessService.java | 130 +++++++
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/task/FavoritePriceDropTask.java | 13
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/TOrderInvoiceCompanyOptionsMapperTest.java | 37 ++
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java | 4
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TOrderInvoiceService.java | 4
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TChargingOrderControllerTest.java | 22 +
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java | 5
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserSiteFavoriteController.java | 5
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java | 31 +
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java | 20 +
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/ChargingOrderClient.java | 6
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml | 23 +
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java | 9
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/EnterpriseChargingAnalysisMapperTest.java | 2
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/PartnerAppUserAccessServiceTest.java | 122 +++++++
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TOrderInvoiceMapper.java | 2
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TVipOrderControllerTest.java | 31 +
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/ChargingOrderFallbackFactory.java | 5
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingCouponSupportTest.java | 67 +++
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ChargingCouponSupport.java | 77 +++-
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java | 8
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TVipOrderController.java | 58 ++-
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java | 3
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java | 2
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VipChargingOrderExportMapperTest.java | 36 ++
28 files changed, 701 insertions(+), 284 deletions(-)
diff --git a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/ChargingOrderFallbackFactory.java b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/ChargingOrderFallbackFactory.java
index 8ea4cee..c04f9de 100644
--- a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/ChargingOrderFallbackFactory.java
+++ b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/ChargingOrderFallbackFactory.java
@@ -224,6 +224,11 @@
return R.fail("获取站点集合中总的充电量失败:" + throwable.getMessage());
}
+ @Override
+ public R<List<Long>> getAppUserIdsBySiteIds(List<Integer> siteIds) {
+ return R.fail("查询站点充电用户失败:" + throwable.getMessage());
+ }
+
// @Override
// public R<List<TChargingOrder>> getOrderListByStatus(List<Integer> statusList) {
// return R.fail("获取站点集合中总的充电量失败:" + throwable.getMessage());
diff --git a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/ChargingOrderClient.java b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/ChargingOrderClient.java
index 66c8d7d..26e3157 100644
--- a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/ChargingOrderClient.java
+++ b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/ChargingOrderClient.java
@@ -326,6 +326,12 @@
@GetMapping("/t-charging-order/getSumDegreeBySiteIds")
R<BigDecimal> getSumDegreeBySiteIds(@RequestParam("siteIds") List<Integer> SiteIds);
+ /**
+ * 查询指定站点中实际发生过充电的用户ID
+ */
+ @PostMapping("/t-charging-order/getAppUserIdsBySiteIds")
+ R<List<Long>> getAppUserIdsBySiteIds(@RequestBody List<Integer> siteIds);
+
// /**
// * 获取充电订单列表
diff --git a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java
index 687897a..f3bd415 100644
--- a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java
+++ b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java
@@ -38,6 +38,8 @@
private String licensePlate;
@ApiModelProperty(value = "手机号")
private String phone;
+ @ApiModelProperty(value = "归属合作商/服务商")
+ private String partnerName;
@ApiModelProperty(value = "uid")
private String uid;
@ApiModelProperty(value = "记录按钮权限 ")
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
index 2373bea..53b0243 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
@@ -157,6 +157,8 @@
private TAppUserWhitelistService appUserWhitelistService;
@Resource
private PartnerClient partnerClient;
+ @Resource
+ private PartnerAppUserAccessService partnerAppUserAccessService;
@Autowired
private RedissonClient redissonClient;
@@ -400,6 +402,10 @@
.eq(userListQueryDto.getVipTypeId() != null, TAppUser::getVipId, userListQueryDto.getVipTypeId())
.in(!userIds.isEmpty(),TAppUser::getId,userIds)
.orderByDesc(BasePojo::getCreateTime);
+ List<Long> partnerVisibleUserIds = partnerAppUserAccessService.getVisibleUserIdsForCurrentPartner();
+ if (partnerVisibleUserIds != null) {
+ query.in(TAppUser::getId, partnerVisibleUserIds);
+ }
if (Integer.valueOf(1).equals(userListQueryDto.getWhitelistFlag())) {
if (whitelistUserIds.isEmpty() && whitelistPhones.isEmpty()) {
query.eq(TAppUser::getId, -1L);
@@ -690,6 +696,7 @@
@PostMapping(value = "/user/addOrUpdate")
@Log(title = "【用户列表】添加编辑用户", businessType = BusinessType.INSERT,operatorType = OperatorType.MANAGE)
public R addOrUpdate(@RequestBody TAppUser tAppUser) {
+ partnerAppUserAccessService.assertWritable();
if (tAppUser.getId() != null){
//为编辑操作,要进行vip的逻辑处理
TAppUser nowUser = appUserService.getById(tAppUser.getId());
@@ -734,10 +741,11 @@
}
- @RequiresPermissions(value = {"/appUser/select", "/vipList/select"}, logical = Logical.OR)
+ @RequiresPermissions(value = {"/appUser/list", "/appUser/select", "/vipList/select"}, logical = Logical.OR)
@ApiOperation(value = "用户详情", tags = {"用户管理-用户列表"})
@GetMapping(value = "/user/detail/{id}")
public R<UserDetailDto> userDetail(@PathVariable Long id) {
+ partnerAppUserAccessService.assertUserVisible(id);
TAppUser user = appUserService.getById(id);
R<Map<Integer, String>> vipMap = otherClient.getVipMap(Collections.singletonList(user.getVipId()));
if (user.getVipEndTime()!=null&&user.getVipEndTime().isAfter(LocalDateTime.now())) {
@@ -772,13 +780,14 @@
return R.ok(userDetailDto);
}
- @RequiresPermissions(value = {"/appUser/select", "/vipList/select"}, logical = Logical.OR)
+ @RequiresPermissions(value = {"/appUser/list", "/appUser/select", "/vipList/select"}, logical = Logical.OR)
@ApiOperation(value = "用户详情积分明细", tags = {"后台-用户管理-用户列表"})
@PostMapping(value = "/user/points/page")
public R<Page<TAppUserIntegralChange>> pointsDetail(@RequestBody PointsQueryDto pointsQueryDto) {
if (pointsQueryDto.getUserId()==null){
pointsQueryDto.setUserId(tokenService.getLoginUserApplet().getUserId());
}
+ partnerAppUserAccessService.assertUserVisible(pointsQueryDto.getUserId());
List<Integer> types = new ArrayList<>();
if (pointsQueryDto.getType()==null){
types.add(1);
@@ -874,10 +883,11 @@
}
- @RequiresPermissions(value = {"/appUser/select", "/vipList/select"}, logical = Logical.OR)
+ @RequiresPermissions(value = {"/appUser/list", "/appUser/select", "/vipList/select"}, logical = Logical.OR)
@ApiOperation(value = "用户详情余额明细", tags = {"后台-用户管理-用户列表"})
@PostMapping(value = "/user/balance/page")
public R<Page<TExchangeCodeUseRecord>> adminBalanceDetail(@RequestBody ExchangeCodeUseRecordQuery queryDto) {
+ partnerAppUserAccessService.assertUserVisible(queryDto.getUserId());
return otherClient.exchangeCodeUseRecordPage(queryDto);
}
@@ -945,6 +955,7 @@
@PostMapping(value = "/user/status/change")
@Log(title = "【用户列表】冻结解冻用户", businessType = BusinessType.UPDATE,operatorType = OperatorType.MANAGE)
public R pointsDetail(@RequestBody UserChangeDto userChangeDto) {
+ partnerAppUserAccessService.assertWritable();
TAppUser appUser = appUserService.getById(userChangeDto.getUserId());
appUser.setStatus(userChangeDto.getStatus());
appUser.setRemark(userChangeDto.getRemark());
@@ -958,6 +969,7 @@
@PostMapping(value = "/user/unit/change")
@Log(title = "【用户列表】修改用户单位", businessType = BusinessType.UPDATE,operatorType = OperatorType.MANAGE)
public R unitChange(@RequestBody UnitChangeDto unitChangeDto) {
+ partnerAppUserAccessService.assertWritable();
for (String s : unitChangeDto.getIds().split(",")) {
TAppUser byId = appUserService.getById(s);
byId.setCompanyId(unitChangeDto.getUnitId());
@@ -972,6 +984,7 @@
@DeleteMapping(value = "/user/delete")
@Log(title = "【用户列表】删除用户", businessType = BusinessType.DELETE,operatorType = OperatorType.MANAGE)
public R userDelete(String ids) {
+ partnerAppUserAccessService.assertWritable();
String[] split = ids.split(",");
for (String s : split) {
appUserService.removeById(s);
@@ -1151,6 +1164,7 @@
@PostMapping(value = "/user/give/vip")
@Log(title = "【用户列表】赠送会员", businessType = BusinessType.OTHER,operatorType = OperatorType.MANAGE)
public R giveVip(@RequestBody GiveVipDto giveVipDto) {
+ partnerAppUserAccessService.assertWritable();
String[] split = giveVipDto.getUserIds().split(",");
for (String s : split) {
TAppUser nowUser = appUserService.getById(s);
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserSiteFavoriteController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserSiteFavoriteController.java
index 4a4c1a7..ce99718 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserSiteFavoriteController.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserSiteFavoriteController.java
@@ -81,11 +81,14 @@
@ApiOperation(value = "开启站点降价提醒", tags = {"小程序-收藏"})
public AjaxResult<Void> subscribe(@PathVariable Integer siteId) {
Long userId = tokenService.getLoginUserApplet().getUserId();
- favoriteService.lambdaUpdate()
+ boolean updated = favoriteService.lambdaUpdate()
.eq(TAppUserSiteFavorite::getAppUserId, userId)
.eq(TAppUserSiteFavorite::getSiteId, siteId)
.set(TAppUserSiteFavorite::getNotifyEnabled, true)
.update();
+ if (!updated) {
+ return AjaxResult.error("收藏记录不存在,请重新收藏后再开启提醒");
+ }
return AjaxResult.success();
}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/PartnerAppUserAccessService.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/PartnerAppUserAccessService.java
new file mode 100644
index 0000000..d665936
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/PartnerAppUserAccessService.java
@@ -0,0 +1,130 @@
+package com.ruoyi.account.service;
+
+import com.ruoyi.chargingPile.api.domain.SiteMenu;
+import com.ruoyi.chargingPile.api.feignClient.PartnerClient;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.order.api.feignClient.ChargingOrderClient;
+import com.ruoyi.system.api.domain.SysUser;
+import com.ruoyi.system.api.feignClient.SysUserClient;
+import com.ruoyi.system.api.model.LoginUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 合作商后台用户数据范围及只读权限控制。
+ */
+@Slf4j
+@Service
+public class PartnerAppUserAccessService {
+
+ private static final Integer PARTNER_ROLE_TYPE = 2;
+ private static final Long IMPOSSIBLE_USER_ID = -1L;
+ private static final String PARTNER_READ_ONLY_MESSAGE = "合作商账号仅支持查看用户信息";
+
+ @Resource
+ private TokenService tokenService;
+ @Resource
+ private SysUserClient sysUserClient;
+ @Resource
+ private PartnerClient partnerClient;
+ @Resource
+ private ChargingOrderClient chargingOrderClient;
+
+ /**
+ * 非合作商返回null,合作商返回允许查看的用户ID;空权限始终返回不可命中的ID。
+ */
+ public List<Long> getVisibleUserIdsForCurrentPartner() {
+ SysUser sysUser = getCurrentSysUser();
+ if (!isPartner(sysUser)) {
+ return null;
+ }
+ if (sysUser.getObjectId() == null) {
+ return impossibleUserIds();
+ }
+ R<Set<Integer>> siteResult = partnerClient.authSite(sysUser.getObjectId(), SiteMenu.SITE_LIST.getValue());
+ if (siteResult == null || !R.isSuccess(siteResult) || siteResult.getData() == null) {
+ return impossibleUserIds();
+ }
+ List<Integer> siteIds = siteResult.getData().stream()
+ .filter(Objects::nonNull)
+ .filter(siteId -> siteId > 0)
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ if (siteIds.isEmpty()) {
+ return impossibleUserIds();
+ }
+ R<List<Long>> userResult = chargingOrderClient.getAppUserIdsBySiteIds(siteIds);
+ if (userResult == null || !R.isSuccess(userResult) || userResult.getData() == null) {
+ log.warn("查询合作商充电用户失败,partnerId={}", sysUser.getObjectId());
+ return impossibleUserIds();
+ }
+ List<Long> userIds = userResult.getData().stream()
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ return userIds.isEmpty() ? impossibleUserIds() : userIds;
+ }
+
+ public boolean isCurrentPartner() {
+ return isPartner(getCurrentSysUser());
+ }
+
+ public void assertUserVisible(Long userId) {
+ List<Long> visibleUserIds = getVisibleUserIdsForCurrentPartner();
+ if (visibleUserIds != null && (userId == null || !visibleUserIds.contains(userId))) {
+ throw new ServiceException("无权查看该用户信息");
+ }
+ }
+
+ public void assertWritable() {
+ if (isCurrentPartner()) {
+ throw new ServiceException(PARTNER_READ_ONLY_MESSAGE);
+ }
+ }
+
+ private boolean isPartner(SysUser sysUser) {
+ return sysUser != null && Objects.equals(PARTNER_ROLE_TYPE, sysUser.getRoleType());
+ }
+
+ private List<Long> impossibleUserIds() {
+ return Collections.singletonList(IMPOSSIBLE_USER_ID);
+ }
+
+ private SysUser getCurrentSysUser() {
+ LoginUser loginUser = null;
+ try {
+ loginUser = tokenService.getLoginUser();
+ } catch (Exception ignored) {
+ // 尝试从安全上下文继续获取。
+ }
+ if (loginUser == null) {
+ try {
+ loginUser = SecurityUtils.getLoginUser();
+ } catch (Exception ignored) {
+ // 当前请求不是管理后台登录上下文。
+ }
+ }
+ if (loginUser == null) {
+ return null;
+ }
+ if (loginUser.getSysUser() != null && loginUser.getSysUser().getRoleType() != null) {
+ return loginUser.getSysUser();
+ }
+ if (loginUser.getUserid() == null) {
+ return loginUser.getSysUser();
+ }
+ R<SysUser> userResult = sysUserClient.getSysUser(loginUser.getUserid());
+ return userResult != null && userResult.getData() != null ? userResult.getData() : loginUser.getSysUser();
+ }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/task/FavoritePriceDropTask.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/task/FavoritePriceDropTask.java
index 0d19882..ea0af40 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/task/FavoritePriceDropTask.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/task/FavoritePriceDropTask.java
@@ -92,12 +92,16 @@
}
TAppUser user = appUserService.getById(favorite.getAppUserId());
if (user == null || !StringUtils.hasText(user.getWxOpenid())) {
+ log.info("收藏站点降价提醒未发送,用户openid为空,userId={}, siteId={}",
+ favorite.getAppUserId(), favorite.getSiteId());
continue;
}
if (sendSubscribeMessage(user.getWxOpenid(), favorite, price)) {
favorite.setNotifyEnabled(false);
favorite.setLastNotifiedPrice(price.getCurrentPrice());
favoriteService.updateById(favorite);
+ log.info("收藏站点降价提醒发送成功,userId={}, siteId={}, currentPrice={}",
+ favorite.getAppUserId(), favorite.getSiteId(), price.getCurrentPrice());
}
}
} catch (InterruptedException e) {
@@ -112,16 +116,13 @@
}
private boolean sendSubscribeMessage(String openId, TAppUserSiteFavorite favorite, FavoriteSitePriceVO price) {
- BigDecimal decrease = favorite.getFavoritePrice().subtract(price.getCurrentPrice());
JSONObject body = new JSONObject();
body.put("touser", openId);
body.put("template_id", templateId);
body.put("page", page + "?siteId=" + favorite.getSiteId());
JSONObject data = new JSONObject();
data.put("thing1", value(limit(price.getSiteName(), 20)));
- data.put("amount2", value(favorite.getFavoritePrice().setScale(4, BigDecimal.ROUND_HALF_UP).toPlainString()));
- data.put("amount3", value(price.getCurrentPrice().setScale(4, BigDecimal.ROUND_HALF_UP).toPlainString()));
- data.put("amount4", value(decrease.setScale(4, BigDecimal.ROUND_HALF_UP).toPlainString()));
+ data.put("amount4", value(formatAmount(price.getCurrentPrice())));
body.put("data", data);
String accessToken = new WxAppletTools(wxRestTemplate, wxConfig, redisService).getAccessToken("");
String result = HttpUtils.post(SEND_URL + accessToken, body.toJSONString());
@@ -140,6 +141,10 @@
return item;
}
+ private String formatAmount(BigDecimal amount) {
+ return amount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "元";
+ }
+
private String limit(String value, int length) {
if (!StringUtils.hasText(value) || value.length() <= length) {
return value;
diff --git a/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/PartnerAppUserAccessServiceTest.java b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/PartnerAppUserAccessServiceTest.java
new file mode 100644
index 0000000..f2aa127
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/PartnerAppUserAccessServiceTest.java
@@ -0,0 +1,122 @@
+package com.ruoyi.account.service;
+
+import com.ruoyi.chargingPile.api.domain.SiteMenu;
+import com.ruoyi.chargingPile.api.feignClient.PartnerClient;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.order.api.feignClient.ChargingOrderClient;
+import com.ruoyi.system.api.domain.SysUser;
+import com.ruoyi.system.api.feignClient.SysUserClient;
+import com.ruoyi.system.api.model.LoginUser;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class PartnerAppUserAccessServiceTest {
+
+ @Mock private TokenService tokenService;
+ @Mock private SysUserClient sysUserClient;
+ @Mock private PartnerClient partnerClient;
+ @Mock private ChargingOrderClient chargingOrderClient;
+
+ private PartnerAppUserAccessService service;
+
+ @BeforeEach
+ void setUp() {
+ service = new PartnerAppUserAccessService();
+ ReflectionTestUtils.setField(service, "tokenService", tokenService);
+ ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient);
+ ReflectionTestUtils.setField(service, "partnerClient", partnerClient);
+ ReflectionTestUtils.setField(service, "chargingOrderClient", chargingOrderClient);
+ }
+
+ @Test
+ void partnerCanSeeOnlyUsersWhoChargedAtAuthorizedSites() {
+ mockCurrentUser(2, 9);
+ when(partnerClient.authSite(9, SiteMenu.SITE_LIST.getValue()))
+ .thenReturn(R.ok(new HashSet<>(Arrays.asList(11, 12))));
+ when(chargingOrderClient.getAppUserIdsBySiteIds(Arrays.asList(11, 12)))
+ .thenReturn(R.ok(Arrays.asList(101L, 102L, 101L)));
+
+ List<Long> result = service.getVisibleUserIdsForCurrentPartner();
+
+ assertEquals(Arrays.asList(101L, 102L), result);
+ verify(chargingOrderClient).getAppUserIdsBySiteIds(Arrays.asList(11, 12));
+ }
+
+ @Test
+ void partnerWithoutAuthorizedSitesGetsImpossibleUserId() {
+ mockCurrentUser(2, 9);
+ when(partnerClient.authSite(9, SiteMenu.SITE_LIST.getValue()))
+ .thenReturn(R.ok(Collections.singleton(-1)));
+
+ assertEquals(Collections.singletonList(-1L), service.getVisibleUserIdsForCurrentPartner());
+ verifyNoInteractions(chargingOrderClient);
+ }
+
+ @Test
+ void orderServiceFailureDoesNotExposeAllUsers() {
+ mockCurrentUser(2, 9);
+ when(partnerClient.authSite(9, SiteMenu.SITE_LIST.getValue()))
+ .thenReturn(R.ok(Collections.singleton(11)));
+ when(chargingOrderClient.getAppUserIdsBySiteIds(Collections.singletonList(11)))
+ .thenReturn(R.fail("order unavailable"));
+
+ assertEquals(Collections.singletonList(-1L), service.getVisibleUserIdsForCurrentPartner());
+ }
+
+ @Test
+ void platformUserKeepsUnrestrictedScope() {
+ mockCurrentUser(1, null);
+
+ assertNull(service.getVisibleUserIdsForCurrentPartner());
+ verifyNoInteractions(partnerClient, chargingOrderClient);
+ }
+
+ @Test
+ void partnerCannotReadUserOutsideVisibleScope() {
+ mockCurrentUser(2, 9);
+ when(partnerClient.authSite(9, SiteMenu.SITE_LIST.getValue()))
+ .thenReturn(R.ok(Collections.singleton(11)));
+ when(chargingOrderClient.getAppUserIdsBySiteIds(Collections.singletonList(11)))
+ .thenReturn(R.ok(Collections.singletonList(101L)));
+
+ assertThrows(ServiceException.class, () -> service.assertUserVisible(999L));
+ }
+
+ @Test
+ void partnerUserManagementIsReadOnly() {
+ mockCurrentUser(2, 9);
+
+ ServiceException exception = assertThrows(ServiceException.class, service::assertWritable);
+
+ assertEquals("合作商账号仅支持查看用户信息", exception.getMessage());
+ }
+
+ private void mockCurrentUser(Integer roleType, Integer objectId) {
+ SysUser sysUser = new SysUser();
+ sysUser.setRoleType(roleType);
+ sysUser.setObjectId(objectId);
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserid(1L);
+ loginUser.setSysUser(sysUser);
+ when(tokenService.getLoginUser()).thenReturn(loginUser);
+ }
+}
diff --git a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java
index 266309d..7b7a762 100644
--- a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java
+++ b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java
@@ -36,6 +36,7 @@
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -173,6 +174,7 @@
@Log(title = "【计费策略】修改计费策略", businessType = BusinessType.UPDATE,operatorType = OperatorType.MANAGE)
@ApiOperation(tags = {"后台-计费策略", "管理后台-站点管理"},value = "修改计费策略")
@PostMapping(value = "/update")
+ @Transactional(rollbackFor = Exception.class)
public AjaxResult<String> update(@RequestBody TAccountingStrategyDTO dto) {
String partnerCheck = fillAndCheckPartner(dto);
if (partnerCheck != null) {
@@ -191,9 +193,7 @@
if (Objects.isNull(original)) {
return AjaxResult.error("计费策略不存在");
}
- if(Objects.isNull(dto.getDiscount())){
- accountingStrategyService.update(Wrappers.<TAccountingStrategy>lambdaUpdate().set(TAccountingStrategy::getDiscount, null).eq(TAccountingStrategy::getId, dto.getId()));
- }
+ boolean clearDiscount = Objects.isNull(dto.getDiscount());
// 历史审核通过的策略在替换旧策略后可能仍保留已删除策略的 parentId。
// 这类正式策略再次编辑会被改为待审核子记录,并被计费策略列表过滤掉,先将其恢复为主策略。
if (original.getParentId() != null && Objects.equals(original.getAuditStatus(), 3)
@@ -220,12 +220,14 @@
} else {
dto.setId(original.getId());
accountingStrategyService.updateById(dto);
+ clearStrategyDiscountIfNeeded(dto.getId(), clearDiscount);
replaceStrategyDetails(dto.getId(), dto.getAccountingStrategyDetails());
}
return AjaxResult.success();
}
- // 判断修改的计费策略是否为已通过或已驳回
- if(null != dto.getAuditStatus() && (dto.getAuditStatus() == 3 || dto.getAuditStatus() == 4)){
+ // 是否为正式/驳回策略必须以数据库原记录为准,不能信任前端回传的审核状态。
+ // 正式策略修改时仅维护待审核子记录,审核通过前站点仍引用原策略。
+ if (Objects.equals(original.getAuditStatus(), 3) || Objects.equals(original.getAuditStatus(), 4)) {
// 查询是否有下级审核中的策略
TAccountingStrategy children = accountingStrategyService.getOne(Wrappers.lambdaQuery(TAccountingStrategy.class)
.eq(TAccountingStrategy::getParentId, dto.getId())
@@ -243,6 +245,7 @@
dto.setFirstAuditTime(null);
dto.setTwoAuditTime(null);
accountingStrategyService.updateById(dto);
+ clearStrategyDiscountIfNeeded(dto.getId(), clearDiscount);
replaceStrategyDetails(children.getId(), dto.getAccountingStrategyDetails());
}else {
Long userId = SecurityUtils.getLoginUser().getUserid();
@@ -257,20 +260,20 @@
}else {
dto.setAuditStatus(1);
accountingStrategyService.updateById(dto);
+ clearStrategyDiscountIfNeeded(dto.getId(), clearDiscount);
replaceStrategyDetails(dto.getId(), dto.getAccountingStrategyDetails());
}
-
- if(null != dto.getSiteId()){
- //下发硬件数据
- List<TChargingPile> list = chargingPileService.lambdaQuery().eq(TChargingPile::getSiteId, dto.getSiteId()).eq(TChargingPile::getDelFlag, 0).list();
- for (TChargingPile tChargingPile : list) {
- integrationClient.setupBillingModel1(tChargingPile.getCode(), JSON.toJSONString(dto.getAccountingStrategyDetails()));
- chengDuJianGuanClient.notificationStationFee(tChargingPile.getId());
- }
- }
return AjaxResult.success();
}
+ private void clearStrategyDiscountIfNeeded(Integer strategyId, boolean clearDiscount) {
+ if (clearDiscount) {
+ accountingStrategyService.update(Wrappers.<TAccountingStrategy>lambdaUpdate()
+ .set(TAccountingStrategy::getDiscount, null)
+ .eq(TAccountingStrategy::getId, strategyId));
+ }
+ }
+
private void replaceStrategyDetails(Integer strategyId, List<TAccountingStrategyDetail> details) {
accountingStrategyDetailService.remove(Wrappers.lambdaQuery(TAccountingStrategyDetail.class)
.eq(TAccountingStrategyDetail::getAccountingStrategyId, strategyId));
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 47d8d27..587ee8a 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
@@ -670,6 +670,7 @@
export.setPartnerName(dto.getPartnerName());
export.setPhone(dto.getPhone());
export.setOrderSource(formatOrderSource(dto.getOrderSource()));
+ export.setCreateTime(dto.getCreateTime() == null ? null : DateUtils.localDateTimeToString(dto.getCreateTime()));
export.setStartTime(dto.getStartTime() == null ? null : DateUtils.localDateTimeToString(dto.getStartTime()));
export.setEndTime(dto.getEndTime() == null ? null : DateUtils.localDateTimeToString(dto.getEndTime()));
export.setLicensePlate(dto.getLicensePlate());
@@ -1285,6 +1286,14 @@
return R.ok(count);
}
+ /**
+ * 查询指定站点中实际发生过充电的用户ID
+ */
+ @PostMapping(value = "/getAppUserIdsBySiteIds")
+ public R<List<Long>> getAppUserIdsBySiteIds(@RequestBody List<Integer> siteIds) {
+ return R.ok(chargingOrderService.getAppUserIdsBySiteIds(siteIds));
+ }
+
//订单详情
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java
index 52078eb..703b103 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java
@@ -85,8 +85,8 @@
@RequiresPermissions(value = {"/invoiceManagement"}, logical = Logical.OR)
@PostMapping("/invoicingCompanyOptions")
@ApiOperation(value = "查询开票公司选项", tags = {"管理后台-发票管理"})
- public AjaxResult<List<String>> invoicingCompanyOptions(@RequestBody TOrderInvoiceQuery query) {
- return AjaxResult.success(orderInvoiceService.invoicingCompanyOptions(query));
+ public AjaxResult<List<String>> invoicingCompanyOptions() {
+ return AjaxResult.success(orderInvoiceService.invoicingCompanyOptions());
}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TVipOrderController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TVipOrderController.java
index ad70d09..fc09307 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TVipOrderController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TVipOrderController.java
@@ -267,41 +267,59 @@
List<ChargingOrderVO> list = chargingOrderService.chargingOrderList(orderQuery).getRecords();
List<EnterpriseChargingDetailExport> exports = new ArrayList<>();
for (ChargingOrderVO order : list) {
- EnterpriseChargingDetailExport export = new EnterpriseChargingDetailExport();
- export.setCode(order.getCode());
- export.setOrderType(Integer.valueOf(1).equals(order.getOrderType()) ? "小程序" : "刷卡");
- export.setOrderClassification("充电订单");
- export.setSiteName(order.getSiteName());
- export.setTerminalName(order.getTerminalName());
- export.setStatus(getChargingOrderStatus(order.getStatus()));
- export.setChargingSecond(order.getChargingSecond());
- export.setTotalAmount(order.getOrderAmount());
- export.setCouponDiscountAmount(order.getCouponDiscountAmount());
- export.setVipDiscountAmount(order.getVipDiscountAmount());
- export.setPaymentAmount(order.getPaymentAmount());
- export.setRefundAmount(order.getRefundAmount());
- export.setPhone(order.getPhone());
- export.setOrderSource(getChargingOrderSource(order.getOrderSource()));
- export.setStartTime(formatExportDateTime(order.getStartTime()));
- export.setEndTime(formatExportDateTime(order.getEndTime()));
- exports.add(export);
+ exports.add(buildVipChargingOrderExport(order));
}
writeVipChargingOrderWorkbook(exports);
}
- private String getChargingOrderStatus(Integer status) {
+ static EnterpriseChargingDetailExport buildVipChargingOrderExport(ChargingOrderVO order) {
+ EnterpriseChargingDetailExport export = new EnterpriseChargingDetailExport();
+ export.setCode(order.getCode());
+ export.setOrderType(Integer.valueOf(1).equals(order.getOrderType()) ? "小程序" : "刷卡");
+ export.setOrderClassification("充电订单");
+ export.setSiteName(order.getSiteName());
+ export.setTerminalName(order.getTerminalName());
+ export.setStatus(getChargingOrderStatus(order.getStatus()));
+ export.setChargingSecond(order.getChargingSecond());
+ export.setTotalAmount(order.getOrderAmount());
+ export.setCouponDiscountAmount(order.getCouponDiscountAmount());
+ export.setVipDiscountAmount(order.getVipDiscountAmount());
+ export.setPaymentAmount(order.getPaymentAmount());
+ export.setRefundAmount(order.getRefundAmount());
+ export.setPartnerName(order.getPartnerName());
+ export.setPhone(order.getPhone());
+ export.setOrderSource(getChargingOrderSource(order.getOrderSource()));
+ export.setCreateTime(formatExportDateTime(order.getCreateTime()));
+ export.setStartTime(formatExportDateTime(order.getStartTime()));
+ export.setEndTime(formatExportDateTime(order.getEndTime()));
+ export.setLicensePlate(order.getLicensePlate());
+ export.setChargingCapacity(order.getChargingCapacity());
+ export.setElectrovalence(order.getElectrovalence());
+ export.setServiceCharge(order.getServiceCharge());
+ export.setEndMode(getChargingOrderEndMode(order.getEndMode()));
+ export.setPeriodCount(order.getCount());
+ return export;
+ }
+
+ private static String getChargingOrderStatus(Integer status) {
if (status == null) return "未知";
String[] names = {"未知", "等待中", "启动中", "充电中", "停止中", "已结束"};
return status >= 0 && status < names.length ? names[status] : "未知";
}
- private String getChargingOrderSource(Integer source) {
+ private static String getChargingOrderSource(Integer source) {
if (Integer.valueOf(1).equals(source)) return "明星充电";
if (Integer.valueOf(2).equals(source)) return "快电";
if (Integer.valueOf(3).equals(source)) return "新电途";
return "";
}
+ private static String getChargingOrderEndMode(Integer endMode) {
+ if (endMode == null) return "";
+ String[] names = {"异常终止", "主动终止", "满电终止", "费用不足终止"};
+ return endMode >= 0 && endMode < names.length ? names[endMode] : "";
+ }
+
static String formatExportDateTime(LocalDateTime time) {
return time == null ? "" : DateUtils.localDateTimeToString(time);
}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java
index 4297e66..91be121 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java
@@ -54,6 +54,9 @@
@Excel(name = "订单来源", width = 20)
private String orderSource;
+ @Excel(name = "下单时间", width = 30)
+ private String createTime;
+
@Excel(name = "开始时间", width = 30)
private String startTime;
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
index a0b6ddf..041be32 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
@@ -209,6 +209,8 @@
List<OrderCountByDate> countOrdersByDate(@Param("startTime")LocalDateTime startTime, @Param("endTime")LocalDateTime endTime,@Param("gunIds")List<Integer> gunIds);
Double getSumDegreeBySiteIds(@Param("siteIds") List<Integer> siteIds);
+
+ List<Long> getAppUserIdsBySiteIds(@Param("siteIds") List<Integer> siteIds);
/**
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TOrderInvoiceMapper.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TOrderInvoiceMapper.java
index a2c0a79..c1f26ca 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TOrderInvoiceMapper.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TOrderInvoiceMapper.java
@@ -33,7 +33,7 @@
/**
* 查询开票公司下拉选项
*/
- List<String> invoicingCompanyOptions(@Param("query") TOrderInvoiceQuery query);
+ List<String> invoicingCompanyOptions();
/**
* 查询开票状态数量
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 dc3dd99..58221c3 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
@@ -393,6 +393,11 @@
R<StatisticsOfBatteryVO> getStatisticsOfBattery(List<Integer> siteIds);
Double getSumDegreeBySiteIds(List<Integer> siteIds);
+
+ /**
+ * 查询指定站点中实际发生过充电的用户ID
+ */
+ List<Long> getAppUserIdsBySiteIds(List<Integer> siteIds);
/**
* 获取启动失败且时间超过1小时的订单
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TOrderInvoiceService.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TOrderInvoiceService.java
index c47f46e..8b05130 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TOrderInvoiceService.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TOrderInvoiceService.java
@@ -56,9 +56,9 @@
PageInfo<TOrderInvoiceVO> pageList(TOrderInvoiceQuery query);
/**
- * 查询当前账号可见的开票公司选项
+ * 查询全部开票公司选项
*/
- List<String> invoicingCompanyOptions(TOrderInvoiceQuery query);
+ List<String> invoicingCompanyOptions();
/**
* 查询开票状态数量
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ChargingCouponSupport.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ChargingCouponSupport.java
index 24be087..6512e0f 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ChargingCouponSupport.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ChargingCouponSupport.java
@@ -11,37 +11,72 @@
private ChargingCouponSupport() {
}
- static BigDecimal calculateUpfrontDiscount(TCoupon coupon, BigDecimal rechargeAmount) {
- if (coupon == null || rechargeAmount == null || rechargeAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ static BigDecimal calculatePrepaymentAmount(BigDecimal rechargeAmount, boolean whitelistAllFree) {
+ if (whitelistAllFree) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
- if (!Integer.valueOf(1).equals(coupon.getType())) {
- throw new IllegalArgumentException("请选择充电优惠券");
+ return money(rechargeAmount).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 兼容曾在预付阶段直接减掉优惠券金额的在途订单,退款基数不能超过真实收款。
+ */
+ static BigDecimal resolveSettlementPrepayment(BigDecimal rechargeAmount,
+ BigDecimal balancePayAmount,
+ BigDecimal thirdPayAmount,
+ BigDecimal upfrontCouponDiscount) {
+ BigDecimal recharge = money(rechargeAmount);
+ BigDecimal collected = money(balancePayAmount).add(money(thirdPayAmount));
+ if (money(upfrontCouponDiscount).compareTo(BigDecimal.ZERO) > 0
+ && collected.compareTo(BigDecimal.ZERO) > 0
+ && collected.compareTo(recharge) < 0) {
+ return collected;
}
- BigDecimal threshold = coupon.getMeetTheConditions() == null
- ? BigDecimal.ZERO : coupon.getMeetTheConditions();
- if (rechargeAmount.compareTo(threshold) < 0) {
- throw new IllegalArgumentException("预付金额未达到优惠券使用门槛");
+ return recharge;
+ }
+
+ /**
+ * 优惠券只在充电结束时结算,且优惠金额不能超过剩余服务费。
+ */
+ static BigDecimal calculateSettlementDiscount(TCoupon coupon,
+ BigDecimal actualAmountBeforeCoupon,
+ BigDecimal remainingServiceCharge) {
+ BigDecimal actualAmount = money(actualAmountBeforeCoupon);
+ BigDecimal serviceCharge = money(remainingServiceCharge);
+ if (coupon == null || actualAmount.compareTo(BigDecimal.ZERO) <= 0
+ || serviceCharge.compareTo(BigDecimal.ZERO) <= 0) {
+ return BigDecimal.ZERO.setScale(4, RoundingMode.HALF_EVEN);
+ }
+ BigDecimal threshold = money(coupon.getMeetTheConditions());
+ if (actualAmount.compareTo(threshold) < 0) {
+ return BigDecimal.ZERO.setScale(4, RoundingMode.HALF_EVEN);
}
- BigDecimal discount;
+ BigDecimal discount = BigDecimal.ZERO;
if (Integer.valueOf(1).equals(coupon.getPreferentialMode())) {
- discount = coupon.getDiscountAmount();
- } else if (Integer.valueOf(2).equals(coupon.getPreferentialMode())) {
- if (coupon.getDiscount() == null) {
- throw new IllegalArgumentException("优惠券折扣配置无效");
- }
- discount = rechargeAmount.multiply(TEN.subtract(coupon.getDiscount()))
- .divide(TEN, 2, RoundingMode.HALF_UP);
+ discount = money(coupon.getDiscountAmount());
+ } else if (Integer.valueOf(2).equals(coupon.getPreferentialMode()) && coupon.getDiscount() != null) {
+ discount = actualAmount.multiply(TEN.subtract(coupon.getDiscount())).divide(TEN);
if (coupon.getMaximumDiscountAmount() != null) {
discount = discount.min(coupon.getMaximumDiscountAmount());
}
- } else {
- throw new IllegalArgumentException("优惠券优惠方式无效");
}
- if (discount == null || discount.compareTo(BigDecimal.ZERO) <= 0) {
- throw new IllegalArgumentException("优惠券优惠金额无效");
+ return discount.max(BigDecimal.ZERO).min(serviceCharge).setScale(4, RoundingMode.HALF_EVEN);
+ }
+
+ static BigDecimal[] calculateWhitelistRefundSplit(boolean whitelistOrder,
+ boolean whitelistAllFree,
+ BigDecimal electrovalence,
+ BigDecimal serviceCharge) {
+ if (!whitelistOrder) {
+ return new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO};
}
- return discount.min(rechargeAmount).setScale(2, RoundingMode.HALF_UP);
+ BigDecimal electricDiscount = whitelistAllFree ? money(electrovalence) : BigDecimal.ZERO;
+ BigDecimal serviceDiscount = money(serviceCharge);
+ return new BigDecimal[]{electricDiscount, serviceDiscount};
+ }
+
+ private static BigDecimal money(BigDecimal value) {
+ return value == null ? BigDecimal.ZERO : value;
}
}
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 851d458..6c1ed87 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
@@ -714,7 +714,7 @@
return chargingOrder != null && "ICBC".equals(chargingOrder.getSourceName());
}
- private BigDecimal resolveUpfrontCouponDiscount(Long appCouponId, Long userId, BigDecimal rechargeAmount) {
+ private void validateSelectedChargingCoupon(Long appCouponId, Long userId) {
TAppCoupon appCoupon = appCouponClient.getAppCouponById(appCouponId).getData();
if (appCoupon == null || !userId.equals(appCoupon.getAppUserId())) {
throw new ServiceException("优惠券不存在或不属于当前用户");
@@ -727,11 +727,9 @@
|| (appCoupon.getEndTime() != null && appCoupon.getEndTime().isBefore(now))) {
throw new ServiceException("优惠券不在有效期内");
}
- try {
- TCoupon coupon = JSON.parseObject(appCoupon.getCouponJson(), TCoupon.class);
- return ChargingCouponSupport.calculateUpfrontDiscount(coupon, rechargeAmount);
- } catch (IllegalArgumentException e) {
- throw new ServiceException(e.getMessage());
+ TCoupon coupon = JSON.parseObject(appCoupon.getCouponJson(), TCoupon.class);
+ if (coupon == null || !Integer.valueOf(1).equals(coupon.getType())) {
+ throw new ServiceException("请选择充电优惠券");
}
}
@@ -765,20 +763,11 @@
* @return 数组下标0=电费减免,下标1=服务费减免
*/
private BigDecimal[] calculateWhitelistRefundSplit(TChargingOrder chargingOrder, BigDecimal electrovalence, BigDecimal serviceCharge) {
- BigDecimal electricDiscount = BigDecimal.ZERO;
- BigDecimal serviceDiscount = BigDecimal.ZERO;
- if (!isWhitelistOrder(chargingOrder)) {
- return new BigDecimal[]{electricDiscount, serviceDiscount};
- }
- if (Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(chargingOrder.getWhitelistBenefitType())) {
- electricDiscount = electrovalence == null ? BigDecimal.ZERO : electrovalence;
- serviceDiscount = serviceCharge == null ? BigDecimal.ZERO : serviceCharge;
- } else if (Integer.valueOf(WHITELIST_BENEFIT_ELECTRIC_FREE).equals(chargingOrder.getWhitelistBenefitType())) {
- electricDiscount = electrovalence == null ? BigDecimal.ZERO : electrovalence;
- } else if (Integer.valueOf(WHITELIST_BENEFIT_SERVICE_FREE).equals(chargingOrder.getWhitelistBenefitType())) {
- serviceDiscount = serviceCharge == null ? BigDecimal.ZERO : serviceCharge;
- }
- return new BigDecimal[]{electricDiscount, serviceDiscount};
+ return ChargingCouponSupport.calculateWhitelistRefundSplit(
+ isWhitelistOrder(chargingOrder),
+ isWhitelistAllFreeOrder(chargingOrder),
+ electrovalence,
+ serviceCharge);
}
private TVip getVipSnapshot(TAppUserVipDetail vipDetail, Integer vipId) {
if (vipDetail != null && StringUtils.hasLength(vipDetail.getVipJson())) {
@@ -839,7 +828,6 @@
&& Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(whitelist.getBenefitType());
BigDecimal userBalance = money(appUser.getBalance());
BigDecimal balancePayAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
- BigDecimal upfrontCouponDiscount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
if (payMethod == 2 && userBalance.compareTo(rechargeAmount) >= 0) {
payMethod = 1;
addChargingOrder.setPayMethod(1);
@@ -847,12 +835,10 @@
if (isBalancePayMode(payMethod)) {
addChargingOrder.setAppUserCouponId(null);
} else if (!whitelistMatched && addChargingOrder.getAppUserCouponId() != null) {
- upfrontCouponDiscount = resolveUpfrontCouponDiscount(
- addChargingOrder.getAppUserCouponId(), userId, rechargeAmount);
+ // 这里只校验优惠券,不抵扣预付金额;实际门槛和优惠金额在充电结束时计算。
+ validateSelectedChargingCoupon(addChargingOrder.getAppUserCouponId(), userId);
}
- BigDecimal payableAmount = whitelistAllFree
- ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
- : rechargeAmount.subtract(upfrontCouponDiscount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
+ BigDecimal payableAmount = ChargingCouponSupport.calculatePrepaymentAmount(rechargeAmount, whitelistAllFree);
BigDecimal thirdPayAmount = payableAmount;
if (isBalancePayMode(payMethod)) {
if (payMethod == 1 && userBalance.compareTo(payableAmount) < 0) {
@@ -956,7 +942,7 @@
chargingOrder.setThirdRefundAmount(BigDecimal.ZERO);
chargingOrder.setRechargeAmount(rechargeAmount);
chargingOrder.setAppCouponId(addChargingOrder.getAppUserCouponId());
- chargingOrder.setCouponDiscountAmount(upfrontCouponDiscount);
+ chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
chargingOrder.setIsWhitelist(whitelistMatched ? 1 : 0);
chargingOrder.setWhitelistId(whitelistMatched ? whitelist.getWhitelistId() : null);
chargingOrder.setWhitelistBenefitType(whitelistMatched ? whitelist.getBenefitType() : null);
@@ -4439,8 +4425,11 @@
BigDecimal rechargeAmount = chargingOrder.getRechargeAmount();
//支付金额
BigDecimal payAmount = periodElectricPrice_total.add(serviceCharge_total);
- //退款金额=充值金额-实际支付金额
- BigDecimal refundAmount = rechargeAmount.subtract(payAmount);
+ //退款金额=真实预付金额-实际支付金额;兼容曾在预付阶段错误抵扣优惠券的在途订单。
+ BigDecimal settlementPrepayment = ChargingCouponSupport.resolveSettlementPrepayment(
+ rechargeAmount, chargingOrder.getBalancePayAmount(), chargingOrder.getThirdPayAmount(),
+ chargingOrder.getCouponDiscountAmount());
+ BigDecimal refundAmount = settlementPrepayment.subtract(payAmount);
UploadRealTimeMonitoringData uploadRealTimeMonitoringData = uploadRealTimeMonitoringDataService.getLastDataById(chargingOrder.getCode());
if (null != uploadRealTimeMonitoringData && null == chargingOrder.getEndMode()) {
@@ -4468,57 +4457,21 @@
}
//计算优惠券
- boolean couponAppliedUpfront = money(chargingOrder.getCouponDiscountAmount()).compareTo(BigDecimal.ZERO) > 0;
- BigDecimal couponDiscount = couponAppliedUpfront
- ? money(chargingOrder.getCouponDiscountAmount()) : BigDecimal.ZERO;
+ BigDecimal couponDiscount = BigDecimal.ZERO;
if (!isWhitelistOrder(chargingOrder) && !isBalancePayMode(chargingOrder.getPayMethod()) && null != chargingOrder.getAppCouponId()) {
+ Long appCouponId = chargingOrder.getAppCouponId();
TAppCoupon appCoupon = appCouponClient.getAppCouponById(chargingOrder.getAppCouponId()).getData();
String couponJson = appCoupon.getCouponJson();
TCoupon tCoupon = JSON.parseObject(couponJson, TCoupon.class);
- Integer preferentialMode = tCoupon.getPreferentialMode();
- if (couponAppliedUpfront) {
+ couponDiscount = ChargingCouponSupport.calculateSettlementDiscount(
+ tCoupon, payAmount, serviceCharge_total);
+ if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
appCoupon.setStatus(2);
appCouponClient.updateAppCoupon(appCoupon);
- } else if (1 == preferentialMode) {
- //满减
- if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
- BigDecimal couponDiscountAmount = tCoupon.getDiscountAmount();
- //如果优惠金额大于服务费金额,以服务费作为最大限制
- if (serviceCharge_total.compareTo(couponDiscountAmount) < 0) {
- couponDiscount = serviceCharge_total;
- serviceCharge_total = BigDecimal.ZERO;
- } else {
- couponDiscount = couponDiscountAmount;
- }
- appCoupon.setStatus(2);
- appCouponClient.updateAppCoupon(appCoupon);
- } else {
- appCouponClient.refund(chargingOrder.getAppCouponId().toString());
- chargingOrder.setAppCouponId(null);
- chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
- }
- }
- if (!couponAppliedUpfront && 2 == preferentialMode) {
- //抵扣
- if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
- //折扣金额
- BigDecimal divide = payAmount.multiply(new BigDecimal(10).subtract(tCoupon.getDiscount())).divide(new BigDecimal(10));
- divide = divide.compareTo(tCoupon.getMaximumDiscountAmount()) > 0 ? tCoupon.getMaximumDiscountAmount() : divide;
- //如果优惠金额大于服务费金额,以服务费作为最大限制
- if (serviceCharge_total.compareTo(divide) < 0) {
- couponDiscount = serviceCharge_total;
- serviceCharge_total = BigDecimal.ZERO;
- } else {
- couponDiscount = divide;
- }
-
- appCoupon.setStatus(2);
- appCouponClient.updateAppCoupon(appCoupon);
- } else {
- chargingOrder.setAppCouponId(null);
- chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
- appCouponClient.refund(chargingOrder.getAppCouponId().toString());
- }
+ } else {
+ appCouponClient.refund(appCouponId.toString());
+ chargingOrder.setAppCouponId(null);
+ chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
}
}
BigDecimal[] whitelistRefundSplit = calculateWhitelistRefundSplit(chargingOrder, periodElectricPrice_total, serviceCharge_total);
@@ -4534,10 +4487,7 @@
}
//优惠券优惠金额
couponDiscount = couponDiscount.setScale(4, RoundingMode.HALF_EVEN);
- //历史订单在结算时计算优惠;新订单已在预付时少收优惠金额,不能再次增加退款。
- if (!couponAppliedUpfront) {
- refundAmount = refundAmount.add(couponDiscount);
- }
+ refundAmount = refundAmount.add(couponDiscount);
//实际支付金额-优惠券
payAmount = payAmount.subtract(couponDiscount).max(BigDecimal.ZERO);
serviceCharge_total = serviceCharge_total.subtract(couponDiscount).max(BigDecimal.ZERO);
@@ -6614,6 +6564,14 @@
}
return this.list(queryWrapper.orderByDesc(TChargingOrder::getCreateTime));
}
+
+ @Override
+ public List<Long> getAppUserIdsBySiteIds(List<Integer> siteIds) {
+ if (siteIds == null || siteIds.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return this.baseMapper.getAppUserIdsBySiteIds(siteIds);
+ }
@Override
public Long countCar(List<String> brands) {
@@ -7323,8 +7281,11 @@
BigDecimal rechargeAmount = chargingOrder.getRechargeAmount();
//支付金额
BigDecimal payAmount = periodElectricPrice_total.add(serviceCharge_total);
- //退款金额=充值金额-实际支付金额
- BigDecimal refundAmount = rechargeAmount.subtract(payAmount);
+ //退款金额=真实预付金额-实际支付金额;兼容曾在预付阶段错误抵扣优惠券的在途订单。
+ BigDecimal settlementPrepayment = ChargingCouponSupport.resolveSettlementPrepayment(
+ rechargeAmount, chargingOrder.getBalancePayAmount(), chargingOrder.getThirdPayAmount(),
+ chargingOrder.getCouponDiscountAmount());
+ BigDecimal refundAmount = settlementPrepayment.subtract(payAmount);
UploadRealTimeMonitoringData uploadRealTimeMonitoringData = uploadRealTimeMonitoringDataService.getLastDataById(chargingOrder.getCode());
if (null != uploadRealTimeMonitoringData && null == chargingOrder.getEndMode()) {
@@ -7352,64 +7313,26 @@
}
//计算优惠券
- boolean couponAppliedUpfront = money(chargingOrder.getCouponDiscountAmount()).compareTo(BigDecimal.ZERO) > 0;
- BigDecimal couponDiscount = couponAppliedUpfront
- ? money(chargingOrder.getCouponDiscountAmount()) : BigDecimal.ZERO;
+ BigDecimal couponDiscount = BigDecimal.ZERO;
if (null != chargingOrder.getAppCouponId()) {
+ Long appCouponId = chargingOrder.getAppCouponId();
TAppCoupon appCoupon = appCouponClient.getAppCouponById(chargingOrder.getAppCouponId()).getData();
String couponJson = appCoupon.getCouponJson();
TCoupon tCoupon = JSON.parseObject(couponJson, TCoupon.class);
- Integer preferentialMode = tCoupon.getPreferentialMode();
- if (couponAppliedUpfront) {
+ couponDiscount = ChargingCouponSupport.calculateSettlementDiscount(
+ tCoupon, payAmount, serviceCharge_total);
+ if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
appCoupon.setStatus(2);
appCouponClient.updateAppCoupon(appCoupon);
- } else if (1 == preferentialMode) {
- //满减
- if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
- BigDecimal couponDiscountAmount = tCoupon.getDiscountAmount();
- //如果优惠金额大于服务费金额,以服务费作为最大限制
- if (serviceCharge_total.compareTo(couponDiscountAmount) < 0) {
- couponDiscount = serviceCharge_total;
- serviceCharge_total = BigDecimal.ZERO;
- } else {
- couponDiscount = couponDiscountAmount;
- }
- appCoupon.setStatus(2);
- appCouponClient.updateAppCoupon(appCoupon);
- } else {
- appCouponClient.refund(chargingOrder.getAppCouponId().toString());
- chargingOrder.setAppCouponId(null);
- chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
- }
- }
- if (!couponAppliedUpfront && 2 == preferentialMode) {
- //抵扣
- if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
- //折扣金额
- BigDecimal divide = payAmount.multiply(new BigDecimal(10).subtract(tCoupon.getDiscount())).divide(new BigDecimal(10));
- divide = divide.compareTo(tCoupon.getMaximumDiscountAmount()) > 0 ? tCoupon.getMaximumDiscountAmount() : divide;
- //如果优惠金额大于服务费金额,以服务费作为最大限制
- if (serviceCharge_total.compareTo(divide) < 0) {
- couponDiscount = serviceCharge_total;
- serviceCharge_total = BigDecimal.ZERO;
- } else {
- couponDiscount = divide;
- }
-
- appCoupon.setStatus(2);
- appCouponClient.updateAppCoupon(appCoupon);
- } else {
- chargingOrder.setAppCouponId(null);
- chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
- appCouponClient.refund(chargingOrder.getAppCouponId().toString());
- }
+ } else {
+ appCouponClient.refund(appCouponId.toString());
+ chargingOrder.setAppCouponId(null);
+ chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
}
}
//优惠券优惠金额
couponDiscount = couponDiscount.setScale(4, RoundingMode.HALF_EVEN);
- if (!couponAppliedUpfront) {
- refundAmount = refundAmount.add(couponDiscount);
- }
+ refundAmount = refundAmount.add(couponDiscount);
//实际支付金额-优惠券
payAmount = payAmount.subtract(couponDiscount).max(BigDecimal.ZERO);
serviceCharge_total = serviceCharge_total.subtract(couponDiscount).max(BigDecimal.ZERO);
@@ -7550,8 +7473,11 @@
BigDecimal rechargeAmount = chargingOrder.getRechargeAmount();
//支付金额
BigDecimal payAmount = periodElectricPrice_total.add(serviceCharge_total);
- //退款金额=充值金额-实际支付金额
- BigDecimal refundAmount = rechargeAmount.subtract(payAmount);
+ //退款金额=真实预付金额-实际支付金额;兼容曾在预付阶段错误抵扣优惠券的在途订单。
+ BigDecimal settlementPrepayment = ChargingCouponSupport.resolveSettlementPrepayment(
+ rechargeAmount, chargingOrder.getBalancePayAmount(), chargingOrder.getThirdPayAmount(),
+ chargingOrder.getCouponDiscountAmount());
+ BigDecimal refundAmount = settlementPrepayment.subtract(payAmount);
UploadRealTimeMonitoringData uploadRealTimeMonitoringData = uploadRealTimeMonitoringDataService.getLastDataById(chargingOrder.getCode());
if (null != uploadRealTimeMonitoringData && null == chargingOrder.getEndMode()) {
@@ -7581,51 +7507,19 @@
//计算优惠券
BigDecimal couponDiscount = BigDecimal.ZERO;
if (null != chargingOrder.getAppCouponId()) {
- //判断实际充电金额是否满足优惠券使用条件,如果不满足则不适用优惠券。
+ Long appCouponId = chargingOrder.getAppCouponId();
TAppCoupon appCoupon = appCouponClient.getAppCouponById(chargingOrder.getAppCouponId()).getData();
String couponJson = appCoupon.getCouponJson();
TCoupon tCoupon = com.alibaba.fastjson2.JSON.parseObject(couponJson, TCoupon.class);
- Integer preferentialMode = tCoupon.getPreferentialMode();
- if (1 == preferentialMode) {
- //满减
- if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
- BigDecimal couponDiscountAmount = tCoupon.getDiscountAmount();
- //如果优惠金额大于服务费金额,以服务费作为最大限制
- if (serviceCharge_total.compareTo(couponDiscountAmount) < 0) {
- couponDiscount = serviceCharge_total;
- serviceCharge_total = BigDecimal.ZERO;
- } else {
- couponDiscount = couponDiscountAmount;
- }
- appCoupon.setStatus(2);
- appCouponClient.updateAppCoupon(appCoupon);
- } else {
- appCouponClient.refund(chargingOrder.getAppCouponId().toString());
- chargingOrder.setAppCouponId(null);
- chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
- }
- }
- if (2 == preferentialMode) {
- //抵扣
- if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
- //折扣金额
- BigDecimal divide = payAmount.multiply(new BigDecimal(10).subtract(tCoupon.getDiscount())).divide(new BigDecimal(10));
- divide = divide.compareTo(tCoupon.getMaximumDiscountAmount()) > 0 ? tCoupon.getMaximumDiscountAmount() : divide;
- //如果优惠金额大于服务费金额,以服务费作为最大限制
- if (serviceCharge_total.compareTo(divide) < 0) {
- couponDiscount = serviceCharge_total;
- serviceCharge_total = BigDecimal.ZERO;
- } else {
- couponDiscount = divide;
- }
-
- appCoupon.setStatus(2);
- appCouponClient.updateAppCoupon(appCoupon);
- } else {
- chargingOrder.setAppCouponId(null);
- chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
- appCouponClient.refund(chargingOrder.getAppCouponId().toString());
- }
+ couponDiscount = ChargingCouponSupport.calculateSettlementDiscount(
+ tCoupon, payAmount, serviceCharge_total);
+ if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
+ appCoupon.setStatus(2);
+ appCouponClient.updateAppCoupon(appCoupon);
+ } else {
+ appCouponClient.refund(appCouponId.toString());
+ chargingOrder.setAppCouponId(null);
+ chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
}
}
//优惠券优惠金额
@@ -7633,10 +7527,8 @@
//退款金额+优惠券
refundAmount = refundAmount.add(couponDiscount);
//实际支付金额-优惠券
- payAmount = payAmount.subtract(couponDiscount);
- if (serviceCharge_total.compareTo(BigDecimal.ZERO) > 0) {
- serviceCharge_total = serviceCharge_total.subtract(couponDiscount);
- }
+ payAmount = payAmount.subtract(couponDiscount).max(BigDecimal.ZERO);
+ serviceCharge_total = serviceCharge_total.subtract(couponDiscount).max(BigDecimal.ZERO);
chargingOrder.setCouponDiscountAmount(couponDiscount.setScale(2, RoundingMode.HALF_EVEN));
chargingOrder.setServiceCharge(serviceCharge_total.setScale(2, RoundingMode.HALF_EVEN));
chargingOrder.setPaymentAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java
index d369fd1..a6e1774 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java
@@ -452,12 +452,8 @@
}
@Override
- public List<String> invoicingCompanyOptions(TOrderInvoiceQuery query) {
- if (query == null) {
- query = new TOrderInvoiceQuery();
- }
- applyPartnerSiteScope(query);
- return this.baseMapper.invoicingCompanyOptions(query);
+ public List<String> invoicingCompanyOptions() {
+ return this.baseMapper.invoicingCompanyOptions();
}
@Override
diff --git a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
index c6a986f..decb83e 100644
--- a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
+++ b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -2,6 +2,19 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.order.mapper.TChargingOrderMapper">
+ <select id="getAppUserIdsBySiteIds" resultType="java.lang.Long">
+ SELECT app_user_id
+ FROM t_charging_order
+ WHERE del_flag = 0
+ AND app_user_id IS NOT NULL
+ AND status IN (3, 4, 5)
+ AND site_id IN
+ <foreach collection="siteIds" item="siteId" open="(" separator="," close=")">
+ #{siteId}
+ </foreach>
+ GROUP BY app_user_id
+ </select>
+
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.ruoyi.order.api.model.TChargingOrder">
<id column="id" property="id"/>
@@ -92,11 +105,13 @@
<select id="chargingOrder" resultType="com.ruoyi.order.api.vo.ChargingOrderVO">
select t1.*,
+ app_user.phone,
+ partner.name as partnerName,
IFNULL(t1.whitelist_electric_refund_amount, 0) + IFNULL(t1.whitelist_service_refund_amount, 0) as whitelistRefundAmount
from t_charging_order t1
- <if test="req.phone != null and req.phone != ''">
- inner join `charging_pile_account`.`t_app_user` app_user on app_user.id = t1.app_user_id
- </if>
+ left join `charging_pile_account`.`t_app_user` app_user on app_user.id = t1.app_user_id
+ left join `charging_pile_service`.`t_site` site on site.id = t1.site_id
+ left join `charging_pile_service`.`t_partner` partner on partner.id = site.partner_id
where t1.del_flag = 0
<if test="null != req.code and req.code!=''">
and t1.code LIKE CONCAT('%',#{req.code},'%')
@@ -2630,7 +2645,7 @@
<sql id="enterpriseChargingBaseWhere">
toi.invoicing_object_type = 2
- AND toi.status IN (1, 2, 3)
+ AND toi.status IN (2, 3)
AND tod.order_type = 1
<if test="data.exactCompanyName != null and data.exactCompanyName != ''">
AND toi.name = #{data.exactCompanyName}
diff --git a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TOrderInvoiceMapper.xml b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TOrderInvoiceMapper.xml
index 9de73d5..cfeef00 100644
--- a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TOrderInvoiceMapper.xml
+++ b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TOrderInvoiceMapper.xml
@@ -100,21 +100,8 @@
<select id="invoicingCompanyOptions" resultType="java.lang.String">
SELECT DISTINCT TRIM(toi.invoicing_company)
FROM t_order_invoice toi
- <where>
- toi.invoicing_company IS NOT NULL
- AND TRIM(toi.invoicing_company) != ''
- <if test="query.siteIds != null and query.siteIds.size() > 0">
- AND toi.site_id IN
- <foreach collection="query.siteIds" close=")" open="(" item="siteId" separator=",">
- #{siteId}
- </foreach>
- </if>
- <if test="query.directOnly != null and query.directOnly == 1">
- AND toi.site_id IN (
- SELECT id FROM charging_pile_service.t_site WHERE business_category = 1
- )
- </if>
- </where>
+ WHERE toi.invoicing_company IS NOT NULL
+ AND TRIM(toi.invoicing_company) != ''
ORDER BY TRIM(toi.invoicing_company)
</select>
<select id="statusCount" resultType="java.util.Map">
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TChargingOrderControllerTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TChargingOrderControllerTest.java
index d49b658..de31d5c 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TChargingOrderControllerTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TChargingOrderControllerTest.java
@@ -12,6 +12,7 @@
import org.springframework.test.util.ReflectionTestUtils;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
@@ -27,9 +28,27 @@
class TChargingOrderControllerTest {
@Test
+ void appUserIdsBySiteIdsDelegatesToOrderService() {
+ TChargingOrderService chargingOrderService = mock(TChargingOrderService.class);
+ TChargingOrderController controller = new TChargingOrderController();
+ ReflectionTestUtils.setField(controller, "chargingOrderService", chargingOrderService);
+ List<Integer> siteIds = Arrays.asList(11, 12);
+ List<Long> userIds = Arrays.asList(101L, 102L);
+ when(chargingOrderService.getAppUserIdsBySiteIds(siteIds)).thenReturn(userIds);
+
+ com.ruoyi.common.core.domain.R<List<Long>> result = controller.getAppUserIdsBySiteIds(siteIds);
+
+ assertEquals(userIds, result.getData());
+ verify(chargingOrderService).getAppUserIdsBySiteIds(siteIds);
+ }
+
+ @Test
void enterpriseChargingDetailExportContainsVisibleDetailColumns() {
EnterpriseChargingDetailVO detail = new EnterpriseChargingDetailVO();
detail.setLicensePlate("川JD20960");
+ detail.setPartnerName("测试合作商");
+ detail.setOrderSource(0);
+ detail.setCreateTime(LocalDateTime.of(2026, 7, 29, 10, 20, 30));
detail.setChargingCapacity(new BigDecimal("40.693"));
detail.setElectrovalence(new BigDecimal("43.0857"));
detail.setServiceCharge(new BigDecimal("16.2772"));
@@ -40,6 +59,9 @@
new TChargingOrderController(), "buildEnterpriseChargingDetailExport", detail);
assertEquals("川JD20960", export.getLicensePlate());
+ assertEquals("测试合作商", export.getPartnerName());
+ assertEquals("自有平台", export.getOrderSource());
+ assertEquals("2026-07-29 10:20:30", export.getCreateTime());
assertEquals(new BigDecimal("40.693"), export.getChargingCapacity());
assertEquals(new BigDecimal("43.0857"), export.getElectrovalence());
assertEquals(new BigDecimal("16.2772"), export.getServiceCharge());
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TVipOrderControllerTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TVipOrderControllerTest.java
index 768f0e5..ac4db38 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TVipOrderControllerTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TVipOrderControllerTest.java
@@ -1,7 +1,10 @@
package com.ruoyi.order.controller;
+import com.ruoyi.order.api.vo.ChargingOrderVO;
+import com.ruoyi.order.export.EnterpriseChargingDetailExport;
import org.junit.jupiter.api.Test;
+import java.math.BigDecimal;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -19,4 +22,32 @@
assertEquals("2026-07-28 09:08:07", TVipOrderController.formatExportDateTime(time));
}
+
+ @Test
+ void fillsVipChargingOrderExportDetailFields() {
+ ChargingOrderVO order = new ChargingOrderVO();
+ order.setPartnerName("测试合作商");
+ order.setPhone("13800000000");
+ order.setOrderSource(1);
+ order.setCreateTime(LocalDateTime.of(2026, 7, 29, 10, 20, 30));
+ order.setLicensePlate("川A12345");
+ order.setChargingCapacity(new BigDecimal("12.3456"));
+ order.setElectrovalence(new BigDecimal("8.12"));
+ order.setServiceCharge(new BigDecimal("3.45"));
+ order.setEndMode(2);
+ order.setCount(4);
+
+ EnterpriseChargingDetailExport export = TVipOrderController.buildVipChargingOrderExport(order);
+
+ assertEquals("测试合作商", export.getPartnerName());
+ assertEquals("13800000000", export.getPhone());
+ assertEquals("明星充电", export.getOrderSource());
+ assertEquals("2026-07-29 10:20:30", export.getCreateTime());
+ assertEquals("川A12345", export.getLicensePlate());
+ assertEquals(new BigDecimal("12.3456"), export.getChargingCapacity());
+ assertEquals(new BigDecimal("8.12"), export.getElectrovalence());
+ assertEquals(new BigDecimal("3.45"), export.getServiceCharge());
+ assertEquals("满电终止", export.getEndMode());
+ assertEquals(4, export.getPeriodCount());
+ }
}
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/EnterpriseChargingAnalysisMapperTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/EnterpriseChargingAnalysisMapperTest.java
index df5d7d8..53b2a48 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/EnterpriseChargingAnalysisMapperTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/EnterpriseChargingAnalysisMapperTest.java
@@ -35,6 +35,8 @@
assertFalse(baseWhere.contains("co.del_flag"));
assertFalse(baseWhere.contains("co.recharge_payment_status"));
+ assertTrue(baseWhere.contains("AND toi.status IN (2, 3)"));
+ assertFalse(baseWhere.contains("AND toi.status IN (1, 2, 3)"));
assertTrue(analysis.contains("LEFT JOIN t_charging_order co ON co.id = tod.order_id"));
assertTrue(analysis.contains("COUNT(1) AS invoiceCount"));
assertTrue(analysis.contains("detailInvoiceAmount"));
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/TOrderInvoiceCompanyOptionsMapperTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/TOrderInvoiceCompanyOptionsMapperTest.java
new file mode 100644
index 0000000..2cc9925
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/TOrderInvoiceCompanyOptionsMapperTest.java
@@ -0,0 +1,37 @@
+package com.ruoyi.order.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TOrderInvoiceCompanyOptionsMapperTest {
+
+ @Test
+ void invoicingCompanyOptionsQueriesAllNonBlankCompanies() throws IOException {
+ InputStream input = getClass().getClassLoader()
+ .getResourceAsStream("mapper/order/TOrderInvoiceMapper.xml");
+ assertNotNull(input);
+
+ String mapper;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
+ mapper = reader.lines().collect(Collectors.joining("\n"));
+ }
+ int start = mapper.indexOf("<select id=\"invoicingCompanyOptions\"");
+ int end = mapper.indexOf("</select>", start);
+ String statement = mapper.substring(start, end);
+
+ assertTrue(statement.contains("SELECT DISTINCT TRIM(toi.invoicing_company)"));
+ assertTrue(statement.contains("toi.invoicing_company IS NOT NULL"));
+ assertFalse(statement.contains("query."));
+ assertFalse(statement.contains("site_id"));
+ }
+}
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VipChargingOrderExportMapperTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VipChargingOrderExportMapperTest.java
new file mode 100644
index 0000000..72fbfc6
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VipChargingOrderExportMapperTest.java
@@ -0,0 +1,36 @@
+package com.ruoyi.order.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class VipChargingOrderExportMapperTest {
+
+ @Test
+ void chargingOrderQueryIncludesUserPhoneAndPartnerName() throws IOException {
+ InputStream input = getClass().getClassLoader()
+ .getResourceAsStream("mapper/order/TChargingOrderMapper.xml");
+ assertNotNull(input);
+
+ String mapper;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
+ mapper = reader.lines().collect(Collectors.joining("\n"));
+ }
+ String statement = mapper.substring(
+ mapper.indexOf("<select id=\"chargingOrder\""),
+ mapper.indexOf("<select id=\"payOrderQuery\""));
+
+ assertTrue(statement.contains("app_user.phone"));
+ assertTrue(statement.contains("partner.name as partnerName"));
+ assertTrue(statement.contains("left join `charging_pile_account`.`t_app_user`"));
+ assertTrue(statement.contains("left join `charging_pile_service`.`t_partner`"));
+ }
+}
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingCouponSupportTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingCouponSupportTest.java
index 602ffcd..c7b2cce 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingCouponSupportTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingCouponSupportTest.java
@@ -6,44 +6,81 @@
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
class ChargingCouponSupportTest {
@Test
- void fullReductionDiscountsThePrepaymentBeforeThirdPartyPay() {
+ void couponDoesNotReducePrepayment() {
TCoupon coupon = chargingCoupon(1);
- coupon.setMeetTheConditions(new BigDecimal("3"));
- coupon.setDiscountAmount(new BigDecimal("1"));
+ coupon.setMeetTheConditions(new BigDecimal("30"));
+ coupon.setDiscountAmount(new BigDecimal("10"));
- BigDecimal discount = ChargingCouponSupport.calculateUpfrontDiscount(coupon, new BigDecimal("3"));
+ BigDecimal paymentAmount = ChargingCouponSupport.calculatePrepaymentAmount(new BigDecimal("30"), false);
- assertEquals(new BigDecimal("1.00"), discount);
- assertEquals(new BigDecimal("2.00"), new BigDecimal("3").subtract(discount));
+ assertEquals(new BigDecimal("30.00"), paymentAmount);
}
@Test
- void percentageDiscountHonorsMaximumDiscount() {
+ void legacyUpfrontDiscountOrderUsesActuallyCollectedAmountAsRefundBase() {
+ BigDecimal prepayment = ChargingCouponSupport.resolveSettlementPrepayment(
+ new BigDecimal("30"), BigDecimal.ZERO, new BigDecimal("20"), new BigDecimal("10"));
+
+ assertEquals(new BigDecimal("20"), prepayment);
+ }
+
+ @Test
+ void fullReductionCannotDiscountElectricity() {
+ TCoupon coupon = chargingCoupon(1);
+ coupon.setMeetTheConditions(new BigDecimal("10"));
+ coupon.setDiscountAmount(new BigDecimal("10"));
+
+ BigDecimal discount = ChargingCouponSupport.calculateSettlementDiscount(
+ coupon, new BigDecimal("15"), new BigDecimal("3"));
+
+ assertEquals(new BigDecimal("3.0000"), discount);
+ }
+
+ @Test
+ void percentageDiscountHonorsMaximumDiscountAndServiceCharge() {
TCoupon coupon = chargingCoupon(2);
coupon.setMeetTheConditions(new BigDecimal("10"));
coupon.setDiscount(new BigDecimal("8"));
coupon.setMaximumDiscountAmount(new BigDecimal("1.50"));
- BigDecimal discount = ChargingCouponSupport.calculateUpfrontDiscount(coupon, new BigDecimal("10"));
+ BigDecimal discount = ChargingCouponSupport.calculateSettlementDiscount(
+ coupon, new BigDecimal("10"), new BigDecimal("5"));
- assertEquals(new BigDecimal("1.50"), discount);
+ assertEquals(new BigDecimal("1.5000"), discount);
}
@Test
- void rejectsPrepaymentBelowCouponThreshold() {
+ void couponBelowActualSettlementThresholdIsNotUsed() {
TCoupon coupon = chargingCoupon(1);
- coupon.setMeetTheConditions(new BigDecimal("3"));
+ coupon.setMeetTheConditions(new BigDecimal("30"));
coupon.setDiscountAmount(new BigDecimal("1"));
- IllegalArgumentException error = assertThrows(IllegalArgumentException.class,
- () -> ChargingCouponSupport.calculateUpfrontDiscount(coupon, new BigDecimal("2.99")));
+ BigDecimal discount = ChargingCouponSupport.calculateSettlementDiscount(
+ coupon, new BigDecimal("29.99"), new BigDecimal("5"));
- assertEquals("预付金额未达到优惠券使用门槛", error.getMessage());
+ assertEquals(new BigDecimal("0.0000"), discount);
+ }
+
+ @Test
+ void nonFullWhitelistOnlyWaivesServiceCharge() {
+ BigDecimal[] split = ChargingCouponSupport.calculateWhitelistRefundSplit(
+ true, false, new BigDecimal("10"), new BigDecimal("5"));
+
+ assertEquals(BigDecimal.ZERO, split[0]);
+ assertEquals(new BigDecimal("5"), split[1]);
+ }
+
+ @Test
+ void fullWhitelistWaivesElectricityAndServiceCharge() {
+ BigDecimal[] split = ChargingCouponSupport.calculateWhitelistRefundSplit(
+ true, true, new BigDecimal("10"), new BigDecimal("5"));
+
+ assertEquals(new BigDecimal("10"), split[0]);
+ assertEquals(new BigDecimal("5"), split[1]);
}
private TCoupon chargingCoupon(int preferentialMode) {
--
Gitblit v1.7.1