puzhibing
2026-07-31 3080d55319ab40fe99064aa4689dbe0c38301e9d
修改管理后台
1、修改退款后没有重新添加新的分账记录
4个文件已修改
217 ■■■■■ 已修改文件
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/SiteServiceImpl.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/test/java/com/ruoyi/chargingPile/service/impl/SiteServiceImplTest.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java
@@ -398,11 +398,17 @@
    @ApiOperation(tags = {"后台-计费策略"},value = "删除计费策略")
    @DeleteMapping(value = "/deleteById")
    public AjaxResult<Boolean> deleteById(@RequestParam("id") Integer id) {
        // 判断策略是否在使用
        // 判断策略是否被站点使用
        long count = siteService.count(Wrappers.lambdaQuery(Site.class)
                .eq(Site::getAccountingStrategyId, id));
        if(count>0){
            return AjaxResult.error("该计费策略正在使用中,无法删除");
            return AjaxResult.error("该计费策略正在被站点使用,无法删除");
        }
        // 判断策略是否被充电枪使用
        long gunCount = chargingGunService.count(Wrappers.lambdaQuery(TChargingGun.class)
                .eq(TChargingGun::getAccountingStrategyId, id));
        if(gunCount>0){
            return AjaxResult.error("该计费策略正在被充电枪使用,无法删除");
        }
        // 刪除计费策略明细信息
        accountingStrategyDetailService.remove(Wrappers.lambdaQuery(TAccountingStrategyDetail.class)
@@ -419,11 +425,17 @@
    @DeleteMapping(value = "/deleteByIds")
    public AjaxResult<Boolean> deleteByIds(@RequestParam String ids) {
        String[] split = ids.split(",");
        // 判断策略是否在使用
        // 判断策略是否被站点使用
        long count = siteService.count(Wrappers.lambdaQuery(Site.class)
                .in(Site::getAccountingStrategyId, Arrays.asList(split)));
        if(count>0){
            return AjaxResult.error("该计费策略正在使用中,无法删除");
            return AjaxResult.error("该计费策略正在被站点使用,无法删除");
        }
        // 判断策略是否被充电枪使用
        long gunCount = chargingGunService.count(Wrappers.lambdaQuery(TChargingGun.class)
                .in(TChargingGun::getAccountingStrategyId, Arrays.asList(split)));
        if(gunCount>0){
            return AjaxResult.error("该计费策略正在被充电枪使用,无法删除");
        }
        // 刪除计费策略明细信息
        accountingStrategyDetailService.remove(Wrappers.lambdaQuery(TAccountingStrategyDetail.class)
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/SiteServiceImpl.java
@@ -958,30 +958,50 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setAccountingStrategy(Integer id, Integer accountingStrategyId) {
        Site site = this.getById(id);
        site.setAccountingStrategyId(accountingStrategyId);
        this.updateById(site);
        List<TChargingGun> list = chargingGunService.list(new LambdaQueryWrapper<TChargingGun>().in(TChargingGun::getSiteId, id).eq(TChargingGun::getDelFlag, 0));
        for (TChargingGun tChargingGun : list) {
            TChargingGun tChargingGun1 = new TChargingGun();
            tChargingGun1.setId(tChargingGun.getId());
            tChargingGun1.setAccountingStrategyId(accountingStrategyId);
            chargingGunService.updateById(tChargingGun1);
        }
        // 只更新计费策略字段,避免 updateById 全字段更新导致 SQL 过长
        this.lambdaUpdate()
                .eq(Site::getId, id)
                .set(Site::getAccountingStrategyId, accountingStrategyId)
                .update();
        chargingGunService.lambdaUpdate()
                .eq(TChargingGun::getSiteId, id)
                .eq(TChargingGun::getDelFlag, 0)
                .set(TChargingGun::getAccountingStrategyId, accountingStrategyId)
                .update();
        
        //下发硬件数据
        //下发硬件数据 - 异步执行,避免事务长时间占用数据库连接导致 Communications link failure
        List<TAccountingStrategyDetail> accountingStrategyDetails = accountingStrategyDetailService.list(new LambdaQueryWrapper<TAccountingStrategyDetail>().eq(TAccountingStrategyDetail::getAccountingStrategyId, accountingStrategyId));
        List<TChargingPile> list1 = chargingPileService.lambdaQuery().eq(TChargingPile::getSiteId, id).eq(TChargingPile::getDelFlag, 0).list();
        for (TChargingPile tChargingPile : list1) {
        List<TChargingPile> chargingPiles = chargingPileService.lambdaQuery().eq(TChargingPile::getSiteId, id).eq(TChargingPile::getDelFlag, 0).list();
        Site site = this.lambdaQuery()
                .select(Site::getId, Site::getIsOpenSynchronization)
                .eq(Site::getId, id)
                .one();
        boolean needSync = site != null && site.getIsOpenSynchronization() != null && site.getIsOpenSynchronization() == 1;
        new Thread(() -> {
            for (TChargingPile tChargingPile : chargingPiles) {
                try {
            integrationClient.setupBillingModel1(tChargingPile.getCode(), JSON.toJSONString(accountingStrategyDetails));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
            chengDuJianGuanClient.notificationStationFee(tChargingPile.getId());
                } catch (Exception e) {
                    e.printStackTrace();
                }
        }
        
        if(1 == site.getIsOpenSynchronization()){
            if (needSync) {
            //推送三方平台
                try {
            tcecClient.notificationStationChange(Arrays.asList(id));
                } catch (Exception e) {
                    e.printStackTrace();
        }
    }
        }).start();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchSetAccountingStrategy(List<Integer> ids, Integer accountingStrategyId) {
ruoyi-service/ruoyi-chargingPile/src/test/java/com/ruoyi/chargingPile/service/impl/SiteServiceImplTest.java
@@ -1,33 +1,76 @@
package com.ruoyi.chargingPile.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.chargingPile.api.dto.GetSiteListDTO;
import com.ruoyi.chargingPile.api.model.Site;
import com.ruoyi.chargingPile.api.model.TAccountingStrategyDetail;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.chargingPile.api.model.TChargingPile;
import com.ruoyi.chargingPile.mapper.SiteMapper;
import com.ruoyi.chargingPile.service.IPartnerService;
import com.ruoyi.chargingPile.service.ITAccountingStrategyDetailService;
import com.ruoyi.chargingPile.service.ITChargingGunService;
import com.ruoyi.chargingPile.service.ITChargingPileService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.integration.api.feignClient.ChengDuJianGuanClient;
import com.ruoyi.integration.api.feignClient.IntegrationClient;
import com.ruoyi.integration.api.feignClient.TcecClient;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.feignClient.SysUserClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class SiteServiceImplTest {
    private SiteServiceImpl service;
    private SiteMapper siteMapper;
    private ITChargingGunService chargingGunService;
    private ITAccountingStrategyDetailService accountingStrategyDetailService;
    private ITChargingPileService chargingPileService;
    private IntegrationClient integrationClient;
    private ChengDuJianGuanClient chengDuJianGuanClient;
    private TcecClient tcecClient;
    @BeforeEach
    void setUp() {
        service = new SiteServiceImpl();
        siteMapper = mock(SiteMapper.class);
        chargingGunService = mock(ITChargingGunService.class);
        accountingStrategyDetailService = mock(ITAccountingStrategyDetailService.class);
        chargingPileService = mock(ITChargingPileService.class);
        integrationClient = mock(IntegrationClient.class);
        chengDuJianGuanClient = mock(ChengDuJianGuanClient.class);
        tcecClient = mock(TcecClient.class);
        ReflectionTestUtils.setField(service, "baseMapper", siteMapper);
        ReflectionTestUtils.setField(service, "chargingGunService", chargingGunService);
        ReflectionTestUtils.setField(service, "accountingStrategyDetailService", accountingStrategyDetailService);
        ReflectionTestUtils.setField(service, "chargingPileService", chargingPileService);
        ReflectionTestUtils.setField(service, "integrationClient", integrationClient);
        ReflectionTestUtils.setField(service, "chengDuJianGuanClient", chengDuJianGuanClient);
        ReflectionTestUtils.setField(service, "tcecClient", tcecClient);
    }
    @Test
    void partnerSiteListIncludesAllSitesRelatedToCurrentPartner() {
        SiteServiceImpl service = new SiteServiceImpl();
        SiteMapper mapper = mock(SiteMapper.class);
        IPartnerService partnerService = mock(IPartnerService.class);
        SysUserClient sysUserClient = mock(SysUserClient.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "partnerService", partnerService);
        ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient);
@@ -37,13 +80,106 @@
        sysUser.setObjectId(715);
        when(sysUserClient.getSysUser(100L)).thenReturn(R.ok(sysUser));
        when(mapper.getSiteOptions(isNull(), eq(715)))
        when(siteMapper.getSiteOptions(isNull(), eq(715)))
                .thenReturn(Collections.<GetSiteListDTO>emptyList());
        service.getSiteList1(100L);
        verify(mapper).getSiteOptions(isNull(), eq(715));
        verify(siteMapper).getSiteOptions(isNull(), eq(715));
        verify(partnerService, never()).authSite(any(), any());
        verify(sysUserClient, never()).isAdmin(any());
    }
    /**
     * 验证 setAccountingStrategy 使用 lambdaUpdate 只更新 accountingStrategyId 字段,
     * 并且远程调用在异步线程中执行,避免事务长时间占用数据库连接
     */
    @Test
    void setAccountingStrategyUpdatesOnlyTargetFieldAndAsyncRemoteCalls() throws Exception {
        Integer siteId = 1;
        Integer accountingStrategyId = 100;
        // mock 充电枪列表
        List<TChargingGun> guns = new ArrayList<>();
        TChargingGun gun = new TChargingGun();
        gun.setId(10);
        gun.setSiteId(siteId);
        guns.add(gun);
        // mock 充电桩列表
        List<TChargingPile> piles = new ArrayList<>();
        TChargingPile pile = new TChargingPile();
        pile.setCode("PILE001");
        pile.setId(200);
        pile.setSiteId(siteId);
        piles.add(pile);
        // mock 计费策略明细
        List<TAccountingStrategyDetail> details = new ArrayList<>();
        // mock Site 查询(用于 isOpenSynchronization)
        Site mockSite = new Site();
        mockSite.setId(siteId);
        mockSite.setIsOpenSynchronization(1);
        when(siteMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.singletonList(mockSite));
        when(chargingGunService.list(any(LambdaQueryWrapper.class))).thenReturn(guns);
        when(accountingStrategyDetailService.list(any(LambdaQueryWrapper.class))).thenReturn(details);
        assertDoesNotThrow(() -> service.setAccountingStrategy(siteId, accountingStrategyId));
        // 验证数据库操作:站点和充电枪都通过 lambdaUpdate 更新
        verify(siteMapper, atLeastOnce()).update(any(), any());
        verify(siteMapper).selectList(any(LambdaQueryWrapper.class));
        // 异步线程执行需要等待一小段时间
        Thread.sleep(500);
        // 验证远程调用在异步线程中执行
        verify(integrationClient, timeout(1000)).setupBillingModel1(eq("PILE001"), any());
        verify(chengDuJianGuanClient, timeout(1000)).notificationStationFee(eq(200));
        verify(tcecClient, timeout(1000)).notificationStationChange(any());
    }
    /**
     * 验证 setAccountingStrategy 在站点无需同步时不会调用三方平台推送
     */
    @Test
    void setAccountingStrategySkipsSyncWhenNotOpen() throws Exception {
        Integer siteId = 2;
        Integer accountingStrategyId = 200;
        // mock 充电枪列表
        TChargingGun gun = new TChargingGun();
        gun.setId(20);
        gun.setSiteId(siteId);
        List<TChargingGun> guns = Collections.singletonList(gun);
        // mock 充电桩列表
        TChargingPile pile = new TChargingPile();
        pile.setCode("PILE002");
        pile.setId(201);
        pile.setSiteId(siteId);
        List<TChargingPile> piles = Collections.singletonList(pile);
        List<TAccountingStrategyDetail> details = new ArrayList<>();
        // isOpenSynchronization = 0,不需要同步
        Site mockSite = new Site();
        mockSite.setId(siteId);
        mockSite.setIsOpenSynchronization(0);
        when(siteMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.singletonList(mockSite));
        when(chargingGunService.list(any(LambdaQueryWrapper.class))).thenReturn(guns);
        when(accountingStrategyDetailService.list(any(LambdaQueryWrapper.class))).thenReturn(details);
        assertDoesNotThrow(() -> service.setAccountingStrategy(siteId, accountingStrategyId));
        Thread.sleep(500);
        verify(integrationClient, timeout(1000)).setupBillingModel1(eq("PILE002"), any());
        verify(chengDuJianGuanClient, timeout(1000)).notificationStationFee(eq(201));
        // tcecClient.notificationStationChange 不应被调用
        verify(tcecClient, never()).notificationStationChange(any());
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -8022,9 +8022,8 @@
    private boolean isNeedChargingOrderShare(TChargingOrder chargingOrder) {
        return chargingOrder != null
                && Integer.valueOf(5).equals(chargingOrder.getStatus())
                && Integer.valueOf(2).equals(chargingOrder.getRechargePaymentStatus())
                && Integer.valueOf(2).equals(chargingOrder.getRefundStatus())
                && (Integer.valueOf(2).equals(chargingOrder.getRefundStatus()) || Integer.valueOf(5).equals(chargingOrder.getStatus()))
                && getChargingOrderShareBaseAmount(chargingOrder).compareTo(BigDecimal.ZERO) > 0;
    }