From 9af2c277033957c095d1090c8e6f77e54b86172f Mon Sep 17 00:00:00 2001
From: shinnosuke <tantao@1hoper.com>
Date: 星期四, 02 七月 2026 16:58:59 +0800
Subject: [PATCH] 新增企业用户充电分析功能页(包含充电明细);新增系统用户管理中的重置操作密码功能;在角色管理中增加独立合作商角色;角色管理中的添加角色操作新增数据驾驶舱和视频监控入口(需在角色管理员有按钮权限控制)。

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java |   57 +++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java            |   11 
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysRoleController.java         |  124 +++++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisQuery.java    |   66 ++++
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java                   |   22 +
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RoleInfoVo.java                 |    6 
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java         |   93 +++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisVO.java       |   45 +++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java          |    5 
 ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml                  |  180 +++++++++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingDetailVO.java         |   73 ++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java  |   62 ++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java    |   99 ++++++
 13 files changed, 831 insertions(+), 12 deletions(-)

diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java
index a1ef53b..c419530 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java
@@ -73,6 +73,12 @@
     
     @TableField(exist = false)
     private Long number;
+
+    @TableField(exist = false)
+    private Boolean systemRole;
+
+    @TableField(exist = false)
+    private Boolean partnerRole;
     
     
     public SysRole()
@@ -214,6 +220,22 @@
     public void setNumber(Long number) {
         this.number = number;
     }
+
+    public Boolean getSystemRole() {
+        return systemRole;
+    }
+
+    public void setSystemRole(Boolean systemRole) {
+        this.systemRole = systemRole;
+    }
+
+    public Boolean getPartnerRole() {
+        return partnerRole;
+    }
+
+    public void setPartnerRole(Boolean partnerRole) {
+        this.partnerRole = partnerRole;
+    }
     
     @Override
     public String toString() {
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysRoleController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysRoleController.java
index a73f98b..c82449f 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysRoleController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysRoleController.java
@@ -3,7 +3,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 import javax.management.relation.Role;
@@ -61,6 +63,11 @@
 @RequestMapping("/role")
 @Api(tags = "角色模块")
 public class SysRoleController extends BaseController {
+	private static final String INDEPENDENT_PARTNER_ROLE_NAME = "独立合作商";
+	private static final String INDEPENDENT_PARTNER_ROLE_KEY = "independent_partner";
+	private static final String OWN_SITE_NAME = "自有电站";
+	private static final String SYSTEM_ROLE_REMARK = "系统内置角色";
+
 	@Resource
 	private ISysRoleService roleService;
 	
@@ -84,6 +91,88 @@
 	
 	@Resource
 	private SiteClient siteClient;
+	private void ensureIndependentPartnerRole() {
+		SysRole role = roleService.getOne(new LambdaQueryWrapper<SysRole>()
+				.eq(SysRole::getRoleKey, INDEPENDENT_PARTNER_ROLE_KEY)
+				.eq(SysRole::getDelFlag, 0)
+				.last("LIMIT 1"));
+		if (role == null) {
+			role = roleService.getOne(new LambdaQueryWrapper<SysRole>()
+					.eq(SysRole::getRoleName, INDEPENDENT_PARTNER_ROLE_NAME)
+					.eq(SysRole::getDelFlag, 0)
+					.last("LIMIT 1"));
+		}
+		if (role != null) {
+			return;
+		}
+
+		SysRole newRole = new SysRole();
+		newRole.setRoleName(INDEPENDENT_PARTNER_ROLE_NAME);
+		newRole.setRoleKey(INDEPENDENT_PARTNER_ROLE_KEY);
+		newRole.setDataScope("5");
+		newRole.setStatus("0");
+		newRole.setDelFlag("0");
+		newRole.setRemark(SYSTEM_ROLE_REMARK);
+		newRole.setCreateBy(SecurityUtils.getUsername());
+		newRole.setCreateTime(new Date());
+		Set<Long> menuIds = getIndependentPartnerMenuIds();
+		newRole.setMenuIds(menuIds.toArray(new Long[0]));
+		roleService.insertRole(newRole);
+	}
+
+	private Set<Long> getIndependentPartnerMenuIds() {
+		List<SysMenus> menus = menuMapper.getAll();
+		Set<Long> menuIds = new HashSet<>();
+		Set<Long> rootIds = new HashSet<>();
+		for (SysMenus menu : menus) {
+			String path = menu.getPath() == null ? "" : menu.getPath();
+			String menuName = menu.getMenuName() == null ? "" : menu.getMenuName();
+			if ("standingPile".equals(path) || "order".equals(path)
+					|| menuName.contains("站桩管理") || menuName.contains("订单管理")) {
+				rootIds.add(menu.getMenuId());
+			}
+		}
+		for (Long rootId : rootIds) {
+			collectMenuTreeIds(rootId, menus, menuIds);
+		}
+		return menuIds;
+	}
+
+	private void collectMenuTreeIds(Long menuId, List<SysMenus> menus, Set<Long> menuIds) {
+		if (menuId == null || menuIds.contains(menuId)) {
+			return;
+		}
+		menuIds.add(menuId);
+		for (SysMenus menu : menus) {
+			if (menuId.equals(menu.getParentId())) {
+				collectMenuTreeIds(menu.getMenuId(), menus, menuIds);
+			}
+		}
+	}
+
+	private boolean isIndependentPartnerRole(SysRole role) {
+		return role != null && (INDEPENDENT_PARTNER_ROLE_KEY.equals(role.getRoleKey())
+				|| INDEPENDENT_PARTNER_ROLE_NAME.equals(role.getRoleName()));
+	}
+
+	private boolean isSystemBuiltinRole(SysRole role) {
+		return role != null && (role.isAdmin() || isIndependentPartnerRole(role));
+	}
+
+	private void fillRoleV2Info(SysRole role) {
+		if (role == null) {
+			return;
+		}
+		boolean partnerRole = isIndependentPartnerRole(role);
+		role.setPartnerRole(partnerRole);
+		role.setSystemRole(isSystemBuiltinRole(role));
+		if (partnerRole) {
+			role.setSiteNames(Arrays.asList(OWN_SITE_NAME));
+			if (StringUtils.isEmpty(role.getRemark())) {
+				role.setRemark(SYSTEM_ROLE_REMARK);
+			}
+		}
+	}
 	
 	
 	
@@ -102,6 +191,7 @@
 	@GetMapping("/listPage")
 	@ApiOperation(value = "获取角色列表", tags = {"管理后台-角色管理"})
 	public AjaxResult listPage(String name, BasePage basePage) {
+		ensureIndependentPartnerRole();
 		PageInfo<SysRole> pageInfo = new PageInfo<>(basePage.getPageCurr(), basePage.getPageSize());
 		LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<SysRole>().eq(SysRole::getStatus, 0).eq(SysRole::getDelFlag, 0);
 		if (StringUtils.isNotEmpty(name)) {
@@ -118,6 +208,7 @@
 			}
 			long count = sysUserRoleService.count(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getRoleId, record.getRoleId()));
 			record.setNumber(count);
+			fillRoleV2Info(record);
 		}
 		return AjaxResult.success(page);
 	}
