longz
2026-07-23 ac4b626821dcc42828e57a0893ff84b5202caf6c
导出模板变更
1个文件已添加
6个文件已修改
462 ■■■■■ 已修改文件
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/ChargingDetails.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/InvoiceApplicationWorkbookBuilder.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/PlatformRevenueAnalysisTest.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderPaymentConfirmationTest.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java
@@ -20,6 +20,7 @@
import com.ruoyi.order.api.vo.TOrderInvoiceVO;
import com.ruoyi.order.dto.*;
import com.ruoyi.order.export.OrderInvoiceExport;
import com.ruoyi.order.export.InvoiceApplicationWorkbookBuilder;
import com.ruoyi.order.service.TOrderInvoiceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -222,18 +223,7 @@
    }
    private Workbook buildInvoiceWorkbook(TOrderInvoiceVO invoice) {
        OrderInvoiceExport export = new OrderInvoiceExport();
        BeanUtils.copyProperties(invoice, export);
        if (invoice.getCreateTime() != null) {
            export.setApplyTime(DateUtils.localDateTimeToString(invoice.getCreateTime()));
        }
        if (invoice.getBillingTime() != null) {
            export.setBillingTime(DateUtils.localDateTimeToString(invoice.getBillingTime()));
        }
        return ExcelExportUtil.exportExcel(
                new ExportParams(),
                OrderInvoiceExport.class,
                new ArrayList<>(Collections.singletonList(export)));
        return InvoiceApplicationWorkbookBuilder.build(invoice);
    }
    private String buildInvoiceFileName(TOrderInvoiceVO invoice) {
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/ChargingDetails.java
@@ -5,6 +5,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * @author zhibing.pu
@@ -39,4 +40,6 @@
    private BigDecimal chargingCost;
    @ApiModelProperty("充电状态(0=未知,1=等待中,2=启动中,3=充电中,4=停止中,5=已结束)")
    private Integer status;
    @ApiModelProperty("预约充电时间")
    private LocalDateTime reservationTime;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/InvoiceApplicationWorkbookBuilder.java
New file
@@ -0,0 +1,151 @@
package com.ruoyi.order.export;
import com.ruoyi.order.api.vo.TOrderInvoiceVO;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/** Builds the fixed-format invoice application form used by the finance department. */
public final class InvoiceApplicationWorkbookBuilder {
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private InvoiceApplicationWorkbookBuilder() {
    }
    public static Workbook build(TOrderInvoiceVO invoice) {
        Workbook workbook = new HSSFWorkbook();
        Sheet sheet = workbook.createSheet("开票申请表");
        int[] widths = {14, 3, 18, 16, 16, 16, 16};
        for (int i = 0; i < widths.length; i++) {
            sheet.setColumnWidth(i, widths[i] * 256);
        }
        sheet.getPrintSetup().setLandscape(false);
        sheet.getPrintSetup().setFitWidth((short) 1);
        sheet.setFitToPage(true);
        sheet.setMargin(Sheet.LeftMargin, 0.25);
        sheet.setMargin(Sheet.RightMargin, 0.25);
        sheet.setMargin(Sheet.TopMargin, 0.35);
        sheet.setMargin(Sheet.BottomMargin, 0.35);
        CellStyle title = style(workbook, false, true, (short) 16);
        CellStyle label = style(workbook, true, false, (short) 11);
        CellStyle value = style(workbook, false, false, (short) 11);
        CellStyle footer = style(workbook, false, false, (short) 11);
        merged(sheet, 0, 0, 0, 6, "开票申请表", title, 30);
        row(sheet, 1, "开票申请时间", value(invoice == null ? null : invoice.getCreateTime()), label, value, 28);
        row(sheet, 2, "充电站名称", text(invoice == null ? null : invoice.getSiteName()), label, value, 28);
        row(sheet, 3, "发票抬头", text(invoice == null ? null : invoice.getName()), label, value, 28);
        row(sheet, 4, "纳税人识别号", text(invoice == null ? null : invoice.getTaxIdentificationNumber()), label, value, 28);
        merged(sheet, 5, 0, 5, 1, "税率%", label, 28);
        merged(sheet, 5, 2, 5, 2, taxRate(invoice), value, 28);
        merged(sheet, 5, 3, 5, 4, "应(已)开票金额(元)", label, 28);
        Cell amountCell = merged(sheet, 5, 5, 5, 6, null, value, 28);
        if (invoice != null && invoice.getTotalAmount() != null) {
            amountCell.setCellValue(invoice.getTotalAmount().doubleValue());
            amountCell.setCellStyle(value);
        }
        row(sheet, 6, "发票类型", text(invoice == null ? null : invoice.getInvoiceType()), label, value, 28);
        row(sheet, 7, "预计回款时间", "", label, value, 28);
        merged(sheet, 8, 0, 8, 6, "", value, 28);
        merged(sheet, 9, 0, 9, 1, "财务科:", footer, 28);
        merged(sheet, 9, 2, 9, 4, "新能源业务中心:", footer, 28);
        merged(sheet, 9, 5, 9, 6, "经办人:", footer, 28);
        return workbook;
    }
    private static void row(Sheet sheet, int row, String labelText, String valueText,
                            CellStyle labelStyle, CellStyle valueStyle, int height) {
        merged(sheet, row, 0, row, 1, labelText, labelStyle, height);
        merged(sheet, row, 2, row, 6, valueText, valueStyle, height);
    }
    private static Cell merged(Sheet sheet, int firstRow, int firstColumn, int lastRow,
                               int lastColumn, String text, CellStyle style, int height) {
        org.apache.poi.ss.usermodel.Row row = sheet.getRow(firstRow);
        if (row == null) {
            row = sheet.createRow(firstRow);
        }
        row.setHeightInPoints(height);
        for (int column = firstColumn; column <= lastColumn; column++) {
            Cell cell = row.getCell(column);
            if (cell == null) {
                cell = row.createCell(column);
            }
            cell.setCellStyle(style);
        }
        Cell cell = row.getCell(firstColumn);
        if (text != null) {
            cell.setCellValue(text);
        }
        if (firstColumn != lastColumn || firstRow != lastRow) {
            sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstColumn, lastColumn));
        }
        return cell;
    }
    private static CellStyle style(Workbook workbook, boolean red, boolean bold, short fontSize) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setWrapText(true);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        Font font = workbook.createFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints(fontSize);
        font.setBold(bold);
        if (red) {
            font.setColor((short) 10);
        }
        style.setFont(font);
        return style;
    }
    private static String text(String value) {
        return value == null ? "" : value;
    }
    private static String value(LocalDateTime value) {
        return value == null ? "" : value.format(DATE_FORMAT);
    }
    private static String taxRate(TOrderInvoiceVO invoice) {
        if (invoice == null) {
            return "";
        }
        BigDecimal rate = firstNonZero(invoice.getElectricityTariff(), invoice.getServiceTariff(), invoice.getAddedServiceTariff());
        if (rate == null) {
            return "";
        }
        if (rate.compareTo(BigDecimal.ONE) <= 0) {
            rate = rate.multiply(BigDecimal.valueOf(100));
        }
        return rate.stripTrailingZeros().setScale(0, RoundingMode.HALF_UP).toPlainString() + "%";
    }
    private static BigDecimal firstNonZero(BigDecimal... values) {
        for (BigDecimal value : values) {
            if (value != null && value.compareTo(BigDecimal.ZERO) != 0) {
                return value;
            }
        }
        return null;
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -739,6 +739,33 @@
        }
        return vipId == null ? null : vipClient.getInfo1(vipId).getData();
    }
    /**
     * 将会员配置转换为订单统一使用的 0~1 折扣系数。
     * 常规会员的计费策略已按系数保存;内部会员后台按“8折”这种 0~10 数值保存。
     */
    BigDecimal resolveChargingVipDiscount(TVip vip, TAccountingStrategy accountingStrategy) {
        if (vip == null) {
            return null;
        }
        BigDecimal discount;
        if (Integer.valueOf(1).equals(vip.getType())) {
            discount = accountingStrategy == null ? null : accountingStrategy.getDiscount();
        } else {
            discount = vip.getDiscount() == null ? null : vip.getDiscount().divide(BigDecimal.TEN);
        }
        return isEffectiveVipDiscount(discount) ? discount : null;
    }
    boolean isEffectiveVipDiscount(BigDecimal discount) {
        return discount != null
                && discount.compareTo(BigDecimal.ZERO) > 0
                && discount.compareTo(BigDecimal.ONE) < 0;
    }
    boolean shouldConsumeVipDiscount(BigDecimal discountAmount) {
        return discountAmount != null && discountAmount.compareTo(BigDecimal.ZERO) > 0;
    }
    @Override
    public AjaxResult paymentChargingOrder(AddChargingOrder addChargingOrder) {
        Long userId = tokenService.getLoginUserApplet().getUserId();
@@ -916,14 +943,7 @@
            TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
            TVip vip = getVipSnapshot(vipDetail, appUser.getVipId());
            if (null != vip && null != vipDetail) {
                BigDecimal discount = null;
                if (Integer.valueOf(1).equals(vip.getType())) {
                    //普通会员折扣使用积分策略上的折扣,且有最高优惠金额
                    discount = accountingStrategy.getDiscount();
                } else {
                    //内部会员使用会员信息设置的折扣,没有最高优惠金额
                    discount = vip.getDiscount();
                }
                BigDecimal discount = resolveChargingVipDiscount(vip, accountingStrategy);
                Integer chargeNum = vipDetail.getChargeNum();
                if (null != discount && null != chargeNum && 0 < chargeNum) {
                    chargingOrder.setVipDiscount(discount);
@@ -954,7 +974,7 @@
            recordBalanceConsume(chargingOrder, balancePayAmount);
        }
        if (thirdPayAmount.compareTo(BigDecimal.ZERO) == 0) {
            return chargingOrderCallback(addChargingOrder.getPaymentType(), chargingOrder.getCode(), whitelistMatched ? "WHITELIST" : "BALANCE", null);
            return completeChargingOrderPayment(chargingOrder, whitelistMatched ? "WHITELIST" : "BALANCE");
        }
        //会员优惠折扣将其计入增加充电时长(增加总充电金额)
        //如果充电总金额未使用完,则需要退回费用=(原金额/总金额)*(总金额-实际充电金额)
@@ -1417,10 +1437,18 @@
        if (chargingOrder == null) {
            return AjaxResult.error("支付回调订单不存在");
        }
        return completeChargingOrderPayment(chargingOrder, transaction_id);
    }
    /**
     * 完成订单支付状态和充电额度初始化。
     * 本地余额、白名单零元支付直接传入刚保存的订单,避免读写分离下立即回查读不到新订单。
     */
    AjaxResult completeChargingOrderPayment(TChargingOrder chargingOrder, String transactionId) {
        if (chargingOrder.getRechargePaymentStatus() == 2) {
            if (isIcbcOrder(chargingOrder) && StringUtils.hasLength(transaction_id)
                    && !transaction_id.equals(chargingOrder.getRechargeSerialNumber())) {
                chargingOrder.setRechargeSerialNumber(transaction_id);
            if (isIcbcOrder(chargingOrder) && StringUtils.hasLength(transactionId)
                    && !transactionId.equals(chargingOrder.getRechargeSerialNumber())) {
                chargingOrder.setRechargeSerialNumber(transactionId);
                this.updateById(chargingOrder);
            }
            return AjaxResult.success();
@@ -1430,7 +1458,7 @@
                .eq(TChargingOrder::getId, chargingOrder.getId())
                .eq(TChargingOrder::getRechargePaymentStatus, 1)
                .set(TChargingOrder::getRechargePaymentStatus, 2)
                .set(TChargingOrder::getRechargeSerialNumber, transaction_id)
                .set(TChargingOrder::getRechargeSerialNumber, transactionId)
                .set(TChargingOrder::getStatus, 2)
                .set(TChargingOrder::getPayTime, callbackPayTime));
        if (claimed == 0) {
@@ -1447,7 +1475,7 @@
            recordBalanceConsume(chargingOrder, money(chargingOrder.getBalancePayAmount()));
        }
        chargingOrder.setRechargePaymentStatus(2);
        chargingOrder.setRechargeSerialNumber(transaction_id);
        chargingOrder.setRechargeSerialNumber(transactionId);
        chargingOrder.setStatus(2);
        chargingOrder.setPayTime(callbackPayTime);
        
@@ -1480,12 +1508,12 @@
        
        TAppUser appUser = appUserClient.getUserById(chargingOrder.getAppUserId()).getData();
        //判断会员是否还有充电优惠次数,计算会员优惠的折扣金额
        if (!isWhitelistOrder(chargingOrder) && null != appUser.getVipId()) {
        if (!isWhitelistOrder(chargingOrder) && null != appUser.getVipId() && isEffectiveVipDiscount(discount)) {
            GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
            getAppUserVipDetail.setAppUserId(chargingOrder.getAppUserId());
            getAppUserVipDetail.setVipId(appUser.getVipId());
            TAppUserVipDetail data = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
            if (null != data && data.getChargeNum() > 0) {
            if (null != data && data.getChargeNum() != null && data.getChargeNum() > 0) {
                //计算折扣
                List<AccountingStrategyDetailOrder> list = accountingStrategyDetailOrderClient.getAllAccountingStrategyDetailOrder(chargingOrder.getId()).getData();
                list.get(list.size() - 1).setEndTime("23:59");
@@ -1530,21 +1558,19 @@
                        }
                    }
                }
                if (null != discount) {
                    discountAmount = serviceCharge.multiply(new BigDecimal(1).subtract(discount));
                }
                if (discountAmount.compareTo(BigDecimal.ZERO) >= 0) {
                    //计算会员最大优惠金额
                    TVip vip = getVipSnapshot(data, appUser.getVipId());
                    BigDecimal maximumDeduction = vip == null ? null : vip.getMaximumDeduction();
                    //普通会员有最高优惠限制
                    if (vip != null && Integer.valueOf(1).equals(vip.getType()) && maximumDeduction != null && discountAmount.compareTo(maximumDeduction) > 0) {
                        discountAmount = maximumDeduction;
                    }
                discountAmount = serviceCharge.multiply(BigDecimal.ONE.subtract(discount));
                //计算会员最大优惠金额
                TVip vip = getVipSnapshot(data, appUser.getVipId());
                BigDecimal maximumDeduction = vip == null ? null : vip.getMaximumDeduction();
                //普通会员有最高优惠限制
                if (vip != null && Integer.valueOf(1).equals(vip.getType()) && maximumDeduction != null
                        && maximumDeduction.compareTo(BigDecimal.ZERO) >= 0
                        && discountAmount.compareTo(maximumDeduction) > 0) {
                    discountAmount = maximumDeduction;
                }
                discountAmount = discountAmount.setScale(4, RoundingMode.HALF_EVEN);
                if (discountAmount.compareTo(BigDecimal.ZERO) >= 0) {
                if (shouldConsumeVipDiscount(discountAmount)) {
                    data.setChargeNum(data.getChargeNum() - 1);
                    appUserVipDetailClient.updateAppUserVipDetail(data);
                }
@@ -2305,7 +2331,11 @@
    public ChargingDetails getChargingDetails(Integer id) {
        Long userId = tokenService.getLoginUserApplet().getUserId();
        TChargingOrder one = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getAppUserId, userId).eq(TChargingOrder::getChargingGunId, id)
                .eq(TChargingOrder::getRechargePaymentStatus, 2).eq(TChargingOrder::getDelFlag, 0).orderByDesc(TChargingOrder::getStartTime).last(" limit 0, 1"));
                .eq(TChargingOrder::getRechargePaymentStatus, 2)
                .in(TChargingOrder::getStatus, Arrays.asList(2, 3, 4))
                .eq(TChargingOrder::getDelFlag, 0)
                .orderByDesc(TChargingOrder::getCreateTime)
                .last(" limit 0, 1"));
        if (null == one || !one.getAppUserId().equals(userId)) {
            return null;
        }
@@ -2316,7 +2346,11 @@
        Site site = siteClient.getSiteByIds(Arrays.asList(chargingPile.getSiteId())).getData().get(0);
        chargingDetails.setName(site.getName() + "-" + chargingPile.getName());
        chargingDetails.setCode(one.getCode());
        chargingDetails.setStatus(one.getStatus());
        chargingDetails.setReservationTime(one.getReservationTime());
        boolean reservationPending = one.getReservationTime() != null
                && one.getReservationTime().isAfter(LocalDateTime.now())
                && Integer.valueOf(2).equals(one.getStatus());
        chargingDetails.setStatus(reservationPending ? 1 : one.getStatus());
        chargingDetails.setChargingCost(one.getResidualAmount());
        UploadRealTimeMonitoringData data = uploadRealTimeMonitoringDataService.getLastDataById(one.getCode());
        if (null != data) {
@@ -3104,12 +3138,43 @@
    
    @Override
    public List<SixChargingDto> charge(LocalDate sixBefore, List<Integer> siteIds, Boolean partnerView) {
        return this.baseMapper.charge(sixBefore, siteIds, partnerView);
        List<SixChargingDto> result = this.baseMapper.charge(sixBefore, siteIds, partnerView);
        if (result != null) {
            for (SixChargingDto item : result) {
                if (item == null) {
                    continue;
                }
                if (item.getMonth() != null) {
                    try {
                        int month = Integer.parseInt(item.getMonth());
                        if (month >= 1 && month <= 12) {
                            item.setMonth(String.format("%02d", month));
                        }
                    } catch (NumberFormatException ignored) {
                    }
                }
                if (item.getElectrovalence() != null) {
                    item.setElectrovalence(item.getElectrovalence().setScale(2, RoundingMode.HALF_UP));
                }
                if (item.getServiceCharge() != null) {
                    item.setServiceCharge(item.getServiceCharge().setScale(2, RoundingMode.HALF_UP));
                }
            }
        }
        return result;
    }
    
    @Override
    public List<SixCircleDto> circle(List<Integer> siteIds, LocalDate sixBefore, Boolean partnerView) {
        return this.baseMapper.circle(siteIds, sixBefore, partnerView);
        List<SixCircleDto> result = this.baseMapper.circle(siteIds, sixBefore, partnerView);
        if (result != null) {
            for (SixCircleDto item : result) {
                if (item != null && item.getPaymentAmount() != null) {
                    item.setPaymentAmount(item.getPaymentAmount().setScale(2, RoundingMode.HALF_UP));
                }
            }
        }
        return result;
    }
    
    @Override
@@ -3213,11 +3278,17 @@
     */
    private BigDecimal getThirdPayNetAmount(TChargingOrder order) {
        BigDecimal payAmount = order.getThirdPayAmount();
        if (payAmount == null && Integer.valueOf(3).equals(order.getPayMethod())) {
            payAmount = firstNonNull(order.getPaymentAmount(), order.getRechargeAmount());
        Integer payMethod = order.getPayMethod();
        BigDecimal totalPaidAmount = firstNonNull(order.getPaymentAmount(), order.getRechargeAmount());
        if (isNullOrZero(payAmount)) {
            if (Integer.valueOf(3).equals(payMethod)) {
                payAmount = totalPaidAmount;
            } else if (Integer.valueOf(2).equals(payMethod) && totalPaidAmount != null) {
                payAmount = nonNegative(totalPaidAmount.subtract(nullToZero(order.getBalancePayAmount())));
            }
        }
        BigDecimal refundAmount = order.getThirdRefundAmount();
        if (refundAmount == null && Integer.valueOf(3).equals(order.getPayMethod())) {
        if (refundAmount == null && Integer.valueOf(3).equals(payMethod)) {
            refundAmount = order.getRefundAmount();
        }
        return nonNegative(nullToZero(payAmount).subtract(nullToZero(refundAmount)));
@@ -3229,8 +3300,13 @@
    private BigDecimal getBalancePayNetAmount(TChargingOrder order) {
        BigDecimal payAmount = order.getBalancePayAmount();
        Integer payMethod = order.getPayMethod();
        if (payAmount == null && (Integer.valueOf(1).equals(payMethod) || Integer.valueOf(2).equals(payMethod))) {
            payAmount = order.getPaymentAmount();
        BigDecimal totalPaidAmount = firstNonNull(order.getPaymentAmount(), order.getRechargeAmount());
        if (isNullOrZero(payAmount)) {
            if (Integer.valueOf(1).equals(payMethod)) {
                payAmount = totalPaidAmount;
            } else if (Integer.valueOf(2).equals(payMethod) && totalPaidAmount != null) {
                payAmount = nonNegative(totalPaidAmount.subtract(nullToZero(order.getThirdPayAmount())));
            }
        }
        BigDecimal refundAmount = order.getBalanceRefundAmount();
        if (refundAmount == null && (Integer.valueOf(1).equals(payMethod) || Integer.valueOf(2).equals(payMethod))) {
@@ -3243,6 +3319,10 @@
        return first != null ? first : second;
    }
    private boolean isNullOrZero(BigDecimal value) {
        return value == null || value.compareTo(BigDecimal.ZERO) == 0;
    }
    private BigDecimal nullToZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -413,9 +413,9 @@
    <select id="charge" resultType="com.ruoyi.order.dto.SixChargingDto">
        <choose>
            <when test="partnerView != null and partnerView">
                SELECT DATE_FORMAT(create_time, '%m') AS MONTH,
                       ROUND(SUM(IFNULL(electricity_share_amount, 0)), 2) AS electrovalence,
                       ROUND(SUM(IFNULL(service_share_amount, 0)), 2) AS service_charge
                SELECT EXTRACT(MONTH FROM create_time) AS MONTH,
                       SUM(IFNULL(electricity_share_amount, 0)) AS electrovalence,
                       SUM(IFNULL(service_share_amount, 0)) AS service_charge
                FROM t_charging_order_share_record
                WHERE del_flag = 0 AND online_share_status IN ('SUCCESS', 'RECORDED')
                <if test="sixBefore != null">
@@ -427,12 +427,12 @@
                        #{siteId}
                    </foreach>
                </if>
                GROUP BY DATE_FORMAT(create_time, '%m')
                GROUP BY EXTRACT(MONTH FROM create_time)
            </when>
            <otherwise>
                SELECT DATE_FORMAT(billing_cycle, '%m') AS MONTH,
                       ROUND(SUM(IFNULL(total_electricity_charge, 0)), 2) AS electrovalence,
                       ROUND(SUM(IFNULL(total_service_fee, 0) - IFNULL(tripartite_commission, 0)), 2) AS service_charge
                SELECT EXTRACT(MONTH FROM billing_cycle) AS MONTH,
                       SUM(IFNULL(total_electricity_charge, 0)) AS electrovalence,
                       SUM(IFNULL(total_service_fee, 0) - IFNULL(tripartite_commission, 0)) AS service_charge
                FROM t_charging_settlement_bill
                WHERE statistical_date_type = 1 AND site_id != 0
                <if test="sixBefore != null">
@@ -444,7 +444,7 @@
                        #{siteId}
                    </foreach>
                </if>
                GROUP BY DATE_FORMAT(billing_cycle, '%m')
                GROUP BY EXTRACT(MONTH FROM billing_cycle)
            </otherwise>
        </choose>
    </select>
@@ -453,7 +453,7 @@
        <choose>
            <when test="partnerView != null and partnerView">
                SELECT site_id AS siteId,
                       ROUND(SUM(IFNULL(share_amount, 0)), 2) AS paymentAmount
                       SUM(IFNULL(share_amount, 0)) AS paymentAmount
                FROM t_charging_order_share_record
                WHERE del_flag = 0 AND online_share_status IN ('SUCCESS', 'RECORDED')
                <if test="siteIds != null and siteIds.size() > 0">
@@ -469,8 +469,8 @@
            </when>
            <otherwise>
                SELECT site_id AS siteId,
                       ROUND(SUM(IFNULL(total_electricity_charge, 0) + IFNULL(total_service_fee, 0)
                           - IFNULL(tripartite_commission, 0)), 2) AS paymentAmount
                       SUM(IFNULL(total_electricity_charge, 0) + IFNULL(total_service_fee, 0)
                           - IFNULL(tripartite_commission, 0)) AS paymentAmount
                FROM t_charging_settlement_bill
                WHERE statistical_date_type = 1 AND site_id != 0
                <if test="siteIds != null and siteIds.size() > 0">
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/PlatformRevenueAnalysisTest.java
@@ -1,12 +1,17 @@
package com.ruoyi.order.service.impl;
import com.ruoyi.order.api.model.TChargingOrder;
import com.ruoyi.order.dto.SixCircleDto;
import com.ruoyi.order.mapper.TChargingOrderMapper;
import com.ruoyi.order.dto.SixChargingDto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -59,4 +64,56 @@
        assertEquals(new BigDecimal("8.90"), result.get("commission_amount"));
        verify(mapper, never()).sumSuccessfulShareAmount(siteIds);
    }
    @Test
    void chargeRoundsMapperTotalsInJavaForMycatCompatibility() {
        SixChargingDto item = new SixChargingDto();
        item.setMonth("7");
        item.setElectrovalence(new BigDecimal("1.234"));
        item.setServiceCharge(new BigDecimal("2.345"));
        when(mapper.charge(LocalDate.of(2026, 1, 1), siteIds, false)).thenReturn(Arrays.asList(item));
        List<SixChargingDto> result = service.charge(LocalDate.of(2026, 1, 1), siteIds, false);
        assertEquals("07", result.get(0).getMonth());
        assertEquals(new BigDecimal("1.23"), result.get(0).getElectrovalence());
        assertEquals(new BigDecimal("2.35"), result.get(0).getServiceCharge());
    }
    @Test
    void circleRoundsMapperTotalsInJavaForMycatCompatibility() {
        SixCircleDto item = new SixCircleDto();
        item.setPaymentAmount(new BigDecimal("12.345"));
        when(mapper.circle(siteIds, LocalDate.of(2026, 1, 1), false)).thenReturn(Arrays.asList(item));
        List<SixCircleDto> result = service.circle(siteIds, LocalDate.of(2026, 1, 1), false);
        assertEquals(new BigDecimal("12.35"), result.get(0).getPaymentAmount());
    }
    @Test
    void incomeCircleFallsBackWhenLegacyThirdPaySplitAmountIsZero() {
        TChargingOrder order = new TChargingOrder();
        order.setPayMethod(3);
        order.setThirdPayAmount(BigDecimal.ZERO);
        order.setPaymentAmount(new BigDecimal("26.80"));
        order.setThirdRefundAmount(BigDecimal.ZERO);
        BigDecimal amount = ReflectionTestUtils.invokeMethod(service, "getThirdPayNetAmount", order);
        assertEquals(new BigDecimal("26.80"), amount);
    }
    @Test
    void incomeCircleFallsBackWhenLegacyBalanceSplitAmountIsZero() {
        TChargingOrder order = new TChargingOrder();
        order.setPayMethod(1);
        order.setBalancePayAmount(BigDecimal.ZERO);
        order.setPaymentAmount(new BigDecimal("18.60"));
        order.setBalanceRefundAmount(BigDecimal.ZERO);
        BigDecimal amount = ReflectionTestUtils.invokeMethod(service, "getBalancePayNetAmount", order);
        assertEquals(new BigDecimal("18.60"), amount);
    }
}
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderPaymentConfirmationTest.java
@@ -1,17 +1,24 @@
package com.ruoyi.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.chargingPile.api.model.TAccountingStrategy;
import com.ruoyi.order.api.model.TChargingOrder;
import com.ruoyi.order.mapper.TChargingOrderMapper;
import com.ruoyi.other.api.domain.TVip;
import com.ruoyi.payment.api.feignClient.IcbcPaymentClient;
import com.ruoyi.payment.api.vo.IcbcPaymentResp;
import com.ruoyi.system.api.model.LoginUserApplet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -19,6 +26,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -35,6 +43,7 @@
    @BeforeEach
    void setUp() {
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), TChargingOrder.class);
        service = spy(new TChargingOrderServiceImpl());
        TokenService tokenService = mock(TokenService.class);
        icbcPaymentClient = mock(IcbcPaymentClient.class);
@@ -77,6 +86,56 @@
        verify(service, never()).chargingOrderCallback(eq(1), eq(ORDER_CODE), any(), eq("1"));
    }
    @Test
    void localBalancePaymentUsesSavedOrderWithoutImmediateReadBack() {
        TChargingOrderMapper mapper = mock(TChargingOrderMapper.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        TChargingOrder order = new TChargingOrder();
        order.setId(9001L);
        order.setCode("CD-BALANCE-WHITELIST");
        order.setRechargePaymentStatus(1);
        when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(0);
        AjaxResult result = service.completeChargingOrderPayment(order, "WHITELIST");
        assertEquals(200, result.get("code"));
        verify(service, never()).getOne(any(Wrapper.class));
        verify(mapper).update(isNull(), any(Wrapper.class));
    }
    @Test
    void internalMemberEightZheIsStoredAsPointEight() {
        TVip vip = new TVip();
        vip.setType(2);
        vip.setDiscount(new BigDecimal("8"));
        BigDecimal discount = service.resolveChargingVipDiscount(vip, null);
        assertEquals(0, discount.compareTo(new BigDecimal("0.8")));
    }
    @Test
    void regularMemberKeepsAccountingStrategyFactor() {
        TVip vip = new TVip();
        vip.setType(1);
        TAccountingStrategy strategy = new TAccountingStrategy();
        strategy.setDiscount(new BigDecimal("0.85"));
        BigDecimal discount = service.resolveChargingVipDiscount(vip, strategy);
        assertEquals(0, discount.compareTo(new BigDecimal("0.85")));
    }
    @Test
    void zeroDiscountAmountDoesNotConsumeMemberUse() {
        assertFalse(service.shouldConsumeVipDiscount(BigDecimal.ZERO));
    }
    @Test
    void positiveDiscountAmountConsumesMemberUse() {
        assertTrue(service.shouldConsumeVipDiscount(new BigDecimal("1.25")));
    }
    private TChargingOrder pendingIcbcOrder() {
        TChargingOrder order = new TChargingOrder();
        order.setCode(ORDER_CODE);