ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TAccountingStrategyController.java
@@ -447,17 +447,17 @@ } @RequiresPermissions(value = {"/billingTemplateAudit/audit"}, logical = Logical.OR) @RequiresPermissions(value = {"/billingTemplateAudit/audit", "/billingTemplateAudit"}, logical = Logical.OR) @ApiOperation(tags = {"后台-申请表单-计费模板审核"},value = "审核") @PostMapping(value = "/auth/pass") @Log(title = "【计费模板审核】审核计费模板", businessType = BusinessType.OTHER,operatorType = OperatorType.MANAGE) public AjaxResult<PageInfo<TAccountingStrategyVO>> authPass(@RequestBody SteategyPassDto steategyPassDto) { TAccountingStrategy byId = accountingStrategyService.getById(steategyPassDto.getId()); Long userId = SecurityUtils.getLoginUser().getUserid(); if (byId.getAuditStatus()==1&&byId.getFirstUserId()!=userId){ if (byId.getAuditStatus()==1 && !Objects.equals(byId.getFirstUserId(), userId)){ return AjaxResult.error("您不是一级审核人员,无法审核"); } if (byId.getAuditStatus()==2&&byId.getTwoUserId()!=userId){ if (byId.getAuditStatus()==2 && !Objects.equals(byId.getTwoUserId(), userId)){ return AjaxResult.error("您不是二级审核人员,无法审核"); } if (steategyPassDto.getPass()==1){ ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/FinancialSettlementController.java
@@ -35,7 +35,8 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -277,6 +278,7 @@ tChargingOrderExports.add(tChargingOrderExport); } Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), TChargingOrderExport.class, tChargingOrderExports); prependChargingPeriodSummary(workbook, buildChargingPeriodExportSummary(exportList)); HttpServletResponse response = WebUtils.response(); response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); @@ -353,6 +355,87 @@ return nullToZero(amount).setScale(2, RoundingMode.HALF_UP).toPlainString(); } static String buildChargingPeriodExportSummary(List<ChargingOrderListVO> list) { BigDecimal electrovalence = BigDecimal.ZERO; BigDecimal serviceCharge = BigDecimal.ZERO; BigDecimal paymentAmount = BigDecimal.ZERO; BigDecimal chargingCapacity = BigDecimal.ZERO; BigDecimal tripartiteCommission = BigDecimal.ZERO; BigDecimal balanceIncome = BigDecimal.ZERO; BigDecimal balanceGiftIncome = BigDecimal.ZERO; BigDecimal vipGiftAmount = BigDecimal.ZERO; long chargingSeconds = 0L; if (list != null) { for (ChargingOrderListVO item : list) { if (item == null) { continue; } electrovalence = electrovalence.add(zero(item.getElectrovalence())); serviceCharge = serviceCharge.add(zero(item.getServiceCharge())); paymentAmount = paymentAmount.add(zero(item.getPaymentAmount())); chargingCapacity = chargingCapacity.add(zero(item.getElectricity())); tripartiteCommission = tripartiteCommission.add(zero(item.getSharingAmount())); balanceIncome = balanceIncome.add(zero(item.getBalanceIncome())); balanceGiftIncome = balanceGiftIncome.add(zero(item.getBalanceGiftIncome())); vipGiftAmount = vipGiftAmount.add(zero(item.getVipDiscountAmount())); chargingSeconds += item.getChargingSecond() == null ? 0L : item.getChargingSecond(); } } int orderCount = list == null ? 0 : list.size(); return "总计:" + orderCount + "条" + " 充电电费:" + money(electrovalence) + "元" + " 充电服务费:" + money(serviceCharge) + "元" + " 充电收入:" + money(paymentAmount) + "元" + " 充电电量:" + quantity(chargingCapacity) + "度" + " 充电时长:" + formatChargingDurationMinutes(chargingSeconds) + "分钟" + " 平台手续费:0.00元" + " 三方平台分佣:" + money(tripartiteCommission) + "元" + " 余额收入:" + money(balanceIncome) + "元" + " 余额赠送:" + money(balanceGiftIncome) + "元" + " 会员赠送:" + money(vipGiftAmount) + "元"; } static void prependChargingPeriodSummary(Workbook workbook, String summary) { if (workbook == null || workbook.getNumberOfSheets() == 0) { return; } Sheet sheet = workbook.getSheetAt(0); int lastRowNum = sheet.getLastRowNum(); if (sheet.getPhysicalNumberOfRows() > 0) { sheet.shiftRows(0, lastRowNum, 1, true, false); } Row summaryRow = sheet.createRow(0); summaryRow.setHeightInPoints(24); Cell summaryCell = summaryRow.createCell(0); summaryCell.setCellValue(summary == null ? "" : summary); CellStyle style = workbook.createCellStyle(); Font font = workbook.createFont(); font.setBold(true); style.setFont(font); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setAlignment(HorizontalAlignment.LEFT); summaryCell.setCellStyle(style); Row headerRow = sheet.getRow(1); int lastColumn = headerRow == null ? 0 : headerRow.getLastCellNum() - 1; if (lastColumn > 0) { sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, lastColumn)); } } private static BigDecimal zero(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private static String money(BigDecimal value) { return zero(value).setScale(2, RoundingMode.HALF_UP).toPlainString(); } private static String quantity(BigDecimal value) { return zero(value).setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString(); } static String formatChargingDurationMinutes(Long chargingSeconds) { if (chargingSeconds == null) { return ""; ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
@@ -97,7 +97,7 @@ List<SixChargingDto> chargeSettlement(@Param("sixBefore") String sixBefore, @Param("siteIds") List<Integer> siteIds); List<SixChargingDto> chargeCurrentMonth(@Param("siteIds") List<Integer> siteIds); List<SixChargingDto> chargeCurrentMonth(@Param("siteIdsCsv") String siteIdsCsv); List<SixCircleDto> circle(@Param("siteIds")List<Integer> siteIds,@Param("sixBefore")LocalDate sixBefore, @Param("partnerView") Boolean partnerView); ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -3219,7 +3219,11 @@ if (settlementIncome != null) { result.addAll(settlementIncome); } List<SixChargingDto> currentMonthIncome = this.baseMapper.chargeCurrentMonth(siteIds); String siteIdsCsv = siteIds == null ? null : siteIds.stream() .filter(Objects::nonNull) .map(String::valueOf) .collect(Collectors.joining(",")); List<SixChargingDto> currentMonthIncome = this.baseMapper.chargeCurrentMonth(siteIdsCsv); if (currentMonthIncome != null) { result.addAll(currentMonthIncome); } @@ -4955,21 +4959,23 @@ } mergeSuccessfulRefundAmounts(order); PayOrderInfoDto dto = new PayOrderInfoDto(); BigDecimal thirdPayAmount = getRefundableThirdPayAmount(order); BigDecimal thirdRefundAmount = money(order.getThirdRefundAmount()); dto.setOrderId(order.getId().toString()); dto.setCode(order.getCode()); dto.setTradeNo(order.getRechargeSerialNumber()); dto.setPayType(order.getRechargePaymentType()); dto.setPayAmount(money(order.getPaymentAmount())); dto.setPayAmount(thirdPayAmount); dto.setPayTime(order.getPayTime()); dto.setRefundAmount(money(order.getRefundAmount())); dto.setRefundAmount(thirdRefundAmount); dto.setRechargeAmount(money(order.getRechargeAmount())); dto.setFinalAmount(money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP)); dto.setFinalAmount(thirdPayAmount.subtract(thirdRefundAmount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP)); dto.setPayMethod(order.getPayMethod()); dto.setBalancePayAmount(order.getBalancePayAmount()); dto.setThirdPayAmount(order.getThirdPayAmount()); dto.setBalanceRefundAmount(order.getBalanceRefundAmount()); dto.setThirdRefundAmount(order.getThirdRefundAmount()); dto.setRefundableAmount(getManualRefundableAmount(order)); dto.setBalancePayAmount(money(order.getBalancePayAmount())); dto.setThirdPayAmount(thirdPayAmount); dto.setBalanceRefundAmount(money(order.getBalanceRefundAmount())); dto.setThirdRefundAmount(thirdRefundAmount); dto.setRefundableAmount(getSpecialRefundableAmount(order)); return R.ok(dto); } @@ -5007,6 +5013,26 @@ .setScale(2, RoundingMode.HALF_UP); } private BigDecimal getSpecialRefundableAmount(TChargingOrder order) { return calculateSpecialRefundableAmount( getManualRefundableAmount(order), getRefundableThirdPayAmount(order), money(order == null ? null : order.getThirdRefundAmount())); } static BigDecimal calculateSpecialRefundableAmount(BigDecimal manualRefundableAmount, BigDecimal thirdPayAmount, BigDecimal thirdRefundAmount) { BigDecimal normalizedManualAmount = manualRefundableAmount == null ? BigDecimal.ZERO : manualRefundableAmount; BigDecimal normalizedThirdPayAmount = thirdPayAmount == null ? BigDecimal.ZERO : thirdPayAmount; BigDecimal normalizedThirdRefundAmount = thirdRefundAmount == null ? BigDecimal.ZERO : thirdRefundAmount; BigDecimal remainingThirdAmount = normalizedThirdPayAmount .subtract(normalizedThirdRefundAmount) .max(BigDecimal.ZERO); return normalizedManualAmount .min(remainingThirdAmount) .setScale(2, RoundingMode.HALF_UP); } @Override @Transactional public R specialRefund(PayOrderRefundDto payOrderQueryDto) { @@ -5032,7 +5058,7 @@ } mergeSuccessfulRefundAmounts(order); BigDecimal refundAmount = money(payOrderQueryDto.getRefundAmount()); BigDecimal refundableAmount = getManualRefundableAmount(order); BigDecimal refundableAmount = getSpecialRefundableAmount(order); if (refundAmount.compareTo(refundableAmount) > 0) { return R.fail("退款金额不能超过剩余可退金额"); } ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -449,28 +449,22 @@ </select> <select id="chargeCurrentMonth" resultType="com.ruoyi.order.dto.SixChargingDto"> SELECT EXTRACT(MONTH FROM charging_order.create_time) AS MONTH, SUM(IFNULL(accounting.period_electric_price, 0)) AS electrovalence, SUM(CASE WHEN charging_order.order_source = 2 THEN IFNULL(accounting.period_service_price, 0) * 0.8 ELSE IFNULL(accounting.period_service_price, 0) END) AS service_charge FROM t_charging_order_accounting_strategy accounting INNER JOIN t_charging_order charging_order ON accounting.charging_order_id = charging_order.id WHERE charging_order.del_flag = 0 AND charging_order.status = 5 AND charging_order.recharge_payment_status = 2 AND charging_order.create_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01') AND charging_order.create_time <= NOW() <if test="siteIds != null and siteIds.size() > 0"> AND charging_order.site_id IN <foreach collection="siteIds" item="siteId" open="(" separator="," close=")"> #{siteId} </foreach> SELECT DATE_FORMAT(CURDATE(), '%m') AS MONTH, IFNULL(SUM(IFNULL(electrovalence, 0)), 0) AS electrovalence, IFNULL(SUM(CASE WHEN order_source = 2 THEN IFNULL(service_charge, 0) * 0.8 ELSE IFNULL(service_charge, 0) END), 0) AS service_charge FROM t_charging_order WHERE del_flag = 0 AND status = 5 AND recharge_payment_status = 2 AND create_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01') AND create_time <= NOW() <if test="siteIdsCsv != null and siteIdsCsv != ''"> AND site_id IN (${siteIdsCsv}) </if> GROUP BY EXTRACT(MONTH FROM charging_order.create_time) </select> <select id="circle" resultType="com.ruoyi.order.dto.SixCircleDto"> ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/FinancialSettlementControllerTest.java
@@ -1,8 +1,16 @@ package com.ruoyi.order.controller; import com.ruoyi.order.api.vo.ChargingOrderListVO; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class FinancialSettlementControllerTest { @@ -12,4 +20,56 @@ assertEquals("34.6", FinancialSettlementController.formatChargingDurationMinutes(2076L)); assertEquals("", FinancialSettlementController.formatChargingDurationMinutes(null)); } @Test void shouldBuildChargingPeriodExportSummaryFromExportRows() { ChargingOrderListVO first = new ChargingOrderListVO(); first.setElectrovalence(new BigDecimal("10.12")); first.setServiceCharge(new BigDecimal("2.34")); first.setPaymentAmount(new BigDecimal("12.46")); first.setElectricity(new BigDecimal("8.1234")); first.setChargingSecond(90L); first.setSharingAmount(new BigDecimal("0.46")); first.setBalanceIncome(new BigDecimal("3.00")); first.setBalanceGiftIncome(new BigDecimal("1.00")); first.setVipDiscountAmount(new BigDecimal("0.50")); ChargingOrderListVO second = new ChargingOrderListVO(); second.setElectrovalence(new BigDecimal("1.00")); second.setServiceCharge(new BigDecimal("1.00")); second.setPaymentAmount(new BigDecimal("2.00")); second.setElectricity(new BigDecimal("1.0000")); second.setChargingSecond(30L); String summary = FinancialSettlementController.buildChargingPeriodExportSummary(Arrays.asList(first, second)); assertTrue(summary.contains("总计:2条")); assertTrue(summary.contains("充电电费:11.12元")); assertTrue(summary.contains("充电服务费:3.34元")); assertTrue(summary.contains("充电收入:14.46元")); assertTrue(summary.contains("充电电量:9.123度")); assertTrue(summary.contains("充电时长:2分钟")); assertTrue(summary.contains("三方平台分佣:0.46元")); assertTrue(summary.contains("余额收入:3.00元")); assertTrue(summary.contains("余额赠送:1.00元")); assertTrue(summary.contains("会员赠送:0.50元")); } @Test void shouldPrependMergedSummaryRowBeforeExportHeader() throws Exception { try (Workbook workbook = new HSSFWorkbook()) { Sheet sheet = workbook.createSheet("充电时段统计"); sheet.createRow(0).createCell(0).setCellValue("订单编号"); sheet.getRow(0).createCell(1).setCellValue("电站名称"); sheet.createRow(1).createCell(0).setCellValue("ORDER-1"); FinancialSettlementController.prependChargingPeriodSummary(workbook, "总计:1条"); assertEquals("总计:1条", sheet.getRow(0).getCell(0).getStringCellValue()); assertEquals("订单编号", sheet.getRow(1).getCell(0).getStringCellValue()); assertEquals("ORDER-1", sheet.getRow(2).getCell(0).getStringCellValue()); assertEquals(1, sheet.getNumMergedRegions()); assertEquals("A1:B1", sheet.getMergedRegion(0).formatAsString()); } } } ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/PlatformRevenueAnalysisTest.java
@@ -76,7 +76,7 @@ currentMonth.setElectrovalence(new BigDecimal("3.456")); currentMonth.setServiceCharge(new BigDecimal("4.567")); when(mapper.chargeSettlement("2026-01-01", siteIds)).thenReturn(Arrays.asList(settlement)); when(mapper.chargeCurrentMonth(siteIds)).thenReturn(Arrays.asList(currentMonth)); when(mapper.chargeCurrentMonth("122,123")).thenReturn(Arrays.asList(currentMonth)); List<SixChargingDto> result = service.charge(LocalDate.of(2026, 1, 1), siteIds, false); @@ -87,6 +87,7 @@ assertEquals("07", result.get(1).getMonth()); assertEquals(new BigDecimal("3.46"), result.get(1).getElectrovalence()); assertEquals(new BigDecimal("4.57"), result.get(1).getServiceCharge()); verify(mapper).chargeCurrentMonth("122,123"); } @Test @@ -99,7 +100,7 @@ assertEquals("07", result.get(0).getMonth()); verify(mapper, never()).chargeSettlement("2026-01-01", siteIds); verify(mapper, never()).chargeCurrentMonth(siteIds); verify(mapper, never()).chargeCurrentMonth("122,123"); } @Test ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplRefundableAmountTest.java
@@ -32,6 +32,22 @@ assertEquals(new BigDecimal("21.75"), refundableAmount); } @Test void specialRefundForCombinedPaymentOnlyUsesThirdPartyAmount() { BigDecimal refundableAmount = TChargingOrderServiceImpl.calculateSpecialRefundableAmount( new BigDecimal("4.00"), new BigDecimal("2.00"), BigDecimal.ZERO); assertEquals(new BigDecimal("2.00"), refundableAmount); } @Test void previousThirdPartyRefundReducesSpecialRefundableAmount() { BigDecimal refundableAmount = TChargingOrderServiceImpl.calculateSpecialRefundableAmount( new BigDecimal("4.00"), new BigDecimal("2.00"), new BigDecimal("0.50")); assertEquals(new BigDecimal("1.50"), refundableAmount); } private TChargingOrderRefund refund(String title, String amount) { TChargingOrderRefund refund = new TChargingOrderRefund(); refund.setRefundTitle(title);