| | |
| | | |
| | | import com.ruoyi.chargingPile.api.feignClient.PartnerClient; |
| | | import com.ruoyi.chargingPile.api.model.Partner; |
| | | import com.ruoyi.chargingPile.api.model.TPartnerAppUser; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public R<List<TPartnerAppUser>> getBoundAppUserRelations(Integer partnerId) { |
| | | return R.fail("获取合作商绑定用户失败:" + throwable.getMessage()); |
| | | } |
| | | |
| | | @Override |
| | | public R<Set<Integer>> authSite(Integer partnerId, String siteMenu) { |
| | | return R.fail("获取合作商授权的站点数据失败:" + throwable.getMessage()); |
| | | } |
| | |
| | | |
| | | import com.ruoyi.chargingPile.api.factory.PartnerFallbackFactory; |
| | | import com.ruoyi.chargingPile.api.model.Partner; |
| | | import com.ruoyi.chargingPile.api.model.TPartnerAppUser; |
| | | import com.ruoyi.common.core.constant.ServiceNameConstants; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.web.domain.AjaxResult; |
| | |
| | | @PostMapping("/partner/getPartnerByAppUserId/{appUserId}") |
| | | R<Partner> getPartnerByAppUserId(@PathVariable("appUserId") Long appUserId); |
| | | |
| | | @PostMapping("/partner/appUser/relations/{partnerId}") |
| | | R<List<TPartnerAppUser>> getBoundAppUserRelations(@PathVariable("partnerId") Integer partnerId); |
| | | |
| | | |
| | | /** |
| | | * 获取合作商授权的站点数据 |
| | |
| | | @ApiOperation(value = "重置操作密码", tags = {"管理后台-系统用户管理"}) |
| | | public AjaxResult resetOperationPwd(@RequestBody Map<String, Object> params) { |
| | | Long userId = parseLong(params.get("userId")); |
| | | String operationPassword = parseString(params.get("operationPassword")); |
| | | String code = parseString(params.get("code")); |
| | | if (userId == null) { |
| | | return AjaxResult.error("用户不存在"); |
| | | } |
| | | if (StringUtils.isEmpty(operationPassword)) { |
| | | return AjaxResult.error("请输入新操作密码"); |
| | | } |
| | | if (StringUtils.isEmpty(code)) { |
| | | return AjaxResult.error("请输入验证码"); |
| | | } |
| | | |
| | | SysUser user = userService.selectUserById(userId); |
| | |
| | | userService.checkUserAllowed(user); |
| | | userService.checkUserDataScope(userId); |
| | | |
| | | user.setOperationPassword(SecurityUtils.encryptPassword("123456")); |
| | | String cacheCode = redisService.getCacheObject(RESET_PWD_CODE_KEY + userId); |
| | | if (StringUtils.isEmpty(cacheCode)) { |
| | | return AjaxResult.error("验证码已过期,请重新获取验证码"); |
| | | } |
| | | if (!code.equals(cacheCode)) { |
| | | return AjaxResult.error("验证码错误"); |
| | | } |
| | | |
| | | user.setOperationPassword(SecurityUtils.encryptPassword(operationPassword)); |
| | | user.setUpdateBy(SecurityUtils.getUsername()); |
| | | return toAjax(userService.updateUser(user)); |
| | | int result = userService.updateUser(user); |
| | | redisService.deleteObject(RESET_PWD_CODE_KEY + userId); |
| | | return toAjax(result); |
| | | } |
| | | private Long parseLong(Object value) { |
| | | if (value == null || StringUtils.isEmpty(String.valueOf(value))) { |
| | |
| | | import com.ruoyi.chargingPile.api.dto.GetPermissionConfigurationDTO; |
| | | import com.ruoyi.chargingPile.api.dto.PartnerListDTO; |
| | | import com.ruoyi.chargingPile.api.model.Partner; |
| | | import com.ruoyi.chargingPile.api.model.TPartnerAppUser; |
| | | import com.ruoyi.chargingPile.api.model.TPartnerSite; |
| | | import com.ruoyi.chargingPile.api.model.TSiteMenu; |
| | | import com.ruoyi.chargingPile.api.query.GetPartnerList; |
| | |
| | | return AjaxResult.success(partnerAppUserService.getBoundUsers(partnerId)); |
| | | } |
| | | |
| | | @PostMapping("/appUser/relations/{partnerId}") |
| | | public R<List<TPartnerAppUser>> getBoundAppUserRelations(@PathVariable Integer partnerId) { |
| | | return R.ok(partnerAppUserService.getRelations(partnerId)); |
| | | } |
| | | |
| | | @RequiresPermissions(value = {"/partners/update"}, logical = Logical.OR) |
| | | @PostMapping("/appUser/bind") |
| | | @ApiOperation(value = "保存合作商绑定的小程序用户", tags = {"管理后台-合作商管理"}) |
| | |
| | | |
| | | List<PartnerAppUserVO> getBoundUsers(Integer partnerId); |
| | | |
| | | List<TPartnerAppUser> getRelations(Integer partnerId); |
| | | |
| | | void saveBindings(PartnerAppUserBindDTO bindDTO); |
| | | |
| | | Partner getPartnerByAppUserId(Long appUserId); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<TPartnerAppUser> getRelations(Integer partnerId) { |
| | | assertPartnerExists(partnerId); |
| | | return list(new LambdaQueryWrapper<TPartnerAppUser>() |
| | | .eq(TPartnerAppUser::getPartnerId, partnerId) |
| | | .orderByAsc(TPartnerAppUser::getCreateTime)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void saveBindings(PartnerAppUserBindDTO bindDTO) { |
| | | Integer partnerId = bindDTO.getPartnerId(); |
| | |
| | | } |
| | | } |
| | | |
| | | remove(new LambdaQueryWrapper<TPartnerAppUser>().eq(TPartnerAppUser::getPartnerId, partnerId)); |
| | | if (selectedIds.isEmpty()) { |
| | | return; |
| | | List<TPartnerAppUser> existingRelations = list(new LambdaQueryWrapper<TPartnerAppUser>() |
| | | .eq(TPartnerAppUser::getPartnerId, partnerId)); |
| | | Set<Long> existingIds = existingRelations.stream() |
| | | .map(TPartnerAppUser::getAppUserId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | Set<Long> removedIds = existingIds.stream() |
| | | .filter(appUserId -> !selectedIds.contains(appUserId)) |
| | | .collect(Collectors.toSet()); |
| | | if (!removedIds.isEmpty()) { |
| | | remove(new LambdaQueryWrapper<TPartnerAppUser>() |
| | | .eq(TPartnerAppUser::getPartnerId, partnerId) |
| | | .in(TPartnerAppUser::getAppUserId, removedIds)); |
| | | } |
| | | |
| | | List<TPartnerAppUser> relations = new ArrayList<>(selectedIds.size()); |
| | | List<TPartnerAppUser> relations = new ArrayList<>(); |
| | | for (Long appUserId : selectedIds) { |
| | | if (existingIds.contains(appUserId)) { |
| | | continue; |
| | | } |
| | | TPartnerAppUser relation = new TPartnerAppUser(); |
| | | relation.setPartnerId(partnerId); |
| | | relation.setAppUserId(appUserId); |
| | | relations.add(relation); |
| | | } |
| | | if (!relations.isEmpty()) { |
| | | saveBatch(relations); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Partner getPartnerByAppUserId(Long appUserId) { |
| | |
| | | import com.ruoyi.chargingPile.api.domain.SiteMenu; |
| | | import com.ruoyi.chargingPile.api.dto.GetSiteListDTO; |
| | | import com.ruoyi.chargingPile.api.feignClient.*; |
| | | import com.ruoyi.chargingPile.api.model.Partner; |
| | | import com.ruoyi.chargingPile.api.model.Site; |
| | | import com.ruoyi.chargingPile.api.model.TChargingGun; |
| | | import com.ruoyi.chargingPile.api.model.TChargingPile; |
| | | import com.ruoyi.chargingPile.api.model.TParkingRecord; |
| | | import com.ruoyi.chargingPile.api.model.TPartnerAppUser; |
| | | import com.ruoyi.chargingPile.api.vo.GetChargingGunByChargingPileIdsVo; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.dto.ChargingOrderGroup; |
| | |
| | | import com.ruoyi.order.export.PayOrderExport; |
| | | import com.ruoyi.order.export.EnterpriseChargingDetailExport; |
| | | import com.ruoyi.order.service.*; |
| | | import com.ruoyi.order.support.FranchiseeUserStatistics; |
| | | import com.ruoyi.order.util.PreviousSixMonths; |
| | | import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService; |
| | | import com.ruoyi.order.vo.EndOfChargePageInfo; |
| | |
| | | |
| | | @PostMapping("/app/franchisee/user") |
| | | public R<Map<String, Object>> franchiseeUser(@RequestBody(required = false) Map<String, Object> params) { |
| | | List<TChargingOrder> orders = franchiseeOrders(params); |
| | | Set<Long> users = orders.stream().map(TChargingOrder::getAppUserId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | List<Integer> siteIds = franchiseeSiteIds(params); |
| | | Partner partner = franchiseePartner(); |
| | | List<Integer> siteIds = partner == null ? Collections.emptyList() : franchiseeSiteIds(params, partner.getId()); |
| | | LocalDateTime[] range = franchiseeRange(params); |
| | | Set<Long> newUsers = new HashSet<>(); |
| | | if (!siteIds.isEmpty() && range[0] != null && range[1] != null) { |
| | | List<TChargingOrder> allOrders = franchiseeOrders(siteIds, null, range[1]); |
| | | Map<Long, LocalDateTime> firstOrder = new HashMap<>(); |
| | | for (TChargingOrder order : allOrders) { |
| | | if (order.getAppUserId() == null || order.getEndTime() == null) continue; |
| | | firstOrder.merge(order.getAppUserId(), order.getEndTime(), (oldTime, newTime) -> oldTime.isBefore(newTime) ? oldTime : newTime); |
| | | List<TChargingOrder> orders = franchiseeOrders(siteIds, range[0], range[1]).stream() |
| | | .filter(order -> Objects.equals(order.getStatus(), 5)) |
| | | .collect(Collectors.toList()); |
| | | Set<Long> users = orders.stream().map(TChargingOrder::getAppUserId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | List<TPartnerAppUser> relations = Collections.emptyList(); |
| | | if (partner != null && partner.getId() != null) { |
| | | R<List<TPartnerAppUser>> relationResult = partnerClient.getBoundAppUserRelations(partner.getId()); |
| | | if (relationResult != null && relationResult.getData() != null) { |
| | | relations = relationResult.getData(); |
| | | } |
| | | firstOrder.forEach((userId, firstTime) -> { if (!firstTime.isBefore(range[0])) newUsers.add(userId); }); |
| | | } |
| | | FranchiseeUserStatistics.Result userStatistics = FranchiseeUserStatistics.calculate( |
| | | orders, relations, range[0], range[1]); |
| | | |
| | | List<TAppUser> appUsers = Collections.emptyList(); |
| | | if (!users.isEmpty()) { |
| | |
| | | |
| | | Map<String, Object> evaluation = franchiseeEvaluation(siteIds); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | Map<String, Object> summary = new LinkedHashMap<>(); summary.put("chargeUserCount", users.size()); summary.put("newUserCount", newUsers.size()); |
| | | result.put("summary", summary); |
| | | result.put("userStats", franchiseeUserTrend(orders, newUsers, range)); |
| | | result.put("summary", userStatistics.getSummary()); |
| | | result.put("userStats", userStatistics.getTrend()); |
| | | result.put("userTags", franchiseeUserTags(orders)); |
| | | Map<Integer, String> vipNames = new HashMap<>(); |
| | | R<List<TVip>> vipResult = vipClient.getAllVip(); |
| | |
| | | } |
| | | |
| | | private List<Integer> franchiseeSiteIds(Map<String, Object> params) { |
| | | if (tokenService.getLoginUserApplet() == null || tokenService.getLoginUserApplet().getUserId() == null) return Collections.emptyList(); |
| | | Long appUserId = tokenService.getLoginUserApplet().getUserId(); |
| | | R<com.ruoyi.chargingPile.api.model.Partner> partnerResult = partnerClient.getPartnerByAppUserId(appUserId); |
| | | com.ruoyi.chargingPile.api.model.Partner partner = partnerResult == null ? null : partnerResult.getData(); |
| | | Partner partner = franchiseePartner(); |
| | | if (partner == null || partner.getId() == null) return Collections.emptyList(); |
| | | return franchiseeSiteIds(params, partner.getId()); |
| | | } |
| | | |
| | | private Partner franchiseePartner() { |
| | | if (tokenService.getLoginUserApplet() == null || tokenService.getLoginUserApplet().getUserId() == null) return null; |
| | | Long appUserId = tokenService.getLoginUserApplet().getUserId(); |
| | | R<Partner> partnerResult = partnerClient.getPartnerByAppUserId(appUserId); |
| | | return partnerResult == null ? null : partnerResult.getData(); |
| | | } |
| | | |
| | | private List<Integer> franchiseeSiteIds(Map<String, Object> params, Integer partnerId) { |
| | | R<List<Site>> siteResult = siteClient.getSiteAll(); |
| | | List<Site> sites = siteResult == null ? null : siteResult.getData(); |
| | | List<Integer> siteIds = sites == null ? new ArrayList<>() : sites.stream().filter(s -> Objects.equals(s.getPartnerId(), partner.getId())).map(Site::getId).collect(Collectors.toList()); |
| | | List<Integer> siteIds = sites == null ? new ArrayList<>() : sites.stream().filter(s -> Objects.equals(s.getPartnerId(), partnerId)).map(Site::getId).collect(Collectors.toList()); |
| | | Object requestedSite = params == null ? null : params.get("siteId"); |
| | | if (requestedSite != null && !String.valueOf(requestedSite).isEmpty()) { |
| | | Integer siteId = Integer.valueOf(String.valueOf(requestedSite)); siteIds = siteIds.contains(siteId) ? Collections.singletonList(siteId) : Collections.singletonList(-1); |
| | | } |
| | | return siteIds; |
| | | } |
| | | |
| | | private Map<String, Object> franchiseeUserTrend(List<TChargingOrder> orders, Set<Long> newUsers, LocalDateTime[] range) { |
| | | LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate(); |
| | | LocalDate end = range[1] == null ? start.plusDays(1) : range[1].toLocalDate(); |
| | | List<String> categories = new ArrayList<>(); |
| | | List<BigDecimal> chargeUsers = new ArrayList<>(), newUserCounts = new ArrayList<>(); |
| | | for (LocalDate day = start; day.isBefore(end); day = day.plusDays(1)) { |
| | | LocalDateTime dayStart = day.atStartOfDay(), dayEnd = day.plusDays(1).atStartOfDay(); |
| | | Set<Long> dayUsers = orders.stream().filter(order -> order.getAppUserId() != null && order.getEndTime() != null |
| | | && !order.getEndTime().isBefore(dayStart) && order.getEndTime().isBefore(dayEnd)) |
| | | .map(TChargingOrder::getAppUserId).collect(Collectors.toSet()); |
| | | long newCount = dayUsers.stream().filter(newUsers::contains).count(); |
| | | categories.add(day.toString().substring(5)); |
| | | chargeUsers.add(BigDecimal.valueOf(dayUsers.size())); |
| | | newUserCounts.add(BigDecimal.valueOf(newCount)); |
| | | } |
| | | Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", categories); |
| | | Map<String, Object> chargeSeries = new LinkedHashMap<>(); chargeSeries.put("name", "充电用户"); chargeSeries.put("data", chargeUsers); |
| | | Map<String, Object> newSeries = new LinkedHashMap<>(); newSeries.put("name", "新增用户"); newSeries.put("data", newUserCounts); |
| | | chart.put("series", Arrays.asList(chargeSeries, newSeries)); return chart; |
| | | } |
| | | |
| | | private List<Map<String, Object>> franchiseeUserTags(List<TChargingOrder> orders) { |
| | |
| | | payOrderInfoDto.setThirdPayAmount(byId.getThirdPayAmount()); |
| | | payOrderInfoDto.setBalanceRefundAmount(byId.getBalanceRefundAmount()); |
| | | payOrderInfoDto.setThirdRefundAmount(byId.getThirdRefundAmount()); |
| | | payOrderInfoDto.setRefundableAmount(chargingOrderService.getManualRefundableAmount(byId.getId())); |
| | | return R.ok(payOrderInfoDto); |
| | | case 2: |
| | | TShoppingOrder byId1 = shoppingOrderService.getById(orderId); |
| | |
| | | private BigDecimal balanceRefundAmount; |
| | | @ApiModelProperty("第三方退款金额") |
| | | private BigDecimal thirdRefundAmount; |
| | | @ApiModelProperty("剩余可人工退款金额(不扣减充电完成自动退款)") |
| | | private BigDecimal refundableAmount; |
| | | |
| | | |
| | | } |
| | |
| | | |
| | | R specialRefund(PayOrderRefundDto payOrderQueryDto); |
| | | |
| | | BigDecimal getManualRefundableAmount(Long chargingOrderId); |
| | | |
| | | ChargingOrderListInfoVO chargingInfo(String uid); |
| | | |
| | | |
| | |
| | | dto.setThirdPayAmount(order.getThirdPayAmount()); |
| | | dto.setBalanceRefundAmount(order.getBalanceRefundAmount()); |
| | | dto.setThirdRefundAmount(order.getThirdRefundAmount()); |
| | | dto.setRefundableAmount(getManualRefundableAmount(order)); |
| | | return R.ok(dto); |
| | | } |
| | | |
| | | @Override |
| | | public BigDecimal getManualRefundableAmount(Long chargingOrderId) { |
| | | if (chargingOrderId == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return getManualRefundableAmount(this.getById(chargingOrderId)); |
| | | } |
| | | |
| | | private BigDecimal getManualRefundableAmount(TChargingOrder order) { |
| | | if (order == null || order.getId() == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | List<TChargingOrderRefund> successfulRefunds = chargingOrderRefundService.list( |
| | | new LambdaQueryWrapper<TChargingOrderRefund>() |
| | | .eq(TChargingOrderRefund::getChargingOrderId, order.getId()) |
| | | .eq(TChargingOrderRefund::getRefundStatus, 2)); |
| | | return calculateManualRefundableAmount(order.getPaymentAmount(), successfulRefunds); |
| | | } |
| | | |
| | | static BigDecimal calculateManualRefundableAmount(BigDecimal paymentAmount, List<TChargingOrderRefund> successfulRefunds) { |
| | | BigDecimal successfulManualRefundAmount = Optional.ofNullable(successfulRefunds) |
| | | .orElse(Collections.emptyList()) |
| | | .stream() |
| | | .filter(refund -> "后台退款".equals(refund.getRefundTitle()) |
| | | || SPECIAL_REFUND_TITLE.equals(refund.getRefundTitle())) |
| | | .map(TChargingOrderRefund::getRefundAmount) |
| | | .map(amount -> amount == null ? BigDecimal.ZERO : amount) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | return (paymentAmount == null ? BigDecimal.ZERO : paymentAmount) |
| | | .subtract(successfulManualRefundAmount) |
| | | .max(BigDecimal.ZERO) |
| | | .setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | mergeSuccessfulRefundAmounts(order); |
| | | BigDecimal refundAmount = money(payOrderQueryDto.getRefundAmount()); |
| | | BigDecimal refundableAmount = money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP); |
| | | BigDecimal refundableAmount = getManualRefundableAmount(order); |
| | | if (refundAmount.compareTo(refundableAmount) > 0) { |
| | | return R.fail("退款金额不能超过剩余可退金额"); |
| | | } |
| | |
| | | return R.fail("充电订单不存在"); |
| | | } |
| | | BigDecimal manualRefundAmount = money(payOrderQueryDto.getRefundAmount()); |
| | | BigDecimal remainingRefundAmount = money(tChargingOrder.getPaymentAmount()) |
| | | .subtract(money(tChargingOrder.getRefundAmount())).max(BigDecimal.ZERO); |
| | | BigDecimal remainingRefundAmount = getManualRefundableAmount(tChargingOrder); |
| | | if (manualRefundAmount.compareTo(remainingRefundAmount) > 0) { |
| | | return R.fail("退款金额不能超过剩余可退金额" + remainingRefundAmount.toPlainString() + "元"); |
| | | } |
| New file |
| | |
| | | package com.ruoyi.order.support; |
| | | |
| | | import com.ruoyi.chargingPile.api.model.TPartnerAppUser; |
| | | import com.ruoyi.order.api.model.TChargingOrder; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | |
| | | /** 加盟商看板用户统计的纯计算逻辑。 */ |
| | | public final class FranchiseeUserStatistics { |
| | | |
| | | private FranchiseeUserStatistics() { |
| | | } |
| | | |
| | | public static Result calculate(List<TChargingOrder> orders, |
| | | List<TPartnerAppUser> relations, |
| | | LocalDateTime start, |
| | | LocalDateTime end) { |
| | | List<TChargingOrder> safeOrders = orders == null ? Collections.emptyList() : orders; |
| | | List<TPartnerAppUser> safeRelations = relations == null ? Collections.emptyList() : relations; |
| | | |
| | | Map<LocalDate, Set<Long>> chargingUsersByDay = new HashMap<>(); |
| | | Set<Long> chargingUsers = new HashSet<>(); |
| | | for (TChargingOrder order : safeOrders) { |
| | | if (order == null || order.getAppUserId() == null || !inRange(order.getEndTime(), start, end)) { |
| | | continue; |
| | | } |
| | | chargingUsers.add(order.getAppUserId()); |
| | | chargingUsersByDay.computeIfAbsent(order.getEndTime().toLocalDate(), key -> new HashSet<>()) |
| | | .add(order.getAppUserId()); |
| | | } |
| | | |
| | | Map<Long, LocalDateTime> firstBindingByUser = new HashMap<>(); |
| | | for (TPartnerAppUser relation : safeRelations) { |
| | | if (relation == null || relation.getAppUserId() == null || relation.getCreateTime() == null) { |
| | | continue; |
| | | } |
| | | firstBindingByUser.merge(relation.getAppUserId(), relation.getCreateTime(), |
| | | (left, right) -> left.isBefore(right) ? left : right); |
| | | } |
| | | Map<LocalDate, Set<Long>> newUsersByDay = new HashMap<>(); |
| | | Set<Long> newUsers = new HashSet<>(); |
| | | firstBindingByUser.forEach((appUserId, createTime) -> { |
| | | if (inRange(createTime, start, end)) { |
| | | newUsers.add(appUserId); |
| | | newUsersByDay.computeIfAbsent(createTime.toLocalDate(), key -> new HashSet<>()).add(appUserId); |
| | | } |
| | | }); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("chargeUserCount", (long) chargingUsers.size()); |
| | | summary.put("newUserCount", (long) newUsers.size()); |
| | | |
| | | LocalDate firstDay = start == null ? LocalDate.now() : start.toLocalDate(); |
| | | LocalDate exclusiveEndDay = end == null ? firstDay.plusDays(1) : end.toLocalDate(); |
| | | List<String> categories = new ArrayList<>(); |
| | | List<Long> chargingSeries = new ArrayList<>(); |
| | | List<Long> newSeries = new ArrayList<>(); |
| | | for (LocalDate day = firstDay; day.isBefore(exclusiveEndDay); day = day.plusDays(1)) { |
| | | categories.add(String.format("%02d-%02d", day.getMonthValue(), day.getDayOfMonth())); |
| | | chargingSeries.add((long) chargingUsersByDay.getOrDefault(day, Collections.emptySet()).size()); |
| | | newSeries.add((long) newUsersByDay.getOrDefault(day, Collections.emptySet()).size()); |
| | | } |
| | | |
| | | Map<String, Object> trend = new LinkedHashMap<>(); |
| | | trend.put("categories", categories); |
| | | trend.put("series", Arrays.asList(series("充电用户", chargingSeries), series("新增用户", newSeries))); |
| | | return new Result(summary, trend); |
| | | } |
| | | |
| | | private static boolean inRange(LocalDateTime time, LocalDateTime start, LocalDateTime end) { |
| | | return time != null |
| | | && (start == null || !time.isBefore(start)) |
| | | && (end == null || time.isBefore(end)); |
| | | } |
| | | |
| | | private static Map<String, Object> series(String name, List<Long> data) { |
| | | Map<String, Object> series = new LinkedHashMap<>(); |
| | | series.put("name", name); |
| | | series.put("data", data); |
| | | return series; |
| | | } |
| | | |
| | | public static final class Result { |
| | | private final Map<String, Object> summary; |
| | | private final Map<String, Object> trend; |
| | | |
| | | private Result(Map<String, Object> summary, Map<String, Object> trend) { |
| | | this.summary = Objects.requireNonNull(summary); |
| | | this.trend = Objects.requireNonNull(trend); |
| | | } |
| | | |
| | | public Map<String, Object> getSummary() { |
| | | return summary; |
| | | } |
| | | |
| | | public Map<String, Object> getTrend() { |
| | | return trend; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.order.service.impl; |
| | | |
| | | import com.ruoyi.order.api.model.TChargingOrderRefund; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | |
| | | class TChargingOrderServiceImplRefundableAmountTest { |
| | | |
| | | @Test |
| | | void completionRefundDoesNotReduceManualRefundableAmount() { |
| | | TChargingOrderRefund completionRefund = refund("充电完成退款", "2.25"); |
| | | |
| | | BigDecimal refundableAmount = TChargingOrderServiceImpl.calculateManualRefundableAmount( |
| | | new BigDecimal("26.75"), Collections.singletonList(completionRefund)); |
| | | |
| | | assertEquals(new BigDecimal("26.75"), refundableAmount); |
| | | } |
| | | |
| | | @Test |
| | | void onlySuccessfulManualRefundReducesManualRefundableAmount() { |
| | | TChargingOrderRefund completionRefund = refund("充电完成退款", "2.25"); |
| | | TChargingOrderRefund manualRefund = refund("后台退款", "5.00"); |
| | | |
| | | BigDecimal refundableAmount = TChargingOrderServiceImpl.calculateManualRefundableAmount( |
| | | new BigDecimal("26.75"), Arrays.asList(completionRefund, manualRefund)); |
| | | |
| | | assertEquals(new BigDecimal("21.75"), refundableAmount); |
| | | } |
| | | |
| | | private TChargingOrderRefund refund(String title, String amount) { |
| | | TChargingOrderRefund refund = new TChargingOrderRefund(); |
| | | refund.setRefundTitle(title); |
| | | refund.setRefundAmount(new BigDecimal(amount)); |
| | | return refund; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.order.support; |
| | | |
| | | import com.ruoyi.chargingPile.api.model.TPartnerAppUser; |
| | | import com.ruoyi.order.api.model.TChargingOrder; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | |
| | | class FranchiseeUserStatisticsTest { |
| | | |
| | | private static final LocalDateTime START = LocalDateTime.of(2026, 7, 1, 0, 0); |
| | | private static final LocalDateTime END = LocalDateTime.of(2026, 7, 4, 0, 0); |
| | | |
| | | @Test |
| | | void countsDistinctChargingUsersAndBucketsBindingsOnTheirExactDate() { |
| | | List<TChargingOrder> orders = Arrays.asList( |
| | | order(1L, LocalDateTime.of(2026, 7, 1, 8, 0)), |
| | | order(1L, LocalDateTime.of(2026, 7, 1, 18, 0)), |
| | | order(1L, LocalDateTime.of(2026, 7, 2, 8, 0)), |
| | | order(2L, LocalDateTime.of(2026, 7, 2, 9, 0)) |
| | | ); |
| | | List<TPartnerAppUser> relations = Arrays.asList( |
| | | relation(1L, LocalDateTime.of(2026, 6, 30, 23, 59)), |
| | | relation(2L, LocalDateTime.of(2026, 7, 2, 10, 0)), |
| | | relation(3L, LocalDateTime.of(2026, 7, 2, 11, 0)), |
| | | relation(3L, LocalDateTime.of(2026, 7, 2, 12, 0)) |
| | | ); |
| | | |
| | | FranchiseeUserStatistics.Result result = FranchiseeUserStatistics.calculate(orders, relations, START, END); |
| | | |
| | | assertEquals(2L, result.getSummary().get("chargeUserCount")); |
| | | assertEquals(2L, result.getSummary().get("newUserCount")); |
| | | assertEquals(Arrays.asList("07-01", "07-02", "07-03"), result.getTrend().get("categories")); |
| | | assertEquals(Arrays.asList(1L, 2L, 0L), seriesData(result.getTrend(), 0)); |
| | | assertEquals(Arrays.asList(0L, 2L, 0L), seriesData(result.getTrend(), 1)); |
| | | } |
| | | |
| | | @Test |
| | | void returnsZeroFilledSeriesWhenThereIsNoActivity() { |
| | | FranchiseeUserStatistics.Result result = FranchiseeUserStatistics.calculate( |
| | | Collections.emptyList(), Collections.emptyList(), START, END); |
| | | |
| | | assertEquals(0L, result.getSummary().get("chargeUserCount")); |
| | | assertEquals(0L, result.getSummary().get("newUserCount")); |
| | | assertEquals(Arrays.asList(0L, 0L, 0L), seriesData(result.getTrend(), 0)); |
| | | assertEquals(Arrays.asList(0L, 0L, 0L), seriesData(result.getTrend(), 1)); |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private List<Long> seriesData(Map<String, Object> trend, int index) { |
| | | List<Map<String, Object>> series = (List<Map<String, Object>>) trend.get("series"); |
| | | return (List<Long>) series.get(index).get("data"); |
| | | } |
| | | |
| | | private TChargingOrder order(Long appUserId, LocalDateTime endTime) { |
| | | TChargingOrder order = new TChargingOrder(); |
| | | order.setAppUserId(appUserId); |
| | | order.setEndTime(endTime); |
| | | return order; |
| | | } |
| | | |
| | | private TPartnerAppUser relation(Long appUserId, LocalDateTime createTime) { |
| | | TPartnerAppUser relation = new TPartnerAppUser(); |
| | | relation.setAppUserId(appUserId); |
| | | relation.setCreateTime(createTime); |
| | | return relation; |
| | | } |
| | | } |
| | |
| | | @Resource |
| | | private TDeviceService deviceService; |
| | | |
| | | @RequiresPermissions(value = {"/deviceManagement/list"}, logical = Logical.OR) |
| | | @RequiresPermissions(value = {"/deviceManagement/list", "/faultInformationList"}, logical = Logical.OR) |
| | | @ResponseBody |
| | | @GetMapping("/pageList") |
| | | @ApiOperation(value = "获取设备列表", tags = {"管理后台-设备管理"}) |
| | |
| | | public class TStationCollectController { |
| | | @Resource private TStationCollectService stationCollectService; |
| | | |
| | | @RequiresPermissions(value = {"/stationCollect/list"}, logical = Logical.OR) |
| | | @RequiresPermissions(value = {"/stationCollect/list", "/faultInformationList"}, logical = Logical.OR) |
| | | @GetMapping("/pageList") @ApiOperation("分页查询逆变器") |
| | | public AjaxResult<PageInfo<TStationCollect>> pageList(StationCollectQuery query) { |
| | | return AjaxResult.success(stationCollectService.pageList(query)); |
| | |
| | | @ApiModelProperty("机型型号") private String deviceModel; |
| | | @ApiModelProperty("逆变器SN") private String inverterId; |
| | | @ApiModelProperty("状态") private String status; |
| | | @ApiModelProperty("是否只查询异常状态(离线、故障)") private Boolean abnormalOnly; |
| | | } |
| | |
| | | .like(StringUtils.isNotBlank(query.getDeviceModel()), TStationCollect::getDeviceModel, query.getDeviceModel()) |
| | | .like(StringUtils.isNotBlank(query.getInverterId()), TStationCollect::getInverterId, query.getInverterId()) |
| | | .eq(StringUtils.isNotBlank(query.getStatus()), TStationCollect::getStatus, query.getStatus()) |
| | | .in(StringUtils.isBlank(query.getStatus()) && Boolean.TRUE.equals(query.getAbnormalOnly()), |
| | | TStationCollect::getStatus, "2", "3") |
| | | .orderByAsc(TStationCollect::getSortOrder).orderByDesc(TStationCollect::getCreateTime).page(page); |
| | | fillStationAddress(page.getRecords()); |
| | | return page; |