| New file |
| | |
| | | package com.ruoyi.chargingPile.service.impl; |
| | | |
| | | import com.ruoyi.chargingPile.api.model.TChargingGun; |
| | | import com.ruoyi.chargingPile.api.model.TChargingPile; |
| | | import com.ruoyi.integration.api.feignClient.ChengDuJianGuanClient; |
| | | import com.ruoyi.integration.api.feignClient.DongFuStationClient; |
| | | import com.ruoyi.integration.api.feignClient.SendMessageClient; |
| | | import com.ruoyi.integration.api.feignClient.TCECClient; |
| | | import com.ruoyi.integration.api.model.QrCodeDelivery; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class ChargingPileEditAsyncService { |
| | | |
| | | private static final String QR_CODE_PREFIX = "https://mxcd.zhinenganguan.com?No="; |
| | | |
| | | @Resource |
| | | private SendMessageClient sendMessageClient; |
| | | @Resource |
| | | private TCECClient tcecClient; |
| | | @Resource |
| | | private ChengDuJianGuanClient chengDuJianGuanClient; |
| | | @Resource |
| | | private DongFuStationClient dongFuStationClient; |
| | | |
| | | @Async |
| | | public void publishEditNotifications(TChargingPile chargingPile, List<TChargingGun> chargingGuns) { |
| | | if (chargingPile == null) { |
| | | return; |
| | | } |
| | | if (chargingGuns != null) { |
| | | for (TChargingGun chargingGun : chargingGuns) { |
| | | deliverQrCode(chargingPile, chargingGun); |
| | | } |
| | | } |
| | | runSafely("TCEC监管推送", () -> tcecClient.superviseNotificationStationInfo(chargingPile.getSiteId())); |
| | | runSafely("成都监管推送", () -> chengDuJianGuanClient.notificationStationInfo(chargingPile.getSiteId())); |
| | | runSafely("东涪推送", () -> dongFuStationClient.pushChargingPile()); |
| | | } |
| | | |
| | | private void deliverQrCode(TChargingPile chargingPile, TChargingGun chargingGun) { |
| | | if (chargingGun == null) { |
| | | return; |
| | | } |
| | | runSafely("二维码下发", () -> { |
| | | QrCodeDelivery delivery = new QrCodeDelivery(); |
| | | delivery.setCharging_pile_code(chargingPile.getCode()); |
| | | delivery.setCharging_gun_code(chargingGun.getCode()); |
| | | delivery.setCode_format(1); |
| | | delivery.setPrefix_length(QR_CODE_PREFIX.length()); |
| | | delivery.setCode_prefix(QR_CODE_PREFIX); |
| | | delivery.setQr_code(QR_CODE_PREFIX + chargingGun.getFullNumber()); |
| | | delivery.setCreate_time(new Date()); |
| | | delivery.setLast_time(new Date()); |
| | | sendMessageClient.qrCodeDelivery(delivery); |
| | | }); |
| | | } |
| | | |
| | | private void runSafely(String operation, Runnable action) { |
| | | try { |
| | | action.run(); |
| | | } catch (Exception e) { |
| | | log.error("编辑充电桩后{}失败", operation, e); |
| | | } |
| | | } |
| | | } |
| | |
| | | import com.ruoyi.common.security.service.TokenService; |
| | | import com.ruoyi.common.security.utils.SecurityUtils; |
| | | import com.ruoyi.integration.api.feignClient.*; |
| | | import com.ruoyi.integration.api.model.QrCodeDelivery; |
| | | import com.ruoyi.integration.api.model.UploadRealTimeMonitoringData; |
| | | import com.ruoyi.integration.api.vo.AddDevice; |
| | | import com.ruoyi.integration.api.vo.AddDeviceResp; |
| | |
| | | private TAccountingStrategyDetailService accountingStrategyDetailService; |
| | | |
| | | @Resource |
| | | private SendMessageClient sendMessageClient; |
| | | @Resource |
| | | private TokenService tokenService; |
| | | @Resource |
| | | private TChargingPileNotificationService chargingPileNotificationService; |
| | |
| | | private ChengDuJianGuanClient chengDuJianGuanClient; |
| | | @Resource |
| | | private DongFuStationClient dongFuStationClient; |
| | | @Resource |
| | | private ChargingPileEditAsyncService chargingPileEditAsyncService; |
| | | |
| | | |
| | | |
| | |
| | | return AjaxResult.error("设备编号已存在"); |
| | | } |
| | | TChargingPile tChargingPile = this.getById(chargingPile.getId()); |
| | | //桩号不相同,需要重新绑定设备 |
| | | if(!tChargingPile.getCode().equals(chargingPile.getCode())){ |
| | | ShowDeviceResp showDeviceResp = iotInterfaceClient.showDeviceRequest(tChargingPile.getCode()).getData(); |
| | | if(null != showDeviceResp){ |
| | | boolean codeChanged = shouldSyncIotDevice(tChargingPile, chargingPile); |
| | | // 设备编号变化时才重新绑定华为 IoT,普通资料编辑不访问外部平台。 |
| | | if(codeChanged){ |
| | | ShowDeviceResp oldDevice = iotInterfaceClient.showDeviceRequest(tChargingPile.getCode()).getData(); |
| | | if(null != oldDevice){ |
| | | DeleteDeviceResp deleteDeviceResp = iotInterfaceClient.deleteDevice(tChargingPile.getCode()).getData(); |
| | | if(null == deleteDeviceResp){ |
| | | return AjaxResult.error("删除设备异常,请查看华为设备管理"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | ShowDeviceResp showDeviceResp = iotInterfaceClient.showDeviceRequest(tChargingPile.getCode()).getData(); |
| | | if(null == showDeviceResp){ |
| | | //调用华为Iot创建设备 |
| | | AddDevice addDevice = new AddDevice(); |
| | | addDevice.setProductId("66da68d21837002b28b34ec0"); |
| | | addDevice.setNodeId(chargingPile.getCode()); |
| | | addDevice.setDeviceName(chargingPile.getName()); |
| | | addDevice.setDescription(chargingPile.getNumber().toString()); |
| | | AddDeviceResp deviceResp = iotInterfaceClient.addDevice(addDevice).getData(); |
| | | if(null != deviceResp){ |
| | | int httpStatusCode = deviceResp.getHttpStatusCode(); |
| | | if(httpStatusCode == 201){ |
| | | ShowDeviceResp newDevice = iotInterfaceClient.showDeviceRequest(chargingPile.getCode()).getData(); |
| | | if(null == newDevice){ |
| | | AddDevice addDevice = new AddDevice(); |
| | | addDevice.setProductId("66da68d21837002b28b34ec0"); |
| | | addDevice.setNodeId(chargingPile.getCode()); |
| | | addDevice.setDeviceName(chargingPile.getName()); |
| | | addDevice.setDescription(chargingPile.getNumber().toString()); |
| | | AddDeviceResp deviceResp = iotInterfaceClient.addDevice(addDevice).getData(); |
| | | if(null != deviceResp && deviceResp.getHttpStatusCode() == 201){ |
| | | chargingPile.setIotdDeviceId(deviceResp.getDeviceId()); |
| | | }else{ |
| | | log.error("华为创建设备失败" + JSON.toJSONString(deviceResp)); |
| | | log.error("华为创建设备失败:{}", JSON.toJSONString(deviceResp)); |
| | | return AjaxResult.error("华为创建设备失败"); |
| | | } |
| | | }else{ |
| | | return AjaxResult.error("华为创建设备失败"); |
| | | chargingPile.setIotdDeviceId(newDevice.getDeviceId()); |
| | | } |
| | | } |
| | | this.updateById(chargingPile); |
| | | |
| | | List<TChargingGun> list = chargingGunService.list(new LambdaQueryWrapper<TChargingGun>().eq(TChargingGun::getChargingPileId, chargingPile.getId()).eq(TChargingGun::getDelFlag, 0)); |
| | | for (TChargingGun chargingGun : list) { |
| | | TChargingGun chargingGun1 = new TChargingGun(); |
| | | chargingGun1.setId(chargingGun.getId()); |
| | | String stableFullNumber = StringUtils.isNotEmpty(chargingGun.getFullNumber()) ? chargingGun.getFullNumber() : chargingPile.getCode() + chargingGun.getCode(); |
| | | chargingGun1.setFullNumber(stableFullNumber); |
| | | chargingGun1.setSiteId(chargingPile.getSiteId()); |
| | | chargingGunService.updateById(chargingGun1); |
| | | |
| | | //下发硬件充电二维码 |
| | | String code_prefix = "https://mxcd.zhinenganguan.com?No="; |
| | | QrCodeDelivery qrCodeDelivery = new QrCodeDelivery(); |
| | | qrCodeDelivery.setCharging_pile_code(chargingPile.getCode()); |
| | | qrCodeDelivery.setCharging_gun_code(chargingGun.getCode()); |
| | | qrCodeDelivery.setCode_format(1); |
| | | qrCodeDelivery.setPrefix_length(code_prefix.length()); |
| | | qrCodeDelivery.setCode_prefix(code_prefix); |
| | | qrCodeDelivery.setQr_code(code_prefix + stableFullNumber); |
| | | qrCodeDelivery.setCreate_time(new Date()); |
| | | qrCodeDelivery.setLast_time(new Date()); |
| | | sendMessageClient.qrCodeDelivery(qrCodeDelivery); |
| | | chargingGun.setFullNumber(stableFullNumber); |
| | | chargingGun.setSiteId(chargingPile.getSiteId()); |
| | | } |
| | | tcecClient.superviseNotificationStationInfo(chargingPile.getSiteId()); |
| | | |
| | | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); |
| | | threadPoolExecutor.execute(()->{ |
| | | //推送成都监管 |
| | | chengDuJianGuanClient.notificationStationInfo(chargingPile.getSiteId()); |
| | | // TODO 东涪推送 |
| | | dongFuStationClient.pushChargingPile(); |
| | | }); |
| | | if (!list.isEmpty()) { |
| | | chargingGunService.updateBatchById(list); |
| | | } |
| | | chargingPileEditAsyncService.publishEditNotifications(chargingPile, list); |
| | | return AjaxResult.success(); |
| | | } |
| | | |
| | | static boolean shouldSyncIotDevice(TChargingPile current, TChargingPile edited) { |
| | | return current != null && edited != null && !Objects.equals(current.getCode(), edited.getCode()); |
| | | } |
| | | |
| | | |
| | | /** |
| New file |
| | |
| | | package com.ruoyi.chargingPile.service.impl; |
| | | |
| | | import com.ruoyi.chargingPile.api.model.TChargingPile; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | | class TChargingPileEditPerformanceTest { |
| | | |
| | | @Test |
| | | void onlySynchronizesIotWhenDeviceCodeChanges() { |
| | | TChargingPile current = new TChargingPile(); |
| | | current.setCode("PILE-001"); |
| | | TChargingPile unchanged = new TChargingPile(); |
| | | unchanged.setCode("PILE-001"); |
| | | TChargingPile changed = new TChargingPile(); |
| | | changed.setCode("PILE-002"); |
| | | |
| | | assertFalse(TChargingPileServiceImpl.shouldSyncIotDevice(current, unchanged)); |
| | | assertTrue(TChargingPileServiceImpl.shouldSyncIotDevice(current, changed)); |
| | | } |
| | | } |
| | |
| | | import com.ruoyi.account.api.dto.GiveVipDto; |
| | | import com.ruoyi.account.api.dto.UserListQueryDto; |
| | | import com.ruoyi.account.api.feignClient.AppUserClient; |
| | | import com.ruoyi.account.api.model.TAppUser; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.utils.DateUtils; |
| | | import com.ruoyi.common.core.utils.WebUtils; |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | orderQuery.setPageCurr(1); |
| | | orderQuery.setPageSize(100000); |
| | | List<ChargingOrderVO> list = chargingOrderService.chargingOrderList(orderQuery).getRecords(); |
| | | R<List<TAppUser>> usersResult = appUserClient.getUserByIds(userIds); |
| | | fillVipChargingOrderPhones(list, usersResult == null ? null : usersResult.getData()); |
| | | List<EnterpriseChargingDetailExport> exports = new ArrayList<>(); |
| | | for (ChargingOrderVO order : list) { |
| | | exports.add(buildVipChargingOrderExport(order)); |
| | |
| | | writeVipChargingOrderWorkbook(exports); |
| | | } |
| | | |
| | | static void fillVipChargingOrderPhones(List<ChargingOrderVO> orders, List<TAppUser> users) { |
| | | if (orders == null || orders.isEmpty() || users == null || users.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<Long, TAppUser> userMap = users.stream() |
| | | .filter(Objects::nonNull) |
| | | .filter(user -> user.getId() != null) |
| | | .collect(Collectors.toMap(TAppUser::getId, Function.identity(), (first, second) -> first)); |
| | | for (ChargingOrderVO order : orders) { |
| | | if (order == null || StringUtils.hasLength(order.getPhone())) { |
| | | continue; |
| | | } |
| | | TAppUser user = userMap.get(order.getAppUserId()); |
| | | if (user != null) { |
| | | order.setPhone(user.getPhone()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | static EnterpriseChargingDetailExport buildVipChargingOrderExport(ChargingOrderVO order) { |
| | | EnterpriseChargingDetailExport export = new EnterpriseChargingDetailExport(); |
| | | export.setCode(order.getCode()); |
| | |
| | | package com.ruoyi.order.dto; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.common.core.web.page.BasePage; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | |
| | | private Integer siteId; |
| | | |
| | | @ApiModelProperty("Order create start time") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime1; |
| | | |
| | | @ApiModelProperty("Order create end time") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime2; |
| | | @ApiModelProperty("Order code") |
| | | private String code; |
| | |
| | | private Integer orderSource; |
| | | |
| | | @ApiModelProperty("Charging start time begin") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime startTime1; |
| | | |
| | | @ApiModelProperty("Charging start time end") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime startTime2; |
| | | |
| | | @ApiModelProperty("Charging end time begin") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime endTime1; |
| | | |
| | | @ApiModelProperty("Charging end time end") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime endTime2; |
| | | } |
| | | } |
| | |
| | | |
| | | List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(@Param("data") EnterpriseChargingAnalysisQuery query); |
| | | |
| | | List<Map<String, Object>> enterpriseChargingPeriodCounts(@Param("orderIds") List<Long> orderIds); |
| | | |
| | | List<TChargingOrderRefund> getRefundList(@Param("pageInfo")PageInfo<TChargingOrderRefund> pageInfo,@Param("data") ChargingRefundDto chargingRefundDto); |
| | | |
| | | List<TChargingOrderRefund> getRefundListWithoutOrderJoin(@Param("pageInfo") PageInfo<TChargingOrderRefund> pageInfo, |
| | | @Param("data") ChargingRefundDto chargingRefundDto); |
| | | |
| | | List<ChargingOrderGroup> chargingOrderGroup(@Param("chargingPercentProvinceDto")ChargingPercentProvinceDto chargingPercentProvinceDto); |
| | | |
| | | List<SixChargingDto> chargePartner(@Param("sixBefore") String sixBefore, @Param("siteIds") List<Integer> siteIds); |
| | |
| | | private static final String SHARE_AFTER_COMMIT_RESOURCE_PREFIX = "chargingOrderShareAfterCommit:"; |
| | | private static final String SPECIAL_REFUND_TITLE = "特殊退款处理"; |
| | | private static final int RESERVATION_RECOMMENDATION_HISTORY_DAYS = 30; |
| | | private static final int ENTERPRISE_DETAIL_PERIOD_BATCH_SIZE = 500; |
| | | |
| | | @Override |
| | | public ReservationRecommendationVO getReservationRecommendation(Integer siteId) { |
| | |
| | | public R<PageInfo<EnterpriseChargingDetailVO>> enterpriseChargingDetail(EnterpriseChargingAnalysisQuery query) { |
| | | PageInfo<EnterpriseChargingDetailVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize()); |
| | | List<EnterpriseChargingDetailVO> list = this.baseMapper.enterpriseChargingDetail(pageInfo, query); |
| | | fillEnterpriseChargingPeriodCounts(list); |
| | | pageInfo.setRecords(list); |
| | | return R.ok(pageInfo); |
| | | } |
| | | |
| | | @Override |
| | | public List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(EnterpriseChargingAnalysisQuery query) { |
| | | return this.baseMapper.enterpriseChargingDetailExport(query); |
| | | List<EnterpriseChargingDetailVO> list = this.baseMapper.enterpriseChargingDetailExport(query); |
| | | fillEnterpriseChargingPeriodCounts(list); |
| | | return list; |
| | | } |
| | | |
| | | private void fillEnterpriseChargingPeriodCounts(List<EnterpriseChargingDetailVO> list) { |
| | | if (list == null || list.isEmpty()) { |
| | | return; |
| | | } |
| | | List<Long> orderIds = list.stream() |
| | | .map(EnterpriseChargingDetailVO::getId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | Map<Long, Integer> periodCountMap = new HashMap<>(); |
| | | for (int start = 0; start < orderIds.size(); start += ENTERPRISE_DETAIL_PERIOD_BATCH_SIZE) { |
| | | int end = Math.min(start + ENTERPRISE_DETAIL_PERIOD_BATCH_SIZE, orderIds.size()); |
| | | List<Map<String, Object>> rows = this.baseMapper.enterpriseChargingPeriodCounts(orderIds.subList(start, end)); |
| | | if (rows == null) { |
| | | continue; |
| | | } |
| | | for (Map<String, Object> row : rows) { |
| | | Long orderId = mapLong(row, "orderId", "orderid", "charging_order_id"); |
| | | Integer periodCount = mapInteger(row, "periodCount", "periodcount"); |
| | | if (orderId != null) { |
| | | periodCountMap.put(orderId, periodCount == null ? 0 : periodCount); |
| | | } |
| | | } |
| | | } |
| | | for (EnterpriseChargingDetailVO detail : list) { |
| | | detail.setPeriodCount(periodCountMap.getOrDefault(detail.getId(), 0)); |
| | | } |
| | | } |
| | | |
| | | private Long mapLong(Map<String, Object> row, String... keys) { |
| | | Object value = mapValue(row, keys); |
| | | return value instanceof Number ? ((Number) value).longValue() : null; |
| | | } |
| | | |
| | | private Integer mapInteger(Map<String, Object> row, String... keys) { |
| | | Object value = mapValue(row, keys); |
| | | return value instanceof Number ? ((Number) value).intValue() : null; |
| | | } |
| | | |
| | | private Object mapValue(Map<String, Object> row, String... keys) { |
| | | if (row == null) { |
| | | return null; |
| | | } |
| | | for (String key : keys) { |
| | | if (row.containsKey(key)) { |
| | | return row.get(key); |
| | | } |
| | | } |
| | | for (Map.Entry<String, Object> entry : row.entrySet()) { |
| | | for (String key : keys) { |
| | | if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)) { |
| | | return entry.getValue(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private EnterpriseChargingAnalysisQuery buildEnterpriseRowQuery(EnterpriseChargingAnalysisQuery source, EnterpriseChargingAnalysisVO row) { |
| | |
| | | @Override |
| | | public R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto) { |
| | | PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(chargingRefundDto.getPageCurr(), chargingRefundDto.getPageSize()); |
| | | List<TChargingOrderRefund> list = this.baseMapper.getRefundList(pageInfo, chargingRefundDto); |
| | | boolean withoutOrderJoin = !StringUtils.hasLength(chargingRefundDto.getSerialNumber()) |
| | | && (chargingRefundDto.getSiteIds() == null || chargingRefundDto.getSiteIds().isEmpty()); |
| | | List<TChargingOrderRefund> list = withoutOrderJoin |
| | | ? this.baseMapper.getRefundListWithoutOrderJoin(pageInfo, chargingRefundDto) |
| | | : this.baseMapper.getRefundList(pageInfo, chargingRefundDto); |
| | | if (withoutOrderJoin) { |
| | | enrichRefundOrderInfo(list); |
| | | } |
| | | for (TChargingOrderRefund refund : list) { |
| | | refund.setUid(refund.getType() + "-" + refund.getId()); |
| | | } |
| | |
| | | |
| | | return R.ok(pageInfo); |
| | | } |
| | | |
| | | private void enrichRefundOrderInfo(List<TChargingOrderRefund> refunds) { |
| | | if (refunds == null || refunds.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<Integer, List<Long>> orderIdsByType = refunds.stream() |
| | | .filter(Objects::nonNull) |
| | | .filter(refund -> refund.getType() != null && StringUtils.hasLength(refund.getOrderId())) |
| | | .collect(Collectors.groupingBy(TChargingOrderRefund::getType, |
| | | Collectors.mapping(refund -> Long.valueOf(refund.getOrderId()), Collectors.toList()))); |
| | | |
| | | Map<Long, TChargingOrder> chargingOrderMap = orderIdsByType.getOrDefault(1, Collections.emptyList()).isEmpty() |
| | | ? Collections.emptyMap() |
| | | : indexByLongId(listByIds(orderIdsByType.get(1)), TChargingOrder::getId); |
| | | Map<Long, TShoppingOrder> shoppingOrderMap = orderIdsByType.getOrDefault(2, Collections.emptyList()).isEmpty() |
| | | ? Collections.emptyMap() |
| | | : indexByLongId(shoppingOrderService.listByIds(orderIdsByType.get(2)), TShoppingOrder::getId); |
| | | Map<Long, TVipOrder> vipOrderMap = orderIdsByType.getOrDefault(3, Collections.emptyList()).isEmpty() |
| | | ? Collections.emptyMap() |
| | | : indexByLongId(vipOrderService.listByIds(orderIdsByType.get(3)), TVipOrder::getId); |
| | | |
| | | for (TChargingOrderRefund refund : refunds) { |
| | | if (refund == null || refund.getType() == null || !StringUtils.hasLength(refund.getOrderId())) { |
| | | continue; |
| | | } |
| | | Long orderId = Long.valueOf(refund.getOrderId()); |
| | | if (refund.getType() == 1) { |
| | | TChargingOrder order = chargingOrderMap.get(orderId); |
| | | if (order != null) { |
| | | refund.setSerailNumber(order.getRechargeSerialNumber()); |
| | | refund.setPaymentAmount(order.getPaymentAmount()); |
| | | refund.setPayTime(order.getCreateTime()); |
| | | refund.setUserId(order.getAppUserId() == null ? null : String.valueOf(order.getAppUserId())); |
| | | refund.setSiteId(order.getSiteId()); |
| | | refund.setPayMethod(order.getPayMethod()); |
| | | } |
| | | } else if (refund.getType() == 2) { |
| | | TShoppingOrder order = shoppingOrderMap.get(orderId); |
| | | if (order != null) { |
| | | refund.setSerailNumber(order.getSerialNumber()); |
| | | refund.setPaymentAmount(order.getPaymentAmount()); |
| | | refund.setPayTime(order.getCreateTime()); |
| | | refund.setUserId(order.getAppUserId() == null ? null : String.valueOf(order.getAppUserId())); |
| | | refund.setPayMethod(3); |
| | | } |
| | | } else if (refund.getType() == 3) { |
| | | TVipOrder order = vipOrderMap.get(orderId); |
| | | if (order != null) { |
| | | refund.setSerailNumber(order.getSerialNumber()); |
| | | refund.setPaymentAmount(order.getPaymentAmount()); |
| | | refund.setPayTime(order.getCreateTime()); |
| | | refund.setUserId(order.getAppUserId() == null ? null : String.valueOf(order.getAppUserId())); |
| | | refund.setPayMethod(3); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<ChargingOrderGroup> chargingOrderGroup(ChargingPercentProvinceDto chargingPercentProvinceDto) { |
| | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.api.feignClient.AppUserClient; |
| | | import com.ruoyi.account.api.model.TAppUser; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.account.api.model.TAppUserCar; |
| | | import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient; |
| | | import com.ruoyi.chargingPile.api.feignClient.SiteClient; |
| | |
| | | return new PageInfo<>(); |
| | | } |
| | | } |
| | | query.setUserIds(tAppUsers.stream().map(TAppUser::getId).collect(Collectors.toList())); |
| | | PageInfo<TOrderInvoiceVO> pageInfo = new PageInfo<>(query.getPageCurr(),query.getPageSize()); |
| | | List<TOrderInvoiceVO> list = this.baseMapper.pageList(query,pageInfo); |
| | | List<Site> siteList = siteClient.getSiteAll().getData(); |
| | | for (TOrderInvoiceVO tOrderInvoiceVO : list) { |
| | | tOrderInvoiceVO.setUid(tOrderInvoiceVO.getId().toString()); |
| | | List<Long> collect = orderInvoiceDetailService.lambdaQuery().eq(TOrderInvoiceDetail::getOrderInvoiceId, tOrderInvoiceVO.getId()).list().stream() |
| | | .map(TOrderInvoiceDetail::getOrderId).collect(Collectors.toList()); |
| | | query.setUserIds(tAppUsers.stream().map(TAppUser::getId).collect(Collectors.toList())); |
| | | PageInfo<TOrderInvoiceVO> pageInfo = new PageInfo<>(query.getPageCurr(),query.getPageSize()); |
| | | List<TOrderInvoiceVO> list = this.baseMapper.pageList(query,pageInfo); |
| | | if (CollectionUtils.isEmpty(list)) { |
| | | pageInfo.setRecords(list); |
| | | return pageInfo; |
| | | } |
| | | List<Long> invoiceIds = list.stream().map(TOrderInvoiceVO::getId).filter(Objects::nonNull).collect(Collectors.toList()); |
| | | List<TOrderInvoiceDetail> orderInvoiceDetailList = invoiceIds.isEmpty() ? new ArrayList<>() |
| | | : orderInvoiceDetailService.list(new LambdaQueryWrapper<TOrderInvoiceDetail>() |
| | | .in(TOrderInvoiceDetail::getOrderInvoiceId, invoiceIds)); |
| | | Map<Long, List<TOrderInvoiceDetail>> invoiceDetailMap = orderInvoiceDetailList.stream() |
| | | .collect(Collectors.groupingBy(TOrderInvoiceDetail::getOrderInvoiceId)); |
| | | List<Long> appUserIds = list.stream().map(TOrderInvoiceVO::getAppUserId) |
| | | .filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | R<List<TAppUser>> appUserResult = appUserIds.isEmpty() ? null : appUserClient.getUserByIds(appUserIds); |
| | | List<TAppUser> pageUsers = appUserResult == null || appUserResult.getData() == null |
| | | ? new ArrayList<>() : appUserResult.getData(); |
| | | Map<Long, TAppUser> appUserMap = pageUsers.stream() |
| | | .filter(user -> user != null && user.getId() != null) |
| | | .collect(Collectors.toMap(TAppUser::getId, user -> user, (first, second) -> first)); |
| | | List<Site> siteList = siteClient.getSiteAll().getData(); |
| | | for (TOrderInvoiceVO tOrderInvoiceVO : list) { |
| | | tOrderInvoiceVO.setUid(tOrderInvoiceVO.getId().toString()); |
| | | List<Long> collect = invoiceDetailMap.getOrDefault(tOrderInvoiceVO.getId(), Collections.emptyList()).stream() |
| | | .map(TOrderInvoiceDetail::getOrderId).collect(Collectors.toList()); |
| | | Integer siteId = tOrderInvoiceVO.getSiteId(); |
| | | if (siteId == null && !CollectionUtils.isEmpty(collect) && Integer.valueOf(1).equals(tOrderInvoiceVO.getOrderType())) { |
| | | List<TChargingOrder> chargingOrders = chargingOrderService.listByIds(collect); |
| | |
| | | } |
| | | } |
| | | } |
| | | // 将其全部转化为String |
| | | tOrderInvoiceVO.setIds(collect.stream().map(String::valueOf).collect(Collectors.toList())); |
| | | TAppUser appUser = appUserClient.getUserById(tOrderInvoiceVO.getAppUserId()).getData(); |
| | | if (appUser!=null){ |
| | | tOrderInvoiceVO.setUserPhone(appUser.getPhone()); |
| | | // 将其全部转化为String |
| | | tOrderInvoiceVO.setIds(collect.stream().map(String::valueOf).collect(Collectors.toList())); |
| | | TAppUser appUser = appUserMap.get(tOrderInvoiceVO.getAppUserId()); |
| | | if (appUser!=null){ |
| | | tOrderInvoiceVO.setUserPhone(appUser.getPhone()); |
| | | } |
| | | fillInvoiceDetailAmount(tOrderInvoiceVO, orderInvoiceDetailList); |
| | | } |
| | | |
| | | } |
| | | // 查询这个开票的订单ids |
| | | |
| | | if (!list.isEmpty()){ |
| | | List<Long> ids = list.stream().map(TOrderInvoiceVO::getId).collect(Collectors.toList()); |
| | | |
| | | List<TOrderInvoiceDetail> orderInvoiceDetailList = orderInvoiceDetailService.list(new LambdaQueryWrapper<TOrderInvoiceDetail>() |
| | | .in(TOrderInvoiceDetail::getOrderInvoiceId, ids)); |
| | | |
| | | List<TAppUser> finalTAppUsers = tAppUsers; |
| | | list.forEach(e -> fillInvoiceDetailAmount(e, orderInvoiceDetailList)); |
| | | } |
| | | |
| | | |
| | | pageInfo.setRecords(list); |
| | | pageInfo.setRecords(list); |
| | | return pageInfo; |
| | | } |
| | | |
| | |
| | | order by o.refund_time desc |
| | | |
| | | </select> |
| | | <select id="getRefundListWithoutOrderJoin" resultType="com.ruoyi.order.api.model.TChargingOrderRefund"> |
| | | SELECT * FROM ( |
| | | SELECT |
| | | re.id, |
| | | re.`code`, |
| | | re.refund_code, |
| | | re.refund_title, |
| | | re.refund_reason, |
| | | re.refund_amount, |
| | | re.balance_refund_amount, |
| | | re.third_refund_amount, |
| | | re.refund_total_amount, |
| | | re.refund_time, |
| | | re.pay_type, |
| | | re.pay_amount, |
| | | re.refund_remark, |
| | | re.charging_order_id AS order_id, |
| | | 1 AS type |
| | | FROM t_charging_order_refund re |
| | | <where> |
| | | <if test="data.specialRefund != null and data.specialRefund"> |
| | | AND re.refund_title = '特殊退款处理' |
| | | </if> |
| | | <if test="data.code != null and data.code != ''"> |
| | | AND re.code LIKE CONCAT('%', #{data.code}, '%') |
| | | </if> |
| | | <if test="data.refundCode != null and data.refundCode != ''"> |
| | | AND re.refund_code LIKE CONCAT('%', #{data.refundCode}, '%') |
| | | </if> |
| | | </where> |
| | | <if test="data.specialRefund == null or !data.specialRefund"> |
| | | UNION ALL |
| | | SELECT |
| | | re.id, |
| | | re.`code`, |
| | | re.refund_code, |
| | | re.refund_title, |
| | | re.refund_reason, |
| | | re.refund_amount, |
| | | 0 AS balance_refund_amount, |
| | | re.refund_amount AS third_refund_amount, |
| | | re.refund_total_amount, |
| | | re.refund_time, |
| | | re.pay_type, |
| | | re.pay_amount, |
| | | re.refund_remark, |
| | | re.shopping_order_id AS order_id, |
| | | 2 AS type |
| | | FROM t_shopping_order_refund re |
| | | <where> |
| | | <if test="data.code != null and data.code != ''"> |
| | | AND re.code LIKE CONCAT('%', #{data.code}, '%') |
| | | </if> |
| | | <if test="data.refundCode != null and data.refundCode != ''"> |
| | | AND re.refund_code LIKE CONCAT('%', #{data.refundCode}, '%') |
| | | </if> |
| | | </where> |
| | | UNION ALL |
| | | SELECT |
| | | re.id, |
| | | re.`code`, |
| | | re.refund_code, |
| | | re.refund_title, |
| | | re.refund_reason, |
| | | re.refund_amount, |
| | | 0 AS balance_refund_amount, |
| | | re.refund_amount AS third_refund_amount, |
| | | re.refund_total_amount, |
| | | re.refund_time, |
| | | re.pay_type, |
| | | re.pay_amount, |
| | | re.refund_remark, |
| | | re.vip_order_id AS order_id, |
| | | 3 AS type |
| | | FROM t_vip_order_refund re |
| | | <where> |
| | | <if test="data.code != null and data.code != ''"> |
| | | AND re.code LIKE CONCAT('%', #{data.code}, '%') |
| | | </if> |
| | | <if test="data.refundCode != null and data.refundCode != ''"> |
| | | AND re.refund_code LIKE CONCAT('%', #{data.refundCode}, '%') |
| | | </if> |
| | | </where> |
| | | </if> |
| | | ) o |
| | | ORDER BY o.refund_time DESC |
| | | </select> |
| | | <select id="chargingOrderGroup" resultType="com.ruoyi.common.core.dto.ChargingOrderGroup"> |
| | | SELECT SUM(tc.charging_capacity) as charging_capacity,tc.site_id |
| | | FROM t_charging_order tc |
| | |
| | | a.recharge_amount as rechargeAmount, |
| | | a.payment_amount as orderAmount, |
| | | a.vip_discount_amount as vipDiscountAmount, |
| | | e.refund_amount as refundAmount, |
| | | a.refund_amount as refundAmount, |
| | | a.is_whitelist as isWhitelist, |
| | | IFNULL(a.whitelist_electric_refund_amount, 0) + IFNULL(a.whitelist_service_refund_amount, 0) as whitelistRefundAmount, |
| | | IFNULL(a.whitelist_electric_refund_amount, 0) as whitelistElectricRefundAmount, |
| | |
| | | a.payment_amount as paymentAmount, |
| | | a.third_pay_amount as thirdPayAmount, |
| | | a.balance_pay_amount as balancePayAmount, |
| | | e.refund_amount as residualAmount, |
| | | a.refund_amount as residualAmount, |
| | | h.electrovalence as electrovalence, |
| | | CASE WHEN a.order_source = 1 THEN h.serviceCharge ELSE h.serviceCharge * 0.8 END as serviceCharge, |
| | | CASE WHEN a.order_source = 2 THEN h.serviceCharge * 0.2 ELSE 0 END as sharingAmount, |
| | |
| | | left join charging_pile_service.t_site b on (a.site_id = b.id) |
| | | left join charging_pile_account.t_app_user_car c on (a.app_user_car_id = c.id) |
| | | left join charging_pile_service.t_charging_gun d on (a.charging_gun_id = d.id) |
| | | left join charging_pile_order.t_charging_order_refund e on (a.id = e.charging_order_id) |
| | | left join charging_pile_account.t_app_user f on (a.app_user_id = f.id) |
| | | left join charging_pile_order.t_accounting_strategy_order g on (a.id = g.charging_order_id) |
| | | left join (select sum(period_electric_price) as electrovalence,sum(period_service_price) as serviceCharge,charging_order_id from charging_pile_order.t_charging_order_accounting_strategy group by charging_order_id) as h on (a.id = h.charging_order_id) |
| | | left join ( |
| | | select charging_order_id, max(`name`) as `name` |
| | | from charging_pile_order.t_accounting_strategy_order |
| | | group by charging_order_id |
| | | ) g on (a.id = g.charging_order_id) |
| | | inner join ( |
| | | select sum(period_electric_price) as electrovalence, |
| | | sum(period_service_price) as serviceCharge, |
| | | charging_order_id |
| | | from charging_pile_order.t_charging_order_accounting_strategy |
| | | group by charging_order_id |
| | | ) as h on (a.id = h.charging_order_id) |
| | | where a.del_flag = 0 and a.recharge_payment_status = 2 |
| | | <if test="null != req.code and req.code!=''"> |
| | | and a.code LIKE CONCAT('%',#{req.code},'%') |
| | |
| | | </sql> |
| | | |
| | | <sql id="enterpriseChargingDetailSelect"> |
| | | SELECT DISTINCT |
| | | SELECT |
| | | co.id, |
| | | co.code, |
| | | co.order_type AS orderType, |
| | |
| | | au.phone AS phone, |
| | | car.license_plate AS licensePlate, |
| | | co.charging_capacity AS chargingCapacity, |
| | | IFNULL(price.electrovalence, 0) AS electrovalence, |
| | | IFNULL(price.serviceCharge, 0) AS serviceCharge, |
| | | IFNULL(tod.electrovalence, 0) AS electrovalence, |
| | | IFNULL(tod.service_charge, 0) AS serviceCharge, |
| | | co.end_mode AS endMode, |
| | | IFNULL(price.periodCount, 0) AS periodCount, |
| | | co.create_time AS createTime, |
| | | co.start_time AS startTime, |
| | | co.end_time AS endTime, |
| | |
| | | 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 |
| | | LEFT JOIN charging_pile_account.t_app_user_car car ON car.id = co.app_user_car_id |
| | | LEFT JOIN ( |
| | | SELECT charging_order_id, |
| | | SUM(period_electric_price) AS electrovalence, |
| | | SUM(period_service_price) AS serviceCharge, |
| | | COUNT(1) AS periodCount |
| | | FROM t_charging_order_accounting_strategy |
| | | GROUP BY charging_order_id |
| | | ) price ON price.charging_order_id = co.id |
| | | </sql> |
| | | |
| | | <select id="enterpriseChargingDetail" resultType="com.ruoyi.order.dto.EnterpriseChargingDetailVO"> |
| | |
| | | ORDER BY co.create_time DESC |
| | | </select> |
| | | |
| | | <select id="enterpriseChargingPeriodCounts" resultType="map"> |
| | | SELECT charging_order_id AS orderId, |
| | | COUNT(1) AS periodCount |
| | | FROM t_charging_order_accounting_strategy |
| | | WHERE charging_order_id IN |
| | | <foreach collection="orderIds" item="orderId" open="(" separator="," close=")"> |
| | | #{orderId} |
| | | </foreach> |
| | | GROUP BY charging_order_id |
| | | </select> |
| | | |
| | | <select id="queryChargingBillIncome" resultType="map"> |
| | | select |
| | | income.site_id, |
| | |
| | | package com.ruoyi.order.controller; |
| | | |
| | | import com.ruoyi.account.api.model.TAppUser; |
| | | import com.ruoyi.order.api.vo.ChargingOrderVO; |
| | | import com.ruoyi.order.export.EnterpriseChargingDetailExport; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collections; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | |
| | | class TVipOrderControllerTest { |
| | | |
| | | @Test |
| | | void fillsMissingVipChargingOrderPhoneFromAccountUser() { |
| | | ChargingOrderVO order = new ChargingOrderVO(); |
| | | order.setAppUserId(123L); |
| | | TAppUser user = new TAppUser(); |
| | | user.setId(123L); |
| | | user.setPhone("13800000000"); |
| | | |
| | | TVipOrderController.fillVipChargingOrderPhones( |
| | | Collections.singletonList(order), Collections.singletonList(user)); |
| | | |
| | | assertEquals("13800000000", order.getPhone()); |
| | | } |
| | | |
| | | @Test |
| | | void formatExportDateTimeReturnsEmptyStringForMissingOrderTime() { |
| | | assertEquals("", TVipOrderController.formatExportDateTime(null)); |
| | | } |
| New file |
| | |
| | | package com.ruoyi.order.dto; |
| | | |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | |
| | | class EnterpriseChargingAnalysisQueryTest { |
| | | |
| | | @Test |
| | | void shouldDeserializeDateTimeWithSpaceSeparator() throws Exception { |
| | | ObjectMapper objectMapper = new ObjectMapper(); |
| | | objectMapper.registerModule(new JavaTimeModule()); |
| | | |
| | | EnterpriseChargingAnalysisQuery query = objectMapper.readValue( |
| | | "{\"startTime1\":\"2026-07-01 00:00:00\",\"startTime2\":\"2026-07-29 00:00:00\"}", |
| | | EnterpriseChargingAnalysisQuery.class |
| | | ); |
| | | |
| | | assertEquals(LocalDateTime.of(2026, 7, 1, 0, 0), query.getStartTime1()); |
| | | assertEquals(LocalDateTime.of(2026, 7, 29, 0, 0), query.getStartTime2()); |
| | | } |
| | | } |
| | |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | |
| | | String detailSelect = mapper.substring( |
| | | mapper.indexOf("<sql id=\"enterpriseChargingDetailSelect\""), |
| | | mapper.indexOf("<select id=\"enterpriseChargingDetail\"")); |
| | | String baseWhere = mapper.substring( |
| | | mapper.indexOf("<sql id=\"enterpriseChargingBaseWhere\""), |
| | | mapper.indexOf("<select id=\"enterpriseChargingAnalysis\"")); |
| | | |
| | | assertFalse(detailSelect.contains("SELECT DISTINCT")); |
| | | assertFalse(detailSelect.contains("FROM t_charging_order_accounting_strategy")); |
| | | assertTrue(detailSelect.contains("IFNULL(tod.electrovalence, 0) AS electrovalence")); |
| | | assertTrue(detailSelect.contains("IFNULL(tod.service_charge, 0) AS serviceCharge")); |
| | | assertTrue(detailSelect.contains("AS licensePlate")); |
| | | assertTrue(detailSelect.contains("AS chargingCapacity")); |
| | | assertTrue(detailSelect.contains("AS electrovalence")); |
| | | assertTrue(detailSelect.contains("AS serviceCharge")); |
| | | assertTrue(detailSelect.contains("AS endMode")); |
| | | assertTrue(detailSelect.contains("AS periodCount")); |
| | | assertTrue(baseWhere.contains("AND toi.status IN (2, 3)")); |
| | | assertFalse(baseWhere.contains("AND toi.status IN (1, 2, 3)")); |
| | | |
| | | assertTrue(mapper.contains("<select id=\"enterpriseChargingPeriodCounts\"")); |
| | | String periodCounts = mapper.substring( |
| | | mapper.indexOf("<select id=\"enterpriseChargingPeriodCounts\""), |
| | | mapper.indexOf("<select id=\"queryChargingBillIncome\"")); |
| | | assertTrue(periodCounts.contains("FROM t_charging_order_accounting_strategy")); |
| | | assertTrue(periodCounts.contains("WHERE charging_order_id IN")); |
| | | assertTrue(periodCounts.contains("GROUP BY charging_order_id")); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.order.service.impl; |
| | | |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.io.IOException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | | class TOrderInvoicePageListQueryTest { |
| | | |
| | | @Test |
| | | void pageListLoadsInvoiceDetailsAndUsersInBatches() throws IOException { |
| | | Path sourcePath = Paths.get("src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java"); |
| | | if (!Files.exists(sourcePath)) { |
| | | sourcePath = Paths.get("ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java"); |
| | | } |
| | | String source = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8); |
| | | String method = source.substring( |
| | | source.indexOf("public PageInfo<TOrderInvoiceVO> pageList"), |
| | | source.indexOf("public List<String> invoicingCompanyOptions")); |
| | | |
| | | assertEquals(1, occurrences(method, "orderInvoiceDetailService.list(")); |
| | | assertTrue(method.contains("appUserClient.getUserByIds(appUserIds)")); |
| | | assertFalse(method.contains("appUserClient.getUserById(")); |
| | | } |
| | | |
| | | private int occurrences(String source, String target) { |
| | | int count = 0; |
| | | int index = 0; |
| | | while ((index = source.indexOf(target, index)) >= 0) { |
| | | count++; |
| | | index += target.length(); |
| | | } |
| | | return count; |
| | | } |
| | | } |