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> <if test="partnerId != null"> and c.partner_id = #{partnerId} </if> <if test="partnerType != null"> and d.partner_type = #{partnerType} </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 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 { outputStream.close(); 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<Integer, BigDecimal> siteServiceShareRatioMap) { 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()); if (serviceShareRatio != null && serviceShareRatio.compareTo(BigDecimal.ZERO) > 0) { partnerIncome = amounts[1].multiply(serviceShareRatio).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP); } } BigDecimal serviceShareRatio = siteServiceShareRatioMap.get(siteId); if (serviceShareRatio != null && serviceShareRatio.compareTo(BigDecimal.ZERO) > 0) { 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 > 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 >= #{startCycle} </if> <if test="endCycle != null"> AND billing_cycle < #{endCycle} </if> AND site_id IN <foreach collection="siteIds" open="(" close=")" separator="," item="siteId"> #{siteId} </foreach> </where> </sql> </mapper> <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()); } }