963 lines
45 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using System.Drawing;
using YD_Event.Application.Auth;
using YD_Event.Application.Event;
using YD_Event.Application.Order.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using YD_Event.Core.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using YD_Event.Core.Domain;
namespace YD_Event.Application.Order;
/// <summary>
/// 订单管理服务
/// </summary>
public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransient
{
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<EventRegistrationItem> _itemRepository;
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
private readonly IIdGenerator _idGenerator;
private readonly AuthManager _authManager;
private readonly ISqlSugarClient _db;
private readonly ISqlSugarRepository<SysUserRole> _sysUserRoleRepository;
private readonly IOptionsMonitor<RoleOptions> _roleOptionsMonitor;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
private readonly ILogger<OrderService> _logger;
private readonly UnifiedRegistrationService _unifiedRegistrationService;
public OrderService(
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<EventRegistrationItem> itemRepository,
ISqlSugarRepository<EventRegistrationPerson> personRepository,
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
ISqlSugarRepository<EventInfo> eventRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
IIdGenerator idGenerator,
AuthManager authManager,
ISqlSugarClient db,
ISqlSugarRepository<SysUserRole> sysUserRoleRepository,
IOptionsMonitor<RoleOptions> roleOptionsMonitor,
OrderNoGeneratorService orderNoGeneratorService,
ILogger<OrderService> logger,
UnifiedRegistrationService unifiedRegistrationService) : base(orderRepository)
{
_orderRepository = orderRepository;
_registrationRepository = registrationRepository;
_itemRepository = itemRepository;
_personRepository = personRepository;
_paymentRepository = paymentRepository;
_eventRepository = eventRepository;
_merchantRepository = merchantRepository;
_idGenerator = idGenerator;
_authManager = authManager;
_db = db;
_sysUserRoleRepository = sysUserRoleRepository;
_roleOptionsMonitor = roleOptionsMonitor;
_orderNoGeneratorService = orderNoGeneratorService;
_logger = logger;
_unifiedRegistrationService = unifiedRegistrationService;
}
/// <summary>
/// 判断当前用户是否是管理员(超级管理员、系统管理员或赛事管理员)
/// </summary>
private async Task<bool> IsCurrentUserAdminAsync()
{
if (_authManager.IsSuperAdmin) return true;
var userId = _authManager.UserId;
var roleOptions = _roleOptionsMonitor.CurrentValue;
return await _sysUserRoleRepository.IsAnyAsync(x =>
x.UserId == userId &&
(x.RoleId == roleOptions.AdminRoleId || x.RoleId == roleOptions.EventManagerRoleId));
}
/// <summary>
/// 创建订单
/// </summary>
[DisplayName("创建订单")]
[HttpPost("create")]
[UnitOfWork]
public async Task<string> Create(CreateOrderInput input)
{
// 1. 创建报名记录
var entity = input.Adapt<EventRegistration>();
entity.Id = _idGenerator.NextId();
entity.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync();
entity.PaymentStatus = 0; // 未支付
entity.RegistrationTime = DateTime.Now;
entity.RegistrationMode = 2; // 后台管理页面只做团队报名
// 计算总金额
decimal totalAmount = 0;
int totalPerson = 0;
if (input.Items != null)
{
foreach (var item in input.Items)
{
totalAmount += item.UnitPrice * item.PersonCount;
totalPerson += item.PersonCount;
}
}
entity.TotalAmount = totalAmount;
entity.TotalPersonCount = totalPerson;
await _registrationRepository.InsertAsync(entity);
// 2. 创建报名项目
var createdItems = new List<EventRegistrationItem>();
if (input.Items != null && input.Items.Any())
{
var items = input.Items.Adapt<List<EventRegistrationItem>>();
items.ForEach(x =>
{
x.Id = _idGenerator.NextId();
x.RegistrationId = entity.Id;
x.TotalPrice = x.UnitPrice * x.PersonCount;
});
await _itemRepository.InsertRangeAsync(items);
createdItems = items;
}
// 3. 创建报名人员
if (input.Persons != null && input.Persons.Any())
{
var persons = input.Persons.Adapt<List<EventRegistrationPerson>>();
persons.ForEach(x =>
{
x.Id = _idGenerator.NextId();
x.RegistrationId = entity.Id;
// 根据项目ID和组别名称关联到报名项目
var linkedItem = createdItems.FirstOrDefault(i => i.ProjectId == x.ProjectId && i.GroupName == x.GroupName);
if (linkedItem != null)
{
x.RegistrationItemId = linkedItem.Id;
}
// 确保组别名称不为空(数据库约束)
if (string.IsNullOrEmpty(x.GroupName)) x.GroupName = "";
});
await _personRepository.InsertRangeAsync(persons);
}
// 4. 创建订单(收入)
// 获取赛事关联的商户ID
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
// 获取当前用户的单位认证ID
var certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == entity.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
var order = new YD_Event.Core.Entities.Order
{
Id = _idGenerator.NextId(),
OrderNo = entity.OrderNo,
OrderType = 1, // 收入
MerchantId = eventInfo?.MerchantId, // 设置商户ID(支付商户)
MerchantCertificationId = certification?.Id, // 设置单位认证ID
EventId = input.EventId,
RegistrationId = entity.Id,
OriginalAmount = totalAmount,
ActualAmount = totalAmount, // 折扣前金额
DiscountAmount = 0,
OrderStatus = OrderStatus.Pending, // 待支付
CreatedTime = DateTime.Now,
CreatedUserId = entity.CreatedUserId
};
await _orderRepository.InsertAsync(order);
return entity.OrderNo;
}
/// <summary>
/// 订单分页查询
/// </summary>
[DisplayName("订单分页查询")]
[HttpGet("page")]
public async Task<PageResult<OrderListOutput>> Page([FromQuery] OrderQueryInput input)
{
var userId = _authManager.UserId;
// 判断是否是单位认证账号
var certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
var query = _orderRepository.AsQueryable()
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
.LeftJoin<YD_Event.Core.Entities.Merchant>((o, e, m) => o.MerchantId == m.Id)
.LeftJoin<EventRegistration>((o, e, m, r) => o.RegistrationId == r.Id)
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) // 自连接获取关联订单编号
// 默认只显示已完成支付流程的订单(已支付、已发货、已完成、已退款、报名失败)
.WhereIF(!input.OrderStatus.HasValue, (o, e, m, r, ro) =>
o.OrderStatus == OrderStatus.Paid ||
o.OrderStatus == OrderStatus.Shipped ||
o.OrderStatus == OrderStatus.Completed ||
o.OrderStatus == OrderStatus.Refunded ||
o.OrderStatus == OrderStatus.RegistrationFailed)
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderNo), (o, e, m, r, ro) => o.OrderNo.Contains(input.OrderNo))
.WhereIF(!string.IsNullOrWhiteSpace(input.EventName), (o, e, m, r, ro) => e.EventName.Contains(input.EventName))
.WhereIF(input.OrderType.HasValue, (o, e, m, r, ro) => o.OrderType == input.OrderType)
.WhereIF(input.OrderStatus.HasValue, (o, e, m, r, ro) => o.OrderStatus == input.OrderStatus)
.WhereIF(input.PaymentMethod.HasValue, (o, e, m, r, ro) => r.PaymentMethod == input.PaymentMethod)
.WhereIF(input.RegistrationMode.HasValue, (o, e, m, r, ro) => r.RegistrationMode == input.RegistrationMode)
.WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), (o, e, m, r, ro) => r.ContactPhone.Contains(input.ContactPhone))
.WhereIF(input.StartTime.HasValue, (o, e, m, r, ro) => o.PaymentTime >= input.StartTime) // 支付时间筛选
.WhereIF(input.EndTime.HasValue, (o, e, m, r, ro) => o.PaymentTime <= input.EndTime)
.OrderByDescending((o, e, m, r, ro) => o.CreatedTime);
// 权限判断:非管理员(超级管理员或系统管理员)必须过滤订单
if (!await IsCurrentUserAdminAsync())
{
if (certification != null)
{
// 有单位认证的用户,按单位认证ID过滤(只能看自己单位的订单)
query = query.Where((o, e, m, r, ro) => o.MerchantCertificationId == certification.Id);
}
else
{
// 无单位认证的用户(包括新注册用户),只能查看自己创建的订单
query = query.Where((o, e, m, r, ro) => o.CreatedUserId == userId);
}
}
var result = await query.Select((o, e, m, r, ro) => new OrderListOutput
{
Id = o.Id,
OrderNo = o.OrderNo,
UnitName = SqlFunc.IsNullOrEmpty(m.MerchantName) ? "/" : m.MerchantName,
EventName = e.EventName,
OrderType = o.OrderType == 1 ? "收入" : "退款",
RelatedOrderNo = ro.OrderNo,
PaymentMethod = r.PaymentMethod == 2 ? "线上" : "线下",
PaymentMethodType = r.PaymentMethodType,
PaymentMethodTypeText =
r.PaymentMethodType == PaymentMethodType.WeChatPay ? "微信支付" :
r.PaymentMethodType == PaymentMethodType.AliPay ? "支付宝支付" :
r.PaymentMethodType == PaymentMethodType.BankTransfer ? "银行卡转账" : "未选择",
PaymentProofUrl = r.PaymentProofUrl,
RegistrationMode = r.RegistrationMode == 1 ? "个人" : "团队",
ContactName = r.ContactName,
ContactPhone = r.ContactPhone,
OriginalAmount = o.OriginalAmount,
DiscountAmount = o.DiscountAmount,
// 收款订单:实付金额=原金额-优惠金额;退款订单:显示原始订单的实付金额
ActualAmount = o.OrderType == 1
? (o.OriginalAmount - o.DiscountAmount > 0 ? o.OriginalAmount - o.DiscountAmount : 0)
: o.ActualAmount,
OrderStatus = o.OrderStatus,
OrderStatusText =
o.OrderStatus == OrderStatus.Pending ? "待支付" :
o.OrderStatus == OrderStatus.Paid ? "已支付" :
o.OrderStatus == OrderStatus.Cancelled ? "已取消" :
o.OrderStatus == OrderStatus.Refunded ? "已退款" :
o.OrderStatus == OrderStatus.Shipped ? "已发货" :
o.OrderStatus == OrderStatus.Completed ? "已完成" :
o.OrderStatus == OrderStatus.RegistrationFailed ? "报名失败" : "",
CreatedTime = o.CreatedTime,
PaymentTime = o.PaymentTime,
RefundAmount = o.OrderType == 2 ? o.ActualAmount : 0, // 如果是退款订单,ActualAmount即为退款金额
RefundTime = o.OrderType == 2 ? o.PaymentTime : null, // 退款时间
RelatedOrderActualAmount = o.OrderType == 2 ? (ro.OriginalAmount - ro.DiscountAmount) : null, // 退款订单显示原始订单实付金额(原金额-优惠金额)
IsRefundOrder = o.OrderType == 2,
PayeeName = o.PayeeName,
PayeeBankName = o.PayeeBankName,
PayeeBankAccount = o.PayeeBankAccount,
Remark = o.Remark
})
.ToPagedListAsync(input);
return result;
}
/// <summary>
/// 获取订单详情
/// </summary>
[DisplayName("获取订单详情")]
[HttpGet("detail")]
public async Task<OrderDetailOutput> Detail([FromQuery] long id)
{
var query = _orderRepository.AsQueryable()
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
.LeftJoin<EventRegistration>((o, e, r) => o.RegistrationId == r.Id)
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, r, ro) => o.RelatedOrderId == ro.Id)
.Where(o => o.Id == id)
.Select((o, e, r, ro) => new { Order = o, EventName = e.EventName, ChargeMode = e.ChargeMode, Registration = r, RelatedOrderNo = ro.OrderNo, RelatedOrderActualAmount = ro.ActualAmount });
var orderInfo = await query.FirstAsync();
if (orderInfo == null || orderInfo.Order == null) throw Oops.Bah("订单不存在");
var registrationId = orderInfo.Order.RegistrationId;
List<EventRegistrationItem> items = new();
List<EventRegistrationPerson> persons = new();
List<OrderItemDto> itemDtos = new();
List<OrderPersonDto> personDtos = new();
string projectNames = "";
string groupNames = "";
if (orderInfo.Order.OrderType == 2)
{
// 退款订单:通过 RefundOrderId 字段精准找到对应的软删除记录
var refundOrderId = orderInfo.Order.Id;
// 先找被软删除的人员中 RefundOrderId 等于当前退款订单ID的
var deletedPersons = await _db.Queryable<EventRegistrationPerson>()
.ClearFilter()
.Where(x => x.RegistrationId == registrationId &&
x.DeleteMark == true &&
x.RefundOrderId == refundOrderId)
.Select(x => x.RegistrationItemId)
.ToListAsync();
if (deletedPersons.Any())
{
// 找出这些人员关联的项目(去重)
var distinctItemIds = deletedPersons.Distinct().ToList();
items = await _db.Queryable<EventRegistrationItem>()
.ClearFilter()
.Where(x => distinctItemIds.Contains(x.Id))
.ToListAsync();
// 查询对应的人员
persons = await _db.Queryable<EventRegistrationPerson>()
.ClearFilter()
.Where(x => x.RegistrationId == registrationId &&
x.DeleteMark == true &&
x.RefundOrderId == refundOrderId)
.ToListAsync();
}
else
{
// 如果没有找到匹配的人员,再试试找项目的 RefundOrderId 等于当前退款订单ID的
items = await _db.Queryable<EventRegistrationItem>()
.ClearFilter()
.Where(x => x.RegistrationId == registrationId &&
x.DeleteMark == true &&
x.RefundOrderId == refundOrderId)
.ToListAsync();
// 查询对应的人员
var itemIds = items.Select(x => x.Id).ToList();
if (itemIds.Any())
{
persons = await _db.Queryable<EventRegistrationPerson>()
.ClearFilter()
.Where(x => itemIds.Contains(x.RegistrationItemId))
.ToListAsync();
}
}
}
else
{
// 普通订单:显示全部的项目(包括已删除的)
items = await _db.Queryable<EventRegistrationItem>()
.ClearFilter()
.Where(x => x.RegistrationId == registrationId)
.ToListAsync();
persons = await _db.Queryable<EventRegistrationPerson>()
.ClearFilter()
.Where(x => x.RegistrationId == registrationId)
.ToListAsync();
}
// 将项目和人员转换为DTO
itemDtos = items.Select(x => new OrderItemDto
{
Id = x.Id,
ProjectId = x.ProjectId,
ProjectName = x.ProjectName,
GroupName = x.GroupName,
Fee = x.TotalPrice
}).ToList();
personDtos = persons.Select(x => new OrderPersonDto
{
Name = x.Name,
GroupName = x.GroupName,
RegistrationItemId = x.RegistrationItemId,
ProjectId = x.ProjectId,
ProjectName = x.ProjectName
}).ToList();
projectNames = string.Join(",", items.Select(x => x.ProjectName).Distinct());
groupNames = string.Join(",", items.Select(x => x.GroupName).Distinct());
return new OrderDetailOutput
{
Id = orderInfo.Order.Id,
OrderNo = orderInfo.Order.OrderNo,
OrderType = orderInfo.Order.OrderType,
OrderTypeText = orderInfo.Order.OrderType == 1 ? "收入" : "退款",
OrderStatus = orderInfo.Order.OrderStatus,
OrderStatusText =
orderInfo.Order.OrderStatus == OrderStatus.Pending ? "待支付" :
orderInfo.Order.OrderStatus == OrderStatus.Paid ? "已支付" :
orderInfo.Order.OrderStatus == OrderStatus.Cancelled ? "已取消" :
orderInfo.Order.OrderStatus == OrderStatus.Refunded ? "已退款" :
orderInfo.Order.OrderStatus == OrderStatus.Shipped ? "已发货" :
orderInfo.Order.OrderStatus == OrderStatus.Completed ? "已完成" :
orderInfo.Order.OrderStatus == OrderStatus.RegistrationFailed ? "取消报名" : "",
EventName = orderInfo.EventName,
ChargeMode = orderInfo.ChargeMode,
ProjectNames = projectNames,
GroupNames = groupNames,
Items = itemDtos,
Persons = personDtos,
PaymentMethod = orderInfo.Registration?.PaymentMethod == 2 ? "线上" : "线下",
PaymentMethodType = orderInfo.Registration?.PaymentMethodType ?? PaymentMethodType.None,
PaymentMethodTypeText =
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.WeChatPay ? "微信支付" :
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.AliPay ? "支付宝支付" :
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.BankTransfer ? "银行卡转账" : "未选择",
PaymentProofUrl = orderInfo.Registration?.PaymentProofUrl,
RegistrationMode = orderInfo.Registration?.RegistrationMode == 1 ? "个人" : "团队",
OriginalAmount = orderInfo.Order.OriginalAmount,
DiscountAmount = orderInfo.Order.DiscountAmount,
ActualAmount = orderInfo.Order.ActualAmount,
CreatedTime = orderInfo.Order.CreatedTime,
RelatedOrderNo = orderInfo.RelatedOrderNo,
ContactName = orderInfo.Registration?.ContactName,
ContactPhone = orderInfo.Registration?.ContactPhone,
RelatedOrderActualAmount = orderInfo.Order.OrderType == 2 ? orderInfo.RelatedOrderActualAmount : null,
IsRefundOrder = orderInfo.Order.OrderType == 2,
Remark = orderInfo.Order.Remark
};
}
/// <summary>
/// 批量更新订单支付状态
/// </summary>
[DisplayName("批量更新订单支付状态")]
[HttpPut("payment-status")]
[UnitOfWork]
public async Task UpdatePaymentStatus(UpdatePaymentStatusInput input)
{
if (input.Ids == null || !input.Ids.Any())
{
throw Oops.Bah("请选择要更新的订单");
}
// 验证支付状态值
if (input.PaymentStatus < 0 || input.PaymentStatus > 3)
{
throw Oops.Bah("无效的支付状态值");
}
// 操作前数据一致性检查:确保所有订单都存在且状态一致
var orders = await _orderRepository.AsQueryable()
.Where(o => input.Ids.Contains(o.Id))
.ToListAsync();
if (!orders.Any())
{
throw Oops.Bah("未找到订单");
}
// 检查是否有重复的订单ID
if (orders.Count != input.Ids.Distinct().Count())
{
throw Oops.Bah("订单ID列表存在重复");
}
// 检查所有订单对应的报名记录是否存在
var registrationIds = orders.Select(o => o.RegistrationId).Distinct().ToList();
var registrations = await _registrationRepository.AsQueryable()
.Where(r => registrationIds.Contains(r.Id))
.ToListAsync();
if (registrations.Count != registrationIds.Count)
{
var missingIds = registrationIds.Except(registrations.Select(r => r.Id)).ToList();
_logger.LogError("批量更新支付状态时发现缺失的报名记录: {MissingIds}", string.Join(",", missingIds));
throw Oops.Bah("部分订单关联的报名记录不存在,请联系管理员");
}
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var updatedCount = 0;
foreach (var order in orders)
{
// 更新订单的 OrderStatus(以 EventRegistration 的支付状态为准)
if (input.PaymentStatus == 1)
{
order.OrderStatus = OrderStatus.Paid;
order.PaymentTime = now;
}
else if (input.PaymentStatus == 3)
{
order.OrderStatus = OrderStatus.Refunded;
order.PaymentTime = now;
}
else
{
order.OrderStatus = OrderStatus.Pending;
}
order.UpdatedTime = now;
order.UpdatedUserId = currentUserId;
await _orderRepository.UpdateAsync(order);
// 同步更新报名记录的支付状态
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
if (reg != null)
{
reg.PaymentStatus = input.PaymentStatus;
reg.PaymentTime = order.PaymentTime;
reg.UpdatedTime = now;
reg.UpdatedUserId = currentUserId;
await _registrationRepository.UpdateAsync(reg);
}
updatedCount++;
}
// 操作后结果校验
var verifyOrders = await _orderRepository.AsQueryable()
.Where(o => input.Ids.Contains(o.Id))
.Select(o => new { o.Id, o.OrderStatus })
.ToListAsync();
var expectedStatus = input.PaymentStatus == 1 ? OrderStatus.Paid :
input.PaymentStatus == 3 ? OrderStatus.Refunded :
OrderStatus.Pending;
var failedOrders = verifyOrders.Where(o => o.OrderStatus != expectedStatus).ToList();
if (failedOrders.Any())
{
_logger.LogError("批量更新支付状态后校验失败: {FailedOrderIds}",
string.Join(",", failedOrders.Select(o => o.Id)));
throw Oops.Bah("部分订单状态更新失败,请重试");
}
_logger.LogInformation("批量更新支付状态成功: 更新数量={Count}, 目标状态={Status}, 操作人={UserId}",
updatedCount, input.PaymentStatus, currentUserId);
}
/// <summary>
/// 获取建议退款金额
/// </summary>
[DisplayName("获取建议退款金额")]
[HttpGet("refund-amount")]
public async Task<decimal> GetRefundAmount([FromQuery] long orderId)
{
var order = await _orderRepository.GetByIdAsync(orderId);
if (order == null) throw Oops.Bah("订单不存在");
var eventInfo = await _eventRepository.GetByIdAsync(order.EventId);
if (eventInfo == null) return order.ActualAmount;
_logger.LogInformation("调用统一退款金额计算:订单ID={OrderId}, 总金额={TotalAmount}, 赛事开始时间={EventStartTime}",
orderId, order.ActualAmount, eventInfo.MatchStartTime);
return await _unifiedRegistrationService.CalculateRefundAmountAsync(order.ActualAmount, eventInfo.MatchStartTime, eventInfo.Id);
}
/// <summary>
/// 订单退款
/// </summary>
[DisplayName("订单退款")]
[HttpPost("refund")]
[UnitOfWork]
public async Task Refund(RefundOrderInput input)
{
var currentUserId = _authManager.UserId;
_logger.LogInformation("调用统一后台退款:订单ID={OrderId}, 操作人={UserId}", input.Id, currentUserId);
await _unifiedRegistrationService.ExecuteAdminRefundAsync(input, currentUserId);
}
/// <summary>
/// 删除订单(软删除)
/// </summary>
[DisplayName("删除订单")]
[HttpPost("delete")]
public async Task Delete([FromBody] List<long> ids)
{
if (ids == null || !ids.Any())
{
throw Oops.Bah("请选择要删除的订单");
}
await _orderRepository.SoftDeleteByIdsAsync(ids);
}
/// <summary>
/// 导出订单
/// </summary>
[DisplayName("导出订单")]
[HttpGet("export")]
public async Task<IActionResult> Export([FromQuery] OrderQueryInput input)
{
var userId = _authManager.UserId;
// 判断是否是单位认证账号
var certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
var query = _orderRepository.AsQueryable()
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
.LeftJoin<YD_Event.Core.Entities.Merchant>((o, e, m) => o.MerchantId == m.Id)
.LeftJoin<EventRegistration>((o, e, m, r) => o.RegistrationId == r.Id)
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, m, r, ro) => o.RelatedOrderId == ro.Id)
// 默认只导出已完成支付流程的订单(已支付、已发货、已完成、已退款、报名失败)
.WhereIF(!input.OrderStatus.HasValue, (o, e, m, r, ro) =>
o.OrderStatus == OrderStatus.Paid ||
o.OrderStatus == OrderStatus.Shipped ||
o.OrderStatus == OrderStatus.Completed ||
o.OrderStatus == OrderStatus.Refunded ||
o.OrderStatus == OrderStatus.RegistrationFailed)
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderNo), (o, e, m, r, ro) => o.OrderNo.Contains(input.OrderNo))
.WhereIF(!string.IsNullOrWhiteSpace(input.EventName), (o, e, m, r, ro) => e.EventName.Contains(input.EventName))
.WhereIF(input.OrderType.HasValue, (o, e, m, r, ro) => o.OrderType == input.OrderType)
.WhereIF(input.OrderStatus.HasValue, (o, e, m, r, ro) => o.OrderStatus == input.OrderStatus)
.WhereIF(input.PaymentMethod.HasValue, (o, e, m, r, ro) => r.PaymentMethod == input.PaymentMethod)
.WhereIF(input.RegistrationMode.HasValue, (o, e, m, r, ro) => r.RegistrationMode == input.RegistrationMode)
.WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), (o, e, m, r, ro) => r.ContactPhone.Contains(input.ContactPhone))
.WhereIF(input.StartTime.HasValue, (o, e, m, r, ro) => o.CreatedTime >= input.StartTime)
.WhereIF(input.EndTime.HasValue, (o, e, m, r, ro) => o.CreatedTime <= input.EndTime)
.OrderByDescending((o, e, m, r, ro) => o.CreatedTime);
// 权限判断:非管理员(超级管理员或系统管理员)必须过滤订单
if (!await IsCurrentUserAdminAsync())
{
if (certification != null)
{
// 有单位认证的用户,按单位认证ID过滤(只能看自己单位的订单)
query = query.Where((o, e, m, r, ro) => o.MerchantCertificationId == certification.Id);
}
else
{
// 无单位认证的用户(包括新注册用户),只能查看自己创建的订单
query = query.Where((o, e, m, r, ro) => o.CreatedUserId == userId);
}
}
var list = await query.Select((o, e, m, r, ro) => new OrderListOutput
{
OrderNo = o.OrderNo,
UnitName = SqlFunc.IsNullOrEmpty(m.MerchantName) ? "/" : m.MerchantName,
EventName = e.EventName,
OrderType = o.OrderType == 1 ? "收入" : "退款",
RelatedOrderNo = ro.OrderNo,
PaymentMethod = r.PaymentMethod == 2 ? "线上" : "线下",
RegistrationMode = r.RegistrationMode == 1 ? "个人" : "团队",
ContactName = r.ContactName,
ContactPhone = r.ContactPhone,
OriginalAmount = o.OriginalAmount,
DiscountAmount = o.DiscountAmount,
// 收款订单:实付金额=原金额-优惠金额;退款订单:显示原始订单的实付金额
ActualAmount = o.OrderType == 1
? (o.OriginalAmount - o.DiscountAmount > 0 ? o.OriginalAmount - o.DiscountAmount : 0)
: o.ActualAmount,
OrderStatus = o.OrderStatus,
OrderStatusText =
o.OrderStatus == OrderStatus.Pending ? "待支付" :
o.OrderStatus == OrderStatus.Paid ? "已支付" :
o.OrderStatus == OrderStatus.Cancelled ? "已取消" :
o.OrderStatus == OrderStatus.Refunded ? "已退款" :
o.OrderStatus == OrderStatus.Shipped ? "已发货" :
o.OrderStatus == OrderStatus.Completed ? "已完成" :
o.OrderStatus == OrderStatus.RegistrationFailed ? "报名失败" : "",
CreatedTime = o.CreatedTime,
RefundAmount = o.OrderType == 2 ? o.ActualAmount : 0,
RelatedOrderActualAmount = o.OrderType == 2 ? (ro.OriginalAmount - ro.DiscountAmount) : null,
IsRefundOrder = o.OrderType == 2,
PayeeName = o.PayeeName,
PayeeBankName = o.PayeeBankName,
PayeeBankAccount = o.PayeeBankAccount
})
.ToListAsync();
var bytes = await GenerateOrderExcelAsync(list);
return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"订单列表_{DateTime.Now:yyyyMMddHHmmss}.xlsx"
};
}
/// <summary>
/// 生成订单Excel文件
/// </summary>
private async Task<byte[]> GenerateOrderExcelAsync(List<OrderListOutput> orders)
{
ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
using var package = new ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add("订单列表");
var rowIndex = 1;
// 计算汇总数据(根据业务要求)
// 1. 原金额:所有支付订单(排除报名失败)的原始报名费用总和
var paymentOrders = orders.Where(x => x.OrderType == "收入" && x.OrderStatus != OrderStatus.RegistrationFailed);
var totalOriginal = paymentOrders.Sum(x => x.OriginalAmount);
// 2. 优惠金额:所有支付订单(排除报名失败)的折扣金额总和
var totalDiscount = paymentOrders.Sum(x => x.DiscountAmount);
// 3. 退款金额:所有退款数据中的金额总和,排除报名失败状态的退款记录
var refundOrders = orders.Where(x => x.OrderType == "退款" && x.OrderStatus != OrderStatus.RegistrationFailed);
var totalRefund = refundOrders.Sum(x => x.ActualAmount);
// 4. 实付金额:所有实际收款的金额总和,排除报名失败状态的订单记录
var totalActual = paymentOrders.Sum(x => x.ActualAmount);
// 汇总信息行
var summaryRange = worksheet.Cells[rowIndex, 1, rowIndex, 19];
summaryRange.Merge = true;
summaryRange.Value = $"汇总信息: 原金额:{totalOriginal:F2}, 优惠金额:{totalDiscount:F2}, 退款金额:{totalRefund:F2}, 实付金额:{totalActual:F2}";
var summaryStyle = summaryRange.Style;
summaryStyle.Font.Bold = true;
summaryStyle.Font.Size = 12;
summaryStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center;
summaryStyle.VerticalAlignment = ExcelVerticalAlignment.Center;
summaryStyle.Fill.PatternType = ExcelFillStyle.Solid;
summaryStyle.Fill.BackgroundColor.SetColor(Color.LightGray);
rowIndex++;
// 列标题
var headers = new[]
{
"订单编号", "单位名称", "所属赛事", "订单类型", "关联订单",
"支付方式", "报名方式", "报名用户", "报名账号", "原金额(元)",
"实付金额(元)", "优惠金额(元)", "退款金额(元)", "订单状态", "生成时间",
"收款人姓名", "收款人开户行", "收款人银行卡号"
};
for (int i = 0; i < headers.Length; i++)
{
worksheet.Cells[rowIndex, i + 1].Value = headers[i];
}
var headerRange = worksheet.Cells[rowIndex, 1, rowIndex, headers.Length];
var headerStyle = headerRange.Style;
headerStyle.Font.Bold = true;
headerStyle.Font.Size = 11;
headerStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center;
headerStyle.VerticalAlignment = ExcelVerticalAlignment.Center;
headerStyle.Fill.PatternType = ExcelFillStyle.Solid;
headerStyle.Fill.BackgroundColor.SetColor(Color.LightBlue);
SetCellBorders(headerRange, ExcelBorderStyle.Thin);
rowIndex++;
// 数据行
foreach (var item in orders)
{
// 统一显示为"收款"而不是"收入"
var orderTypeText = item.OrderType == "收入" ? "收款" : item.OrderType;
// 计算金额(与页面展示完全一致)
decimal actualPayAmount;
decimal refundAmount;
if (item.IsRefundOrder)
{
// 退款订单:实付金额显示原始订单的实付金额
actualPayAmount = item.RelatedOrderActualAmount ?? 0;
refundAmount = item.ActualAmount;
}
else
{
// 收款订单:实付金额=原金额-优惠金额
actualPayAmount = item.ActualAmount; // 已经在查询时计算好了
refundAmount = 0;
}
worksheet.Cells[rowIndex, 1].Value = item.OrderNo;
worksheet.Cells[rowIndex, 2].Value = item.UnitName;
worksheet.Cells[rowIndex, 3].Value = item.EventName;
worksheet.Cells[rowIndex, 4].Value = orderTypeText;
worksheet.Cells[rowIndex, 5].Value = item.RelatedOrderNo;
worksheet.Cells[rowIndex, 6].Value = item.PaymentMethod;
worksheet.Cells[rowIndex, 7].Value = item.RegistrationMode;
worksheet.Cells[rowIndex, 8].Value = item.ContactName;
worksheet.Cells[rowIndex, 9].Value = item.ContactPhone;
// 金额为0时显示为空白
if (item.OriginalAmount != 0)
{
worksheet.Cells[rowIndex, 10].Value = item.OriginalAmount;
worksheet.Cells[rowIndex, 10].Style.Numberformat.Format = "0.00";
}
if (actualPayAmount != 0)
{
worksheet.Cells[rowIndex, 11].Value = actualPayAmount;
worksheet.Cells[rowIndex, 11].Style.Numberformat.Format = "0.00";
}
if (item.DiscountAmount != 0)
{
worksheet.Cells[rowIndex, 12].Value = item.DiscountAmount;
worksheet.Cells[rowIndex, 12].Style.Numberformat.Format = "0.00";
}
if (refundAmount != 0)
{
worksheet.Cells[rowIndex, 13].Value = refundAmount;
worksheet.Cells[rowIndex, 13].Style.Numberformat.Format = "0.00";
}
worksheet.Cells[rowIndex, 14].Value = item.OrderStatusText;
worksheet.Cells[rowIndex, 15].Value = item.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss");
worksheet.Cells[rowIndex, 16].Value = item.PayeeName;
worksheet.Cells[rowIndex, 17].Value = item.PayeeBankName;
worksheet.Cells[rowIndex, 18].Value = item.PayeeBankAccount;
SetDataRowStyle(worksheet, rowIndex, 18);
rowIndex++;
}
// 设置列宽
var columnWidths = new double[] { 18, 15, 15, 10, 12, 10, 10, 12, 14, 12, 14, 14, 14, 10, 18, 15, 20, 20 };
for (int i = 0; i < columnWidths.Length; i++)
{
worksheet.Column(i + 1).Width = columnWidths[i];
}
// 冻结表头
worksheet.View.FreezePanes(3, 1);
return await package.GetAsByteArrayAsync();
}
/// <summary>
/// 设置单元格边框
/// </summary>
private void SetCellBorders(ExcelRange range, ExcelBorderStyle borderStyle)
{
range.Style.Border.Top.Style = borderStyle;
range.Style.Border.Bottom.Style = borderStyle;
range.Style.Border.Left.Style = borderStyle;
range.Style.Border.Right.Style = borderStyle;
}
/// <summary>
/// 设置数据行样式
/// </summary>
private void SetDataRowStyle(ExcelWorksheet worksheet, int rowIndex, int colCount)
{
var range = worksheet.Cells[rowIndex, 1, rowIndex, colCount];
var style = range.Style;
style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
style.VerticalAlignment = ExcelVerticalAlignment.Center;
SetCellBorders(range, ExcelBorderStyle.Thin);
}
/// <summary>
/// 获取订单统计数据
/// </summary>
[DisplayName("获取订单统计数据")]
[HttpGet("stats")]
public async Task<OrderStatsOutput> GetStats([FromQuery] OrderQueryInput input)
{
var userId = _authManager.UserId;
// 判断是否是单位认证账号
var certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
var query = _orderRepository.AsQueryable()
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
.LeftJoin<YD_Event.Core.Entities.Merchant>((o, e, m) => o.MerchantId == m.Id)
.LeftJoin<EventRegistration>((o, e, m, r) => o.RegistrationId == r.Id)
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, m, r, ro) => o.RelatedOrderId == ro.Id)
// 默认只统计已完成支付流程的订单(已支付、已发货、已完成、已退款、报名失败)
.WhereIF(!input.OrderStatus.HasValue, (o, e, m, r, ro) =>
o.OrderStatus == OrderStatus.Paid ||
o.OrderStatus == OrderStatus.Shipped ||
o.OrderStatus == OrderStatus.Completed ||
o.OrderStatus == OrderStatus.Refunded ||
o.OrderStatus == OrderStatus.RegistrationFailed)
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderNo), (o, e, m, r, ro) => o.OrderNo.Contains(input.OrderNo))
.WhereIF(!string.IsNullOrWhiteSpace(input.EventName), (o, e, m, r, ro) => e.EventName.Contains(input.EventName))
.WhereIF(input.OrderType.HasValue, (o, e, m, r, ro) => o.OrderType == input.OrderType)
.WhereIF(input.OrderStatus.HasValue, (o, e, m, r, ro) => o.OrderStatus == input.OrderStatus)
.WhereIF(input.PaymentMethod.HasValue, (o, e, m, r, ro) => r.PaymentMethod == input.PaymentMethod)
.WhereIF(input.RegistrationMode.HasValue, (o, e, m, r, ro) => r.RegistrationMode == input.RegistrationMode)
.WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), (o, e, m, r, ro) => r.ContactPhone.Contains(input.ContactPhone))
.WhereIF(input.StartTime.HasValue, (o, e, m, r, ro) => o.CreatedTime >= input.StartTime)
.WhereIF(input.EndTime.HasValue, (o, e, m, r, ro) => o.CreatedTime <= input.EndTime);
// 权限判断:非管理员(超级管理员或系统管理员)必须过滤订单
if (!await IsCurrentUserAdminAsync())
{
if (certification != null)
{
// 有单位认证的用户,按单位认证ID过滤(只能看自己单位的订单)
query = query.Where((o, e, m, r, ro) => o.MerchantCertificationId == certification.Id);
}
else
{
// 无单位认证的用户(包括新注册用户),只能查看自己创建的订单
query = query.Where((o, e, m, r, ro) => o.CreatedUserId == userId);
}
}
var list = await query.Select((o, e, m, r, ro) => new OrderListOutput
{
OrderNo = o.OrderNo,
UnitName = SqlFunc.IsNullOrEmpty(m.MerchantName) ? "/" : m.MerchantName,
EventName = e.EventName,
OrderType = o.OrderType == 1 ? "收入" : "退款",
RelatedOrderNo = ro.OrderNo,
PaymentMethod = r.PaymentMethod == 2 ? "线上" : "线下",
RegistrationMode = r.RegistrationMode == 1 ? "个人" : "团队",
ContactName = r.ContactName,
ContactPhone = r.ContactPhone,
OriginalAmount = o.OriginalAmount,
DiscountAmount = o.DiscountAmount,
// 收款订单:实付金额=原金额-优惠金额;退款订单:显示原始订单的实付金额
ActualAmount = o.OrderType == 1
? (o.OriginalAmount - o.DiscountAmount > 0 ? o.OriginalAmount - o.DiscountAmount : 0)
: o.ActualAmount,
OrderStatus = o.OrderStatus,
OrderStatusText =
o.OrderStatus == OrderStatus.Pending ? "待支付" :
o.OrderStatus == OrderStatus.Paid ? "已支付" :
o.OrderStatus == OrderStatus.Cancelled ? "已取消" :
o.OrderStatus == OrderStatus.Refunded ? "已退款" :
o.OrderStatus == OrderStatus.Shipped ? "已发货" :
o.OrderStatus == OrderStatus.Completed ? "已完成" :
o.OrderStatus == OrderStatus.RegistrationFailed ? "报名失败" : "",
CreatedTime = o.CreatedTime,
RefundAmount = o.OrderType == 2 ? o.ActualAmount : 0,
RelatedOrderActualAmount = o.OrderType == 2 ? (ro.OriginalAmount - ro.DiscountAmount) : null,
IsRefundOrder = o.OrderType == 2,
PayeeName = o.PayeeName,
PayeeBankName = o.PayeeBankName,
PayeeBankAccount = o.PayeeBankAccount
})
.ToListAsync();
// 计算统计数据(与导出功能保持完全一致的逻辑)
// 1. 原金额:所有支付订单(排除报名失败)的原始报名费用总和
var paymentOrders = list.Where(x => x.OrderType == "收入" && x.OrderStatus != OrderStatus.RegistrationFailed);
var totalOriginal = paymentOrders.Sum(x => x.OriginalAmount);
// 2. 优惠金额:所有支付订单(排除报名失败)的折扣金额总和
var totalDiscount = paymentOrders.Sum(x => x.DiscountAmount);
// 3. 退款金额:所有退款数据中的金额总和,排除报名失败状态的退款记录
var refundOrders = list.Where(x => x.OrderType == "退款" && x.OrderStatus != OrderStatus.RegistrationFailed);
var totalRefund = refundOrders.Sum(x => x.ActualAmount);
// 4. 实付金额:所有实际收款的金额总和,排除报名失败状态的订单记录
var totalActual = paymentOrders.Sum(x => x.ActualAmount);
return new OrderStatsOutput
{
TotalOriginalAmount = totalOriginal,
TotalDiscountAmount = totalDiscount,
TotalActualAmount = totalActual,
TotalRefundAmount = totalRefund
};
}
}