ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/FinancialSettlementController.java
@@ -361,6 +361,8 @@ BigDecimal paymentAmount = BigDecimal.ZERO; BigDecimal chargingCapacity = BigDecimal.ZERO; BigDecimal tripartiteCommission = BigDecimal.ZERO; BigDecimal platformIncome = BigDecimal.ZERO; BigDecimal partnerIncome = BigDecimal.ZERO; BigDecimal balanceIncome = BigDecimal.ZERO; BigDecimal balanceGiftIncome = BigDecimal.ZERO; BigDecimal vipGiftAmount = BigDecimal.ZERO; @@ -375,6 +377,8 @@ paymentAmount = paymentAmount.add(zero(item.getPaymentAmount())); chargingCapacity = chargingCapacity.add(zero(item.getElectricity())); tripartiteCommission = tripartiteCommission.add(zero(item.getSharingAmount())); platformIncome = platformIncome.add(zero(item.getPlatformIncome())); partnerIncome = partnerIncome.add(zero(item.getPartnerIncome())); balanceIncome = balanceIncome.add(zero(item.getBalanceIncome())); balanceGiftIncome = balanceGiftIncome.add(zero(item.getBalanceGiftIncome())); vipGiftAmount = vipGiftAmount.add(zero(item.getVipDiscountAmount())); @@ -388,7 +392,8 @@ + " 充电收入:" + money(paymentAmount) + "元" + " 充电电量:" + quantity(chargingCapacity) + "度" + " 充电时长:" + formatChargingDurationMinutes(chargingSeconds) + "分钟" + " 平台手续费:0.00元" + " 平台收入:" + money(platformIncome) + "元" + " 合作商分成:" + money(partnerIncome) + "元" + " 三方平台分佣:" + money(tripartiteCommission) + "元" + " 余额收入:" + money(balanceIncome) + "元" + " 余额赠送:" + money(balanceGiftIncome) + "元" ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TChargingOrderMapper.java
@@ -8,6 +8,7 @@ import com.ruoyi.order.api.model.ChargingListQuery; import com.ruoyi.order.api.model.TChargingOrder; import com.ruoyi.order.api.model.TChargingOrderRefund; import com.ruoyi.order.api.model.TOrderInvoiceDetail; import com.ruoyi.order.api.query.ChargingOrderQuery; import com.ruoyi.order.api.vo.ChargingOrderListVO; import com.ruoyi.order.api.vo.ChargingOrderVO; @@ -84,10 +85,17 @@ List<Map<String, Object>> enterpriseChargingTimesByCompanies(@Param("data") EnterpriseChargingAnalysisQuery query, @Param("companyNames") List<String> companyNames); List<EnterpriseChargingDetailVO> enterpriseChargingDetail(@Param("pageInfo") PageInfo<EnterpriseChargingDetailVO> pageInfo, @Param("data") EnterpriseChargingAnalysisQuery query); List<Long> enterpriseInvoiceIds(@Param("data") EnterpriseChargingAnalysisQuery query); List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(@Param("data") EnterpriseChargingAnalysisQuery query); List<TOrderInvoiceDetail> enterpriseInvoiceChargingDetails(@Param("invoiceIds") List<Long> invoiceIds); Long enterpriseChargingDetailCount(@Param("orderIds") List<Long> orderIds, @Param("data") EnterpriseChargingAnalysisQuery query); List<EnterpriseChargingDetailVO> enterpriseChargingDetail(@Param("orderIds") List<Long> orderIds, @Param("data") EnterpriseChargingAnalysisQuery query, @Param("offset") long offset, @Param("pageSize") long pageSize); List<Map<String, Object>> enterpriseChargingPeriodCounts(@Param("orderIds") List<Long> orderIds); ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -141,6 +141,8 @@ 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; private static final int ENTERPRISE_INVOICE_BATCH_SIZE = 500; private static final int ENTERPRISE_ORDER_ID_BATCH_SIZE = 500; @Override public ReservationRecommendationVO getReservationRecommendation(Integer siteId) { @@ -3139,7 +3141,22 @@ @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); Map<Long, TOrderInvoiceDetail> invoiceDetailMap = getEnterpriseInvoiceDetailMap(query); if (invoiceDetailMap.isEmpty()) { pageInfo.setTotal(0L); pageInfo.setRecords(Collections.emptyList()); return R.ok(pageInfo); } List<Long> orderIds = new ArrayList<>(invoiceDetailMap.keySet()); long total = countEnterpriseChargingDetails(orderIds, query); pageInfo.setTotal(total); if (total == 0L) { pageInfo.setRecords(Collections.emptyList()); return R.ok(pageInfo); } List<EnterpriseChargingDetailVO> list = queryEnterpriseChargingDetailPage( orderIds, query, pageInfo.offset(), pageInfo.getSize()); fillEnterpriseInvoiceAmounts(list, invoiceDetailMap); fillEnterpriseChargingPeriodCounts(list); pageInfo.setRecords(list); return R.ok(pageInfo); @@ -3147,11 +3164,95 @@ @Override public List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(EnterpriseChargingAnalysisQuery query) { List<EnterpriseChargingDetailVO> list = this.baseMapper.enterpriseChargingDetailExport(query); Map<Long, TOrderInvoiceDetail> invoiceDetailMap = getEnterpriseInvoiceDetailMap(query); if (invoiceDetailMap.isEmpty()) { return Collections.emptyList(); } List<EnterpriseChargingDetailVO> list = queryEnterpriseChargingDetailPage( new ArrayList<>(invoiceDetailMap.keySet()), query, 0L, Long.MAX_VALUE); fillEnterpriseInvoiceAmounts(list, invoiceDetailMap); fillEnterpriseChargingPeriodCounts(list); return list; } private long countEnterpriseChargingDetails(List<Long> orderIds, EnterpriseChargingAnalysisQuery query) { long total = 0L; for (int start = 0; start < orderIds.size(); start += ENTERPRISE_ORDER_ID_BATCH_SIZE) { int end = Math.min(start + ENTERPRISE_ORDER_ID_BATCH_SIZE, orderIds.size()); Long batchTotal = this.baseMapper.enterpriseChargingDetailCount(orderIds.subList(start, end), query); if (batchTotal != null) { total += batchTotal; } } return total; } private List<EnterpriseChargingDetailVO> queryEnterpriseChargingDetailPage(List<Long> orderIds, EnterpriseChargingAnalysisQuery query, long offset, long pageSize) { if (orderIds == null || orderIds.isEmpty() || pageSize <= 0L) { return Collections.emptyList(); } long requiredRows = pageSize == Long.MAX_VALUE || offset > Long.MAX_VALUE - pageSize ? Long.MAX_VALUE : offset + pageSize; List<EnterpriseChargingDetailVO> candidates = new ArrayList<>(); for (int start = 0; start < orderIds.size(); start += ENTERPRISE_ORDER_ID_BATCH_SIZE) { int end = Math.min(start + ENTERPRISE_ORDER_ID_BATCH_SIZE, orderIds.size()); List<Long> batchOrderIds = orderIds.subList(start, end); long batchLimit = Math.min(requiredRows, batchOrderIds.size()); candidates.addAll(this.baseMapper.enterpriseChargingDetail(batchOrderIds, query, 0L, batchLimit)); } candidates.sort(Comparator .comparing(EnterpriseChargingDetailVO::getCreateTime, Comparator.nullsLast(Comparator.reverseOrder())) .thenComparing(EnterpriseChargingDetailVO::getId, Comparator.nullsLast(Comparator.reverseOrder()))); int fromIndex = (int) Math.min(offset, candidates.size()); long requestedEnd = pageSize == Long.MAX_VALUE || fromIndex > Long.MAX_VALUE - pageSize ? Long.MAX_VALUE : fromIndex + pageSize; int toIndex = (int) Math.min(requestedEnd, candidates.size()); return new ArrayList<>(candidates.subList(fromIndex, toIndex)); } private Map<Long, TOrderInvoiceDetail> getEnterpriseInvoiceDetailMap(EnterpriseChargingAnalysisQuery query) { List<Long> invoiceIds = this.baseMapper.enterpriseInvoiceIds(query); if (invoiceIds == null || invoiceIds.isEmpty()) { return Collections.emptyMap(); } Map<Long, TOrderInvoiceDetail> detailMap = new LinkedHashMap<>(); for (int start = 0; start < invoiceIds.size(); start += ENTERPRISE_INVOICE_BATCH_SIZE) { int end = Math.min(start + ENTERPRISE_INVOICE_BATCH_SIZE, invoiceIds.size()); List<TOrderInvoiceDetail> details = this.baseMapper.enterpriseInvoiceChargingDetails(invoiceIds.subList(start, end)); if (details == null) { continue; } for (TOrderInvoiceDetail detail : details) { if (detail == null || detail.getOrderId() == null) { continue; } detailMap.merge(detail.getOrderId(), detail, this::mergeEnterpriseInvoiceDetail); } } return detailMap; } private TOrderInvoiceDetail mergeEnterpriseInvoiceDetail(TOrderInvoiceDetail current, TOrderInvoiceDetail addition) { current.setElectrovalence(nullToZero(current.getElectrovalence()).add(nullToZero(addition.getElectrovalence()))); current.setServiceCharge(nullToZero(current.getServiceCharge()).add(nullToZero(addition.getServiceCharge()))); return current; } private void fillEnterpriseInvoiceAmounts(List<EnterpriseChargingDetailVO> list, Map<Long, TOrderInvoiceDetail> invoiceDetailMap) { if (list == null || list.isEmpty()) { return; } for (EnterpriseChargingDetailVO detail : list) { TOrderInvoiceDetail invoiceDetail = invoiceDetailMap.get(detail.getId()); detail.setElectrovalence(invoiceDetail == null ? BigDecimal.ZERO : nullToZero(invoiceDetail.getElectrovalence())); detail.setServiceCharge(invoiceDetail == null ? BigDecimal.ZERO : nullToZero(invoiceDetail.getServiceCharge())); } } private void fillEnterpriseChargingPeriodCounts(List<EnterpriseChargingDetailVO> list) { if (list == null || list.isEmpty()) { return; ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -2874,8 +2874,56 @@ ORDER BY toi.name, co.start_time </select> <sql id="enterpriseChargingDetailWhere"> <include refid="enterpriseChargingBaseWhere" /> <select id="enterpriseInvoiceIds" resultType="long"> SELECT id FROM t_order_invoice <where> invoicing_object_type = 2 AND status IN (2, 3) <if test="data.exactCompanyName != null and data.exactCompanyName != ''"> AND name = #{data.exactCompanyName} </if> <if test="(data.exactCompanyName == null or data.exactCompanyName == '') and data.companyName != null and data.companyName != ''"> AND name LIKE CONCAT('%', #{data.companyName}, '%') </if> <if test="data.socialCreditCode != null and data.socialCreditCode != ''"> AND IFNULL(tax_identification_number, '') = #{data.socialCreditCode} </if> </where> </select> <select id="enterpriseInvoiceChargingDetails" resultType="com.ruoyi.order.api.model.TOrderInvoiceDetail"> SELECT order_id AS orderId, electrovalence, service_charge AS serviceCharge FROM t_order_invoice_detail WHERE order_type = 1 AND order_invoice_id IN <foreach collection="invoiceIds" item="invoiceId" open="(" separator="," close=")"> #{invoiceId} </foreach> </select> <sql id="enterpriseChargingOrderWhere"> co.id IN <foreach collection="orderIds" item="orderId" open="(" separator="," close=")"> #{orderId} </foreach> <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> <if test="data.code != null and data.code != ''"> AND co.code LIKE CONCAT('%', #{data.code}, '%') </if> @@ -2908,6 +2956,23 @@ </if> </sql> <select id="enterpriseChargingDetailCount" resultType="long"> SELECT COUNT(*) FROM t_charging_order co <if test="data.siteType != null or data.partnerId != null or (data.partnerName != null and data.partnerName != '')"> INNER JOIN charging_pile_service.t_site s ON s.id = co.site_id </if> <if test="data.partnerName != null and data.partnerName != ''"> INNER JOIN charging_pile_service.t_partner p ON p.id = s.partner_id </if> <if test="data.phone != null and data.phone != ''"> INNER JOIN charging_pile_account.t_app_user au ON au.id = co.app_user_id </if> <where> <include refid="enterpriseChargingOrderWhere" /> </where> </select> <sql id="enterpriseChargingDetailSelect"> SELECT co.id, @@ -2928,16 +2993,12 @@ au.phone AS phone, car.license_plate AS licensePlate, co.charging_capacity AS chargingCapacity, IFNULL(tod.electrovalence, 0) AS electrovalence, IFNULL(tod.service_charge, 0) AS serviceCharge, co.end_mode AS endMode, 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 FROM t_charging_order co 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 @@ -2949,17 +3010,10 @@ <select id="enterpriseChargingDetail" resultType="com.ruoyi.order.dto.EnterpriseChargingDetailVO"> <include refid="enterpriseChargingDetailSelect" /> <where> <include refid="enterpriseChargingDetailWhere" /> <include refid="enterpriseChargingOrderWhere" /> </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 LIMIT #{offset}, #{pageSize} </select> <select id="enterpriseChargingPeriodCounts" resultType="map"> ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/controller/FinancialSettlementControllerTest.java
@@ -30,6 +30,8 @@ first.setElectricity(new BigDecimal("8.1234")); first.setChargingSecond(90L); first.setSharingAmount(new BigDecimal("0.46")); first.setPlatformIncome(new BigDecimal("1.24")); first.setPartnerIncome(new BigDecimal("0.64")); first.setBalanceIncome(new BigDecimal("3.00")); first.setBalanceGiftIncome(new BigDecimal("1.00")); first.setVipDiscountAmount(new BigDecimal("0.50")); @@ -40,6 +42,8 @@ second.setPaymentAmount(new BigDecimal("2.00")); second.setElectricity(new BigDecimal("1.0000")); second.setChargingSecond(30L); second.setPlatformIncome(new BigDecimal("0.76")); second.setPartnerIncome(new BigDecimal("0.36")); String summary = FinancialSettlementController.buildChargingPeriodExportSummary(Arrays.asList(first, second)); @@ -49,6 +53,8 @@ assertTrue(summary.contains("充电收入:14.46元")); assertTrue(summary.contains("充电电量:9.123度")); assertTrue(summary.contains("充电时长:2分钟")); assertTrue(summary.contains("平台收入:2.00元")); assertTrue(summary.contains("合作商分成:1.00元")); assertTrue(summary.contains("三方平台分佣:0.46元")); assertTrue(summary.contains("余额收入:3.00元")); assertTrue(summary.contains("余额赠送:1.00元")); ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/EnterpriseChargingDetailMapperTest.java
@@ -35,16 +35,40 @@ 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")); assertFalse(detailSelect.contains("t_order_invoice")); assertFalse(detailSelect.contains("t_order_invoice_detail")); assertTrue(detailSelect.contains("AS licensePlate")); assertTrue(detailSelect.contains("AS chargingCapacity")); assertTrue(detailSelect.contains("AS electrovalence")); assertTrue(detailSelect.contains("AS serviceCharge")); assertFalse(detailSelect.contains("AS electrovalence")); assertFalse(detailSelect.contains("AS serviceCharge")); assertTrue(detailSelect.contains("AS endMode")); assertTrue(baseWhere.contains("AND toi.status IN (2, 3)")); assertFalse(baseWhere.contains("AND toi.status IN (1, 2, 3)")); String countSelect = mapper.substring( mapper.indexOf("<select id=\"enterpriseChargingDetailCount\""), mapper.indexOf("<sql id=\"enterpriseChargingDetailSelect\"")); assertFalse(countSelect.contains("t_order_invoice")); assertFalse(countSelect.contains("t_order_invoice_detail")); assertFalse(countSelect.contains("t_charging_pile cp")); assertFalse(countSelect.contains("t_charging_gun cg")); assertFalse(countSelect.contains("t_app_user_car")); assertTrue(countSelect.contains("FROM t_charging_order co")); assertTrue(countSelect.contains("<include refid=\"enterpriseChargingOrderWhere\"")); String invoiceIds = mapper.substring( mapper.indexOf("<select id=\"enterpriseInvoiceIds\""), mapper.indexOf("<select id=\"enterpriseInvoiceChargingDetails\"")); assertTrue(invoiceIds.contains("FROM t_order_invoice")); assertFalse(invoiceIds.contains("JOIN")); String invoiceDetails = mapper.substring( mapper.indexOf("<select id=\"enterpriseInvoiceChargingDetails\""), mapper.indexOf("<sql id=\"enterpriseChargingOrderWhere\"")); assertTrue(invoiceDetails.contains("FROM t_order_invoice_detail")); assertTrue(invoiceDetails.contains("order_invoice_id IN")); assertFalse(invoiceDetails.contains("JOIN")); assertTrue(mapper.contains("<select id=\"enterpriseChargingPeriodCounts\"")); String periodCounts = mapper.substring( mapper.indexOf("<select id=\"enterpriseChargingPeriodCounts\""),