puzhibing
2026-09-23 488f2ca889255b55c085d3acc02ed1dd0e029a66
新增加功能
1个文件已删除
18个文件已添加
3个文件已修改
946 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/dto/TBillingTemplateDTO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/model/TBillingTemplate.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/model/TBillingTemplateDetail.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/dto/FleetExcelDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/dto/FleetExportDTO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/dto/FleetImportDTO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/model/TCompanyRechargeRecord.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TBillingTemplateController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/mapper/TBillingTemplateDetailMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/mapper/TBillingTemplateMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TBillingTemplateService.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TBillingTemplateServiceImpl.java 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/TBillingTemplateDetailMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/TBillingTemplateMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TCompanyRechargeController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TFleetController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/mapper/TCompanyRechargeRecordMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TCompanyRechargeRecordService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TFleetService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TCompanyRechargeRecordServiceImpl.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TFleetServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TCompanyRechargeRecordMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/dto/TBillingTemplateDTO.java
New file
@@ -0,0 +1,25 @@
package com.ruoyi.chargingPile.api.dto;
import com.ruoyi.chargingPile.api.model.TBillingTemplate;
import com.ruoyi.chargingPile.api.model.TBillingTemplateDetail;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * <p>
 * 计费模板数据传输对象
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Data
@ApiModel(value = "TBillingTemplateDTO对象", description = "计费模板")
public class TBillingTemplateDTO extends TBillingTemplate {
    @ApiModelProperty(value = "计费模板明细")
    private List<TBillingTemplateDetail> details;
}
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/model/TBillingTemplate.java
New file
@@ -0,0 +1,47 @@
package com.ruoyi.chargingPile.api.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.web.domain.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
 * <p>
 * 计费模板(与计费策略 t_accounting_strategy 完全独立)
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_billing_template")
@ApiModel(value = "TBillingTemplate对象", description = "计费模板")
public class TBillingTemplate extends BasePojo {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty(value = "模板名称")
    @TableField("name")
    private String name;
    @ApiModelProperty(value = "模板描述")
    @TableField("description")
    private String description;
    @ApiModelProperty(value = "会员折扣")
    @TableField("discount")
    private BigDecimal discount;
}
ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/model/TBillingTemplateDetail.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.chargingPile.api.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.web.domain.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
 * <p>
 * 计费模板明细(与计费策略明细 t_accounting_strategy_detail 完全独立)
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_billing_template_detail")
@ApiModel(value = "TBillingTemplateDetail对象", description = "计费模板明细")
public class TBillingTemplateDetail extends BasePojo {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty(value = "模板id")
    @TableField("template_id")
    private Integer templateId;
    @ApiModelProperty(value = "阶段(1=尖阶段,2=峰阶段,3=平阶段,4=谷阶段)")
    @TableField("type")
    private Integer type;
    @ApiModelProperty(value = "开始时间")
    @TableField("start_time")
    private String startTime;
    @ApiModelProperty(value = "结束时间")
    @TableField("end_time")
    private String endTime;
    @ApiModelProperty(value = "电价")
    @TableField("electrovalence")
    private BigDecimal electrovalence;
    @ApiModelProperty(value = "服务费")
    @TableField("service_charge")
    private BigDecimal serviceCharge;
    @ApiModelProperty(value = "原价服务费")
    @TableField("cost_service_charge")
    private BigDecimal costServiceCharge;
}
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/dto/FleetExcelDTO.java
File was deleted
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/dto/FleetExportDTO.java
New file
@@ -0,0 +1,40 @@
package com.ruoyi.other.api.dto;
import com.ruoyi.common.core.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
/**
 * 车队导出 Excel DTO(列与车队列表字段一致)
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Data
public class FleetExportDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    @Excel(name = "车队名", width = 24)
    private String fleetName;
    @Excel(name = "所属公司", width = 24)
    private String companyName;
    @Excel(name = "VIN码车数", width = 12)
    private String vinCount;
    @Excel(name = "可充电站数", width = 12)
    private String stationCount;
    @Excel(name = "月充电", width = 12)
    private String monthCharge;
    @Excel(name = "当前可用余额", width = 14)
    private String availableBalance;
    @Excel(name = "车队状态", width = 14, readConverterExp = "1=启用,0=停用")
    private String status;
}
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/dto/FleetImportDTO.java
New file
@@ -0,0 +1,34 @@
package com.ruoyi.other.api.dto;
import com.ruoyi.common.core.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
/**
 * 车队导入 Excel DTO(模板下载、导入解析共用,列与添加车队表单字段一致)
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Data
public class FleetImportDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    @Excel(name = "公司名称", width = 24, prompt = "必填,系统内已存在的公司名称")
    private String companyName;
    @Excel(name = "车队名称", width = 24, prompt = "必填,最多128个字符")
    private String fleetName;
    @Excel(name = "联系人电话", width = 16)
    private String contactPhone;
    @Excel(name = "联系人", width = 16)
    private String contactName;
    @Excel(name = "车队状态", width = 14, readConverterExp = "1=启用,0=停用", combo = {"启用", "停用"})
    private String status;
}
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/model/TCompanyRechargeRecord.java
New file
@@ -0,0 +1,85 @@
package com.ruoyi.other.api.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.core.web.domain.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * 公司充值记录(公司管理-充值功能使用)
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_company_recharge_record")
@ApiModel(value = "TCompanyRechargeRecord对象", description = "公司充值记录")
public class TCompanyRechargeRecord extends BasePojo {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty(value = "公司ID")
    @TableField("company_id")
    private Integer companyId;
    @ApiModelProperty(value = "公司名称")
    @TableField(exist = false)
    private String companyName;
    @ApiModelProperty(value = "实付金额")
    @TableField("pay_amount")
    private BigDecimal payAmount;
    @ApiModelProperty(value = "赠送金额")
    @TableField("gift_amount")
    private BigDecimal giftAmount;
    @ApiModelProperty(value = "到账金额")
    @TableField("arrival_amount")
    private BigDecimal arrivalAmount;
    @ApiModelProperty(value = "优惠方式(1=不优惠 2=按比例优惠 3=按优惠单价收费)")
    @TableField("preferential_type")
    private Integer preferentialType;
    @ApiModelProperty(value = "服务费优惠比例(%,仅按比例优惠时使用)")
    @TableField("service_discount_rate")
    private BigDecimal serviceDiscountRate;
    @ApiModelProperty(value = "计费模板ID(仅按优惠单价收费时使用)")
    @TableField("billing_template_id")
    private Integer billingTemplateId;
    @ApiModelProperty(value = "计费模板名称")
    @TableField(exist = false)
    private String billingTemplateName;
    @ApiModelProperty(value = "操作人ID")
    @TableField("operator_id")
    private Long operatorId;
    @ApiModelProperty(value = "操作人名称")
    @TableField(exist = false)
    private String operatorName;
    @ApiModelProperty(value = "充值时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("recharge_time")
    private LocalDateTime rechargeTime;
}
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TBillingTemplateController.java
New file
@@ -0,0 +1,98 @@
package com.ruoyi.chargingPile.controller;
import com.ruoyi.chargingPile.api.dto.TBillingTemplateDTO;
import com.ruoyi.chargingPile.api.model.TBillingTemplate;
import com.ruoyi.chargingPile.service.TBillingTemplateService;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
 * <p>
 * 计费模板 前端控制器(与计费策略完全独立)
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@RestController
@RequestMapping("/t-billing-template")
public class TBillingTemplateController {
    @Resource
    private TBillingTemplateService billingTemplateService;
    /**
     * 分页列表
     */
    @GetMapping("/page")
    @ApiOperation(tags = {"管理后台-计费模板"}, value = "计费模板分页列表")
    public AjaxResult<PageInfo<TBillingTemplate>> page(@RequestParam(value = "current", defaultValue = "1") Integer current,
                                                       @RequestParam(value = "size", defaultValue = "10") Integer size,
                                                       @RequestParam(value = "name", required = false) String name) {
        return AjaxResult.success(billingTemplateService.pageList(current, size, name));
    }
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperation(tags = {"管理后台-计费模板"}, value = "计费模板详情")
    public AjaxResult<TBillingTemplateDTO> detail(@RequestParam("id") Integer id) {
        return AjaxResult.success(billingTemplateService.getDetail(id));
    }
    /**
     * 新增
     */
    @PostMapping("/add")
    @ApiOperation(tags = {"管理后台-计费模板"}, value = "新增计费模板")
    @Log(title = "【计费模板】新增计费模板", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody TBillingTemplateDTO dto) {
        billingTemplateService.addTemplate(dto);
        return AjaxResult.success(dto.getId());
    }
    /**
     * 编辑
     */
    @PostMapping("/edit")
    @ApiOperation(tags = {"管理后台-计费模板"}, value = "编辑计费模板")
    @Log(title = "【计费模板】编辑计费模板", businessType = BusinessType.UPDATE)
    public AjaxResult edit(@RequestBody TBillingTemplateDTO dto) {
        billingTemplateService.editTemplate(dto);
        return AjaxResult.success();
    }
    /**
     * 删除
     */
    @DeleteMapping("/del")
    @ApiOperation(tags = {"管理后台-计费模板"}, value = "删除计费模板")
    @Log(title = "【计费模板】删除计费模板", businessType = BusinessType.DELETE)
    public AjaxResult del(@RequestParam("id") Integer id) {
        billingTemplateService.deleteTemplate(id);
        return AjaxResult.success();
    }
    /**
     * 下拉选择列表(用于充值弹窗)
     */
    @GetMapping("/options")
    @ApiOperation(tags = {"管理后台-计费模板"}, value = "计费模板下拉列表")
    public AjaxResult<List<TBillingTemplate>> options() {
        return AjaxResult.success(billingTemplateService.options());
    }
}
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/mapper/TBillingTemplateDetailMapper.java
New file
@@ -0,0 +1,17 @@
package com.ruoyi.chargingPile.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.chargingPile.api.model.TBillingTemplateDetail;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * 计费模板明细 Mapper 接口
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Mapper
public interface TBillingTemplateDetailMapper extends BaseMapper<TBillingTemplateDetail> {
}
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/mapper/TBillingTemplateMapper.java
New file
@@ -0,0 +1,17 @@
package com.ruoyi.chargingPile.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.chargingPile.api.model.TBillingTemplate;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * 计费模板 Mapper 接口
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Mapper
public interface TBillingTemplateMapper extends BaseMapper<TBillingTemplate> {
}
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/TBillingTemplateService.java
New file
@@ -0,0 +1,47 @@
package com.ruoyi.chargingPile.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.chargingPile.api.dto.TBillingTemplateDTO;
import com.ruoyi.chargingPile.api.model.TBillingTemplate;
import com.ruoyi.common.core.web.page.PageInfo;
/**
 * <p>
 * 计费模板 服务类
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
public interface TBillingTemplateService extends IService<TBillingTemplate> {
    /**
     * 计费模板分页列表
     */
    PageInfo<TBillingTemplate> pageList(Integer current, Integer size, String name);
    /**
     * 计费模板详情(含明细)
     */
    TBillingTemplateDTO getDetail(Integer id);
    /**
     * 新增计费模板(主表 + 明细)
     */
    void addTemplate(TBillingTemplateDTO dto);
    /**
     * 编辑计费模板(主表 + 明细全量替换)
     */
    void editTemplate(TBillingTemplateDTO dto);
    /**
     * 删除计费模板(主表 + 明细)
     */
    void deleteTemplate(Integer id);
    /**
     * 下拉列表(仅启用数据)
     */
    java.util.List<TBillingTemplate> options();
}
ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/TBillingTemplateServiceImpl.java
New file
@@ -0,0 +1,161 @@
package com.ruoyi.chargingPile.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.chargingPile.api.dto.TBillingTemplateDTO;
import com.ruoyi.chargingPile.api.model.TBillingTemplate;
import com.ruoyi.chargingPile.api.model.TBillingTemplateDetail;
import com.ruoyi.chargingPile.mapper.TBillingTemplateDetailMapper;
import com.ruoyi.chargingPile.mapper.TBillingTemplateMapper;
import com.ruoyi.chargingPile.service.TBillingTemplateService;
import com.ruoyi.common.core.web.page.PageInfo;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * <p>
 * 计费模板 服务实现类
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Service
public class TBillingTemplateServiceImpl extends ServiceImpl<TBillingTemplateMapper, TBillingTemplate> implements TBillingTemplateService {
    @Resource
    private TBillingTemplateDetailMapper detailMapper;
    @Override
    public PageInfo<TBillingTemplate> pageList(Integer current, Integer size, String name) {
        Page<TBillingTemplate> page = new Page<>(current, size);
        LambdaQueryWrapper<TBillingTemplate> wrapper = Wrappers.lambdaQuery(TBillingTemplate.class)
                .eq(TBillingTemplate::getDelFlag, false)
                .like(StringUtils.hasText(name), TBillingTemplate::getName, name)
                .orderByDesc(TBillingTemplate::getId);
        Page<TBillingTemplate> result = this.page(page, wrapper);
        PageInfo<TBillingTemplate> pageInfo = new PageInfo<>(result.getCurrent(), result.getSize());
        pageInfo.setRecords(result.getRecords());
        pageInfo.setTotal(result.getTotal());
        return pageInfo;
    }
    @Override
    public TBillingTemplateDTO getDetail(Integer id) {
        if (id == null) {
            return new TBillingTemplateDTO();
        }
        TBillingTemplate template = this.getById(id);
        if (template == null) {
            return new TBillingTemplateDTO();
        }
        TBillingTemplateDTO dto = new TBillingTemplateDTO();
        BeanUtils.copyProperties(template, dto);
        List<TBillingTemplateDetail> details = detailMapper.selectList(
                Wrappers.lambdaQuery(TBillingTemplateDetail.class)
                        .eq(TBillingTemplateDetail::getTemplateId, id)
                        .eq(TBillingTemplateDetail::getDelFlag, false));
        dto.setDetails(details);
        return dto;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addTemplate(TBillingTemplateDTO dto) {
        validateDetails(dto.getDetails());
        dto.setId(null);
        dto.setDelFlag(false);
        this.save(dto);
        bindAndSaveDetails(dto.getId(), dto.getDetails());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editTemplate(TBillingTemplateDTO dto) {
        if (dto.getId() == null) {
            throw new IllegalArgumentException("模板ID不能为空");
        }
        validateDetails(dto.getDetails());
        TBillingTemplate update = new TBillingTemplate();
        BeanUtils.copyProperties(dto, update, "details", "delFlag");
        this.updateById(update);
        detailMapper.delete(Wrappers.lambdaQuery(TBillingTemplateDetail.class).eq(TBillingTemplateDetail::getTemplateId, dto.getId()));
        bindAndSaveDetails(dto.getId(), dto.getDetails());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTemplate(Integer id) {
        if (id == null) {
            throw new IllegalArgumentException("模板ID不能为空");
        }
        this.removeById(id);
        detailMapper.delete(Wrappers.lambdaQuery(TBillingTemplateDetail.class).eq(TBillingTemplateDetail::getTemplateId, id));
    }
    @Override
    public List<TBillingTemplate> options() {
        return this.list(Wrappers.lambdaQuery(TBillingTemplate.class).eq(TBillingTemplate::getDelFlag, false).orderByDesc(TBillingTemplate::getId));
    }
    private void bindAndSaveDetails(Integer templateId, List<TBillingTemplateDetail> details) {
        if (CollectionUtils.isEmpty(details)) {
            return;
        }
        for (TBillingTemplateDetail d : details) {
            d.setId(null);
            d.setTemplateId(templateId);
            d.setDelFlag(false);
            detailMapper.insert(d);
        }
    }
    private void validateDetails(List<TBillingTemplateDetail> details) {
        if (CollectionUtils.isEmpty(details)) {
            throw new IllegalArgumentException("请填写费率明细");
        }
        // 时间段连续性
        List<TBillingTemplateDetail> sorted = details.stream()
                .sorted(Comparator.comparing(TBillingTemplateDetail::getStartTime))
                .collect(Collectors.toList());
        boolean firstValid = sorted.isEmpty() || !sorted.get(0).getStartTime().equals(sorted.get(sorted.size() - 1).getEndTime());
        boolean continuous = sorted.stream()
                .skip(1)
                .allMatch(d -> d.getStartTime().equals(sorted.get(sorted.indexOf(d) - 1).getEndTime()));
        if (firstValid || !continuous) {
            throw new IllegalArgumentException("计费模板时间设置有误差");
        }
        // 同阶段价格一致
        Map<Integer, BigDecimal> phaseElectrovalence = new HashMap<>();
        Map<Integer, BigDecimal> phaseServiceFee = new HashMap<>();
        Map<Integer, BigDecimal> phaseCostServiceFee = new HashMap<>();
        for (TBillingTemplateDetail d : details) {
            Integer type = d.getType();
            if (phaseElectrovalence.containsKey(type)) {
                if (phaseElectrovalence.get(type).compareTo(d.getElectrovalence()) != 0
                        || phaseServiceFee.get(type).compareTo(d.getServiceCharge()) != 0
                        || phaseCostServiceFee.get(type).compareTo(d.getCostServiceCharge()) != 0) {
                    throw new IllegalArgumentException("同阶段费率值需一致");
                }
            } else {
                phaseElectrovalence.put(type, d.getElectrovalence());
                phaseServiceFee.put(type, d.getServiceCharge());
                phaseCostServiceFee.put(type, d.getCostServiceCharge());
            }
        }
    }
}
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/TBillingTemplateDetailMapper.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.chargingPile.mapper.TBillingTemplateDetailMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.chargingPile.api.model.TBillingTemplateDetail">
        <id column="id" property="id" />
        <result column="template_id" property="templateId" />
        <result column="`type`" property="type" />
        <result column="start_time" property="startTime" />
        <result column="end_time" property="endTime" />
        <result column="electrovalence" property="electrovalence" />
        <result column="service_charge" property="serviceCharge" />
        <result column="cost_service_charge" property="costServiceCharge" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, template_id, `type`, start_time, end_time, electrovalence, service_charge, cost_service_charge
    </sql>
</mapper>
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/TBillingTemplateMapper.xml
New file
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.chargingPile.mapper.TBillingTemplateMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.chargingPile.api.model.TBillingTemplate">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="description" property="description" />
        <result column="discount" property="discount" />
        <result column="create_time" property="createTime" />
        <result column="del_flag" property="delFlag" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, description, discount, create_time, del_flag
    </sql>
</mapper>
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TCompanyRechargeController.java
New file
@@ -0,0 +1,58 @@
package com.ruoyi.other.controller;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.Logical;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.other.api.model.TCompanyRechargeRecord;
import com.ruoyi.other.service.TCompanyRechargeRecordService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * <p>
 * 公司充值 前端控制器(公司管理-充值功能使用)
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@RestController
@RequestMapping("/company-recharge")
public class TCompanyRechargeController {
    @Resource
    private TCompanyRechargeRecordService rechargeRecordService;
    /**
     * 公司充值
     */
    @RequiresPermissions(value = {"/companyManagement/list"}, logical = Logical.OR)
    @PostMapping("/recharge")
    @ApiOperation(tags = {"管理后台-公司管理"}, value = "公司充值")
    @Log(title = "【公司管理】公司充值", businessType = BusinessType.INSERT)
    public AjaxResult recharge(@RequestBody TCompanyRechargeRecord record) {
        return AjaxResult.success(rechargeRecordService.recharge(record));
    }
    /**
     * 充值记录分页
     */
    @RequiresPermissions(value = {"/companyManagement/list"}, logical = Logical.OR)
    @GetMapping("/page")
    @ApiOperation(tags = {"管理后台-公司管理"}, value = "公司充值记录分页")
    public AjaxResult<PageInfo<TCompanyRechargeRecord>> page(@RequestParam(value = "current", defaultValue = "1") Integer current,
                                                             @RequestParam(value = "size", defaultValue = "10") Integer size,
                                                             @RequestParam(value = "companyId", required = false) Integer companyId) {
        return AjaxResult.success(rechargeRecordService.pageList(current, size, companyId, null));
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TFleetController.java
@@ -9,7 +9,8 @@
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.other.api.domain.TFleet;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.api.dto.FleetExcelDTO;
import com.ruoyi.other.api.dto.FleetExportDTO;
import com.ruoyi.other.api.dto.FleetImportDTO;
import com.ruoyi.other.api.dto.FleetQueryDTO;
import com.ruoyi.other.service.TFleetService;
import com.ruoyi.other.service.TGroupCompanyService;
@@ -160,7 +161,7 @@
    @ApiOperation(value = "导入车队", tags = {"管理后台-车队管理"})
    @Log(title = "【车队管理】导入车队", businessType = BusinessType.IMPORT)
    public AjaxResult<String> importData(@RequestPart("file") MultipartFile file) throws Exception {
        List<FleetExcelDTO> items = new ExcelUtil<>(FleetExcelDTO.class).importExcel(file.getInputStream());
        List<FleetImportDTO> items = new ExcelUtil<>(FleetImportDTO.class).importExcel(file.getInputStream());
        return AjaxResult.success(fleetService.importFleets(items));
    }
@@ -171,7 +172,7 @@
    @GetMapping("/import-template")
    @ApiOperation(value = "下载车队导入模板", tags = {"管理后台-车队管理"})
    public void template(HttpServletResponse response) {
        new ExcelUtil<>(FleetExcelDTO.class).importTemplateExcel(response, "车队导入模板");
        new ExcelUtil<>(FleetImportDTO.class).importTemplateExcel(response, "车队导入模板");
    }
    /**
@@ -182,7 +183,7 @@
    @ApiOperation(value = "导出车队", tags = {"管理后台-车队管理"})
    @Log(title = "【车队管理】导出车队", businessType = BusinessType.EXPORT)
    public void export(HttpServletResponse response, @RequestBody(required = false) FleetQueryDTO query) {
        new ExcelUtil<>(FleetExcelDTO.class).exportExcel(response, fleetService.selectExportList(query), "车队数据");
        new ExcelUtil<>(FleetExportDTO.class).exportExcel(response, fleetService.selectExportList(query), "车队数据");
    }
    /**
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/mapper/TCompanyRechargeRecordMapper.java
New file
@@ -0,0 +1,17 @@
package com.ruoyi.other.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.other.api.model.TCompanyRechargeRecord;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * 公司充值记录 Mapper 接口
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Mapper
public interface TCompanyRechargeRecordMapper extends BaseMapper<TCompanyRechargeRecord> {
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TCompanyRechargeRecordService.java
New file
@@ -0,0 +1,28 @@
package com.ruoyi.other.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.other.api.model.TCompanyRechargeRecord;
import com.ruoyi.common.core.web.page.PageInfo;
/**
 * <p>
 * 公司充值记录 服务类
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
public interface TCompanyRechargeRecordService extends IService<TCompanyRechargeRecord> {
    /**
     * 公司充值(更新公司余额 + 保存记录)
     *
     * @return 充值记录主键
     */
    Integer recharge(TCompanyRechargeRecord record);
    /**
     * 充值记录分页
     */
    PageInfo<TCompanyRechargeRecord> pageList(Integer current, Integer size, Integer companyId, String companyName);
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TFleetService.java
@@ -2,7 +2,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.other.api.domain.TFleet;
import com.ruoyi.other.api.dto.FleetExcelDTO;
import com.ruoyi.other.api.dto.FleetExportDTO;
import com.ruoyi.other.api.dto.FleetImportDTO;
import com.ruoyi.other.api.dto.FleetQueryDTO;
import java.util.List;
@@ -23,7 +24,7 @@
     * @param items Excel 解析出的车队数据
     * @return 导入结果提示
     */
    String importFleets(List<FleetExcelDTO> items);
    String importFleets(List<FleetImportDTO> items);
    /**
     * 按条件查询车队导出列表(含公司名称回填)
@@ -31,6 +32,6 @@
     * @param query 导出查询条件
     * @return 导出数据
     */
    List<FleetExcelDTO> selectExportList(FleetQueryDTO query);
    List<FleetExportDTO> selectExportList(FleetQueryDTO query);
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TCompanyRechargeRecordServiceImpl.java
New file
@@ -0,0 +1,82 @@
package com.ruoyi.other.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.api.model.TCompanyRechargeRecord;
import com.ruoyi.other.mapper.TCompanyRechargeRecordMapper;
import com.ruoyi.other.mapper.TGroupCompanyMapper;
import com.ruoyi.other.service.TCompanyRechargeRecordService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * 公司充值记录 服务实现类
 * </p>
 *
 * @author mxcd
 * @since 2026-09-23
 */
@Service
public class TCompanyRechargeRecordServiceImpl extends ServiceImpl<TCompanyRechargeRecordMapper, TCompanyRechargeRecord> implements TCompanyRechargeRecordService {
    @Resource
    private TGroupCompanyMapper groupCompanyMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer recharge(TCompanyRechargeRecord record) {
        if (record.getCompanyId() == null) {
            throw new IllegalArgumentException("公司ID不能为空");
        }
        if (record.getPayAmount() == null || record.getPayAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("实付金额不能为空且必须大于等于0");
        }
        BigDecimal giftAmount = record.getGiftAmount() == null ? BigDecimal.ZERO : record.getGiftAmount();
        if (giftAmount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("赠送金额不能小于0");
        }
        TGroupCompany company = groupCompanyMapper.selectById(record.getCompanyId());
        if (company == null) {
            throw new IllegalArgumentException("公司不存在");
        }
        BigDecimal arrival = record.getPayAmount().add(giftAmount);
        record.setArrivalAmount(arrival);
        record.setId(null);
        record.setDelFlag(false);
        record.setRechargeTime(LocalDateTime.now());
        try {
            record.setOperatorId(SecurityUtils.getLoginUser().getUserid());
        } catch (Exception ignored) {
        }
        // 更新公司余额(原子加):拼接 toString 防止 SQL 注入,BigDecimal 是受控数值
        groupCompanyMapper.update(null, Wrappers.lambdaUpdate(TGroupCompany.class)
                .eq(TGroupCompany::getId, record.getCompanyId())
                .setSql("available_balance = IFNULL(available_balance, 0) + " + arrival.toPlainString()));
        this.save(record);
        return record.getId();
    }
    @Override
    public PageInfo<TCompanyRechargeRecord> pageList(Integer current, Integer size, Integer companyId, String companyName) {
        Page<TCompanyRechargeRecord> page = new Page<>(current, size);
        LambdaQueryWrapper<TCompanyRechargeRecord> wrapper = Wrappers.lambdaQuery(TCompanyRechargeRecord.class)
                .eq(TCompanyRechargeRecord::getDelFlag, false)
                .eq(companyId != null, TCompanyRechargeRecord::getCompanyId, companyId)
                .orderByDesc(TCompanyRechargeRecord::getId);
        Page<TCompanyRechargeRecord> result = this.page(page, wrapper);
        PageInfo<TCompanyRechargeRecord> pageInfo = new PageInfo<>(result.getCurrent(), result.getSize());
        pageInfo.setRecords(result.getRecords());
        pageInfo.setTotal(result.getTotal());
        return pageInfo;
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TFleetServiceImpl.java
@@ -4,7 +4,8 @@
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.other.api.domain.TFleet;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.api.dto.FleetExcelDTO;
import com.ruoyi.other.api.dto.FleetExportDTO;
import com.ruoyi.other.api.dto.FleetImportDTO;
import com.ruoyi.other.api.dto.FleetQueryDTO;
import com.ruoyi.other.mapper.TFleetMapper;
import com.ruoyi.other.service.TFleetService;
@@ -43,14 +44,14 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String importFleets(List<FleetExcelDTO> items) {
    public String importFleets(List<FleetImportDTO> items) {
        if (items == null || items.isEmpty()) {
            throw new ServiceException("导入数据不能为空");
        }
        Set<String> fleetNames = new HashSet<>();
        Set<String> companyNames = new HashSet<>();
        int row = 1;
        for (FleetExcelDTO item : items) {
        for (FleetImportDTO item : items) {
            row++;
            if (item == null || !StringUtils.hasText(item.getFleetName())) {
                throw new ServiceException("第" + row + "行车队名称不能为空");
@@ -75,7 +76,7 @@
                .stream()
                .collect(Collectors.toMap(TGroupCompany::getName, TGroupCompany::getId, (a, b) -> a));
        row = 1;
        for (FleetExcelDTO item : items) {
        for (FleetImportDTO item : items) {
            row++;
            String companyName = item.getCompanyName().trim();
            if (!companyMap.containsKey(companyName)) {
@@ -83,13 +84,12 @@
            }
        }
        LocalDateTime now = LocalDateTime.now();
        for (FleetExcelDTO item : items) {
        for (FleetImportDTO item : items) {
            TFleet fleet = new TFleet();
            fleet.setCompanyId(companyMap.get(item.getCompanyName().trim()));
            fleet.setFleetName(item.getFleetName().trim());
            fleet.setContactName(trim(item.getContactName()));
            fleet.setContactPhone(trim(item.getContactPhone()));
            fleet.setRemark(trim(item.getRemark()));
            fleet.setStatus(parseStatus(item.getStatus(), 0));
            fleet.setDelFlag(false);
            fleet.setCreateTime(now);
@@ -99,7 +99,7 @@
    }
    @Override
    public List<FleetExcelDTO> selectExportList(FleetQueryDTO query) {
    public List<FleetExportDTO> selectExportList(FleetQueryDTO query) {
        FleetQueryDTO safe = query == null ? new FleetQueryDTO() : query;
        List<TFleet> fleets = lambdaQuery()
                .like(StringUtils.hasText(safe.getFleetName()), TFleet::getFleetName, safe.getFleetName())
@@ -118,13 +118,11 @@
                : groupCompanyService.listByIds(companyIds).stream()
                        .collect(Collectors.toMap(TGroupCompany::getId, TGroupCompany::getName, (a, b) -> a));
        return fleets.stream().map(item -> {
            FleetExcelDTO dto = new FleetExcelDTO();
            FleetExportDTO dto = new FleetExportDTO();
            dto.setFleetName(item.getFleetName());
            dto.setCompanyName(nameMap.get(item.getCompanyId()));
            dto.setContactName(item.getContactName());
            dto.setContactPhone(item.getContactPhone());
            // VIN码车数/可充电站数/月充电/当前可用余额后端暂无数据源,导出留空,与列表"-"占位一致
            dto.setStatus(item.getStatus() == null ? null : String.valueOf(item.getStatus()));
            dto.setRemark(item.getRemark());
            return dto;
        }).collect(Collectors.toList());
    }
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TCompanyRechargeRecordMapper.xml
New file
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.other.mapper.TCompanyRechargeRecordMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.other.api.model.TCompanyRechargeRecord">
        <id column="id" property="id" />
        <result column="company_id" property="companyId" />
        <result column="pay_amount" property="payAmount" />
        <result column="gift_amount" property="giftAmount" />
        <result column="arrival_amount" property="arrivalAmount" />
        <result column="preferential_type" property="preferentialType" />
        <result column="service_discount_rate" property="serviceDiscountRate" />
        <result column="billing_template_id" property="billingTemplateId" />
        <result column="operator_id" property="operatorId" />
        <result column="recharge_time" property="rechargeTime" />
        <result column="create_time" property="createTime" />
        <result column="del_flag" property="delFlag" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, company_id, pay_amount, gift_amount, arrival_amount, preferential_type,
        service_discount_rate, billing_template_id, operator_id, recharge_time, create_time, del_flag
    </sql>
</mapper>