| | |
| | | .divide(BigDecimal.valueOf(3600), 2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private static final int SERIAL_ORDER_ID_SEARCH_LIMIT = 5000; |
| | | private static final int SITE_ORDER_ID_SEARCH_LIMIT = 50000; |
| | | |
| | | @Override |
| | | public R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto) { |
| | | PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(chargingRefundDto.getPageCurr(), chargingRefundDto.getPageSize()); |
| | | 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); |
| | | int pageCurr = chargingRefundDto.getPageCurr() == null ? 1 : chargingRefundDto.getPageCurr(); |
| | | int pageSize = chargingRefundDto.getPageSize() == null ? 50 : chargingRefundDto.getPageSize(); |
| | | PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(pageCurr, pageSize); |
| | | long offset = (long) (pageCurr - 1) * pageSize; |
| | | // 每个 UNION 分支须取 offset+pageSize 行,外层再按 offset,pageSize 切页,否则翻页时池子不足导致丢数据 |
| | | int branchLimit = (int) Math.min(offset + pageSize, Integer.MAX_VALUE); |
| | | boolean filterBySerial = StringUtils.hasLength(chargingRefundDto.getSerialNumber()); |
| | | boolean filterBySite = chargingRefundDto.getSiteIds() != null && !chargingRefundDto.getSiteIds().isEmpty(); |
| | | |
| | | List<TChargingOrderRefund> list; |
| | | if (!filterBySerial && !filterBySite) { |
| | | // 无筛选:手工分页。分页拦截器会对 UNION 查询生成全量 COUNT 且无法把 ORDER BY/LIMIT 下推到分片, |
| | | // 这里改为分支内下推(getRefundListPage)加分支 COUNT 求和(countRefundList) |
| | | pageInfo.setTotal(this.baseMapper.countRefundList(chargingRefundDto)); |
| | | list = this.baseMapper.getRefundListPage(chargingRefundDto, offset, pageSize, branchLimit); |
| | | } else { |
| | | // 筛选:逆反查。先按条件查订单 id(t_charging_order 等可下推/走索引), |
| | | // 再按退款表分片键 IN 过滤(可路由),替代跨分片 LEFT JOIN |
| | | RefundOrderIds ids = resolveRefundOrderIds(chargingRefundDto); |
| | | if (ids.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | pageInfo.setTotal(this.baseMapper.countRefundListByOrderIds(chargingRefundDto, |
| | | ids.type1OrderIds, ids.type2OrderIds, ids.type3OrderIds)); |
| | | list = this.baseMapper.getRefundListByOrderIds(chargingRefundDto, |
| | | ids.type1OrderIds, ids.type2OrderIds, ids.type3OrderIds, offset, pageSize, branchLimit); |
| | | } |
| | | enrichRefundOrderInfo(list); |
| | | for (TChargingOrderRefund refund : list) { |
| | | refund.setUid(refund.getType() + "-" + refund.getId()); |
| | | } |
| | | pageInfo.setRecords(list); |
| | | |
| | | return R.ok(pageInfo); |
| | | } |
| | | |
| | | /** |
| | | * 逆反查订单 id:serialNumber 匹配充电/商城/会员订单 id(各自 LIMIT 防爆), |
| | | * siteIds 匹配充电订单 id;两者同时存在时取交集。 |
| | | * 返回约定:null = 该类型不过滤;空列表 = 该类型无匹配(SQL 侧 AND 1=0)。 |
| | | */ |
| | | private RefundOrderIds resolveRefundOrderIds(ChargingRefundDto chargingRefundDto) { |
| | | List<Long> type1OrderIds = null; |
| | | List<Long> type2OrderIds = null; |
| | | List<Long> type3OrderIds = null; |
| | | if (StringUtils.hasLength(chargingRefundDto.getSerialNumber())) { |
| | | // 模糊匹配结果可能很大(短流水号),截断上限防 IN 列表爆炸;匹配不到则列表为空 |
| | | type1OrderIds = safeIds(this.baseMapper.listChargingOrderIdsBySerialNumber( |
| | | chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT)); |
| | | type2OrderIds = safeIds(this.baseMapper.listShoppingOrderIdsBySerialNumber( |
| | | chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT)); |
| | | type3OrderIds = safeIds(this.baseMapper.listVipOrderIdsBySerialNumber( |
| | | chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT)); |
| | | warnIfTruncated(chargingRefundDto.getSerialNumber(), type1OrderIds, |
| | | SERIAL_ORDER_ID_SEARCH_LIMIT, "充电订单"); |
| | | warnIfTruncated(chargingRefundDto.getSerialNumber(), type2OrderIds, |
| | | SERIAL_ORDER_ID_SEARCH_LIMIT, "商城订单"); |
| | | warnIfTruncated(chargingRefundDto.getSerialNumber(), type3OrderIds, |
| | | SERIAL_ORDER_ID_SEARCH_LIMIT, "会员订单"); |
| | | } |
| | | if (chargingRefundDto.getSiteIds() != null && !chargingRefundDto.getSiteIds().isEmpty()) { |
| | | List<Long> siteMatched = safeIds(this.baseMapper.listChargingOrderIdsBySiteIds( |
| | | chargingRefundDto.getSiteIds(), SITE_ORDER_ID_SEARCH_LIMIT)); |
| | | warnIfTruncated(String.valueOf(chargingRefundDto.getSiteIds()), siteMatched, |
| | | SITE_ORDER_ID_SEARCH_LIMIT, "站点订单"); |
| | | if (type1OrderIds == null) { |
| | | type1OrderIds = siteMatched; |
| | | } else { |
| | | type1OrderIds.retainAll(new HashSet<>(siteMatched)); |
| | | } |
| | | } |
| | | return new RefundOrderIds(type1OrderIds, type2OrderIds, type3OrderIds); |
| | | } |
| | | |
| | | /** 反查结果达到上限说明可能被截断,告警便于排查(截断会导致部分退款不在列表内) */ |
| | | private void warnIfTruncated(String condition, List<Long> ids, int limit, String orderType) { |
| | | if (ids.size() >= limit) { |
| | | log.warn("退款列表逆反查:条件[{}]匹配{}数达到上限{},结果可能被截断", condition, orderType, limit); |
| | | } |
| | | } |
| | | |
| | | private static List<Long> safeIds(List<Long> ids) { |
| | | return ids == null ? new ArrayList<>() : new ArrayList<>(ids); |
| | | } |
| | | |
| | | /** 逆反查结果:null = 不过滤;空列表 = 无匹配 */ |
| | | private static class RefundOrderIds { |
| | | private final List<Long> type1OrderIds; |
| | | private final List<Long> type2OrderIds; |
| | | private final List<Long> type3OrderIds; |
| | | |
| | | private RefundOrderIds(List<Long> type1OrderIds, List<Long> type2OrderIds, List<Long> type3OrderIds) { |
| | | this.type1OrderIds = type1OrderIds; |
| | | this.type2OrderIds = type2OrderIds; |
| | | this.type3OrderIds = type3OrderIds; |
| | | } |
| | | |
| | | /** 三个类型全部明确无匹配(非 null 且为空)时,整页为空,可短路 */ |
| | | private boolean isEmpty() { |
| | | return type1OrderIds != null && type1OrderIds.isEmpty() |
| | | && type2OrderIds != null && type2OrderIds.isEmpty() |
| | | && type3OrderIds != null && type3OrderIds.isEmpty(); |
| | | } |
| | | } |
| | | |
| | | private void enrichRefundOrderInfo(List<TChargingOrderRefund> refunds) { |
| | |
| | | } |
| | | } else { |
| | | List<SixChargingDto> settlementIncome = this.baseMapper.chargeSettlement(sixBeforeValue, siteIds); |
| | | if (settlementIncome != null) { |
| | | result.addAll(settlementIncome); |
| | | } |
| | | List<SixChargingDto> orderHistoryIncome = this.baseMapper.chargeOrderHistory(sixBeforeValue, siteIds); |
| | | result.addAll(mergeMissingMonthlyIncome(settlementIncome, orderHistoryIncome)); |
| | | String siteIdsCsv = siteIds == null ? null : siteIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(String::valueOf) |
| | |
| | | return result; |
| | | } |
| | | |
| | | static List<SixChargingDto> mergeMissingMonthlyIncome(List<SixChargingDto> settlementIncome, |
| | | List<SixChargingDto> orderHistoryIncome) { |
| | | List<SixChargingDto> merged = new ArrayList<>(); |
| | | Set<Integer> settledMonths = new HashSet<>(); |
| | | if (settlementIncome != null) { |
| | | for (SixChargingDto item : settlementIncome) { |
| | | if (item == null) { |
| | | continue; |
| | | } |
| | | merged.add(item); |
| | | Integer month = incomeMonth(item); |
| | | if (month != null) { |
| | | settledMonths.add(month); |
| | | } |
| | | } |
| | | } |
| | | if (orderHistoryIncome != null) { |
| | | for (SixChargingDto item : orderHistoryIncome) { |
| | | Integer month = incomeMonth(item); |
| | | if (item != null && month != null && settledMonths.add(month)) { |
| | | merged.add(item); |
| | | } |
| | | } |
| | | } |
| | | return merged; |
| | | } |
| | | |
| | | private static Integer incomeMonth(SixChargingDto item) { |
| | | if (item == null || item.getMonth() == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | int month = Integer.parseInt(item.getMonth()); |
| | | return month >= 1 && month <= 12 ? month : null; |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<SixCircleDto> circle(List<Integer> siteIds, LocalDate sixBefore, Boolean partnerView) { |
| | | List<SixCircleDto> result = this.baseMapper.circle(siteIds, sixBefore, partnerView); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> countAll(List<Integer> siteIds, Boolean partnerView) { |
| | | Map<String, Object> result = this.baseMapper.countAll(siteIds, partnerView); |
| | | public Map<String, Object> countAll(List<Integer> siteIds, Boolean partnerView, LocalDate sixBefore) { |
| | | Map<String, Object> result = this.baseMapper.countAll(siteIds, partnerView, sixBefore); |
| | | if (!Boolean.TRUE.equals(partnerView) && result != null) { |
| | | result.put("commission_amount", this.baseMapper.sumSuccessfulShareAmount(siteIds)); |
| | | result.put("commission_amount", this.baseMapper.sumSuccessfulShareAmount(siteIds, sixBefore)); |
| | | } |
| | | return result; |
| | | } |