From faaf70b3fc0cff90d25fa30b37f225f679a0d5a5 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 20 七月 2026 19:30:08 +0800
Subject: [PATCH] 特殊退款查询

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java |  294 +++++++++++++++++++++++------
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java  |  120 +++++++++++
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/config/IcbcPayProperties.java      |   14 
 ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUser.java                 |    4 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java      |   34 ++
 sql/20260720_special_refund_menu.sql                                                                |   60 ++++++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TUserTagController.java          |    6 
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderRefundDto.java                  |    2 
 ruoyi-service/ruoyi-chargingPile/src/main/resources/logback.xml                                     |    2 
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java    |    8 
 10 files changed, 460 insertions(+), 84 deletions(-)

diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUser.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUser.java
index 2ad2a5d..3d22d5b 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUser.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUser.java
@@ -132,6 +132,10 @@
     @TableField(exist = false)
     private String tagName;
 
+    @ApiModelProperty(value = "是否白名单:1=是,2=否")
+    @TableField(exist = false)
+    private Integer whitelistFlag;
+
     @ApiModelProperty(value = "单位名称")
     @TableField(exist = false)
     private String companyName;
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 c06423e..fd2bb96 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
@@ -428,6 +428,31 @@
             return R.ok(page);
         }
         List<Long> pageUserIds = page.getRecords().stream().map(TAppUser::getId).collect(Collectors.toList());
