package com.tourguide.modules.finance.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tourguide.common.api.PageResponse; import com.tourguide.common.exception.BusinessException; import com.tourguide.modules.finance.dto.FinanceLedgerPageResponse; import com.tourguide.modules.finance.dto.FinanceLedgerResponse; import com.tourguide.modules.finance.dto.WithdrawalProcessRequest; import com.tourguide.modules.finance.dto.WithdrawalResponse; import com.tourguide.modules.finance.entity.GuideWithdrawal; import com.tourguide.modules.finance.mapper.GuideWithdrawalMapper; import com.tourguide.modules.operation.dto.PlatformSettingsResponse; import com.tourguide.modules.operation.service.PlatformSettingsService; import com.tourguide.modules.order.entity.TourOrder; import com.tourguide.modules.order.mapper.TourOrderMapper; import com.tourguide.modules.user.entity.GuideTeam; import com.tourguide.modules.user.entity.GuideUser; import com.tourguide.modules.user.mapper.GuideTeamMapper; import com.tourguide.modules.user.mapper.GuideUserMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @Service public class AdminFinanceService { private final TourOrderMapper orderMapper; private final GuideWithdrawalMapper withdrawalMapper; private final GuideUserMapper guideUserMapper; private final GuideTeamMapper guideTeamMapper; private final PlatformSettingsService settingsService; private final GuideWithdrawalService guideWithdrawalService; public AdminFinanceService(TourOrderMapper orderMapper, GuideWithdrawalMapper withdrawalMapper, GuideUserMapper guideUserMapper, GuideTeamMapper guideTeamMapper, PlatformSettingsService settingsService, GuideWithdrawalService guideWithdrawalService) { this.orderMapper = orderMapper; this.withdrawalMapper = withdrawalMapper; this.guideUserMapper = guideUserMapper; this.guideTeamMapper = guideTeamMapper; this.settingsService = settingsService; this.guideWithdrawalService = guideWithdrawalService; } public FinanceLedgerPageResponse ledger(long page, long size, String direction, String keyword, String paymentMethod, LocalDate startDate, LocalDate endDate) { boolean refund = "REFUND".equals(direction); LambdaQueryWrapper query = ledgerQuery(refund, keyword, paymentMethod, startDate, endDate); Page result = orderMapper.selectPage(new Page(page, size), query); PlatformSettingsResponse settings = settingsService.getSettings(); Map guides = loadGuides(result.getRecords()); Map teams = loadTeams(result.getRecords(), guides.values()); List records = result.getRecords().stream() .map(order -> toLedger(order, refund, settings, guides.get(order.getGuideId()), teams)) .collect(Collectors.toList()); BigDecimal totalAmount = orderMapper.selectList( ledgerQuery(refund, keyword, paymentMethod, startDate, endDate)).stream() .map(order -> refund ? safeAmount(order.getRefundAmount()) : paymentAmount(order)) .reduce(BigDecimal.ZERO, BigDecimal::add); if (refund) totalAmount = totalAmount.negate(); return new FinanceLedgerPageResponse( new PageResponse(records, result.getTotal(), result.getCurrent(), result.getSize()), totalAmount.setScale(2, RoundingMode.HALF_UP)); } public PageResponse withdrawals(long page, long size, String keyword, String status, LocalDate startDate, LocalDate endDate) { LambdaQueryWrapper query = new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { String value = keyword.trim(); query.and(wrapper -> wrapper.like(GuideWithdrawal::getWithdrawalNo, value) .or().like(GuideWithdrawal::getGuideName, value) .or().like(GuideWithdrawal::getGuideMobile, value)); } query.eq(StringUtils.hasText(status), GuideWithdrawal::getStatus, status) .ge(startDate != null, GuideWithdrawal::getAppliedAt, startDate == null ? null : startDate.atStartOfDay()) .lt(endDate != null, GuideWithdrawal::getAppliedAt, endDate == null ? null : endDate.plusDays(1).atStartOfDay()) .orderByDesc(GuideWithdrawal::getId); Page result = withdrawalMapper.selectPage(new Page(page, size), query); return new PageResponse(result.getRecords().stream() .map(WithdrawalResponse::new).collect(Collectors.toList()), result.getTotal(), result.getCurrent(), result.getSize()); } @Transactional public WithdrawalResponse processWithdrawal(Long id, WithdrawalProcessRequest request, String processor) { return guideWithdrawalService.process(id, request.getStatus(), request.getAuditRemark(), processor); } private LambdaQueryWrapper ledgerQuery(boolean refund, String keyword, String paymentMethod, LocalDate startDate, LocalDate endDate) { LambdaQueryWrapper query = new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { String value = keyword.trim(); query.and(wrapper -> wrapper.like(TourOrder::getOrderNo, value) .or().like(TourOrder::getPaymentOrderNo, value) .or().like(TourOrder::getRefundOrderNo, value)); } if (refund) { query.isNotNull(TourOrder::getRefundTime) .ge(startDate != null, TourOrder::getRefundTime, startDate == null ? null : startDate.atStartOfDay()) .lt(endDate != null, TourOrder::getRefundTime, endDate == null ? null : endDate.plusDays(1).atStartOfDay()) .orderByDesc(TourOrder::getRefundTime); } else { query.isNotNull(TourOrder::getPaymentTime) .ge(startDate != null, TourOrder::getPaymentTime, startDate == null ? null : startDate.atStartOfDay()) .lt(endDate != null, TourOrder::getPaymentTime, endDate == null ? null : endDate.plusDays(1).atStartOfDay()) .orderByDesc(TourOrder::getPaymentTime); } query.eq(StringUtils.hasText(paymentMethod), TourOrder::getPaymentMethod, paymentMethod); return query; } private FinanceLedgerResponse toLedger(TourOrder order, boolean refund, PlatformSettingsResponse settings, GuideUser guide, Map teams) { BigDecimal amount = refund ? safeAmount(order.getRefundAmount()) : paymentAmount(order); BigDecimal orderAmount = paymentAmount(order); BigDecimal guideIncome; BigDecimal platformIncome; if (order.getGuideEarningAmount() != null) { guideIncome = order.getGuideEarningAmount().setScale(2, RoundingMode.HALF_UP); platformIncome = orderAmount.subtract(guideIncome).setScale(2, RoundingMode.HALF_UP); } else { BigDecimal commissionRate = settings.getCommissionRate().movePointLeft(2); platformIncome = orderAmount.multiply(commissionRate).setScale(2, RoundingMode.HALF_UP); guideIncome = orderAmount.subtract(platformIncome).setScale(2, RoundingMode.HALF_UP); } if (refund) { platformIncome = platformIncome.negate(); guideIncome = guideIncome.negate(); amount = amount.negate(); } LocalDateTime settledAt = order.getSettledAt() == null ? order.getSettlementDueAt() : order.getSettledAt(); Long teamId = order.getCommissionTeamId() != null ? order.getCommissionTeamId() : guide == null ? null : guide.getTeamId(); String teamName = teamId == null ? "个人导游" : teams.get(teamId); return new FinanceLedgerResponse(order.getId(), refund ? order.getRefundOrderNo() : "IN" + order.getOrderNo(), order.getOrderNo(), paymentAmount(order), platformIncome, guideIncome, guideName(order, guide), guideMobile(order, guide), teamName, refund ? order.getRefundOrderNo() : order.getPaymentOrderNo(), refund ? order.getRefundMethod() : order.getPaymentMethod(), amount, refund ? order.getRefundTime() : order.getPaymentTime(), settledAt); } private String guideName(TourOrder order, GuideUser guide) { return StringUtils.hasText(order.getGuideName()) ? order.getGuideName() : guide == null ? null : StringUtils.hasText(guide.getRealName()) ? guide.getRealName() : guide.getUsername(); } private String guideMobile(TourOrder order, GuideUser guide) { return StringUtils.hasText(order.getGuideMobile()) ? order.getGuideMobile() : guide == null ? null : guide.getMobile(); } private Map loadGuides(List orders) { Set guideIds = orders.stream().map(TourOrder::getGuideId) .filter(id -> id != null).collect(Collectors.toSet()); if (guideIds.isEmpty()) return new HashMap(); return guideUserMapper.selectBatchIds(guideIds).stream() .collect(Collectors.toMap(GuideUser::getId, guide -> guide)); } private Map loadTeams(List orders, java.util.Collection guides) { Set teamIds = guides.stream().map(GuideUser::getTeamId) .filter(id -> id != null).collect(Collectors.toSet()); orders.stream().map(TourOrder::getCommissionTeamId) .filter(id -> id != null).forEach(teamIds::add); if (teamIds.isEmpty()) return new HashMap(); return guideTeamMapper.selectBatchIds(teamIds).stream() .collect(Collectors.toMap(GuideTeam::getId, GuideTeam::getTeamName)); } private BigDecimal paymentAmount(TourOrder order) { return safeAmount(order.getPaymentAmount() == null ? order.getAmount() : order.getPaymentAmount()); } private BigDecimal safeAmount(BigDecimal amount) { return amount == null ? BigDecimal.ZERO : amount; } }