longz
2026-07-30 d83061ad5d5088eb32c71c32d21dd5db278d503a
各类修复
3个文件已添加
10个文件已修改
449 ■■■■■ 已修改文件
docs/superpowers/plans/2026-07-30-partner-appeal-site-scope.md 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TChargingPileController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TChargingPileService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TChargingPileServiceImpl.java 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/test/java/com/ruoyi/chargingPile/service/impl/TChargingPileMonitoringPermissionTest.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/ChargingBillController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/FinancialSettlementController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderAppealController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/FinancialSettlementControllerTest.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TOrderAppealControllerTest.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/ChargingOrderShareIncomeMapperTest.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/superpowers/plans/2026-07-30-partner-appeal-site-scope.md
New file
@@ -0,0 +1,36 @@
# Partner Appeal Site Scope Fix Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Ensure partner accounts see charging-order appeals for every site associated with their account while still hiding appeals from unrelated sites.
**Architecture:** Reuse the partner account's site list returned by `SiteClient.getSiteListByUserId(userId)` as the single source of truth for both partner data scope and optional site filters. Keep per-record view/handle buttons controlled by the existing site-menu permission lookup.
**Tech Stack:** Java 8, Spring Boot, MyBatis-Plus, JUnit 5, Mockito
## Global Constraints
- Partner accounts may only see charging-order appeals whose charging order belongs to one of their associated sites.
- Visibility of an appeal record must not depend on whether the site has separately granted the order-appeal menu flag.
- View and handle operations continue to use the existing role and per-site permission controls.
- Platform accounts retain the existing unrestricted behavior unless a site filter is submitted.
- Do not modify unrelated workspace changes.
---
### Task 1: Restore associated-site appeal visibility
**Files:**
- Modify: `ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderAppealController.java`
- Modify: `ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TOrderAppealControllerTest.java`
**Interfaces:**
- Consumes: `SiteClient.getSiteListByUserId(Long userId)`.
- Produces: `getVisibleSiteMap(SysUser sysUser)` containing all sites associated with the logged-in partner.
- [x] **Step 1: Replace the regression test that expects `PartnerClient.authSite(...ORDER_APPEAL)` with a test that expects all positive site IDs returned by `SiteClient.getSiteListByUserId(userId)`.**
- [x] **Step 2: Run `TOrderAppealControllerTest` and verify the old implementation fails the new expectation.**
- [x] **Step 3: Load the visible-site map for every partner request and use its key set as the partner appeal data scope.**
- [x] **Step 4: Remove the `SiteMenu.ORDER_APPEAL` data-scope lookup while retaining `managePageListMenu` for view/handle flags.**
- [x] **Step 5: Run the focused order-module test and compile dependent modules.**
- [x] **Step 6: Run `git diff --check` and review the final diff for unrelated changes.**
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TChargingPileController.java
@@ -308,6 +308,14 @@
        ChargeMonitoring chargeMonitoring = chargingPileService.chargeMonitoring(siteId);
        return AjaxResult.success(chargeMonitoring);
    }
    @RequiresPermissions(value = {"/chargingEquipmentMonitoring"}, logical = Logical.OR)
    @ResponseBody
    @GetMapping("/getChargingMonitoringSiteList")
    @ApiOperation(value = "获取当前账号允许监控的站点", tags = {"管理后台-充电设备监控"})
    public AjaxResult<List<Site>> getChargingMonitoringSiteList() {
        return AjaxResult.success(chargingPileService.getChargingMonitoringSiteList());
    }
    
    
    @RequiresPermissions(value = {"/chargingEquipmentMonitoring", "/generalBigScreen", "/dataCockpit"}, logical = Logical.OR)
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TChargingPileService.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.chargingPile.api.dto.PageChargingPileListDTO;
import com.ruoyi.chargingPile.api.model.TChargingPile;
import com.ruoyi.chargingPile.api.model.Site;
import com.ruoyi.chargingPile.api.query.BatchSetAccountingStrategy;
import com.ruoyi.chargingPile.api.query.PageChargingPileList;
import com.ruoyi.chargingPile.api.vo.TChargingPileVO;
@@ -100,6 +101,8 @@
     */
    ChargeMonitoring chargeMonitoring(Integer siteId);
    List<Site> getChargingMonitoringSiteList();
    
    /**
     * 获取充电枪各种状态汇总
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TChargingPileServiceImpl.java
@@ -731,37 +731,72 @@
     * @return
     */
    @Override
    public ChargeMonitoring chargeMonitoring(Integer siteId) {
        //获取当前登录账户的站点权限数据
        Set<Integer> siteIds = new HashSet<>();
        if(0 == siteId){
            SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
            Integer roleType = sysUser.getRoleType();
            Integer objectId = sysUser.getObjectId();
            if(2 == roleType){
                siteIds = partnerService.authSite(objectId, SiteMenu.CHARGING_FACILITY_MONITORING);
            }else{
                //非管理员需要根据角色和用户配置查询允许的站点数据
                if(!SecurityUtils.isAdmin(tokenService.getLoginUser().getUserid())){
                    List<Integer> data = userSiteClient.getSiteIds(sysUser.getUserId()).getData();
                    List<SysUserRoleVo> data2 = sysUserRoleClient.getRoleByUserId(sysUser.getUserId()).getData();
                    List<Integer> data1 = roleSiteClient.getSiteIds(data2.get(0).getRoleId()).getData();
                    data.addAll(data1);
                    siteIds = new HashSet<>(data);
                }else{
                    siteIds = null;
    public List<Site> getChargingMonitoringSiteList() {
        Set<Integer> siteIds = resolveChargingMonitoringSiteIds(0);
        return siteService.list(new LambdaQueryWrapper<Site>()
                .in(Site::getId, siteIds)
                .eq(Site::getDelFlag, 0)
                .orderByAsc(Site::getId));
    }
    private Set<Integer> resolveChargingMonitoringSiteIds(Integer requestedSiteId) {
        Long userId = tokenService.getLoginUser().getUserid();
        Set<Integer> allowedSiteIds = new HashSet<>();
        if (SecurityUtils.isAdmin(userId)) {
            allowedSiteIds.addAll(siteService.list(new LambdaQueryWrapper<Site>()
                    .eq(Site::getDelFlag, 0))
                    .stream().map(Site::getId).collect(Collectors.toSet()));
            return filterRequestedMonitoringSiteIds(allowedSiteIds, requestedSiteId);
        }
        R<SysUser> userResult = sysUserClient.getSysUser(userId);
        SysUser sysUser = userResult == null ? null : userResult.getData();
        if (sysUser == null) {
            return Collections.singleton(-1);
        }
        if (Integer.valueOf(2).equals(sysUser.getRoleType())) {
            Set<Integer> partnerSiteIds = partnerService.authSite(
                    sysUser.getObjectId(), SiteMenu.CHARGING_FACILITY_MONITORING);
            if (partnerSiteIds != null) {
                allowedSiteIds.addAll(partnerSiteIds);
            }
        } else {
            R<List<Integer>> userSiteResult = userSiteClient.getSiteIds(userId);
            if (userSiteResult != null && userSiteResult.getData() != null) {
                allowedSiteIds.addAll(userSiteResult.getData());
            }
            R<List<SysUserRoleVo>> roleResult = sysUserRoleClient.getRoleByUserId(userId);
            List<SysUserRoleVo> roles = roleResult == null ? null : roleResult.getData();
            if (roles != null) {
                for (SysUserRoleVo role : roles) {
                    if (role == null || role.getRoleId() == null) {
                        continue;
                    }
                    R<List<Integer>> roleSiteResult = roleSiteClient.getSiteIds(role.getRoleId());
                    if (roleSiteResult != null && roleSiteResult.getData() != null) {
                        allowedSiteIds.addAll(roleSiteResult.getData());
                    }
                }
            }
        }else{
            siteIds.add(siteId);
        }
        List<Site> sites = null;
        if(null == siteIds){
            sites = siteService.list(new LambdaQueryWrapper<Site>().eq(Site::getDelFlag, 0));
            siteIds = sites.stream().map(Site::getId).collect(Collectors.toSet());
        }else{
            sites = siteService.listByIds(siteIds);
        return filterRequestedMonitoringSiteIds(allowedSiteIds, requestedSiteId);
    }
    static Set<Integer> filterRequestedMonitoringSiteIds(Set<Integer> allowedSiteIds, Integer requestedSiteId) {
        Set<Integer> allowed = allowedSiteIds == null ? Collections.emptySet() : allowedSiteIds;
        if (requestedSiteId != null && requestedSiteId > 0) {
            return allowed.contains(requestedSiteId)
                    ? Collections.singleton(requestedSiteId) : Collections.singleton(-1);
        }
        return allowed.isEmpty() ? Collections.singleton(-1) : new HashSet<>(allowed);
    }
    @Override
    public ChargeMonitoring chargeMonitoring(Integer siteId) {
        Set<Integer> siteIds = resolveChargingMonitoringSiteIds(siteId);
        List<Site> sites = siteService.list(new LambdaQueryWrapper<Site>()
                .in(Site::getId, siteIds)
                .eq(Site::getDelFlag, 0));
        List<TChargingPile> piles = this.list(new LambdaQueryWrapper<TChargingPile>()
                .in(TChargingPile::getSiteId, siteIds)
                .eq(TChargingPile::getDelFlag, 0));
@@ -831,30 +866,7 @@
     */
    @Override
    public ChargingGunCountMonitoring getChargingGunCountMonitoring(Integer siteId) {
        //获取当前登录账户的站点权限数据
        Set<Integer> siteIds = new HashSet<>();
        if(0 == siteId){
            SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
            Integer roleType = sysUser.getRoleType();
            Integer objectId = sysUser.getObjectId();
            if(2 == roleType){
                siteIds = partnerService.authSite(objectId, SiteMenu.CHARGING_FACILITY_MONITORING);
            }else{
                //非管理员需要根据角色和用户配置查询允许的站点数据
                if(!SecurityUtils.isAdmin(tokenService.getLoginUser().getUserid())){
                    List<Integer> data = userSiteClient.getSiteIds(sysUser.getUserId()).getData();
                    List<SysUserRoleVo> data2 = sysUserRoleClient.getRoleByUserId(sysUser.getUserId()).getData();
                    List<Integer> data1 = roleSiteClient.getSiteIds(data2.get(0).getRoleId()).getData();
                    data.addAll(data1);
                    siteIds = new HashSet<>(data);
                }else{
                    List<Site> list = siteService.list(new LambdaQueryWrapper<Site>().eq(Site::getDelFlag, 0));
                    siteIds = list.stream().map(Site::getId).collect(Collectors.toSet());
                }
            }
        }else{
            siteIds.add(siteId);
        }
        Set<Integer> siteIds = resolveChargingMonitoringSiteIds(siteId);
        List<TChargingGun> list = chargingGunService.list(new LambdaQueryWrapper<TChargingGun>().in(TChargingGun::getSiteId, siteIds).eq(TChargingGun::getDelFlag, 0));
        //空闲
        long leisure = list.stream().filter(s -> s.getStatus().equals(2)).count();
@@ -882,31 +894,7 @@
     */
    @Override
    public PageInfo<ChargingGunMonitoring> getChargingGunMonitoring(GetChargingGunMonitoring query) {
        //获取当前登录账户的站点权限数据
        Set<Integer> siteIds = new HashSet<>();
        if(0 == query.getSiteId()){
            SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
            R<Integer> admin = sysUserClient.isAdmin(tokenService.getLoginUser().getUserid());
            Integer roleType = sysUser.getRoleType();
            Integer objectId = sysUser.getObjectId();
            if(2 == roleType){
                siteIds = partnerService.authSite(objectId, SiteMenu.CHARGING_FACILITY_MONITORING);
            }else{
                //非管理员需要根据角色和用户配置查询允许的站点数据
                if(admin.getData()!=1){
                    List<Integer> data = userSiteClient.getSiteIds(sysUser.getUserId()).getData();
                    List<SysUserRoleVo> data2 = sysUserRoleClient.getRoleByUserId(sysUser.getUserId()).getData();
                    List<Integer> data1 = roleSiteClient.getSiteIds(data2.get(0).getRoleId()).getData();
                    data.addAll(data1);
                    siteIds = new HashSet<>(data);
                }else{
                    List<Site> list = siteService.list(new LambdaQueryWrapper<Site>().eq(Site::getDelFlag, 0));
                    siteIds = list.stream().map(Site::getId).collect(Collectors.toSet());
                }
            }
        }else{
            siteIds.add(query.getSiteId());
        }
        Set<Integer> siteIds = resolveChargingMonitoringSiteIds(query.getSiteId());
        
        if(null != query.getStatus() && 0 < query.getStatus().size()){
            if(query.getStatus().contains(3)){
ruoyi-service/ruoyi-chargingPile/src/test/java/com/ruoyi/chargingPile/service/impl/TChargingPileMonitoringPermissionTest.java
New file
@@ -0,0 +1,35 @@
package com.ruoyi.chargingPile.service.impl;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TChargingPileMonitoringPermissionTest {
    @Test
    void keepsAllAuthorizedSitesWhenNoSiteIsSelected() {
        assertEquals(new HashSet<>(Arrays.asList(10, 20)),
                TChargingPileServiceImpl.filterRequestedMonitoringSiteIds(
                        new HashSet<>(Arrays.asList(10, 20)), 0));
    }
    @Test
    void allowsOnlyAnAuthorizedRequestedSite() {
        assertEquals(Collections.singleton(20),
                TChargingPileServiceImpl.filterRequestedMonitoringSiteIds(
                        new HashSet<>(Arrays.asList(10, 20)), 20));
    }
    @Test
    void rejectsUnauthorizedOrEmptySiteScopes() {
        assertEquals(Collections.singleton(-1),
                TChargingPileServiceImpl.filterRequestedMonitoringSiteIds(
                        new HashSet<>(Arrays.asList(10, 20)), 30));
        assertEquals(Collections.singleton(-1),
                TChargingPileServiceImpl.filterRequestedMonitoringSiteIds(Collections.emptySet(), 0));
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/ChargingBillController.java
@@ -86,6 +86,19 @@
        taskUtil.refreshChargingSettlementBill(targetMonth.atDay(1));
        return R.ok();
    }
    @RequiresPermissions(value = {"/accountSettlementStatement"}, logical = Logical.OR)
    @PostMapping(value = "/generateAccount")
    @ApiOperation(value = "主动生成指定月份未出账账户结算账单", tags = {"管理后台-账户结算账单"})
    public R generateAccountSettlementBill(@RequestParam(value = "billingMonth", required = false) String billingMonth) {
        YearMonth targetMonth = billingMonth != null && !billingMonth.trim().isEmpty()
                ? YearMonth.parse(billingMonth.trim()) : YearMonth.now();
        if (targetMonth.isAfter(YearMonth.now())) {
            return R.fail("不能生成未来月份账单");
        }
        taskUtil.refreshAccountSettlementStatement(targetMonth.atDay(1));
        return R.ok();
    }
    @GetMapping(value = "/test")
    public R accountAdd() {
//        List<TChargingBill> list = tChargingBillService.lambdaQuery().list();
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/FinancialSettlementController.java
@@ -205,7 +205,7 @@
            tChargingOrderExport.setRechargePaymentStatus(chargingOrderListVO.getRechargePaymentStatus()+"");
            tChargingOrderExport.setType("充电订单");
            tChargingOrderExport.setChargingType("单桩双充");
            tChargingOrderExport.setEndmode(chargingOrderListVO.getEndMode()+"");
            tChargingOrderExport.setEndmode(formatChargingEndMode(chargingOrderListVO.getEndMode()));
            tChargingOrderExport.setChargingEndAccount("平台");
            tChargingOrderExport.setIsFree("否");
            tChargingOrderExport.setElectrovalence(chargingOrderListVO.getElectrovalence()+"");
@@ -450,6 +450,10 @@
                .stripTrailingZeros()
                .toPlainString();
    }
    static String formatChargingEndMode(Integer endMode) {
        return endMode == null ? "" : endMode.toString();
    }
    
    
    
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderAppealController.java
@@ -6,7 +6,6 @@
import com.ruoyi.account.api.feignClient.AppUserClient;
import com.ruoyi.account.api.model.TAppUser;
import com.ruoyi.chargingPile.api.dto.GetSiteListDTO;
import com.ruoyi.chargingPile.api.domain.SiteMenu;
import com.ruoyi.chargingPile.api.feignClient.PartnerClient;
import com.ruoyi.chargingPile.api.feignClient.SiteClient;
import com.ruoyi.common.core.domain.R;
@@ -93,8 +92,10 @@
        SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
        Integer roleType = sysUser.getRoleType();
        boolean partnerUser = Objects.equals(roleType, 2);
        Map<Integer, GetSiteListDTO> visibleSiteMap = hasSiteFilter ? getVisibleSiteMap(sysUser) : Collections.emptyMap();
        Set<Integer> partnerSiteIds = partnerUser ? getPartnerAppealSiteIds(sysUser) : Collections.emptySet();
        Map<Integer, GetSiteListDTO> visibleSiteMap = partnerUser || hasSiteFilter
                ? getVisibleSiteMap(sysUser)
                : Collections.emptyMap();
        Set<Integer> partnerSiteIds = partnerUser ? visibleSiteMap.keySet() : Collections.emptySet();
        List<TOrderAppeal> res = new ArrayList<>();
        for (TOrderAppeal record : page.getRecords()) {
            record.setUid(record.getId() + "");
@@ -134,29 +135,19 @@
        return R.ok(page);
    }
    Set<Integer> getPartnerAppealSiteIds(SysUser sysUser) {
        if (sysUser == null || sysUser.getObjectId() == null) {
            return Collections.emptySet();
        }
        R<Set<Integer>> result = partnerClient.authSite(sysUser.getObjectId(), SiteMenu.ORDER_APPEAL.getValue());
        if (result == null || result.getData() == null) {
            return Collections.emptySet();
        }
        return result.getData().stream()
                .filter(Objects::nonNull)
                .filter(siteId -> siteId > 0)
                .collect(java.util.stream.Collectors.toSet());
    }
    private Map<Integer, GetSiteListDTO> getVisibleSiteMap(SysUser sysUser) {
    Map<Integer, GetSiteListDTO> getVisibleSiteMap(SysUser sysUser) {
        if (sysUser == null || sysUser.getUserId() == null) {
            return Collections.emptyMap();
        }
        List<GetSiteListDTO> data = siteClient.getSiteListByUserId(sysUser.getUserId()).getData();
        R<List<GetSiteListDTO>> result = siteClient.getSiteListByUserId(sysUser.getUserId());
        Map<Integer, GetSiteListDTO> siteMap = new HashMap<>();
        if (result == null || result.getData() == null) {
            return siteMap;
        }
        List<GetSiteListDTO> data = result.getData();
        if (data != null) {
            for (GetSiteListDTO datum : data) {
                if (datum.getId() != null) {
                if (datum != null && datum.getId() != null && datum.getId() > 0) {
                    siteMap.put(datum.getId(), datum);
                }
            }
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java
@@ -1124,29 +1124,37 @@
     * 添加账户结算账单数据
     */
    public void addAccountSettlementStatement(){
        int day = LocalDate.now().getDayOfMonth();
        int lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
        //时间在当月的2号到当月的最后一天才执行汇总数据和生成新的汇总数据(中间有修改数据的情况)
        if (2 <= day && day <= lastDay) {
            refreshAccountSettlementStatement(LocalDate.now().minusMonths(1));
        }
    }
    /**
     * 主动刷新指定月份的账户结算账单。只重建该月未出账数据,已出账数据不受影响。
     */
    public synchronized void refreshAccountSettlementStatement(LocalDate billingMonth) {
        try {
            int day = LocalDate.now().getDayOfMonth();
            int lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
            //时间在当月的2号到当月的最后一天才执行汇总数据和生成新的汇总数据(中间有修改数据的情况)
            if(2 <= day && day <= lastDay){
                // 获取上个月的开始和结束日期
                LocalDate firstDayOfLastMonth = LocalDate.now().minusMonths(1).
                        withDayOfMonth(2);
                String string = firstDayOfLastMonth.toString();
                if (billingMonth == null) {
                    throw new IllegalArgumentException("账单月份不能为空");
                }
                LocalDate firstDayOfBillingMonth = billingMonth.withDayOfMonth(2);
                String string = firstDayOfBillingMonth.toString();
                // 将-替换为空字符串
                string = string.replace("-", "");
                //上个月
                String lastMonth = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
                String targetMonth = billingMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                //统计数据的开始时间
                String startTime = lastMonth + "-01 00:00:00";
                String startTime = targetMonth + "-01 00:00:00";
                //统计数据的结束时间
                int lasDay = LocalDate.now().minusMonths(1).with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
                String endTime = lastMonth + "-" + String.format("%02d", lasDay) + " 23:59:59";
                LocalDateTime createTime = LocalDateTime.parse(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")) + "-02 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                int targetLastDay = billingMonth.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
                String endTime = targetMonth + "-" + String.format("%02d", targetLastDay) + " 23:59:59";
                LocalDateTime createTime = LocalDateTime.now();
                //删除之前生成的汇总数据,在添加最新的汇总数据
                String format = firstDayOfLastMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                String format = firstDayOfBillingMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                accountSettlementStatementService.remove(new QueryWrapper<TAccountSettlementStatement>().eq("status", 1).last(" and DATE_FORMAT(billing_cycle, '%Y-%m') = '" + format + "'"));
                Map<Integer, BigDecimal> siteServiceShareRatioMap = loadSiteServiceShareRatioMap();
@@ -1160,7 +1168,7 @@
                            .collect(Collectors.joining()); // 将其连接成一个字符串
                    accountSettlementStatement.setSiteId(0);
                    accountSettlementStatement.setAccountType(i);
                    accountSettlementStatement.setBillingCycle(firstDayOfLastMonth);
                    accountSettlementStatement.setBillingCycle(firstDayOfBillingMonth);
                    accountSettlementStatement.setStatus(1);
                    accountSettlementStatement.setCode("JSD"+string+randomDigits);
@@ -1190,7 +1198,7 @@
                                .collect(Collectors.joining()); // 将其连接成一个字符串
                        accountSettlementStatement1.setSiteId(siteId);
                        accountSettlementStatement1.setAccountType(i);
                        accountSettlementStatement1.setBillingCycle(firstDayOfLastMonth);
                        accountSettlementStatement1.setBillingCycle(firstDayOfBillingMonth);
                        accountSettlementStatement1.setStatus(1);
                        accountSettlementStatement1.setCode("JSD"+string+randomDigits1);
                        accountSettlementStatement1.setRechargeAmount(recharge_amount.setScale(2, RoundingMode.HALF_EVEN));
@@ -1221,9 +1229,9 @@
                    accountSettlementStatementService.save(accountSettlementStatement);
                    accountSettlementStatementService.saveBatch(list);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("生成{}账户结算账单失败", billingMonth, e);
            throw new RuntimeException("生成账户结算账单失败", e);
        }
    }
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -2420,25 +2420,11 @@
    <select id="chargingOrderShareIncomeByOrderIds" resultType="map">
        select
        s.charging_order_id as orderId,
        sum(
            IFNULL(s.channel_pay_amount, 0)
            - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END)
            - IFNULL(s.share_amount, 0)
                * (IFNULL(s.channel_pay_amount, 0)
                    - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END))
                / IFNULL(s.channel_pay_amount, 0)
        ) as platformIncome,
        sum(
            IFNULL(s.share_amount, 0)
            * (IFNULL(s.channel_pay_amount, 0)
                - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END))
            / IFNULL(s.channel_pay_amount, 0)
        ) as partnerIncome
        sum(IFNULL(s.channel_pay_amount, 0) - IFNULL(s.share_amount, 0)) as platformIncome,
        sum(IFNULL(s.share_amount, 0)) as partnerIncome
        from `charging_pile_order`.t_charging_order_share_record s
        inner join `charging_pile_order`.t_charging_order o on o.id = s.charging_order_id
        where s.del_flag = 0 and s.charging_order_id is not null
        and IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END)
            &lt; IFNULL(s.channel_pay_amount, 0)
        <if test="orderIds != null and orderIds.size()>0">
            and s.charging_order_id in
            <foreach collection="orderIds" item="item" index="index" separator="," open="(" close=")">
@@ -2451,12 +2437,7 @@
    <select id="chargingOrderShareIncomeByPayChannel" resultType="map">
        select
        s.pay_channel as payChannel,
        sum(
            IFNULL(s.share_amount, 0)
            * (IFNULL(s.channel_pay_amount, 0)
                - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END))
            / IFNULL(s.channel_pay_amount, 0)
        ) as shareAmount
        sum(IFNULL(s.share_amount, 0)) as shareAmount
        from t_charging_order_share_record s
        inner join t_charging_order o on s.charging_order_id = o.id
        where s.del_flag = 0
@@ -2464,8 +2445,6 @@
        and o.del_flag = 0
        and o.recharge_payment_status = 2
        and o.status = 5
        and IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END)
            &lt; IFNULL(s.channel_pay_amount, 0)
        <if test="sixBefore != null">
            and o.create_time &gt;= #{sixBefore}
        </if>
@@ -2480,25 +2459,11 @@
    <select id="chargingOrderShareIncomeSummaryByOrderIds" resultType="map">
        select
        sum(
            IFNULL(s.channel_pay_amount, 0)
            - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END)
            - IFNULL(s.share_amount, 0)
                * (IFNULL(s.channel_pay_amount, 0)
                    - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END))
                / IFNULL(s.channel_pay_amount, 0)
        ) as platformIncomeTotal,
        sum(
            IFNULL(s.share_amount, 0)
            * (IFNULL(s.channel_pay_amount, 0)
                - IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END))
            / IFNULL(s.channel_pay_amount, 0)
        ) as partnerIncomeTotal
        sum(IFNULL(s.channel_pay_amount, 0) - IFNULL(s.share_amount, 0)) as platformIncomeTotal,
        sum(IFNULL(s.share_amount, 0)) as partnerIncomeTotal
        from `charging_pile_order`.t_charging_order_share_record s
        inner join `charging_pile_order`.t_charging_order o on o.id = s.charging_order_id
        where s.del_flag = 0 and s.charging_order_id is not null
        and IFNULL(o.third_refund_amount, CASE WHEN o.pay_method = 3 THEN IFNULL(o.refund_amount, 0) ELSE 0 END)
            &lt; IFNULL(s.channel_pay_amount, 0)
        <if test="orderIds != null and orderIds.size()>0">
            and s.charging_order_id in
            <foreach collection="orderIds" item="item" index="index" separator="," open="(" close=")">
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/FinancialSettlementControllerTest.java
@@ -22,6 +22,13 @@
    }
    @Test
    void shouldFormatNullChargingEndModeAsBlank() {
        assertEquals("", FinancialSettlementController.formatChargingEndMode(null));
        assertEquals("0", FinancialSettlementController.formatChargingEndMode(0));
        assertEquals("3", FinancialSettlementController.formatChargingEndMode(3));
    }
    @Test
    void shouldBuildChargingPeriodExportSummaryFromExportRows() {
        ChargingOrderListVO first = new ChargingOrderListVO();
        first.setElectrovalence(new BigDecimal("10.12"));
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/TOrderAppealControllerTest.java
@@ -1,7 +1,7 @@
package com.ruoyi.order.controller;
import com.ruoyi.chargingPile.api.domain.SiteMenu;
import com.ruoyi.chargingPile.api.feignClient.PartnerClient;
import com.ruoyi.chargingPile.api.dto.GetSiteListDTO;
import com.ruoyi.chargingPile.api.feignClient.SiteClient;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.order.service.TOrderAppealService;
@@ -11,7 +11,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
@@ -21,21 +21,28 @@
class TOrderAppealControllerTest {
    @Test
    void partnerAppealSitesUseOrderAppealPermissionInsteadOfSiteListPermission() {
    void partnerAppealSitesUseAllSitesAssociatedWithCurrentAccount() {
        TOrderAppealService appealService = mock(TOrderAppealService.class);
        TokenService tokenService = mock(TokenService.class);
        PartnerClient partnerClient = mock(PartnerClient.class);
        SiteClient siteClient = mock(SiteClient.class);
        TOrderAppealController controller = new TOrderAppealController(appealService, tokenService);
        ReflectionTestUtils.setField(controller, "partnerClient", partnerClient);
        ReflectionTestUtils.setField(controller, "siteClient", siteClient);
        SysUser partnerUser = new SysUser();
        partnerUser.setUserId(7L);
        partnerUser.setObjectId(9);
        when(partnerClient.authSite(9, SiteMenu.ORDER_APPEAL.getValue()))
                .thenReturn(R.ok(new HashSet<>(Arrays.asList(-1, 11, 12))));
        when(siteClient.getSiteListByUserId(7L))
                .thenReturn(R.ok(Arrays.asList(site(-1), site(11), null, site(12))));
        Set<Integer> result = controller.getPartnerAppealSiteIds(partnerUser);
        Map<Integer, GetSiteListDTO> result = controller.getVisibleSiteMap(partnerUser);
        assertEquals(new HashSet<>(Arrays.asList(11, 12)), result);
        verify(partnerClient).authSite(9, SiteMenu.ORDER_APPEAL.getValue());
        assertEquals(new HashSet<>(Arrays.asList(11, 12)), result.keySet());
        verify(siteClient).getSiteListByUserId(7L);
    }
    private GetSiteListDTO site(Integer id) {
        GetSiteListDTO site = new GetSiteListDTO();
        site.setId(id);
        return site;
    }
}
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/ChargingOrderShareIncomeMapperTest.java
New file
@@ -0,0 +1,50 @@
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 ChargingOrderShareIncomeMapperTest {
    @Test
    void usesNetShareRecordAmountsWithoutDeductingOrderRefundAgain() throws IOException {
        String mapper = resource("mapper/order/TChargingOrderMapper.xml");
        String detailQuery = select(mapper, "chargingOrderShareIncomeByOrderIds");
        String channelQuery = select(mapper, "chargingOrderShareIncomeByPayChannel");
        String summaryQuery = select(mapper, "chargingOrderShareIncomeSummaryByOrderIds");
        assertTrue(detailQuery.contains("sum(IFNULL(s.share_amount, 0)) as partnerIncome"));
        assertTrue(detailQuery.contains("sum(IFNULL(s.channel_pay_amount, 0) - IFNULL(s.share_amount, 0)) as platformIncome"));
        assertTrue(channelQuery.contains("sum(IFNULL(s.share_amount, 0)) as shareAmount"));
        assertTrue(summaryQuery.contains("sum(IFNULL(s.share_amount, 0)) as partnerIncomeTotal"));
        assertTrue(summaryQuery.contains("sum(IFNULL(s.channel_pay_amount, 0) - IFNULL(s.share_amount, 0)) as platformIncomeTotal"));
        assertFalse(detailQuery.contains("third_refund_amount"));
        assertFalse(channelQuery.contains("third_refund_amount"));
        assertFalse(summaryQuery.contains("third_refund_amount"));
    }
    private String select(String mapper, String id) {
        int start = mapper.indexOf("<select id=\"" + id + "\"");
        int end = mapper.indexOf("</select>", start);
        assertTrue(start >= 0 && end > start);
        return mapper.substring(start, end);
    }
    private String resource(String path) throws IOException {
        InputStream input = getClass().getClassLoader().getResourceAsStream(path);
        assertNotNull(input);
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
            return reader.lines().collect(Collectors.joining("\n"));
        }
    }
}