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() { 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)); } 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); } /** 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; 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") ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisQuery.java
New file @@ -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; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingAnalysisVO.java
New file @@ -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; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/EnterpriseChargingDetailVO.java
New file @@ -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; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/EnterpriseChargingDetailExport.java
New file @@ -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; } 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); 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); 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()); 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 >= #{data.createTime1} </if> <if test="data.createTime2 != null"> AND co.create_time <= #{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 > 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 >= #{data.startTime1} </if> <if test="data.startTime2 != null"> AND co.start_time <= #{data.startTime2} </if> <if test="data.endTime1 != null"> AND co.end_time >= #{data.endTime1} </if> <if test="data.endTime2 != null"> AND co.end_time <= #{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>