+        List<String> pageUserPhones = page.getRecords().stream()
+                .map(TAppUser::getPhone)
+                .filter(StringUtils::isNotBlank)
+                .map(String::trim)
+                .distinct()
+                .collect(Collectors.toList());
+        LambdaQueryChainWrapper<TAppUserWhitelist> pageWhitelistQuery = appUserWhitelistService.lambdaQuery()
+                .eq(TAppUserWhitelist::getStatus, 1)
+                .eq(TAppUserWhitelist::getDelFlag, false)
+                .and(wrapper -> {
+                    wrapper.in(TAppUserWhitelist::getAppUserId, pageUserIds);
+                    if (!pageUserPhones.isEmpty()) {
+                        wrapper.or().in(TAppUserWhitelist::getPhone, pageUserPhones);
+                    }
+                });
+        List<TAppUserWhitelist> pageWhitelistList = pageWhitelistQuery.list();
+        Set<Long> activeWhitelistUserIds = pageWhitelistList.stream()
+                .map(TAppUserWhitelist::getAppUserId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Set<String> activeWhitelistPhones = pageWhitelistList.stream()
+                .map(TAppUserWhitelist::getPhone)
+                .filter(StringUtils::isNotBlank)
+                .map(String::trim)
+                .collect(Collectors.toSet());
         Map<Long, String> userTagNameMap = getUserTagNameMap(pageUserIds);
         Map<Long, BigDecimal> exchangeCodeBalanceMap = otherClient.getExchangeCodeBalanceMap(pageUserIds).getData();
         if (exchangeCodeBalanceMap == null) {
@@ -441,6 +466,9 @@
         for (TAppUser appUser : page.getRecords()) {
             appUser.setExchangeCodeBalance(exchangeCodeBalanceMap.getOrDefault(appUser.getId(), BigDecimal.ZERO));
             appUser.setTagName(userTagNameMap.get(appUser.getId()));
+            boolean activeWhitelist = activeWhitelistUserIds.contains(appUser.getId())
+                    || (StringUtils.isNotBlank(appUser.getPhone()) && activeWhitelistPhones.contains(appUser.getPhone().trim()));
+            appUser.setWhitelistFlag(activeWhitelist ? 1 : 2);
             if (appUser.getVipEndTime()!=null&&appUser.getVipEndTime().isAfter(LocalDateTime.now())) {
                 //匹配vipMap的值
                 appUser.setVipName(vipMap.getData().get(appUser.getVipId()));
@@ -731,11 +759,7 @@
         List<TAppUserCar> carList = appUserCarService.lambdaQuery().eq(TAppUserCar::getAppUserId, id).orderByDesc(BasePojo::getCreateTime).list();
         userDetailDto.setCarList(carList);
 
-        List<Integer> tagIds = appUserTagService.lambdaQuery().eq(TAppUserTag::getAppUserId, id).orderByDesc(TAppUserTag::getCreateTime).list().stream().map(TAppUserTag::getUserTagId).collect(Collectors.toList());
-        R<Map<Integer, String>> tagMap = otherClient.getTagMap(tagIds);
-        userDetailDto.setTagName(tagMap.getData().values()
-                .stream()
-                .collect(Collectors.joining(",")));
+        userDetailDto.setTagName(getUserTagNameMap(Collections.singletonList(id)).get(id));
 
 
 
diff --git a/ruoyi-service/ruoyi-chargingPile/src/main/resources/logback.xml b/ruoyi-service/ruoyi-chargingPile/src/main/resources/logback.xml
index 3865fab..2e40bbb 100644
--- a/ruoyi-service/ruoyi-chargingPile/src/main/resources/logback.xml
+++ b/ruoyi-service/ruoyi-chargingPile/src/main/resources/logback.xml
@@ -8,7 +8,7 @@
 	
 	<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
 <!--	<property name="log.path" value="/data/log/app/ruoyi-chargingPile"/>-->
-	<property name="log.path" value="/Users/mitao/Documents/log/mx_charging_pile/log/app/ruoyi-chargingPile"/>
+	<property name="log.path" value="/Users/PC/Documents/log/mx_charging_pile/log/app/ruoyi-chargingPile"/>
 
 	<!--0. 日志格式和颜色渲染 -->
 	<!-- 彩色日志依赖的渲染类 -->
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 df707b0..31539c6 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
@@ -440,14 +440,16 @@
 
     @ResponseBody
     @PostMapping(value = "/pay/order/special/refund/query")
-    @ApiOperation(value = "特殊退款查询", tags = {"管理后台-支付订单-订单信息"})
+    @RequiresPermissions(value = {"/paymentOrder/specialRefund", "/paymentOrder/specialRefund/refund"}, logical = Logical.OR)
+    @ApiOperation(value = "特殊退款查询", tags = {"管理后台-支付订单-特殊退款处理"})
     public R<PayOrderInfoDto> specialRefundQuery(@RequestBody PayOrderRefundDto payOrderQueryDto) {
         return chargingOrderService.getSpecialRefundOrder(payOrderQueryDto);
     }
 
     @ResponseBody
     @PostMapping(value = "/pay/order/special/refund")
-    @ApiOperation(value = "特殊退款", tags = {"管理后台-支付订单-订单信息"})
+    @RequiresPermissions(value = {"/paymentOrder/specialRefund/refund"}, logical = Logical.OR)
+    @ApiOperation(value = "特殊退款", tags = {"管理后台-支付订单-特殊退款处理"})
     public R specialRefund(@RequestBody PayOrderRefundDto payOrderQueryDto) {
         return chargingOrderService.specialRefund(payOrderQueryDto);
     }
@@ -616,7 +618,7 @@
     
     
     
-    @RequiresPermissions(value = {"/paymentOrder/refund", "/paymentOrder/order"}, logical = Logical.OR)
+    @RequiresPermissions(value = {"/paymentOrder/refund", "/paymentOrder/order", "/paymentOrder/specialRefund"}, logical = Logical.OR)
     @ResponseBody
     @PostMapping(value = "/pay/order/refund/list")
     @ApiOperation(value = "列表", tags = {"管理后台-支付订单-退款订单"})
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderRefundDto.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderRefundDto.java
index b6b9006..107ad23 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderRefundDto.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/PayOrderRefundDto.java
@@ -16,7 +16,7 @@
     private String code;
     @ApiModelProperty("支付流水号")
     private String serialNumber;
-    @ApiModelProperty("支付宝特殊退款方式:ALI=支付宝退款,ICBC=工商退款")
+    @ApiModelProperty("退款通道(兼容旧参数,特殊退款按原支付路径退款)")
     private String refundChannel;
     private BigDecimal refundAmount;
     private String reason;
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java
index 29e25b1..b3d838c 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java
@@ -36,6 +36,7 @@
 import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
@@ -43,6 +44,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -1057,10 +1060,10 @@
         List<Map<String, Object>> list = chargingSettlementBillService.queryList(pageInfo, dto.getType(), dto.getState(), dto.getDateRange(), statisticalDateType, dto.getSiteIds());
         ChargingBillWapper chargingBillVO = new ChargingBillWapper();
         List<Map<String, Object>> pageList = new ArrayList<>();
-        List<TChargingOrder> total = chargingOrderList.selectList(new LambdaQueryWrapper<>(TChargingOrder.class)
-                .isNotNull(TChargingOrder::getPayTime));
-        List<TChargingOrderAccountingStrategy> strategyList = chargingOrderAccountingStrategyService.list();
         Map<Integer, BigDecimal> siteServiceShareRatioMap = loadSiteServiceShareRatioMap();
+        PageInfo<Map<String, Object>> sumPageInfo = new PageInfo<>(1, 100000);
+        List<Map<String, Object>> sumList = chargingSettlementBillService.queryList(sumPageInfo, dto.getType(), dto.getState(), dto.getDateRange(), statisticalDateType, dto.getSiteIds());
+        Map<String, Map<String, BigDecimal>> incomeIndex = buildChargingBillIncomeIndex(sumList, siteServiceShareRatioMap);
         if(null != list){
             for (Map<String, Object> map : list) {
                 Integer site_id = Integer.valueOf(map.get("site_id").toString());
@@ -1077,7 +1080,7 @@
                 String chargingCapacity = map.get("total_electricity").toString();
                 String electrovalence = map.get("total_electricity_charge").toString();
                 String serviceCharge = map.get("total_service_fee").toString();
-                Map<String, BigDecimal> incomeMap = calculateChargingBillIncome(dto.getType(), site_id, billingCycle, total, strategyList, siteServiceShareRatioMap);
+                Map<String, BigDecimal> incomeMap = getChargingBillIncome(incomeIndex, site_id, billingCycle);
                 Map<String, Object> data1 = new HashMap<>();
                 data1.put("code", code);
                 data1.put("orderState", 2);
@@ -1118,13 +1121,11 @@
         BigDecimal discount = (null == map || null == map.get("couponDeduction")) ? BigDecimal.ZERO : new BigDecimal(map.get("couponDeduction").toString());
         BigDecimal platformIncome = BigDecimal.ZERO;
         BigDecimal partnerIncome = BigDecimal.ZERO;
-        PageInfo<Map<String, Object>> sumPageInfo = new PageInfo<>(1, 100000);
-        List<Map<String, Object>> sumList = chargingSettlementBillService.queryList(sumPageInfo, dto.getType(), dto.getState(), dto.getDateRange(), statisticalDateType, dto.getSiteIds());
         if (sumList != null) {
             for (Map<String, Object> row : sumList) {
                 Integer siteId = Integer.valueOf(row.get("site_id").toString());
                 String billingCycle = row.get("billingCycle").toString();
-                Map<String, BigDecimal> incomeMap = calculateChargingBillIncome(dto.getType(), siteId, billingCycle, total, strategyList, siteServiceShareRatioMap);
+                Map<String, BigDecimal> incomeMap = getChargingBillIncome(incomeIndex, siteId, billingCycle);
                 platformIncome = platformIncome.add(incomeMap.get("platformIncome"));
                 partnerIncome = partnerIncome.add(incomeMap.get("partnerIncome"));
             }
@@ -1145,6 +1146,111 @@
         return chargingBillVO;
     }
 
+    private Map<String, Map<String, BigDecimal>> buildChargingBillIncomeIndex(List<Map<String, Object>> billRows,
+                                                                               Map<Integer, BigDecimal> siteServiceShareRatioMap) {
+        Map<String, Map<String, BigDecimal>> incomeIndex = new HashMap<>();
+        if (billRows == null || billRows.isEmpty()) {
+            return incomeIndex;
+        }
+        Set<String> billingCycles = billRows.stream()
+                .map(row -> row.get("billingCycle"))
+                .filter(Objects::nonNull)
+                .map(Object::toString)
+                .collect(Collectors.toSet());
+        if (billingCycles.isEmpty()) {
+            return incomeIndex;
+        }
+        LocalDate firstMonth = billingCycles.stream()
+                .map(cycle -> LocalDate.parse(cycle + "-01"))
+                .min(LocalDate::compareTo)
+                .orElse(null);
+        LocalDate lastMonth = billingCycles.stream()
+                .map(cycle -> LocalDate.parse(cycle + "-01"))
+                .max(LocalDate::compareTo)
+                .orElse(null);
+        if (firstMonth == null || lastMonth == null) {
+            return incomeIndex;
+        }
+
+        List<TChargingOrder> orders = chargingOrderList.selectList(new LambdaQueryWrapper<>(TChargingOrder.class)
+                .select(TChargingOrder::getId, TChargingOrder::getSiteId, TChargingOrder::getPayTime, TChargingOrder::getOrderSource)
+                .ge(TChargingOrder::getPayTime, firstMonth.atStartOfDay())
+                .lt(TChargingOrder::getPayTime, lastMonth.plusMonths(1).atStartOfDay()));
+        if (orders == null || orders.isEmpty()) {
+            return incomeIndex;
+        }
+
+        Map<Long, BigDecimal[]> strategyAmountMap = new HashMap<>();
+        List<Long> orderIds = orders.stream().map(TChargingOrder::getId).filter(Objects::nonNull).collect(Collectors.toList());
+        final int batchSize = 1000;
+        for (int from = 0; from < orderIds.size(); from += batchSize) {
+            List<Long> batchIds = orderIds.subList(from, Math.min(from + batchSize, orderIds.size()));
+            List<TChargingOrderAccountingStrategy> strategies = chargingOrderAccountingStrategyService.list(
+                    new LambdaQueryWrapper<TChargingOrderAccountingStrategy>()
+                            .select(TChargingOrderAccountingStrategy::getChargingOrderId,
+                                    TChargingOrderAccountingStrategy::getPeriodElectricPrice,
+                                    TChargingOrderAccountingStrategy::getPeriodServicePrice)
+                            .in(TChargingOrderAccountingStrategy::getChargingOrderId, batchIds));
+            for (TChargingOrderAccountingStrategy strategy : strategies) {
+                BigDecimal[] amounts = strategyAmountMap.computeIfAbsent(strategy.getChargingOrderId(), key -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
+                amounts[0] = amounts[0].add(strategy.getPeriodElectricPrice() == null ? BigDecimal.ZERO : strategy.getPeriodElectricPrice());
+                amounts[1] = amounts[1].add(strategy.getPeriodServicePrice() == null ? BigDecimal.ZERO : strategy.getPeriodServicePrice());
+            }
+        }
+
+        for (TChargingOrder order : orders) {
+            if (order.getPayTime() == null || order.getSiteId() == null) {
+                continue;
+            }
+            String billingCycle = order.getPayTime().format(DateTimeFormatter.ofPattern("yyyy-MM"));
+            if (!billingCycles.contains(billingCycle)) {
+                continue;
+            }
+            BigDecimal[] amounts = strategyAmountMap.getOrDefault(order.getId(), new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
+            BigDecimal paymentAmount;
+            BigDecimal partnerIncome = BigDecimal.ZERO;
+            if (Integer.valueOf(2).equals(order.getOrderSource())) {
+                paymentAmount = amounts[0].add(amounts[1].multiply(new BigDecimal("0.8")).setScale(2, RoundingMode.DOWN));
+            } else {
+                paymentAmount = amounts[0].add(amounts[1]);
+                if (Integer.valueOf(1).equals(order.getOrderSource())) {
+                    BigDecimal serviceShareRatio = siteServiceShareRatioMap.get(order.getSiteId());
+                    if (serviceShareRatio != null && serviceShareRatio.compareTo(BigDecimal.ZERO) > 0) {
+                        partnerIncome = amounts[1].multiply(serviceShareRatio).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
+                    }
+                }
+            }
+            addChargingBillIncome(incomeIndex, chargingBillIncomeKey(0, billingCycle), paymentAmount, partnerIncome);
+            addChargingBillIncome(incomeIndex, chargingBillIncomeKey(order.getSiteId(), billingCycle), paymentAmount, partnerIncome);
+        }
+        return incomeIndex;
+    }
+
+    private void addChargingBillIncome(Map<String, Map<String, BigDecimal>> incomeIndex, String key,
+                                       BigDecimal paymentAmount, BigDecimal partnerIncome) {
+        Map<String, BigDecimal> income = incomeIndex.computeIfAbsent(key, ignored -> {
+            Map<String, BigDecimal> value = new HashMap<>();
+            value.put("platformIncome", BigDecimal.ZERO);
+            value.put("partnerIncome", BigDecimal.ZERO);
+            return value;
+        });
+        income.put("platformIncome", income.get("platformIncome").add(paymentAmount).subtract(partnerIncome));
+        income.put("partnerIncome", income.get("partnerIncome").add(partnerIncome));
+    }
+
+    private Map<String, BigDecimal> getChargingBillIncome(Map<String, Map<String, BigDecimal>> incomeIndex,
+                                                           Integer siteId, String billingCycle) {
+        Map<String, BigDecimal> income = incomeIndex.get(chargingBillIncomeKey(siteId, billingCycle));
+        Map<String, BigDecimal> result = new HashMap<>();
+        result.put("platformIncome", income == null ? BigDecimal.ZERO : income.get("platformIncome").max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP));
+        result.put("partnerIncome", income == null ? BigDecimal.ZERO : income.get("partnerIncome").setScale(2, RoundingMode.HALF_UP));
+        return result;
+    }
+
+    private String chargingBillIncomeKey(Integer siteId, String billingCycle) {
+        return (siteId == null ? 0 : siteId) + "|" + billingCycle;
+    }
+
     @Override
     public ChargingBillVO chargingBillListExport(ChargingListQuery dto) {
         if (dto.getState()!=null){
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 baff07a..c506ddc 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
@@ -966,7 +966,13 @@
 			req.setReturnUrl(addChargingOrder.getReturnUrl());
 			req.setOpenId(appUser.getWxOpenid());
 			req.setProfitSharing(buildIcbcProfitSharing(site));
-			IcbcPaymentResp data = icbcPaymentClient.payment(req).getData();
+			R<IcbcPaymentResp> result = icbcPaymentClient.payment(req);
+			if (result == null || R.isError(result)) {
+				String reason = result == null ? "工行支付服务无响应" : firstNotBlank(result.getMsg(), "工行支付服务调用失败");
+				log.error("调起工行支付失败,订单号:{},原因:{}", chargingOrder.getCode(), reason);
+				throw new RuntimeException("调起工行支付失败:" + reason);
+			}
+			IcbcPaymentResp data = result.getData();
 			if (isIcbcPaymentCreated(data)) {
 				if (StringUtils.hasLength(data.getTradeNo())) {
 					chargingOrder.setRechargeSerialNumber(data.getTradeNo());
@@ -974,7 +980,9 @@
 				}
 				return AjaxResult.success(data);
 			}
-			throw new RuntimeException("调起工行支付失败");
+			String reason = buildIcbcPaymentFailReason(data);
+			log.error("调起工行支付失败,订单号:{},工行返回:{}", chargingOrder.getCode(), reason);
+			throw new RuntimeException("调起工行支付失败:" + reason);
 		}
 		if (Integer.valueOf(2).equals(addChargingOrder.getPaymentType())) {
 			AliPaymentReq req = new AliPaymentReq();
@@ -2385,12 +2393,23 @@
 	 * @param chargingOrder
 	 */
 	public void editUserTag(TChargingOrder chargingOrder) {
+		if (chargingOrder == null || chargingOrder.getAppUserId() == null) {
+			return;
+		}
 		//处理用户标签数据
-		List<TUserTag> data = userTagClient.getAllUserTag().getData();
+		R<List<TUserTag>> tagResult = userTagClient.getAllUserTag();
+		List<TUserTag> data = tagResult == null ? null : tagResult.getData();
+		if (data == null || data.isEmpty()) {
+			return;
+		}
 		//累计充电次数
-		List<TUserTag> userTagList1 = data.stream().filter(s -> s.getStandardCondition() == 1).collect(Collectors.toList());
+		List<TUserTag> userTagList1 = data.stream().filter(s -> Integer.valueOf(1).equals(s.getStandardCondition())).collect(Collectors.toList());
 		for (TUserTag tUserTag : userTagList1) {
-			Integer times = JSON.parseObject(tUserTag.getConditions()).getInteger("times");
+			JSONObject conditions = JSON.parseObject(tUserTag.getConditions());
+			Integer times = conditions == null ? null : conditions.getInteger("times");
+			if (times == null || times <= 0 || tUserTag.getCreateTime() == null) {
+				continue;
+			}
 			TAppUserTag appUserTag = new TAppUserTag();
 			appUserTag.setAppUserId(chargingOrder.getAppUserId());
 			appUserTag.setUserTagId(tUserTag.getId());
@@ -2409,18 +2428,21 @@
 			}
 		}
 		//充电频率
-		List<TUserTag> userTagList2 = data.stream().filter(s -> s.getStandardCondition() == 2).collect(Collectors.toList());
+		List<TUserTag> userTagList2 = data.stream().filter(s -> Integer.valueOf(2).equals(s.getStandardCondition())).collect(Collectors.toList());
 		for (TUserTag tUserTag : userTagList2) {
 			TAppUserTag appUserTag = new TAppUserTag();
 			appUserTag.setAppUserId(chargingOrder.getAppUserId());
 			appUserTag.setUserTagId(tUserTag.getId());
 			TAppUserTag data1 = appUserTagClient.getUserTag(appUserTag).getData();
 			
-			Integer day = JSON.parseObject(tUserTag.getConditions()).getInteger("day");
-			Integer times = JSON.parseObject(tUserTag.getConditions()).getInteger("times");
+			JSONObject conditions = JSON.parseObject(tUserTag.getConditions());
+			Integer day = conditions == null ? null : conditions.getInteger("day");
+			Integer times = conditions == null ? null : conditions.getInteger("times");
+			if (day == null || day <= 0 || times == null || times <= 0) {
+				continue;
+			}
 			LocalDateTime endTime = LocalDateTime.now();
-			endTime.plusDays(day);
-			String start = tUserTag.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+			String start = endTime.minusDays(day).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
 			String end = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
 			long count1 = this.count(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getAppUserId, chargingOrder.getAppUserId())
 					.eq(TChargingOrder::getRechargePaymentStatus, 2).between(TChargingOrder::getStartTime, start, end).eq(TChargingOrder::getDelFlag, 0));
@@ -2934,13 +2956,23 @@
 
 	@Override
 	public R<PageInfo<PayOrderDto>> payOrderQuery(PayOrderQueryDto payOrderQueryDto) {
+		if (payOrderQueryDto == null) {
+			payOrderQueryDto = new PayOrderQueryDto();
+		}
 		PageInfo<PayOrderDto> pageInfo = new PageInfo<>(payOrderQueryDto.getPageCurr(), payOrderQueryDto.getPageSize());
 		List<PayOrderDto> list = this.baseMapper.payOrderQuery(pageInfo, payOrderQueryDto);
+		if (list == null || list.isEmpty()) {
+			pageInfo.setRecords(Collections.emptyList());
+			return R.ok(pageInfo);
+		}
 		for (PayOrderDto payOrderDto : list) {
-			if (payOrderDto.getType() == 2 || payOrderDto.getType() == 3) {
+			if (payOrderDto == null) {
+				continue;
+			}
+			if (Integer.valueOf(2).equals(payOrderDto.getType()) || Integer.valueOf(3).equals(payOrderDto.getType())) {
 				payOrderDto.setFinalAmount(payOrderDto.getOrderAmount());
 			}
-			if (payOrderDto.getType() == 1 && payOrderDto.getStatus() == 3) {
+			if (Integer.valueOf(1).equals(payOrderDto.getType()) && Integer.valueOf(3).equals(payOrderDto.getStatus())) {
 				payOrderDto.setFinalAmount(payOrderDto.getOrderAmount());
 				payOrderDto.setOrderAmount(payOrderDto.getOrderAmount());
 				payOrderDto.setPaymentAmount(payOrderDto.getOrderAmount());
@@ -4700,61 +4732,189 @@
 		if (order == null) {
 			return R.fail("未查询到支付成功的充电订单");
 		}
-		BigDecimal refundAmount = money(payOrderQueryDto.getRefundAmount());
-		BigDecimal refundableAmount = money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP);
-		if (refundAmount.compareTo(refundableAmount) > 0) {
-			return R.fail("退款金额不能超过剩余可退金额");
+		RLock lock = redissonClient.getLock("SPECIAL_REFUND_ORDER_LOCK:" + order.getId());
+		if (!lock.tryLock()) {
+			return R.fail("该订单正在退款处理中,请勿重复提交");
 		}
-		BigDecimal refundedThirdAmount = money(order.getThirdRefundAmount());
-		BigDecimal refundableThirdAmount = money(order.getThirdPayAmount()).subtract(refundedThirdAmount);
-		if (refundableThirdAmount.compareTo(BigDecimal.ZERO) < 0) {
-			refundableThirdAmount = BigDecimal.ZERO;
+		try {
+			order = this.getById(order.getId());
+			if (order == null) {
+				return R.fail("充电订单不存在");
+			}
+			BigDecimal refundAmount = money(payOrderQueryDto.getRefundAmount());
+			BigDecimal refundableAmount = money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP);
+			if (refundAmount.compareTo(refundableAmount) > 0) {
+				return R.fail("退款金额不能超过剩余可退金额");
+			}
+			BigDecimal refundedThirdAmount = money(order.getThirdRefundAmount());
+			BigDecimal refundableThirdAmount = money(order.getThirdPayAmount()).subtract(refundedThirdAmount);
+			if (refundableThirdAmount.compareTo(BigDecimal.ZERO) < 0) {
+				refundableThirdAmount = BigDecimal.ZERO;
+			}
+			BigDecimal thirdRefundAmount = isBalancePayMode(order.getPayMethod())
+					? refundAmount.min(refundableThirdAmount).setScale(2, RoundingMode.HALF_UP)
+					: refundAmount;
+			BigDecimal balanceRefundAmount = refundAmount.subtract(thirdRefundAmount).setScale(2, RoundingMode.HALF_UP);
+			if (balanceRefundAmount.compareTo(BigDecimal.ZERO) < 0) {
+				balanceRefundAmount = BigDecimal.ZERO;
+			}
+			TChargingOrderRefund refund = buildSpecialChargingRefund(order, payOrderQueryDto, refundAmount, balanceRefundAmount, thirdRefundAmount);
+			if (thirdRefundAmount.compareTo(BigDecimal.ZERO) == 0) {
+				refund.setRefundSerialNumber("BALANCE");
+				finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
+				return R.ok();
+			}
+			Integer payType = order.getRechargePaymentType();
+			boolean success = false;
+			if (Integer.valueOf(1).equals(payType)) {
+				R shareRefund = refundIcbcShareBeforeMainRefund(order, refund, thirdRefundAmount);
+				if (shareRefund.getCode() != 200) {
+					return shareRefund;
+				}
+				success = refundByIcbcAndFinish(order, refund, thirdRefundAmount, SPECIAL_REFUND_TITLE, true, refundAmount, balanceRefundAmount);
+			} else if (Integer.valueOf(2).equals(payType)) {
+				RefundReq dto = new RefundReq();
+				dto.setOutTradeNo(order.getCode());
+				dto.setOutRequestNo(refund.getRefundCode());
+				dto.setRefundAmount(thirdRefundAmount.toString());
+				dto.setRefundReason(SPECIAL_REFUND_TITLE);
+				RefundResp resp = aliPaymentClient.refund(dto).getData();
+				if (resp != null) {
+					refund.setRefundSerialNumber(resp.getTradeNo());
+					refund.setRefundTime(LocalDateTime.now());
+					finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
+					success = true;
+				}
+			} else if (Integer.valueOf(3).equals(payType)) {
+				RefundReq dto = new RefundReq();
+				dto.setOutTradeNo(order.getRechargeSerialNumber());
+				dto.setOutRequestNo(refund.getRefundCode());
+				dto.setRefundAmount(thirdRefundAmount.toString());
+				dto.setRefundReason(SPECIAL_REFUND_TITLE);
+				RefundResp resp = h5AliPaymentClient.refund(dto).getData();
+				if (resp != null && "10000".equals(resp.getCode())) {
+					refund.setRefundSerialNumber(resp.getTradeNo());
+					refund.setRefundTime(LocalDateTime.now());
+					finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
+					success = true;
+				}
+			}
+			return success ? R.ok() : R.fail("退款失败,请检查支付渠道或稍后重试");
+		} finally {
+			if (lock.isHeldByCurrentThread()) {
+				lock.unlock();
+			}
 		}
-		BigDecimal thirdRefundAmount = isBalancePayMode(order.getPayMethod())
-				? refundAmount.min(refundableThirdAmount).setScale(2, RoundingMode.HALF_UP)
-				: refundAmount;
-		BigDecimal balanceRefundAmount = refundAmount.subtract(thirdRefundAmount).setScale(2, RoundingMode.HALF_UP);
-		if (balanceRefundAmount.compareTo(BigDecimal.ZERO) < 0) {
-			balanceRefundAmount = BigDecimal.ZERO;
-		}
-		TChargingOrderRefund refund = buildSpecialChargingRefund(order, payOrderQueryDto, refundAmount, balanceRefundAmount, thirdRefundAmount);
-		if (thirdRefundAmount.compareTo(BigDecimal.ZERO) == 0) {
-			refund.setRefundSerialNumber("BALANCE");
-			finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
+	}
+
+	private R refundIcbcShareBeforeMainRefund(TChargingOrder order, TChargingOrderRefund refund, BigDecimal thirdRefundAmount) {
+		if (order == null || refund == null || thirdRefundAmount == null || thirdRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
 			return R.ok();
 		}
-		Integer payType = order.getRechargePaymentType();
-		boolean success = false;
-		if (Integer.valueOf(1).equals(payType) || "ICBC".equalsIgnoreCase(payOrderQueryDto.getRefundChannel())) {
-			success = refundByIcbcAndFinish(order, refund, thirdRefundAmount, SPECIAL_REFUND_TITLE, true, refundAmount, balanceRefundAmount);
-		} else if (Integer.valueOf(2).equals(payType)) {
-			RefundReq dto = new RefundReq();
-			dto.setOutTradeNo(order.getCode());
-			dto.setOutRequestNo(refund.getRefundCode());
-			dto.setRefundAmount(thirdRefundAmount.toString());
-			dto.setRefundReason(SPECIAL_REFUND_TITLE);
-			RefundResp resp = aliPaymentClient.refund(dto).getData();
-			if (resp != null) {
-				refund.setRefundSerialNumber(resp.getTradeNo());
-				refund.setRefundTime(LocalDateTime.now());
-				finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
-				success = true;
+		if (!"ICBC".equals(order.getSourceName())) {
+			return R.ok();
+		}
+		List<TChargingOrderShareRecord> shareRecords = chargingOrderShareRecordMapper.selectList(
+				new LambdaQueryWrapper<TChargingOrderShareRecord>()
+						.eq(TChargingOrderShareRecord::getChargingOrderId, order.getId())
+						.eq(TChargingOrderShareRecord::getPayChannel, "ICBC")
+						.eq(TChargingOrderShareRecord::getNeedOnlineShare, true)
+						.eq(TChargingOrderShareRecord::getOnlineShareStatus, "SUCCESS")
+						.eq(TChargingOrderShareRecord::getDelFlag, false)
+						.orderByAsc(TChargingOrderShareRecord::getId));
+		if (shareRecords == null || shareRecords.isEmpty()) {
+			return R.ok();
+		}
+		BigDecimal thirdPayAmount = getRefundableThirdPayAmount(order);
+		if (thirdPayAmount.compareTo(BigDecimal.ZERO) <= 0) {
+			return R.fail("订单已分账但第三方支付金额异常,禁止退款");
+		}
+		BigDecimal cumulativeThirdRefund = money(order.getThirdRefundAmount()).add(thirdRefundAmount).min(thirdPayAmount).setScale(2, RoundingMode.HALF_UP);
+		for (TChargingOrderShareRecord shareRecord : shareRecords) {
+			BigDecimal shareAmount = money(shareRecord.getShareAmount());
+			if (shareAmount.compareTo(BigDecimal.ZERO) <= 0) {
+				continue;
 			}
-		} else if (Integer.valueOf(3).equals(payType)) {
-			RefundReq dto = new RefundReq();
-			dto.setOutTradeNo(order.getRechargeSerialNumber());
-			dto.setOutRequestNo(refund.getRefundCode());
-			dto.setRefundAmount(thirdRefundAmount.toString());
-			dto.setRefundReason(SPECIAL_REFUND_TITLE);
-			RefundResp resp = h5AliPaymentClient.refund(dto).getData();
-			if (resp != null && "10000".equals(resp.getCode())) {
-				refund.setRefundSerialNumber(resp.getTradeNo());
-				refund.setRefundTime(LocalDateTime.now());
-				finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
-				success = true;
+			BigDecimal targetRefundAmount = shareAmount.multiply(cumulativeThirdRefund).divide(thirdPayAmount, 2, RoundingMode.HALF_UP).min(shareAmount).setScale(2, RoundingMode.HALF_UP);
+			BigDecimal refundedShareAmount;
+			try {
+				refundedShareAmount = queryIcbcShareRefundAmount(order, shareRecord);
+			} catch (ServiceException e) {
+				return R.fail(e.getMessage());
+			}
+			BigDecimal shareRefundAmount = targetRefundAmount.subtract(refundedShareAmount).setScale(2, RoundingMode.HALF_UP);
+			if (shareRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
+				continue;
+			}
+			R result = refundIcbcShareRecord(order, shareRecord, refund, shareRefundAmount);
+			if (result.getCode() != 200) {
+				return result;
 			}
 		}
-		return success ? R.ok() : R.fail("退款失败,请检查支付渠道或稍后重试");
+		return R.ok();
+	}
+
+	private BigDecimal queryIcbcShareRefundAmount(TChargingOrder order, TChargingOrderShareRecord shareRecord) {
+		try {
+			IcbcSubOrderQueryReq queryReq = IcbcAdminFundOperationSupport.buildSubOrderQueryRequest(order, shareRecord);
+			R<IcbcSubOrderQueryResp> queryResult = icbcPaymentClient.querySubOrder(queryReq);
+			IcbcSubOrderQueryResp queryResp = queryResult == null ? null : queryResult.getData();
+			if (queryResult == null || queryResult.getCode() != 200 || queryResp == null || !"SUCCESS".equals(queryResp.getStatus())) {
+				throw new ServiceException(queryResult == null ? "工行子订单查询无响应" : firstNotBlank(queryResult.getMsg(), "工行子订单查询失败"));
+			}
+			return parseIcbcAmount(queryResp.getOrderRefundAmnt());
+		} catch (IllegalArgumentException e) {
+			throw new ServiceException(e.getMessage());
+		} catch (ServiceException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new ServiceException("工行子订单查询异常:" + firstNotBlank(e.getMessage(), "未知异常"));
+		}
+	}
+
+	private R refundIcbcShareRecord(TChargingOrder order, TChargingOrderShareRecord shareRecord, TChargingOrderRefund refund, BigDecimal shareRefundAmount) {
+		String suffix = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
+		String retSubOrderId = ("SPSUB" + refund.getRefundCode() + shareRecord.getId()).substring(0, Math.min(50, ("SPSUB" + refund.getRefundCode() + shareRecord.getId()).length()));
+		String outtrxSerialNo = ("SPSUBREF" + suffix + shareRecord.getId()).substring(0, Math.min(50, ("SPSUBREF" + suffix + shareRecord.getId()).length()));
+		try {
+			IcbcSubOrderRefundReq refundReq = IcbcAdminFundOperationSupport.buildSubOrderRefundRequest(
+					order, shareRecord, shareRefundAmount, retSubOrderId, outtrxSerialNo);
+			refundReq.setMerSpInfor("特殊退款分账退回:" + order.getCode());
+			R<IcbcSubOrderRefundResp> refundResult = icbcPaymentClient.refundSubOrder(refundReq);
+			IcbcSubOrderRefundResp refundResp = refundResult == null ? null : refundResult.getData();
+			if (refundResult == null || refundResult.getCode() != 200 || refundResp == null || !"SUCCESS".equals(refundResp.getStatus())) {
+				String message = refundResult == null ? "工行分账退款无响应" : firstNotBlank(refundResult.getMsg(), refundResp == null ? null : refundResp.getRawResponse(), "工行分账退款失败");
+				log.error("特殊退款分账退回失败,订单号:{},退款单号:{},分账记录:{},金额:{},原因:{}",
+						order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount, message);
+				return R.fail("分账退回失败:" + message);
+			}
+			log.info("特殊退款分账退回成功,订单号:{},退款单号:{},分账记录:{},金额:{}",
+					order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount);
+			return R.ok();
+		} catch (IllegalArgumentException | ServiceException e) {
+			log.error("特殊退款分账退回失败,订单号:{},退款单号:{},分账记录:{},金额:{}",
+					order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount, e);
+			return R.fail("分账退回失败:" + e.getMessage());
+		} catch (Exception e) {
+			log.error("特殊退款分账退回异常,订单号:{},退款单号:{},分账记录:{},金额:{}",
+					order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount, e);
+			return R.fail("分账退回异常:" + firstNotBlank(e.getMessage(), "未知异常"));
+		}
+	}
+
+	private BigDecimal parseIcbcAmount(String amount) {
+		if (!StringUtils.hasLength(amount)) {
+			return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+		}
+		String value = amount.trim();
+		try {
+			if (value.contains(".")) {
+				return money(new BigDecimal(value));
+			}
+			return new BigDecimal(value).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
+		} catch (NumberFormatException e) {
+			throw new ServiceException("工行金额格式异常:" + amount);
+		}
 	}
 
 	private R validateOperationPassword(PayOrderRefundDto payOrderQueryDto) {
@@ -7038,6 +7198,20 @@
 		}
 	}
 
+	private String buildIcbcPaymentFailReason(IcbcPaymentResp resp) {
+		if (resp == null) {
+			return "工行支付服务未返回支付数据";
+		}
+		String reason = firstNotBlank(resp.getRawResponse(), resp.getStatus());
+		if (!StringUtils.hasLength(reason)) {
+			return "工行支付服务返回数据不完整";
+		}
+		if (reason.length() > 500) {
+			return reason.substring(0, 500);
+		}
+		return reason;
+	}
+
 	private void triggerChargingOrderShare(TChargingOrder chargingOrder, Site site) {
 		if (!isNeedChargingOrderShare(chargingOrder)) {
 			return;
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TUserTagController.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TUserTagController.java
index a4f5fa9..c82aeb9 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TUserTagController.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TUserTagController.java
@@ -14,6 +14,7 @@
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -59,8 +60,11 @@
     //获取标签map
     @PostMapping(value = "/getTagMap")
     public R<Map<Integer, String>> getTagMap(@RequestBody List<Integer> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return R.ok(new HashMap<>());
+        }
 
-        List<TUserTag> list = tUserTagService.lambdaQuery().in(!ids.isEmpty(), TUserTag::getId, ids).list();
+        List<TUserTag> list = tUserTagService.lambdaQuery().in(TUserTag::getId, ids).list();
         Map<Integer, String> idNameMap = list.stream()
                 .collect(Collectors.toMap(
                         TUserTag::getId,
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/config/IcbcPayProperties.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/config/IcbcPayProperties.java
index 5ea37d0..1866103 100644
--- a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/config/IcbcPayProperties.java
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/config/IcbcPayProperties.java
@@ -13,18 +13,20 @@
 @Configuration
 @ConfigurationProperties(prefix = "icbc.pay")
 public class IcbcPayProperties {
+    private static final String ICBC_GATEWAY = "https://gw.open.icbc.com.cn/api/cardbusiness";
+
     private String appId;
     private String privateKey;
     private String icbcPublicKey;
     private String signType = "RSA2";
     private String charset = "UTF-8";
     private String format = "json";
-    private String gatewayUrl;
-    private String orderQueryUrl;
-    private String refundUrl;
-    private String subOrderSplitUrl;
-    private String subOrderRefundUrl;
-    private String subOrderQueryUrl;
+    private String gatewayUrl = ICBC_GATEWAY + "/aggregatepay/b2c/online/consumepurchase/V2";
+    private String orderQueryUrl = ICBC_GATEWAY + "/aggregatepay/b2c/online/orderqry/V2";
+    private String refundUrl = ICBC_GATEWAY + "/aggregatepay/b2c/online/merrefund/V1";
+    private String subOrderSplitUrl = ICBC_GATEWAY + "/aggregatepay/b2c/online/sersuborder/V1";
+    private String subOrderRefundUrl = ICBC_GATEWAY + "/aggregatepay/b2c/online/sersuborderrefund/V1";
+    private String subOrderQueryUrl = ICBC_GATEWAY + "/order/suborderquery/V1";
     private String notifyUrl;
     private String merId;
     private String merPrtclNo;
diff --git a/sql/20260720_special_refund_menu.sql b/sql/20260720_special_refund_menu.sql
new file mode 100644
index 0000000..554875b
--- /dev/null
+++ b/sql/20260720_special_refund_menu.sql
@@ -0,0 +1,60 @@
+-- 支付订单:特殊退款处理菜单及执行按钮权限
+
+SET @payment_order_id := (
+    SELECT menu_id
+    FROM sys_menu
+    WHERE menu_name = '支付订单'
+      AND (perms = '/paymentOrder' OR path = '/order/payMentOrder' OR path = 'payMentOrder')
+    ORDER BY menu_id ASC
+    LIMIT 1
+);
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
+SELECT '特殊退款处理', @payment_order_id, 3, '/order/payMentOrder/specialRefund', 'order/payMentOrder/specialRefund', 1, 0, 'C', '0', 0, '/paymentOrder/specialRefund', '#', 'admin', NOW(), '支付订单特殊退款处理'
+WHERE @payment_order_id IS NOT NULL
+  AND NOT EXISTS (
+      SELECT 1
+      FROM sys_menu
+      WHERE parent_id = @payment_order_id
+        AND (perms = '/paymentOrder/specialRefund' OR path = '/order/payMentOrder/specialRefund')
+  );
+
+SET @special_refund_id := (
+    SELECT menu_id
+    FROM sys_menu
+    WHERE parent_id = @payment_order_id
+      AND (perms = '/paymentOrder/specialRefund' OR path = '/order/payMentOrder/specialRefund')
+    ORDER BY menu_id ASC
+    LIMIT 1
+);
+
+UPDATE sys_menu
+SET menu_name = '特殊退款处理',
+    parent_id = @payment_order_id,
+    order_num = 3,
+    path = '/order/payMentOrder/specialRefund',
+    component = 'order/payMentOrder/specialRefund',
+    menu_type = 'C',
+    visible = '0',
+    status = 0,
+    perms = '/paymentOrder/specialRefund',
+    update_time = NOW()
+WHERE @special_refund_id IS NOT NULL
+  AND menu_id = @special_refund_id;
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
+SELECT '发起特殊退款', @special_refund_id, 1, '', NULL, 1, 0, 'F', '0', 0, '/paymentOrder/specialRefund/refund', '#', 'admin', NOW(), '发起特殊退款按钮权限'
+WHERE @special_refund_id IS NOT NULL
+  AND NOT EXISTS (
+      SELECT 1
+      FROM sys_menu
+      WHERE parent_id = @special_refund_id
+        AND perms = '/paymentOrder/specialRefund/refund'
+  );
+
+-- 管理员角色补上新权限;其他角色请在角色管理中按需勾选。
+INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
+SELECT 1, menu_id
+FROM sys_menu
+WHERE menu_id IN (@special_refund_id)
+   OR parent_id = @special_refund_id;

--
Gitblit v1.7.1