longz
2026-07-21 83b85ebe8d7c5cd66e88d61d5bbc95c9e256a352
充电时段统计fix
3个文件已添加
17个文件已修改
601 ■■■■■ 已修改文件
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/SiteServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TChargingPileServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/SiteMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/TFaultMessageMapper.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/test/java/com/ruoyi/chargingPile/service/impl/SiteImportExcelTest.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/ChargingBillController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TAccountSettlementStatementMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingSettlementBillMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/ITAccountSettlementStatementService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/ITChargingSettlementBillService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingBillService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TAccountSettlementStatementServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingSettlementBillServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TAccountSettlementStatementMapper.xml 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingSettlementBillMapper.xml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingBillIncomeIndexTest.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingPeriodIncomeTest.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
@@ -748,6 +748,12 @@
        UserDetailDto userDetailDto =  new UserDetailDto();
        BeanUtils.copyProperties(user,userDetailDto);
        Map<Long, BigDecimal> exchangeCodeBalanceMap = otherClient
                .getExchangeCodeBalanceMap(Collections.singletonList(id))
                .getData();
        userDetailDto.setExchangeCodeBalance(exchangeCodeBalanceMap == null
                ? BigDecimal.ZERO
                : exchangeCodeBalanceMap.getOrDefault(id, BigDecimal.ZERO));
        //获取累计充电数量
        R<Long> useOrderCount = chargingOrderClient.useOrderCount(user.getId());
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/PartnerController.java
@@ -64,7 +64,7 @@
    
    
    
    @RequiresPermissions(value = {"/balanceSheetRecord", "/partners", "/parkingRecord", "/chargingPile/add", "/chargingPile/select", "/chargingPile/update", "/invoiceManagement"}, logical = Logical.OR)
    @RequiresPermissions(value = {"/balanceSheetRecord", "/partners", "/parkingRecord", "/chargingPile/add", "/chargingPile/select", "/chargingPile/update", "/invoiceManagement", "/faultInformationList"}, logical = Logical.OR)
    @ResponseBody
    @GetMapping("/getPartnerList")
    @ApiOperation(value = "获取合作商列表", tags = {"管理后台-合作商管理"})
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/SiteServiceImpl.java
@@ -50,6 +50,7 @@
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.*;
@@ -179,7 +180,13 @@
            throw new ServiceException("请选择要导入的Excel文件");
        }
        ExcelUtil<SiteExcelDTO> util = new ExcelUtil<>(SiteExcelDTO.class);
        List<SiteExcelDTO> rows = util.importExcel(file.getInputStream(), 0);
        List<SiteExcelDTO> rows;
        try (InputStream inputStream = file.getInputStream()) {
            rows = util.importExcel(inputStream, 0);
        } catch (Exception e) {
            throw new ServiceException("Excel文件已损坏或格式不正确,请重新下载站点导入模板后填写")
                    .setDetailMessage(e.getMessage());
        }
        if (rows == null || rows.isEmpty()) {
            throw new ServiceException("导入文件没有数据");
        }
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TChargingPileServiceImpl.java
@@ -423,6 +423,12 @@
        ChargingPileBatchBuildResultDTO result = new ChargingPileBatchBuildResultDTO();
        TChargingPile pileTemplate = dto.getChargingPile();
        TChargingGunDTO gunTemplate = dto.getChargingGun();
        if (gunTemplate.getAuxPower() == null) {
            throw new ServiceException("辅助电源不能为空");
        }
        if (gunTemplate.getOpreateStatus() == null) {
            throw new ServiceException("运营状态不能为空");
        }
        String namePrefix = StringUtils.isEmpty(dto.getNamePrefix()) ? "" : dto.getNamePrefix().trim();
        for (int i = 0; i < codes.size(); i++) {
            TChargingPile chargingPile = new TChargingPile();
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/SiteMapper.xml
@@ -258,7 +258,7 @@
        </where>
    </select>
        <select id="getSiteListGun" resultType="com.ruoyi.chargingPile.api.model.Site">
        select id, `name`, partner_id
        select id, `name`, partner_id, venue_partner_id
        from t_site
        <where>
            <if test="null != ids and ids.size()>0">
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/TFaultMessageMapper.xml
@@ -34,7 +34,6 @@
        from t_fault_message a
        left join t_charging_pile b on (a.charging_pile_id = b.id)
        left join t_site c on (a.site_id = c.id)
        left join t_partner d on (c.partner_id = d.id)
        where a.del_flag = 0
        <if test="null != siteIds and siteIds.size() > 0">
            and a.site_id in
@@ -42,12 +41,30 @@
                #{item}
            </foreach>
        </if>
        <choose>
            <when test="partnerType != null and partnerType == 2">
                <if test="partnerId != null">
                    and c.venue_partner_id = #{partnerId}
                </if>
                and exists (
                    select 1 from t_partner venue_partner
                    where venue_partner.id = c.venue_partner_id
                      and venue_partner.partner_type = 2
                )
            </when>
            <otherwise>
        <if test="partnerId != null">
            and c.partner_id = #{partnerId}
        </if>
        <if test="partnerType != null">
            and d.partner_type = #{partnerType}
                    and exists (
                        select 1 from t_partner owner_partner
                        where owner_partner.id = c.partner_id
                          and owner_partner.partner_type = #{partnerType}
                    )
        </if>
            </otherwise>
        </choose>
        order by a.create_time desc
    </select>
</mapper>
ruoyi-service/ruoyi-chargingPile/src/test/java/com/ruoyi/chargingPile/service/impl/SiteImportExcelTest.java
New file
@@ -0,0 +1,52 @@
package com.ruoyi.chargingPile.service.impl;
import com.ruoyi.chargingPile.dto.SiteExcelDTO;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockMultipartFile;
import java.io.ByteArrayInputStream;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SiteImportExcelTest {
    @Test
    void generatedTemplateCanBeOpenedAsXlsx() throws Exception {
        MockHttpServletResponse response = new MockHttpServletResponse();
        ExcelUtil<SiteExcelDTO> util = new ExcelUtil<>(SiteExcelDTO.class);
        util.importTemplateExcel(response, "站点导入模板", "填写说明");
        byte[] content = response.getContentAsByteArray();
        assertTrue(content.length > 4);
        assertEquals('P', content[0]);
        assertEquals('K', content[1]);
        assertDoesNotThrow(() -> {
            try (Workbook ignored = WorkbookFactory.create(new ByteArrayInputStream(content))) {
                // Opening and closing the generated workbook verifies the complete XLSX ZIP structure.
            }
        });
    }
    @Test
    void corruptWorkbookReturnsReadableBusinessError() {
        SiteServiceImpl service = new SiteServiceImpl();
        MockMultipartFile file = new MockMultipartFile(
                "file", "站点导入模板.xlsx",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                new byte[]{'P', 'K', 3, 4, 1, 2, 3});
        ServiceException error = assertThrows(ServiceException.class,
                () -> service.importSites(file, false));
        assertEquals("Excel文件已损坏或格式不正确,请重新下载站点导入模板后填写", error.getMessage());
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/ChargingBillController.java
@@ -145,20 +145,7 @@
    @PostMapping(value = "/accountBillList")
    @ApiOperation(value = "列表查询", tags = {"管理后台-账户结算账单"})
    public R<AccountListVO> accountBillList(@RequestBody ChargingListQuery dto) {
        AccountListVO accountListVO = new AccountListVO();
        ChargingBillVO res = chargingBillService.chargingBillList1(dto);
        List<ChargingBillListVO> records = res.getList().getRecords();
        accountListVO.setBillCount(records.size());
        accountListVO.setTotalAmount(res.getPaymentAmount().subtract(res.getCommissionAmount()).subtract(res.getSharingAmount()).setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setPaymentAmount(res.getPaymentAmount().setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setRefundAmount(res.getRefundAmount().setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setCommissionAmount(res.getCommissionAmount().setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setSharingAmount(res.getSharingAmount().setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setPlatformIncome(res.getPlatformIncome().setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setPartnerIncome(res.getPartnerIncome().setScale(2, BigDecimal.ROUND_DOWN));
        accountListVO.setList(res.getList());
        accountListVO.setOrderList(res.getExportList());
        return R.ok(accountListVO);
        return R.ok(chargingBillService.accountSettlementBillList(dto));
    }
@@ -262,7 +249,9 @@
    @PutMapping("/exportAccount")
    public void exportAccount(@RequestBody ChargingListQuery dto)
    {
        ChargingBillVO res = chargingBillService.chargingBillList1(dto);
        dto.setPageCurr(1);
        dto.setPageSize(100000);
        AccountListVO res = chargingBillService.accountSettlementBillList(dto);
        List<ChargingBillListVO> list = res.getList().getRecords();
        List<TChargingBillExport> tChargingBillExports = new ArrayList<>();
        for (ChargingBillListVO orderInvoiceVO : list) {
@@ -285,7 +274,7 @@
            tChargingBillExport.setElectrovalence(orderInvoiceVO.getElectrovalence());
            tChargingBillExport.setServiceCharge(orderInvoiceVO.getServiceCharge());
            tChargingBillExport.setChargingCapacity(orderInvoiceVO.getChargingCapacity());
            Integer chargingSecond = orderInvoiceVO.getChargingSecond();
            Integer chargingSecond = Optional.ofNullable(orderInvoiceVO.getChargingSecond()).orElse(0);
            // 根据秒数 转换为xx小时xx分钟xx秒 如果小时为0不展示 如果分钟为0则不展示
            // 计算小时、分钟和秒
            int hours = chargingSecond / 60 / 60;
@@ -304,7 +293,8 @@
            }
            tChargingBillExport.setChargingTime(result.toString());
            tChargingBillExport.setOrderCount(orderInvoiceVO.getOrderCount());
            tChargingBillExport.setBillTime(orderInvoiceVO.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            tChargingBillExport.setBillTime(orderInvoiceVO.getCreateTime() == null ? "-"
                    : orderInvoiceVO.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            tChargingBillExports.add(tChargingBillExport);
        }
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), TChargingBillExport.class, tChargingBillExports);
@@ -313,7 +303,7 @@
        response.setCharacterEncoding("utf-8");
        ServletOutputStream outputStream = null;
        try {
            String fileName = URLEncoder.encode("月账单-"+res.getCategory()+"-"+res.getBillWeek()+"-"+res.getSiteName()+".xls", "utf-8");
            String fileName = URLEncoder.encode("账户结算账单.xls", "utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            response.setHeader("Pragma", "no-cache");
@@ -324,7 +314,9 @@
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TAccountSettlementStatementMapper.java
@@ -1,7 +1,29 @@
package com.ruoyi.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.order.api.model.ChargingListQuery;
import com.ruoyi.order.api.model.TAccountSettlementStatement;
import com.ruoyi.order.api.vo.AccountListVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
@Mapper
public interface TAccountSettlementStatementMapper extends BaseMapper<TAccountSettlementStatement> {
    List<TAccountSettlementStatement> selectStatementPage(
            @Param("pageInfo") PageInfo<TAccountSettlementStatement> pageInfo,
            @Param("req") ChargingListQuery req,
            @Param("siteIds") List<Integer> siteIds,
            @Param("startCycle") LocalDate startCycle,
            @Param("endCycle") LocalDate endCycle);
    AccountListVO selectStatementSummary(
            @Param("req") ChargingListQuery req,
            @Param("siteIds") List<Integer> siteIds,
            @Param("startCycle") LocalDate startCycle,
            @Param("endCycle") LocalDate endCycle);
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingSettlementBillMapper.java
@@ -23,6 +23,11 @@
                                        @Param("status") Integer status, @Param("dateRange") List<String> dateRange,
                                        @Param("statisticalDateType") Integer statisticalDateType, @Param("siteIds") List<Integer> siteIds);
    List<Map<String, Object>> queryIncomeList(@Param("status") Integer status,
                                              @Param("dateRange") List<String> dateRange,
                                              @Param("statisticalDateType") Integer statisticalDateType,
                                              @Param("siteIds") List<Integer> siteIds);
    /**
     * 获取汇总数据
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/ITAccountSettlementStatementService.java
@@ -1,7 +1,19 @@
package com.ruoyi.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.order.api.model.ChargingListQuery;
import com.ruoyi.order.api.model.TAccountSettlementStatement;
import com.ruoyi.order.api.vo.AccountListVO;
import java.time.LocalDate;
import java.util.List;
public interface ITAccountSettlementStatementService extends IService<TAccountSettlementStatement> {
    PageInfo<TAccountSettlementStatement> queryPage(ChargingListQuery req, List<Integer> siteIds,
                                                     LocalDate startCycle, LocalDate endCycle);
    AccountListVO querySummary(ChargingListQuery req, List<Integer> siteIds,
                               LocalDate startCycle, LocalDate endCycle);
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/ITChargingSettlementBillService.java
@@ -20,6 +20,8 @@
     */
    List<Map<String, Object>> queryList(PageInfo<Map<String, Object>> page, Integer type, Integer status, List<String> dateRange, Integer statisticalDateType, List<Integer> siteIds);
    List<Map<String, Object>> queryIncomeList(Integer status, List<String> dateRange, Integer statisticalDateType, List<Integer> siteIds);
    /**
     * 获取汇总数据
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingBillService.java
@@ -4,6 +4,7 @@
import com.ruoyi.order.api.model.TChargingBill;
import com.ruoyi.order.api.vo.ChargingBillVO;
import com.ruoyi.order.api.model.ChargingListQuery;
import com.ruoyi.order.api.vo.AccountListVO;
import com.ruoyi.order.api.vo.ChargingBillWapper;
/**
@@ -24,6 +25,8 @@
    ChargingBillVO chargingBillList1(ChargingListQuery dto);
    AccountListVO accountSettlementBillList(ChargingListQuery dto);
    ChargingBillVO chargingBillListExport(ChargingListQuery dto);
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TAccountSettlementStatementServiceImpl.java
@@ -1,11 +1,31 @@
package com.ruoyi.order.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.order.api.model.ChargingListQuery;
import com.ruoyi.order.api.model.TAccountSettlementStatement;
import com.ruoyi.order.api.vo.AccountListVO;
import com.ruoyi.order.mapper.TAccountSettlementStatementMapper;
import com.ruoyi.order.service.ITAccountSettlementStatementService;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
@Service
public class TAccountSettlementStatementServiceImpl extends ServiceImpl<TAccountSettlementStatementMapper, TAccountSettlementStatement> implements ITAccountSettlementStatementService {
    @Override
    public PageInfo<TAccountSettlementStatement> queryPage(ChargingListQuery req, List<Integer> siteIds,
                                                            LocalDate startCycle, LocalDate endCycle) {
        PageInfo<TAccountSettlementStatement> page = new PageInfo<>(req.getPageCurr(), req.getPageSize());
        page.setRecords(baseMapper.selectStatementPage(page, req, siteIds, startCycle, endCycle));
        return page;
    }
    @Override
    public AccountListVO querySummary(ChargingListQuery req, List<Integer> siteIds,
                                      LocalDate startCycle, LocalDate endCycle) {
        return baseMapper.selectStatementSummary(req, siteIds, startCycle, endCycle);
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingBillServiceImpl.java
@@ -23,6 +23,7 @@
import com.ruoyi.order.api.vo.ChargingBillListVO;
import com.ruoyi.order.api.vo.ChargingBillVO;
import com.ruoyi.order.api.vo.ChargingBillWapper;
import com.ruoyi.order.api.vo.AccountListVO;
import com.ruoyi.order.mapper.TChargingBillMapper;
import com.ruoyi.order.mapper.TChargingOrderMapper;
import com.ruoyi.order.service.*;
@@ -87,6 +88,137 @@
    private TChargingOrderService chargingOrderService;
    @Resource
    private ITChargingSettlementBillService chargingSettlementBillService;
    @Resource
    private ITAccountSettlementStatementService accountSettlementStatementService;
    @Override
    public AccountListVO accountSettlementBillList(ChargingListQuery dto) {
        SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
        List<Integer> siteIds = resolveAccountStatementSiteIds(dto, sysUser);
        LocalDate[] cycleRange = resolveBillingCycleRange(dto.getDateRange());
        PageInfo<TAccountSettlementStatement> statementPage = accountSettlementStatementService.queryPage(
                dto, siteIds, cycleRange[0], cycleRange[1]);
        AccountListVO result = accountSettlementStatementService.querySummary(
                dto, siteIds, cycleRange[0], cycleRange[1]);
        if (result == null) {
            result = new AccountListVO();
        }
        Set<Integer> pageSiteIds = statementPage.getRecords().stream()
                .map(TAccountSettlementStatement::getSiteId)
                .filter(siteId -> siteId != null && siteId > 0)
                .collect(Collectors.toSet());
        Map<Integer, String> siteNameMap = pageSiteIds.isEmpty() ? Collections.emptyMap()
                : safeData(siteClient.getSiteByIds(new ArrayList<>(pageSiteIds))).stream()
                .collect(Collectors.toMap(Site::getId, Site::getName, (first, ignored) -> first));
        List<ChargingBillListVO> records = statementPage.getRecords().stream()
                .map(statement -> toAccountStatementVO(statement, siteNameMap))
                .collect(Collectors.toList());
        PageInfo<ChargingBillListVO> page = new PageInfo<>(statementPage.getCurrent(), statementPage.getSize());
        page.setTotal(statementPage.getTotal());
        page.setRecords(records);
        result.setList(page);
        result.setOrderList(Collections.emptyList());
        result.setBillCount(result.getBillCount() == null ? 0 : result.getBillCount());
        result.setTotalAmount(zeroIfNull(result.getTotalAmount()));
        result.setPaymentAmount(zeroIfNull(result.getPaymentAmount()));
        result.setRefundAmount(zeroIfNull(result.getRefundAmount()));
        result.setCommissionAmount(zeroIfNull(result.getCommissionAmount()));
        result.setSharingAmount(zeroIfNull(result.getSharingAmount()));
        result.setPlatformIncome(zeroIfNull(result.getPlatformIncome()));
        result.setPartnerIncome(zeroIfNull(result.getPartnerIncome()));
        return result;
    }
    private List<Integer> resolveAccountStatementSiteIds(ChargingListQuery dto, SysUser sysUser) {
        List<Integer> siteIds = new ArrayList<>();
        if (dto.getSiteId() != null) {
            siteIds.add(dto.getSiteId());
        } else {
            List<GetSiteListDTO> sites = safeData(siteClient.getSiteListByUserId(tokenService.getLoginUser().getUserid()));
            siteIds.addAll(sites.stream().map(GetSiteListDTO::getId).filter(Objects::nonNull).collect(Collectors.toList()));
        }
        if (Integer.valueOf(2).equals(sysUser.getRoleType())) {
            siteIds = siteIds.stream().filter(siteId -> {
                List<Boolean> permissions = safeData(partnerClient.getAccountMenu(sysUser.getObjectId(), siteId));
                return permissions.size() > 1 && Boolean.TRUE.equals(permissions.get(1));
            }).collect(Collectors.toList());
        }
        if (Integer.valueOf(1).equals(sysUser.getRoleType())) {
            siteIds.add(0);
        }
        if (siteIds.isEmpty()) {
            siteIds.add(-1);
        }
        return siteIds.stream().distinct().collect(Collectors.toList());
    }
    private LocalDate[] resolveBillingCycleRange(List<String> dateRange) {
        LocalDate start = null;
        LocalDate end = null;
        if (dateRange != null && !dateRange.isEmpty()) {
            start = parseBillingMonth(dateRange.get(0));
            String endValue = dateRange.size() > 1 ? dateRange.get(1) : dateRange.get(0);
            LocalDate endMonth = parseBillingMonth(endValue);
            end = endMonth == null ? null : endMonth.plusMonths(1);
        }
        return new LocalDate[]{start, end};
    }
    private LocalDate parseBillingMonth(String value) {
        if (!StringUtils.hasLength(value) || value.length() < 7) {
            return null;
        }
        return LocalDate.parse(value.substring(0, 7) + "-01");
    }
    private ChargingBillListVO toAccountStatementVO(TAccountSettlementStatement statement,
                                                      Map<Integer, String> siteNameMap) {
        ChargingBillListVO vo = new ChargingBillListVO();
        vo.setId(statement.getId());
        vo.setUid(String.valueOf(statement.getId()));
        vo.setCode(statement.getCode());
        vo.setType(Integer.valueOf(0).equals(statement.getSiteId()) ? 1 : 2);
        vo.setSiteId(statement.getSiteId());
        vo.setSiteName(Integer.valueOf(0).equals(statement.getSiteId())
                ? "全站" : siteNameMap.getOrDefault(statement.getSiteId(), "-"));
        vo.setStatus(statement.getStatus());
        vo.setOrderState(2);
        vo.setPayType(statement.getAccountType());
        vo.setPayTypeName(accountTypeName(statement.getAccountType()));
        vo.setBillWeek(statement.getBillingCycle() == null
                ? null : statement.getBillingCycle().format(DateTimeFormatter.ofPattern("yyyy-MM")));
        vo.setBillTime(statement.getCreateTime());
        vo.setCreateTime(statement.getCreateTime());
        vo.setPaymentAmount(zeroIfNull(statement.getRechargeAmount()));
        vo.setRefundAmount(zeroIfNull(statement.getRefundAmount()));
        vo.setCommissionAmount(zeroIfNull(statement.getPlatformFee()));
        vo.setSharingAmount(zeroIfNull(statement.getTripartiteCommission()));
        vo.setBillMoney(zeroIfNull(statement.getRecordedAmount()));
        vo.setPlatformIncome(BigDecimal.ZERO);
        vo.setPartnerIncome(BigDecimal.ZERO);
        vo.setAuthDownLoad(false);
        return vo;
    }
    private String accountTypeName(Integer accountType) {
        if (Integer.valueOf(1).equals(accountType)) {
            return "微信商户";
        }
        if (Integer.valueOf(2).equals(accountType)) {
            return "支付宝商户";
        }
        if (Integer.valueOf(3).equals(accountType)) {
            return "工商银行商户";
        }
        return "-";
    }
    private BigDecimal zeroIfNull(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    @Override
@@ -1063,6 +1195,7 @@
        Integer roleType = sysUser.getRoleType();
        //如果没传siteId,获取当前登陆人所有的siteIds
        List<Integer> siteIds = new ArrayList<>();
        Map<Integer, List<Boolean>> chargingBillPermissionMap = new HashMap<>();
        List<GetSiteListDTO> data = siteClient.getSiteListByUserId(userId).getData();
        for (GetSiteListDTO datum : data) {
            siteIds.add(datum.getId());
@@ -1074,10 +1207,9 @@
                List<Integer> integers = new ArrayList<>();
                for (Integer siteId : siteIds) {
                    // 校验有没有这个站点的权限
                    List<Boolean> t1= partnerClient.getChargingBillMenu(sysUser.getObjectId(),siteId).getData();
                    Boolean b = t1.get(1);
                    if (b){
                    List<Boolean> permissions = safeData(partnerClient.getChargingBillMenu(sysUser.getObjectId(), siteId));
                    chargingBillPermissionMap.put(siteId, permissions);
                    if (permissions.size() > 1 && Boolean.TRUE.equals(permissions.get(1))){
                        integers.add(siteId);
                    }
                }
@@ -1098,9 +1230,9 @@
        ChargingBillWapper chargingBillVO = new ChargingBillWapper();
        List<Map<String, Object>> pageList = new ArrayList<>();
        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);
        List<Map<String, Object>> incomeRows = chargingSettlementBillService.queryIncomeList(
                dto.getState(), dto.getDateRange(), statisticalDateType, dto.getSiteIds());
        Map<String, Map<String, BigDecimal>> incomeIndex = buildChargingBillIncomeIndex(incomeRows, siteServiceShareRatioMap);
        Set<Integer> settlementSiteIds = new HashSet<>();
        if (list != null) {
            list.stream().map(item -> item.get("site_id")).filter(Objects::nonNull)
@@ -1147,9 +1279,8 @@
                data1.put("serviceCharge", serviceCharge);
                data1.put("authDownLoad", true);
                if (roleType == 2 && 0 != site_id){
                    // 校验有没有这个站点的权限
                    List<Boolean> t1= partnerClient.getChargingBillMenu(sysUser.getObjectId(),site_id).getData();
                    data1.put("authDownLoad", t1.get(0));
                    List<Boolean> permissions = chargingBillPermissionMap.getOrDefault(site_id, Collections.emptyList());
                    data1.put("authDownLoad", !permissions.isEmpty() && Boolean.TRUE.equals(permissions.get(0)));
                }
                pageList.add(data1);
            }
@@ -1167,8 +1298,8 @@
        BigDecimal discount = (null == map || null == map.get("couponDeduction")) ? BigDecimal.ZERO : new BigDecimal(map.get("couponDeduction").toString());
        BigDecimal platformIncome = BigDecimal.ZERO;
        BigDecimal partnerIncome = BigDecimal.ZERO;
        if (sumList != null) {
            for (Map<String, Object> row : sumList) {
        if (incomeRows != null) {
            for (Map<String, Object> row : incomeRows) {
                Integer siteId = Integer.valueOf(row.get("site_id").toString());
                String billingCycle = row.get("billingCycle").toString();
                Map<String, BigDecimal> incomeMap = getChargingBillIncome(incomeIndex, siteId, billingCycle);
@@ -1192,86 +1323,40 @@
        return chargingBillVO;
    }
    private Map<String, Map<String, BigDecimal>> buildChargingBillIncomeIndex(List<Map<String, Object>> billRows,
    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) {
        for (Map<String, Object> row : billRows) {
            if (row == null || row.get("site_id") == null || row.get("billingCycle") == 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;
            Integer siteId = Integer.valueOf(row.get("site_id").toString());
            String billingCycle = row.get("billingCycle").toString();
            BigDecimal electricAmount = decimalValue(row.get("total_electricity_charge"));
            BigDecimal serviceAmount = decimalValue(row.get("total_service_fee"));
            BigDecimal tripartiteCommission = decimalValue(row.get("tripartite_commission"));
            BigDecimal paymentAmount = electricAmount.add(serviceAmount).subtract(tripartiteCommission);
            BigDecimal tripartiteServiceAmount = tripartiteCommission.multiply(new BigDecimal("5"));
            BigDecimal commissionableServiceAmount = serviceAmount.subtract(tripartiteServiceAmount).max(BigDecimal.ZERO);
            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());
            BigDecimal serviceShareRatio = siteServiceShareRatioMap.get(siteId);
                    if (serviceShareRatio != null && serviceShareRatio.compareTo(BigDecimal.ZERO) > 0) {
                        partnerIncome = amounts[1].multiply(serviceShareRatio).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
                    }
                }
                partnerIncome = commissionableServiceAmount.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);
            addChargingBillIncome(incomeIndex, chargingBillIncomeKey(siteId, billingCycle), paymentAmount, partnerIncome);
        }
        return incomeIndex;
    }
    private BigDecimal decimalValue(Object value) {
        return value == null ? BigDecimal.ZERO : new BigDecimal(value.toString());
    }
    private void addChargingBillIncome(Map<String, Map<String, BigDecimal>> incomeIndex, String key,
                                       BigDecimal paymentAmount, BigDecimal partnerIncome) {
        Map<String, BigDecimal> income = incomeIndex.computeIfAbsent(key, ignored -> {
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingSettlementBillServiceImpl.java
@@ -28,6 +28,11 @@
        return this.baseMapper.queryList(page, type, status, dateRange, statisticalDateType, siteIds);
    }
    @Override
    public List<Map<String, Object>> queryIncomeList(Integer status, List<String> dateRange, Integer statisticalDateType, List<Integer> siteIds) {
        return this.baseMapper.queryIncomeList(status, dateRange, statisticalDateType, siteIds);
    }
    /**
     * 获取汇总数据
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TAccountSettlementStatementMapper.xml
@@ -2,5 +2,56 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.order.mapper.TAccountSettlementStatementMapper">
    <sql id="statementWhere">
        <where>
            <if test="req.type != null and req.type == 1">
                AND site_id = 0
            </if>
            <if test="req.type != null and req.type == 2">
                AND site_id &gt; 0
            </if>
            <if test="req.state != null">
                AND status = #{req.state}
            </if>
            <if test="req.payType != null and req.payType != 0">
                AND account_type = #{req.payType}
            </if>
            <if test="req.uid != null and req.uid != ''">
                AND id = #{req.uid}
            </if>
            <if test="startCycle != null">
                AND billing_cycle &gt;= #{startCycle}
            </if>
            <if test="endCycle != null">
                AND billing_cycle &lt; #{endCycle}
            </if>
            AND site_id IN
            <foreach collection="siteIds" open="(" close=")" separator="," item="siteId">
                #{siteId}
            </foreach>
        </where>
    </sql>
    <select id="selectStatementPage" resultType="com.ruoyi.order.api.model.TAccountSettlementStatement">
        SELECT id, site_id, billing_cycle, status, code, account_type,
               recharge_amount, refund_amount, platform_fee,
               tripartite_commission, recorded_amount, create_time
        FROM t_account_settlement_statement
        <include refid="statementWhere"/>
        ORDER BY billing_cycle DESC, account_type ASC, site_id ASC
    </select>
    <select id="selectStatementSummary" resultType="com.ruoyi.order.api.vo.AccountListVO">
        SELECT COUNT(*) AS billCount,
               COALESCE(SUM(recorded_amount), 0) AS totalAmount,
               COALESCE(SUM(recharge_amount), 0) AS paymentAmount,
               COALESCE(SUM(refund_amount), 0) AS refundAmount,
               COALESCE(SUM(platform_fee), 0) AS commissionAmount,
               COALESCE(SUM(tripartite_commission), 0) AS sharingAmount,
               0 AS platformIncome,
               0 AS partnerIncome
        FROM t_account_settlement_statement
        <include refid="statementWhere"/>
    </select>
</mapper>
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingSettlementBillMapper.xml
@@ -37,6 +37,37 @@
        order by a.site_id
    </select>
    <select id="queryIncomeList" resultType="map">
        select
        site_id,
        DATE_FORMAT(billing_cycle, '%Y-%m') as billingCycle,
        total_electricity_charge,
        total_service_fee,
        tripartite_commission
        from t_charging_settlement_bill
        <where>
            and site_id != 0
            <if test="null != status">
                and `status` = #{status}
            </if>
            <if test="null != statisticalDateType">
                and statistical_date_type = #{statisticalDateType}
            </if>
            <if test="null != dateRange and dateRange.size() > 0">
                and DATE_FORMAT(billing_cycle, '%Y-%m') in
                <foreach collection="dateRange" item="item" open="(" separator="," close=")">
                    #{item}
                </foreach>
            </if>
            <if test="null != siteIds and siteIds.size() > 0">
                and site_id in
                <foreach collection="siteIds" item="item" open="(" separator="," close=")">
                    #{item}
                </foreach>
            </if>
        </where>
    </select>
    <select id="queryListSum" resultType="map">
        select
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingBillIncomeIndexTest.java
New file
@@ -0,0 +1,47 @@
package com.ruoyi.order.service.impl;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ChargingBillIncomeIndexTest {
    @Test
    void calculatesIncomeFromSettlementRowsWithoutSharingThirdPartyServiceFees() {
        TChargingBillServiceImpl service = new TChargingBillServiceImpl();
        Map<Integer, BigDecimal> ratios = new HashMap<>();
        ratios.put(1, new BigDecimal("15"));
        ratios.put(2, new BigDecimal("50"));
        Map<String, Map<String, BigDecimal>> incomeIndex = service.buildChargingBillIncomeIndex(
                Arrays.asList(
                        settlementRow(1, "2026-06", "6.12", "1.85", "0.00"),
                        settlementRow(2, "2026-06", "1.00", "1.00", "0.20")
                ),
                ratios
        );
        assertEquals(new BigDecimal("7.69"), incomeIndex.get("1|2026-06").get("platformIncome"));
        assertEquals(new BigDecimal("0.28"), incomeIndex.get("1|2026-06").get("partnerIncome"));
        assertEquals(new BigDecimal("1.80"), incomeIndex.get("2|2026-06").get("platformIncome"));
        assertEquals(new BigDecimal("0.00"), incomeIndex.get("2|2026-06").get("partnerIncome"));
        assertEquals(new BigDecimal("9.49"), incomeIndex.get("0|2026-06").get("platformIncome"));
        assertEquals(new BigDecimal("0.28"), incomeIndex.get("0|2026-06").get("partnerIncome"));
    }
    private Map<String, Object> settlementRow(Integer siteId, String billingCycle, String electricAmount,
                                               String serviceAmount, String tripartiteCommission) {
        Map<String, Object> row = new HashMap<>();
        row.put("site_id", siteId);
        row.put("billingCycle", billingCycle);
        row.put("total_electricity_charge", new BigDecimal(electricAmount));
        row.put("total_service_fee", new BigDecimal(serviceAmount));
        row.put("tripartite_commission", new BigDecimal(tripartiteCommission));
        return row;
    }
}
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/ChargingPeriodIncomeTest.java
New file
@@ -0,0 +1,38 @@
package com.ruoyi.order.service.impl;
import com.ruoyi.order.api.vo.ChargingOrderListVO;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ChargingPeriodIncomeTest {
    @Test
    void usesActualChargingOrderShareIncome() {
        TChargingOrderServiceImpl service = new TChargingOrderServiceImpl();
        ChargingOrderListVO item = new ChargingOrderListVO();
        Map<String, Object> row = new HashMap<>();
        row.put("platformIncome", new BigDecimal("0.73"));
        row.put("partnerIncome", new BigDecimal("0.27"));
        service.applyChargingPeriodShareIncome(item, row);
        assertEquals(new BigDecimal("0.73"), item.getPlatformIncome());
        assertEquals(new BigDecimal("0.27"), item.getPartnerIncome());
    }
    @Test
    void usesZeroIncomeWhenNoEffectiveShareRecordExists() {
        TChargingOrderServiceImpl service = new TChargingOrderServiceImpl();
        ChargingOrderListVO item = new ChargingOrderListVO();
        service.applyChargingPeriodShareIncome(item, null);
        assertEquals(BigDecimal.ZERO, item.getPlatformIncome());
        assertEquals(BigDecimal.ZERO, item.getPartnerIncome());
    }
}