package com.tcm.training.admin.member.service; import cn.idev.excel.FastExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tcm.training.admin.app.auth.service.AppAuthSessionService; import com.tcm.training.admin.app.member.vo.AppMemberPurchaseOrderItemVO; import com.tcm.training.admin.common.DateTimes; import com.tcm.training.admin.common.PageResponse; import com.tcm.training.admin.common.PhoneMasker; import com.tcm.training.admin.member.entity.MemberPurchaseOrder; import com.tcm.training.admin.member.excel.MemberPurchaseOrderExcelRow; import com.tcm.training.admin.member.mapper.MemberPurchaseOrderMapper; import com.tcm.training.admin.member.row.MemberPurchaseOrderRow; import com.tcm.training.admin.member.vo.MemberPurchaseOrderListItemVO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.net.URLEncoder; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Service public class MemberPurchaseOrderAppService { private static final String STATUS_WAITING_PAYMENT = "WAITING_PAYMENT"; private static final String STATUS_PAID = "PAID"; private static final String STATUS_REFUNDED = "REFUNDED"; private static final String STATUS_CANCELLED = "CANCELLED"; private static final String MEMBER_TYPE_MONTH_CARD = "MONTH_CARD"; private static final String MEMBER_TYPE_QUARTER_CARD = "QUARTER_CARD"; private static final String PAYMENT_METHOD_WECHAT = "WECHAT"; private static final String PAYMENT_METHOD_ALIPAY = "ALIPAY"; private final MemberPurchaseOrderMapper memberPurchaseOrderMapper; private final AppAuthSessionService appAuthSessionService; /** * 注入会员订单查询能力。 */ public MemberPurchaseOrderAppService(MemberPurchaseOrderMapper memberPurchaseOrderMapper, AppAuthSessionService appAuthSessionService) { this.memberPurchaseOrderMapper = memberPurchaseOrderMapper; this.appAuthSessionService = appAuthSessionService; } /** * 分页查询后台会员购买订单。 */ public PageResponse page(int page, int pageSize, String orderNo, String userPhone, String memberType, String paymentMethod, String status, LocalDateTime createdFrom, LocalDateTime createdTo) { IPage result = memberPurchaseOrderMapper.selectOrderPage( new Page(page, pageSize), trimToNull(orderNo), trimToNull(userPhone), trimToNull(memberType), trimToNull(paymentMethod), normalizeStatus(status), createdFrom, createdTo ); return PageResponse.of(toList(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize()); } /** * 分页查询当前登录用户的 APP 会员购买记录。 */ public PageResponse appUserPage(int page, int pageSize) { Long userId = appAuthSessionService.requireLoginUserId(); IPage result = memberPurchaseOrderMapper.selectAppUserOrderPage( new Page(page, pageSize), userId ); return PageResponse.of(toAppList(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize()); } /** * 导出后台会员购买订单。 */ public void export(String orderNo, String userPhone, String memberType, String paymentMethod, String status, LocalDateTime createdFrom, LocalDateTime createdTo, HttpServletResponse response) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String filename = URLEncoder.encode("会员购买订单", "UTF-8").replace("+", "%20"); response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx"); List rows = memberPurchaseOrderMapper.selectOrderList( trimToNull(orderNo), trimToNull(userPhone), trimToNull(memberType), trimToNull(paymentMethod), normalizeStatus(status), createdFrom, createdTo ); List exports = new ArrayList(); for (MemberPurchaseOrderRow row : rows) { MemberPurchaseOrderExcelRow export = new MemberPurchaseOrderExcelRow(); export.setOrderNo(row.getOrderNo()); export.setUserNickname(defaultText(row.getUserNickname())); export.setUserPhone(PhoneMasker.mask(row.getUserPhone())); export.setMemberType(memberTypeText(row.getMemberType())); export.setMemberDaysText(memberDaysText(row.getMemberDays())); export.setOrderAmount(row.getOrderAmount()); export.setPaymentMethod(paymentMethodText(row.getPaymentMethod())); export.setStatus(statusText(row.getStatus())); export.setRefundAmount(resolveRefundAmount(row.getStatus(), row.getOrderAmount(), row.getRefundAmount())); export.setRefundRemark(defaultText(row.getRefundRemark())); export.setCreatedAt(DateTimes.format(row.getCreatedAt())); exports.add(export); } FastExcel.write(response.getOutputStream(), MemberPurchaseOrderExcelRow.class) .sheet("会员购买订单") .doWrite(exports); } public void refund(Long id) { MemberPurchaseOrder order = requireOrder(id); if (!STATUS_PAID.equals(order.getStatus())) { throw new IllegalArgumentException("仅已支付订单允许退款"); } // TODO: 支付方案确认后,在这里接入真实退款流程并在退款成功后更新订单状态与退款时间。 throw new IllegalArgumentException("退款功能待支付方案确认后接入"); } /** * 删除后台已退款会员订单。 */ @Transactional public void delete(Long id) { MemberPurchaseOrder order = requireOrder(id); if (!STATUS_REFUNDED.equals(order.getStatus())) { throw new IllegalArgumentException("仅已退款订单允许删除"); } memberPurchaseOrderMapper.deleteById(id); } /** * 校验会员购买订单存在。 */ private MemberPurchaseOrder requireOrder(Long id) { MemberPurchaseOrder order = memberPurchaseOrderMapper.selectOne(new LambdaQueryWrapper() .eq(MemberPurchaseOrder::getId, id) .eq(MemberPurchaseOrder::getDeleted, 0) .last("limit 1")); if (order == null) { throw new IllegalArgumentException("会员购买订单不存在"); } return order; } /** * 将后台订单查询结果转换为后台列表响应。 */ private List toList(List rows) { List list = new ArrayList(); for (MemberPurchaseOrderRow row : rows) { MemberPurchaseOrderListItemVO item = new MemberPurchaseOrderListItemVO(); item.setId(row.getId()); item.setOrderNo(row.getOrderNo()); item.setUserNickname(row.getUserNickname()); item.setUserPhone(PhoneMasker.mask(row.getUserPhone())); item.setMemberType(row.getMemberType()); item.setMemberDays(row.getMemberDays()); item.setOrderAmount(row.getOrderAmount()); item.setPaymentMethod(row.getPaymentMethod()); item.setRefundAmount(resolveRefundAmount(row.getStatus(), row.getOrderAmount(), row.getRefundAmount())); item.setRefundRemark(row.getRefundRemark()); item.setStatus(row.getStatus()); item.setPaidAt(row.getPaidAt()); item.setRefundedAt(row.getRefundedAt()); item.setCreatedAt(row.getCreatedAt()); list.add(item); } return list; } /** * 将订单查询结果转换为 APP 购买记录列表响应。 */ private List toAppList(List rows) { List list = new ArrayList(); for (MemberPurchaseOrderRow row : rows) { AppMemberPurchaseOrderItemVO item = new AppMemberPurchaseOrderItemVO(); item.setId(row.getId()); item.setOrderNo(row.getOrderNo()); item.setMemberType(row.getMemberType()); item.setMemberTypeText(memberTypeText(row.getMemberType())); item.setOrderAmount(row.getOrderAmount()); item.setPaymentMethod(row.getPaymentMethod()); item.setPaymentMethodText(paymentMethodText(row.getPaymentMethod())); item.setStatus(row.getStatus()); // APP 购买记录页在退款场景下要展示退款金额标签,没有单独退款额时回退订单金额。 item.setRefundAmount(resolveRefundAmount(row.getStatus(), row.getOrderAmount(), row.getRefundAmount())); item.setShowRefundTag(Boolean.valueOf(STATUS_REFUNDED.equals(row.getStatus()))); item.setPaidAt(row.getPaidAt()); list.add(item); } return list; } /** * 解析订单应展示的退款金额。 */ private BigDecimal resolveRefundAmount(String status, BigDecimal orderAmount, BigDecimal refundAmount) { if (!STATUS_REFUNDED.equals(status)) { return null; } return refundAmount == null ? orderAmount : refundAmount; } private String normalizeStatus(String status) { String normalized = trimToNull(status); if (normalized == null) { return null; } if (STATUS_PAID.equals(normalized) || STATUS_REFUNDED.equals(normalized)) { return normalized; } return null; } /** * 将空白文本归一化为 null。 */ private String trimToNull(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } /** * 缺省文本展示兜底。 */ private String defaultText(String value) { return value == null || value.trim().isEmpty() ? "-" : value.trim(); } /** * 将会员天数转换为展示文案。 */ private String memberDaysText(Integer memberDays) { return memberDays == null ? "-" : memberDays + "天"; } /** * 将会员类型转换为展示文案。 */ private String memberTypeText(String memberType) { if (MEMBER_TYPE_MONTH_CARD.equals(memberType)) { return "月卡"; } if (MEMBER_TYPE_QUARTER_CARD.equals(memberType)) { return "季度会员"; } return "-"; } /** * 将支付方式转换为展示文案。 */ private String paymentMethodText(String paymentMethod) { if (PAYMENT_METHOD_WECHAT.equals(paymentMethod)) { return "微信支付"; } if (PAYMENT_METHOD_ALIPAY.equals(paymentMethod)) { return "支付宝支付"; } return "-"; } /** * 将订单状态转换为展示文案。 */ private String statusText(String status) { if (STATUS_WAITING_PAYMENT.equals(status)) { return "待支付"; } if (STATUS_PAID.equals(status)) { return "已支付"; } if (STATUS_REFUNDED.equals(status)) { return "已退款"; } if (STATUS_CANCELLED.equals(status)) { return "已取消"; } return "-"; } }