@@ -169,6 +260,9 @@
 		RoleInfoVo roleInfoVo = new RoleInfoVo();
 		roleInfoVo.setRoleId(role.getRoleId());
 		roleInfoVo.setRoleName(role.getRoleName());
+		roleInfoVo.setRoleKey(role.getRoleKey());
+		roleInfoVo.setPartnerRole(isIndependentPartnerRole(role));
+		roleInfoVo.setSystemRole(isSystemBuiltinRole(role));
 		// 获取当前角色的菜单id
 		List<Long> menusId = sysRoleMenuMapper.selectList(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, id)).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
 		if (menusId.size() == 0) {
@@ -194,12 +288,20 @@
 		}
 		roleInfoVo.setMenus(menusId);
 		roleInfoVo.setRemark(role.getRemark());
-		List<Integer> siteIds = roleSiteClient.getSiteIds(role.getRoleId()).getData();
-		if (null != siteIds && siteIds.size() > 0) {
-			List<Site> sites = siteClient.getSiteByIds(siteIds).getData();
-			List<String> siteNames = sites.stream().map(Site::getName).collect(Collectors.toList());
-			roleInfoVo.setSiteNames(siteNames);
-			roleInfoVo.setSiteIds(siteIds);
+		if (isIndependentPartnerRole(role)) {
+			roleInfoVo.setSiteNames(Arrays.asList(OWN_SITE_NAME));
+			roleInfoVo.setSiteIds(new ArrayList<>());
+			if (StringUtils.isEmpty(roleInfoVo.getRemark())) {
+				roleInfoVo.setRemark(SYSTEM_ROLE_REMARK);
+			}
+		} else {
+			List<Integer> siteIds = roleSiteClient.getSiteIds(role.getRoleId()).getData();
+			if (null != siteIds && siteIds.size() > 0) {
+				List<Site> sites = siteClient.getSiteByIds(siteIds).getData();
+				List<String> siteNames = sites.stream().map(Site::getName).collect(Collectors.toList());
+				roleInfoVo.setSiteNames(siteNames);
+				roleInfoVo.setSiteIds(siteIds);
+			}
 		}
 		return AjaxResult.success(roleInfoVo);
 	}
@@ -209,6 +311,10 @@
 	@PostMapping("/roleUpdate")
 	@ApiOperation(value = "编辑角色", tags = {"管理后台-角色管理"})
 	public AjaxResult roleUpdate(@Validated @RequestBody RoleUpdateDto dto) {
+		SysRole oldRole = roleService.selectRoleById(dto.getRoleId());
+		if (isSystemBuiltinRole(oldRole)) {
+			return AjaxResult.error("系统内置角色不可编辑");
+		}
 		SysRole role = new SysRole();
 		role.setRoleName(dto.getRoleName());
 		SysRole one = roleService.getOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, dto.getRoleName()));
@@ -269,6 +375,12 @@
 	@DeleteMapping("/{roleIds}")
 	@ApiOperation(value = "删除角色", tags = {"管理后台-角色管理"})
 	public AjaxResult remove(@PathVariable Long[] roleIds) {
+		for (Long roleId : roleIds) {
+			SysRole role = roleService.selectRoleById(roleId);
+			if (isSystemBuiltinRole(role)) {
+				return AjaxResult.error("系统内置角色不可删除");
+			}
+		}
 		return toAjax(roleService.deleteRoleByIds(roleIds));
 	}
 	
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
index 849327f..d94a6e6 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
@@ -1,6 +1,7 @@
 package com.ruoyi.system.controller;
 
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 
@@ -8,8 +9,12 @@
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ruoyi.chargingPile.api.feignClient.SiteClient;
 import com.ruoyi.chargingPile.api.model.Site;
+import com.ruoyi.common.core.constant.MsgConstants;
+import com.ruoyi.common.core.utils.HttpUtils;
+import com.ruoyi.common.core.utils.MsgUtil;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.core.web.page.PageInfo;
+import com.ruoyi.common.redis.service.RedisService;
 import com.ruoyi.common.security.service.TokenService;
 import com.ruoyi.common.security.annotation.Logical;
 import com.ruoyi.other.api.domain.TUserSite;
@@ -48,6 +53,9 @@
 @RequestMapping("/user")
 @Api(tags = "用户信息")
 public class SysUserController extends BaseController {
+    private static final String RESET_PWD_CODE_KEY = "system:user:resetPwd:code:";
+    private static final String RESET_PWD_LIMIT_KEY = "system:user:resetPwd:limit:";
+
     @Resource
     private ISysUserService userService;
 
@@ -80,6 +88,9 @@
     
     @Resource
     private SiteClient siteClient;
+
+    @Resource
+    private RedisService redisService;
     
     
     
@@ -541,18 +552,92 @@
 
 
     /**
+     * 发送重置密码验证码
+     */
+    @RequiresPermissions(value = {"/systemUserManagement/reset_passwords"}, logical = Logical.OR)
+    @PostMapping("/resetPwd/sendCode")
+    @ApiOperation(value = "发送重置密码验证码", tags = {"管理后台-系统用户管理"})
+    public AjaxResult sendResetPwdCode(@RequestBody Map<String, Object> params) {
+        Long userId = parseLong(params.get("userId"));
+        if (userId == null) {
+            return AjaxResult.error("用户不存在");
+        }
+        SysUser user = userService.selectUserById(userId);
+        if (user == null) {
+            return AjaxResult.error("用户不存在");
+        }
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(userId);
+        if (StringUtils.isEmpty(user.getPhonenumber())) {
+            return AjaxResult.error("当前用户未维护手机号");
+        }
+
+        String limitKey = RESET_PWD_LIMIT_KEY + userId;
+        if (Boolean.TRUE.equals(redisService.hasKey(limitKey))) {
+            return AjaxResult.error("验证码发送过于频繁,请30秒后再试");
+        }
+
+        String code = MsgUtil.createCode();
+        String reqStr = MsgUtil.codeMsg(user.getPhonenumber(), code);
+        HttpUtils.post(MsgConstants.SEND_URL, reqStr);
+
+        redisService.setCacheObject(RESET_PWD_CODE_KEY + userId, code, 5L, TimeUnit.MINUTES);
+        redisService.setCacheObject(limitKey, "1", 30L, TimeUnit.SECONDS);
+        return AjaxResult.success("验证码已发送");
+    }
+
+    /**
      * 重置密码
      */
     @RequiresPermissions(value = {"/systemUserManagement/reset_passwords"}, logical = Logical.OR)
     @Log(title = "用户管理", businessType = BusinessType.UPDATE)
     @PutMapping("/resetPwd")
     @ApiOperation(value = "重置密码", tags = {"管理后台-系统用户管理"})
-    public AjaxResult resetPwd(@RequestBody SysUser user) {
+    public AjaxResult resetPwd(@RequestBody Map<String, Object> params) {
+        Long userId = parseLong(params.get("userId"));
+        String password = parseString(params.get("password"));
+        String code = parseString(params.get("code"));
+        if (userId == null) {
+            return AjaxResult.error("用户不存在");
+        }
+        if (StringUtils.isEmpty(password)) {
+            return AjaxResult.error("请输入新密码");
+        }
+        if (StringUtils.isEmpty(code)) {
+            return AjaxResult.error("请输入验证码");
+        }
+
+        SysUser user = userService.selectUserById(userId);
+        if (user == null) {
+            return AjaxResult.error("用户不存在");
+        }
         userService.checkUserAllowed(user);
-        userService.checkUserDataScope(user.getUserId());
-        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        userService.checkUserDataScope(userId);
+
+        String cacheCode = redisService.getCacheObject(RESET_PWD_CODE_KEY + userId);
+        if (StringUtils.isEmpty(cacheCode)) {
+            return AjaxResult.error("验证码已过期,请重新获取验证码");
+        }
+        if (!code.equals(cacheCode)) {
+            return AjaxResult.error("验证码错误");
+        }
+
+        user.setPassword(SecurityUtils.encryptPassword(password));
         user.setUpdateBy(SecurityUtils.getUsername());
-        return toAjax(userService.resetPwd(user));
+        int result = userService.resetPwd(user);
+        redisService.deleteObject(RESET_PWD_CODE_KEY + userId);
+        return toAjax(result);
+    }
+
+    private Long parseLong(Object value) {
+        if (value == null || StringUtils.isEmpty(String.valueOf(value))) {
+            return null;
+        }
+        return Long.valueOf(String.valueOf(value));
+    }
+
+    private String parseString(Object value) {
+        return value == null ? null : String.valueOf(value);
     }
 
     /**
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RoleInfoVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RoleInfoVo.java
index a935fd0..c621eeb 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RoleInfoVo.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RoleInfoVo.java
@@ -13,6 +13,12 @@
 
     private String roleName;
 
+    private String roleKey;
+
+    private Boolean systemRole;
+
+    private Boolean partnerRole;
+
     @ApiModelProperty("菜单id")
     private List<Long> menus;
     
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
index fd39ada..3a23f90 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -53,6 +53,7 @@
 import com.ruoyi.order.dto.OrderEvaluateVo;
 import com.ruoyi.order.dto.*;
 import com.ruoyi.order.export.PayOrderExport;
+import com.ruoyi.order.export.EnterpriseChargingDetailExport;
 import com.ruoyi.order.service.*;
 import com.ruoyi.order.util.PreviousSixMonths;
 import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService;
@@ -217,6 +218,104 @@
     }
 
     
+    @RequiresPermissions(value = {"/chargingUserAnalysis"}, logical = Logical.OR)
+    @ResponseBody
+    @PostMapping(value = "/enterprise/charging/analysis")
+    @ApiOperation(value = "企业用户充电分析", tags = {"管理后台-数据分析-企业用户充电分析"})
+    public R<PageInfo<EnterpriseChargingAnalysisVO>> enterpriseChargingAnalysis(@RequestBody EnterpriseChargingAnalysisQuery query) {
+        return chargingOrderService.enterpriseChargingAnalysis(query);
+    }
+
+    @RequiresPermissions(value = {"/chargingUserAnalysis"}, logical = Logical.OR)
+    @ResponseBody
+    @PostMapping(value = "/enterprise/charging/detail")
+    @ApiOperation(value = "企业用户充电明细", tags = {"管理后台-数据分析-企业用户充电分析"})
+    public R<PageInfo<EnterpriseChargingDetailVO>> enterpriseChargingDetail(@RequestBody EnterpriseChargingAnalysisQuery query) {
+        return chargingOrderService.enterpriseChargingDetail(query);
+    }
+
+    @RequiresPermissions(value = {"/chargingUserAnalysis"}, logical = Logical.OR)
+    @ResponseBody
+    @PutMapping(value = "/enterprise/charging/detail/export")
+    @ApiOperation(value = "企业用户充电明细导出", tags = {"管理后台-数据分析-企业用户充电分析"})
+    public void exportEnterpriseChargingDetail(@RequestBody EnterpriseChargingAnalysisQuery query) {
+        List<EnterpriseChargingDetailVO> records = chargingOrderService.enterpriseChargingDetailExport(query);
+        List<EnterpriseChargingDetailExport> exports = records.stream().map(this::buildEnterpriseChargingDetailExport).collect(Collectors.toList());
+        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), EnterpriseChargingDetailExport.class, exports);
+        HttpServletResponse response = WebUtils.response();
+        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
+        response.setCharacterEncoding("utf-8");
+        response.setHeader("Pragma", "no-cache");
+        response.setHeader("Cache-Control", "no-cache");
+        try {
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("企业用户充电明细.xls", "utf-8"));
+            try (ServletOutputStream outputStream = response.getOutputStream()) {
+                workbook.write(outputStream);
+            }
+            workbook.close();
+        } catch (IOException e) {
+            log.error("导出企业用户充电明细失败", e);
+        }
+    }
+
+    private EnterpriseChargingDetailExport buildEnterpriseChargingDetailExport(EnterpriseChargingDetailVO dto) {
+        EnterpriseChargingDetailExport export = new EnterpriseChargingDetailExport();
+        export.setCode(dto.getCode());
+        export.setOrderType(formatEnterpriseOrderType(dto.getOrderType()));
+        export.setOrderClassification(formatOrderClassification(dto.getOrderClassification()));
+        export.setSiteName(dto.getSiteName());
+        export.setTerminalName(dto.getTerminalName());
+        export.setStatus(formatPayOrderStatus(1, dto.getStatus()));
+        export.setChargingSecond(dto.getChargingSecond());
+        export.setTotalAmount(dto.getTotalAmount());
+        export.setCouponDiscountAmount(dto.getCouponDiscountAmount());
+        export.setVipDiscountAmount(dto.getVipDiscountAmount());
+        export.setPaymentAmount(dto.getPaymentAmount());
+        export.setRefundAmount(dto.getRefundAmount());
+        export.setPartnerName(dto.getPartnerName());
+        export.setPhone(dto.getPhone());
+        export.setOrderSource(formatOrderSource(dto.getOrderSource()));
+        export.setStartTime(dto.getStartTime() == null ? null : DateUtils.localDateTimeToString(dto.getStartTime()));
+        export.setEndTime(dto.getEndTime() == null ? null : DateUtils.localDateTimeToString(dto.getEndTime()));
+        return export;
+    }
+
+    private String formatEnterpriseOrderType(Integer type) {
+        if (type == null) {
+            return "-";
+        }
+        if (type == 1) {
+            return "充电订单(小程序)";
+        }
+        if (type == 2) {
+            return "充电订单(刷卡)";
+        }
+        return "充电订单";
+    }
+
+    private String formatOrderClassification(Integer type) {
+        if (type == null) {
+            return "-";
+        }
+        return type == 1 ? "线上订单" : "线下订单";
+    }
+
+    private String formatOrderSource(Integer source) {
+        if (source == null) {
+            return "-";
+        }
+        if (source == 0) {
+            return "自有平台";
+        }
+        if (source == 1) {
+            return "第三方平台";
+        }
+        if (source == 2) {
+            return "引流平台";
+        }
+        return "其他";
+    }
+
     @RequiresPermissions(value = {"/invoiceManagement/select_order", "/paymentOrder/order"}, logical = Logical.OR)
     @ResponseBody
     @PostMapping(value = "/pay/order/list")
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisQuery.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisQuery.java
new file mode 100644
index 0000000..5da2b90
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisQuery.java
@@ -0,0 +1,66 @@
+package com.ruoyi.order.dto;
+
+import com.ruoyi.common.core.web.page.BasePage;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@ApiModel("Enterprise charging analysis query")
+public class EnterpriseChargingAnalysisQuery extends BasePage {
+
+    @ApiModelProperty("Company name")
+    private String companyName;
+
+    @ApiModelProperty("Social credit code")
+    private String socialCreditCode;
+
+    @ApiModelProperty("Exact company name")
+    private String exactCompanyName;
+
+    @ApiModelProperty("Site type")
+    private Integer siteType;
+
+    @ApiModelProperty("Partner id")
+    private Integer partnerId;
+
+    @ApiModelProperty("Site id")
+    private Integer siteId;
+
+    @ApiModelProperty("Order create start time")
+    private LocalDateTime createTime1;
+
+    @ApiModelProperty("Order create end time")
+    private LocalDateTime createTime2;
+    @ApiModelProperty("Order code")
+    private String code;
+
+    @ApiModelProperty("Charging order type")
+    private Integer orderType;
+
+    @ApiModelProperty("User phone")
+    private String phone;
+
+    @ApiModelProperty("Order status")
+    private Integer status;
+
+    @ApiModelProperty("Partner or service provider name")
+    private String partnerName;
+
+    @ApiModelProperty("Order source")
+    private Integer orderSource;
+
+    @ApiModelProperty("Charging start time begin")
+    private LocalDateTime startTime1;
+
+    @ApiModelProperty("Charging start time end")
+    private LocalDateTime startTime2;
+
+    @ApiModelProperty("Charging end time begin")
+    private LocalDateTime endTime1;
+
+    @ApiModelProperty("Charging end time end")
+    private LocalDateTime endTime2;
+}
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisVO.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisVO.java
new file mode 100644
index 0000000..bca19c4
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisVO.java
@@ -0,0 +1,45 @@
+package com.ruoyi.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@ApiModel("Enterprise charging analysis view")
+public class EnterpriseChargingAnalysisVO {
+
+    @ApiModelProperty("Company name")
+    private String companyName;
+
+    @ApiModelProperty("Social credit code")
+    private String socialCreditCode;
+
+    @ApiModelProperty("Company address")
+    private String companyAddress;
+
+    @ApiModelProperty("Company phone")
+    private String companyPhone;
+
+    @ApiModelProperty("Charging count")
+    private Long chargingCount;
+
+    @ApiModelProperty("Invoice count")
+    private Long invoiceCount;
+
+    @ApiModelProperty("Invoice amount")
+    private BigDecimal invoiceAmount;
+
+    @ApiModelProperty("Top site names")
+    private String topSiteNames;
+
+    @ApiModelProperty("Latest charging time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime latestChargingTime;
+
+    @ApiModelProperty("Average charging interval hours")
+    private BigDecimal averageChargingInterval;
+}
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingDetailVO.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingDetailVO.java
new file mode 100644
index 0000000..5a89bbf
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingDetailVO.java
@@ -0,0 +1,73 @@
+package com.ruoyi.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@ApiModel("Enterprise charging detail view")
+public class EnterpriseChargingDetailVO {
+
+    private Long id;
+
+    @ApiModelProperty("Order code")
+    private String code;
+
+    @ApiModelProperty("Charging order type")
+    private Integer orderType;
+
+    @ApiModelProperty("Order classification")
+    private Integer orderClassification;
+
+    @ApiModelProperty("Site name")
+    private String siteName;
+
+    @ApiModelProperty("Terminal name")
+    private String terminalName;
+
+    @ApiModelProperty("Status")
+    private Integer status;
+
+    @ApiModelProperty("Charging seconds")
+    private Long chargingSecond;
+
+    @ApiModelProperty("Total amount")
+    private BigDecimal totalAmount;
+
+    @ApiModelProperty("Coupon discount amount")
+    private BigDecimal couponDiscountAmount;
+
+    @ApiModelProperty("Vip discount amount")
+    private BigDecimal vipDiscountAmount;
+
+    @ApiModelProperty("Actual payment amount")
+    private BigDecimal paymentAmount;
+
+    @ApiModelProperty("Refund amount")
+    private BigDecimal refundAmount;
+
+    @ApiModelProperty("Order source")
+    private Integer orderSource;
+
+    @ApiModelProperty("Partner name")
+    private String partnerName;
+
+    @ApiModelProperty("Phone")
+    private String phone;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime startTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime endTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime payTime;
+}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java
new file mode 100644
index 0000000..bcac15b
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java
@@ -0,0 +1,62 @@
+package com.ruoyi.order.export;
+
+import cn.afterturn.easypoi.excel.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class EnterpriseChargingDetailExport implements Serializable {
+
+    @Excel(name = "订单编号", width = 30)
+    private String code;
+
+    @Excel(name = "订单类型", width = 20)
+    private String orderType;
+
+    @Excel(name = "订单分类", width = 20)
+    private String orderClassification;
+
+    @Excel(name = "站点名称", width = 30)
+    private String siteName;
+
+    @Excel(name = "充电终端名称", width = 30)
+    private String terminalName;
+
+    @Excel(name = "状态", width = 20)
+    private String status;
+
+    @Excel(name = "充电时长(秒)", width = 20)
+    private Long chargingSecond;
+
+    @Excel(name = "总金额", width = 20)
+    private BigDecimal totalAmount;
+
+    @Excel(name = "优惠券抵扣", width = 20)
+    private BigDecimal couponDiscountAmount;
+
+    @Excel(name = "会员抵扣", width = 20)
+    private BigDecimal vipDiscountAmount;
+
+    @Excel(name = "实付金额", width = 20)
+    private BigDecimal paymentAmount;
+
+    @Excel(name = "退款金额", width = 20)
+    private BigDecimal refundAmount;
+
+    @Excel(name = "归属合作商/服务商", width = 30)
+    private String partnerName;
+
+    @Excel(name = "手机号", width = 20)
+    private String phone;
+
+    @Excel(name = "订单来源", width = 20)
+    private String orderSource;
+
+    @Excel(name = "开始时间", width = 30)
+    private String startTime;
+
+    @Excel(name = "最后时间", width = 30)
+    private String endTime;
+}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
index b72e2a1..3ca1971 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
@@ -67,6 +67,17 @@
     List<ChargingOrderBalanceIncomeDetailVO> exchangeCodeBalanceIncomeDetail(@Param("orderIds") List<Long> orderIds);
 
     List<PayOrderDto> payOrderQuery(@Param("pageInfo")PageInfo<PayOrderDto> pageInfo,@Param("data") PayOrderQueryDto payOrderQueryDto);
+    List<EnterpriseChargingAnalysisVO> enterpriseChargingAnalysis(@Param("pageInfo") PageInfo<EnterpriseChargingAnalysisVO> pageInfo,
+                                                                  @Param("data") EnterpriseChargingAnalysisQuery query);
+
+    List<Map<String, Object>> enterpriseTopSites(@Param("data") EnterpriseChargingAnalysisQuery query);
+
+    List<LocalDateTime> enterpriseChargingTimes(@Param("data") EnterpriseChargingAnalysisQuery query);
+
+    List<EnterpriseChargingDetailVO> enterpriseChargingDetail(@Param("pageInfo") PageInfo<EnterpriseChargingDetailVO> pageInfo,
+                                                              @Param("data") EnterpriseChargingAnalysisQuery query);
+
+    List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(@Param("data") EnterpriseChargingAnalysisQuery query);
 
 	List<TChargingOrderRefund> getRefundList(@Param("pageInfo")PageInfo<TChargingOrderRefund> pageInfo,@Param("data")  ChargingRefundDto chargingRefundDto);
 
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java
index b80ced4..2eeade7 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TChargingOrderService.java
@@ -147,6 +147,11 @@
 	List<ChargingOrderBalanceIncomeDetailVO> balanceIncomeDetail(ChargingOrderQuery dto);
 
 	R<PageInfo<PayOrderDto>> payOrderQuery(PayOrderQueryDto payOrderQueryDto);
+	R<PageInfo<EnterpriseChargingAnalysisVO>> enterpriseChargingAnalysis(EnterpriseChargingAnalysisQuery query);
+
+	R<PageInfo<EnterpriseChargingDetailVO>> enterpriseChargingDetail(EnterpriseChargingAnalysisQuery query);
+
+	List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(EnterpriseChargingAnalysisQuery query);
 
     R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto);
 
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index dfd718e..85f821c 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -2228,7 +2228,62 @@
 		pageInfo.setRecords(list);
 		return R.ok(pageInfo);
 	}
-	
+		@Override
+	public R<PageInfo<EnterpriseChargingAnalysisVO>> enterpriseChargingAnalysis(EnterpriseChargingAnalysisQuery query) {
+		PageInfo<EnterpriseChargingAnalysisVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize());
+		List<EnterpriseChargingAnalysisVO> list = this.baseMapper.enterpriseChargingAnalysis(pageInfo, query);
+		for (EnterpriseChargingAnalysisVO row : list) {
+			EnterpriseChargingAnalysisQuery rowQuery = buildEnterpriseRowQuery(query, row);
+			List<Map<String, Object>> topSites = this.baseMapper.enterpriseTopSites(rowQuery);
+			String topSiteNames = topSites.stream()
+					.map(item -> Objects.toString(item.get("siteName"), ""))
+					.filter(org.springframework.util.StringUtils::hasText)
+					.collect(Collectors.joining("、"));
+			row.setTopSiteNames(topSiteNames);
+			row.setAverageChargingInterval(calculateAverageChargingInterval(this.baseMapper.enterpriseChargingTimes(rowQuery)));
+		}
+		pageInfo.setRecords(list);
+		return R.ok(pageInfo);
+	}
+
+	@Override
+	public R<PageInfo<EnterpriseChargingDetailVO>> enterpriseChargingDetail(EnterpriseChargingAnalysisQuery query) {
+		PageInfo<EnterpriseChargingDetailVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize());
+		List<EnterpriseChargingDetailVO> list = this.baseMapper.enterpriseChargingDetail(pageInfo, query);
+		pageInfo.setRecords(list);
+		return R.ok(pageInfo);
+	}
+
+	@Override
+	public List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(EnterpriseChargingAnalysisQuery query) {
+		return this.baseMapper.enterpriseChargingDetailExport(query);
+	}
+
+	private EnterpriseChargingAnalysisQuery buildEnterpriseRowQuery(EnterpriseChargingAnalysisQuery source, EnterpriseChargingAnalysisVO row) {
+		EnterpriseChargingAnalysisQuery rowQuery = new EnterpriseChargingAnalysisQuery();
+		BeanUtils.copyProperties(source, rowQuery);
+		rowQuery.setCompanyName(null);
+		rowQuery.setExactCompanyName(row.getCompanyName());
+		rowQuery.setSocialCreditCode(row.getSocialCreditCode());
+		return rowQuery;
+	}
+
+	private BigDecimal calculateAverageChargingInterval(List<LocalDateTime> times) {
+		if (times == null || times.size() < 2) {
+			return BigDecimal.ZERO;
+		}
+		long totalMinutes = 0L;
+		for (int i = 1; i < times.size(); i++) {
+			long minutes = ChronoUnit.MINUTES.between(times.get(i - 1), times.get(i));
+			if (minutes > 0) {
+				totalMinutes += minutes;
+			}
+		}
+		return BigDecimal.valueOf(totalMinutes)
+				.divide(BigDecimal.valueOf(times.size() - 1L), 4, RoundingMode.HALF_UP)
+				.divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+	}
+
 	@Override
 	public R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto) {
 		PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(chargingRefundDto.getPageCurr(), chargingRefundDto.getPageSize());
diff --git a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
index 9eaf884..7dbaa6e 100644
--- a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
+++ b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?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.order.mapper.TChargingOrderMapper">
 
@@ -2454,4 +2454,182 @@
             group by orderId,recharge_payment_type,site_id,recharge_amount,refund_amount,platformFee
         ) as aa group by aa.site_id,aa.recharge_payment_type order by aa.site_id
     </select>
+
+    <sql id="enterpriseChargingBaseWhere">
+        toi.invoicing_object_type = 2
+        AND toi.status IN (2, 3)
+        AND tod.order_type = 1
+        AND co.del_flag = 0
+        AND co.recharge_payment_status = 2
+        <if test="data.exactCompanyName != null and data.exactCompanyName != ''">
+            AND toi.name = #{data.exactCompanyName}
+        </if>
+        <if test="(data.exactCompanyName == null or data.exactCompanyName == '') and data.companyName != null and data.companyName != ''">
+            AND toi.name LIKE CONCAT('%', #{data.companyName}, '%')
+        </if>
+        <if test="data.socialCreditCode != null and data.socialCreditCode != ''">
+            AND IFNULL(toi.tax_identification_number, '') = #{data.socialCreditCode}
+        </if>
+        <if test="data.siteType != null">
+            AND s.site_type = #{data.siteType}
+        </if>
+        <if test="data.partnerId != null">
+            AND s.partner_id = #{data.partnerId}
+        </if>
+        <if test="data.siteId != null">
+            AND co.site_id = #{data.siteId}
+        </if>
+        <if test="data.createTime1 != null">
+            AND co.create_time &gt;= #{data.createTime1}
+        </if>
+        <if test="data.createTime2 != null">
+            AND co.create_time &lt;= #{data.createTime2}
+        </if>
+    </sql>
+
+    <select id="enterpriseChargingAnalysis" resultType="com.ruoyi.order.dto.EnterpriseChargingAnalysisVO">
+        SELECT
+            b.companyName,
+            b.socialCreditCode,
+            MAX(b.companyAddress) AS companyAddress,
+            MAX(b.companyPhone) AS companyPhone,
+            SUM(b.orderCount) AS chargingCount,
+            COUNT(1) AS invoiceCount,
+            SUM(CASE WHEN b.detailInvoiceAmount &gt; 0 THEN b.detailInvoiceAmount ELSE b.totalAmount END) AS invoiceAmount,
+            MAX(b.latestChargingTime) AS latestChargingTime
+        FROM (
+            SELECT
+                toi.id AS invoiceId,
+                toi.name AS companyName,
+                IFNULL(toi.tax_identification_number, '') AS socialCreditCode,
+                MAX(toi.company_address) AS companyAddress,
+                MAX(toi.company_phone) AS companyPhone,
+                MAX(IFNULL(toi.total_amount, 0)) AS totalAmount,
+                SUM(IFNULL(tod.invoice_amount, 0)) AS detailInvoiceAmount,
+                COUNT(DISTINCT co.id) AS orderCount,
+                MAX(co.end_time) AS latestChargingTime
+            FROM t_order_invoice toi
+            INNER JOIN t_order_invoice_detail tod ON tod.order_invoice_id = toi.id
+            INNER JOIN t_charging_order co ON co.id = tod.order_id
+            LEFT JOIN charging_pile_service.t_site s ON s.id = co.site_id
+            <where>
+                <include refid="enterpriseChargingBaseWhere" />
+            </where>
+            GROUP BY toi.id, toi.name, toi.tax_identification_number
+        ) b
+        GROUP BY b.companyName, b.socialCreditCode
+        ORDER BY chargingCount DESC
+    </select>
+
+    <select id="enterpriseTopSites" resultType="map">
+        SELECT
+            s.name AS siteName,
+            COUNT(DISTINCT co.id) AS counts
+        FROM t_order_invoice toi
+        INNER JOIN t_order_invoice_detail tod ON tod.order_invoice_id = toi.id
+        INNER JOIN t_charging_order co ON co.id = tod.order_id
+        LEFT JOIN charging_pile_service.t_site s ON s.id = co.site_id
+        <where>
+            <include refid="enterpriseChargingBaseWhere" />
+            AND s.name IS NOT NULL
+        </where>
+        GROUP BY s.id, s.name
+        ORDER BY counts DESC
+        LIMIT 3
+    </select>
+
+    <select id="enterpriseChargingTimes" resultType="java.time.LocalDateTime">
+        SELECT DISTINCT co.start_time
+        FROM t_order_invoice toi
+        INNER JOIN t_order_invoice_detail tod ON tod.order_invoice_id = toi.id
+        INNER JOIN t_charging_order co ON co.id = tod.order_id
+        LEFT JOIN charging_pile_service.t_site s ON s.id = co.site_id
+        <where>
+            <include refid="enterpriseChargingBaseWhere" />
+            AND co.start_time IS NOT NULL
+        </where>
+        ORDER BY co.start_time ASC
+    </select>
+
+    <sql id="enterpriseChargingDetailWhere">
+        <include refid="enterpriseChargingBaseWhere" />
+        <if test="data.code != null and data.code != ''">
+            AND co.code LIKE CONCAT('%', #{data.code}, '%')
+        </if>
+        <if test="data.orderType != null">
+            AND co.order_type = #{data.orderType}
+        </if>
+        <if test="data.phone != null and data.phone != ''">
+            AND au.phone LIKE CONCAT('%', #{data.phone}, '%')
+        </if>
+        <if test="data.status != null">
+            AND co.status = #{data.status}
+        </if>
+        <if test="data.partnerName != null and data.partnerName != ''">
+            AND p.name LIKE CONCAT('%', #{data.partnerName}, '%')
+        </if>
+        <if test="data.orderSource != null">
+            AND co.order_source = #{data.orderSource}
+        </if>
+        <if test="data.startTime1 != null">
+            AND co.start_time &gt;= #{data.startTime1}
+        </if>
+        <if test="data.startTime2 != null">
+            AND co.start_time &lt;= #{data.startTime2}
+        </if>
+        <if test="data.endTime1 != null">
+            AND co.end_time &gt;= #{data.endTime1}
+        </if>
+        <if test="data.endTime2 != null">
+            AND co.end_time &lt;= #{data.endTime2}
+        </if>
+    </sql>
+
+    <sql id="enterpriseChargingDetailSelect">
+        SELECT DISTINCT
+            co.id,
+            co.code,
+            co.order_type AS orderType,
+            co.order_classification AS orderClassification,
+            s.name AS siteName,
+            CONCAT(IFNULL(cp.name, ''), IFNULL(cg.name, '')) AS terminalName,
+            co.status,
+            (UNIX_TIMESTAMP(co.end_time) - UNIX_TIMESTAMP(co.start_time)) AS chargingSecond,
+            co.recharge_amount AS totalAmount,
+            IFNULL(co.coupon_discount_amount, 0) AS couponDiscountAmount,
+            IFNULL(co.vip_discount_amount, 0) AS vipDiscountAmount,
+            co.payment_amount AS paymentAmount,
+            IFNULL(co.refund_amount, 0) AS refundAmount,
+            co.order_source AS orderSource,
+            p.name AS partnerName,
+            au.phone AS phone,
+            co.create_time AS createTime,
+            co.start_time AS startTime,
+            co.end_time AS endTime,
+            co.pay_time AS payTime
+        FROM t_order_invoice toi
+        INNER JOIN t_order_invoice_detail tod ON tod.order_invoice_id = toi.id
+        INNER JOIN t_charging_order co ON co.id = tod.order_id
+        LEFT JOIN charging_pile_service.t_site s ON s.id = co.site_id
+        LEFT JOIN charging_pile_service.t_partner p ON p.id = s.partner_id
+        LEFT JOIN charging_pile_service.t_charging_pile cp ON cp.id = co.charging_pile_id
+        LEFT JOIN charging_pile_service.t_charging_gun cg ON cg.id = co.charging_gun_id
+        LEFT JOIN charging_pile_account.t_app_user au ON au.id = co.app_user_id
+    </sql>
+
+    <select id="enterpriseChargingDetail" resultType="com.ruoyi.order.dto.EnterpriseChargingDetailVO">
+        <include refid="enterpriseChargingDetailSelect" />
+        <where>
+            <include refid="enterpriseChargingDetailWhere" />
+        </where>
+        ORDER BY co.create_time DESC
+    </select>
+
+    <select id="enterpriseChargingDetailExport" resultType="com.ruoyi.order.dto.EnterpriseChargingDetailVO">
+        <include refid="enterpriseChargingDetailSelect" />
+        <where>
+            <include refid="enterpriseChargingDetailWhere" />
+        </where>
+        ORDER BY co.create_time DESC
+    </select>
 </mapper>

--
Gitblit v1.7.1