using System.Text; using YD_Event.Application.Auth; using YD_Event.Application.Order.Dtos; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using YD_Event.Application.Refund; namespace YD_Event.Application.Order; /// /// 订单管理服务 /// public class OrderService : BaseService, ITransient { private readonly ISqlSugarRepository _orderRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _itemRepository; private readonly ISqlSugarRepository _personRepository; private readonly ISqlSugarRepository _paymentRepository; private readonly ISqlSugarRepository _eventRepository; private readonly ISqlSugarRepository _merchantRepository; private readonly RefundConfigService _refundConfigService; private readonly IIdGenerator _idGenerator; private readonly AuthManager _authManager; public OrderService( ISqlSugarRepository orderRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository itemRepository, ISqlSugarRepository personRepository, ISqlSugarRepository paymentRepository, ISqlSugarRepository eventRepository, ISqlSugarRepository merchantRepository, RefundConfigService refundConfigService, IIdGenerator idGenerator, AuthManager authManager) : base(orderRepository) { _orderRepository = orderRepository; _registrationRepository = registrationRepository; _itemRepository = itemRepository; _personRepository = personRepository; _paymentRepository = paymentRepository; _eventRepository = eventRepository; _merchantRepository = merchantRepository; _refundConfigService = refundConfigService; _idGenerator = idGenerator; _authManager = authManager; } /// /// 创建订单 /// [DisplayName("创建订单")] [HttpPost("create")] [UnitOfWork] public async Task Create(CreateOrderInput input) { // 1. Create Registration var entity = input.Adapt(); entity.Id = _idGenerator.NextId(); entity.OrderNo = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(1000, 9999); // Simple generation entity.RegistrationStatus = 0; // Pending entity.PaymentStatus = 0; // Unpaid entity.RegistrationTime = DateTime.Now; // Calculate Total 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. Create Items var createdItems = new List(); if (input.Items != null && input.Items.Any()) { var items = input.Items.Adapt>(); items.ForEach(x => { x.Id = _idGenerator.NextId(); x.RegistrationId = entity.Id; x.TotalPrice = x.UnitPrice * x.PersonCount; }); await _itemRepository.InsertRangeAsync(items); createdItems = items; } // 3. Create Persons if (input.Persons != null && input.Persons.Any()) { var persons = input.Persons.Adapt>(); persons.ForEach(x => { x.Id = _idGenerator.NextId(); x.RegistrationId = entity.Id; // Link to Item based on ProjectId and GroupName var linkedItem = createdItems.FirstOrDefault(i => i.ProjectId == x.ProjectId && i.GroupName == x.GroupName); if (linkedItem != null) { x.RegistrationItemId = linkedItem.Id; } // Ensure GroupName is not null (DB Constraint) if (string.IsNullOrEmpty(x.GroupName)) x.GroupName = ""; }); await _personRepository.InsertRangeAsync(persons); } // 4. Create Order (Income) // 获取赛事关联的商户ID var eventInfo = await _eventRepository.GetByIdAsync(input.EventId); var order = new YD_Event.Core.Entities.Order { Id = _idGenerator.NextId(), OrderNo = entity.OrderNo, OrderType = 1, // Income PaymentMethod = 1, // Default Online? Or unknown yet. RegistrationMode = input.RegistrationMode, MerchantId = eventInfo?.MerchantId, // 设置商户ID EventId = input.EventId, RegistrationId = entity.Id, OriginalAmount = totalAmount, ActualAmount = totalAmount, // Before discount DiscountAmount = 0, OrderStatus = OrderStatus.Pending, // Pending CreatedTime = DateTime.Now, CreatedUserId = entity.CreatedUserId }; await _orderRepository.InsertAsync(order); return entity.OrderNo; } /// /// 订单分页查询 /// [DisplayName("订单分页查询")] [HttpGet("page")] public async Task> Page([FromQuery] OrderQueryInput input) { var userId = _authManager.UserId; // 判断是否是商户账号 var merchant = await _merchantRepository.GetFirstAsync(m => m.CreatedUserId == userId); var query = _orderRepository.AsQueryable() .LeftJoin((o, e) => o.EventId == e.Id) .LeftJoin((o, e, m) => o.MerchantId == m.Id) .LeftJoin((o, e, m, r) => o.RegistrationId == r.Id) .LeftJoin((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) // Join self for Related Order No .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) => o.PaymentMethod == input.PaymentMethod) .WhereIF(input.RegistrationMode.HasValue, (o, e, m, r, ro) => o.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 (merchant != null && !_authManager.IsSuperAdmin) { // 个人用户只能看到自己关联的订单 query = query.Where(o => o.MerchantId == merchant.Id); } 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 = o.PaymentMethod == 1 ? "线上" : "线下", PaymentProofUrl = o.PaymentProofUrl, RegistrationMode = o.RegistrationMode == 1 ? "个人" : "团队", ContactName = r.ContactName, ContactPhone = r.ContactPhone, OriginalAmount = o.OriginalAmount, DiscountAmount = o.DiscountAmount, ActualAmount = o.ActualAmount, OrderStatus = o.OrderStatus, CreatedTime = o.CreatedTime, PaymentTime = o.PaymentTime, RefundAmount = o.OrderType == 2 ? o.ActualAmount : 0, // 如果是退款订单,ActualAmount即为退款金额 RefundTime = o.OrderType == 2 ? o.PaymentTime : null // 退款时间 }) .ToPagedListAsync(input); return result; } /// /// 获取订单详情 /// [DisplayName("获取订单详情")] [HttpGet("detail")] public async Task Detail([FromQuery] long id) { var order = await _orderRepository.AsQueryable() .LeftJoin((o, e) => o.EventId == e.Id) .Where(o => o.Id == id) .Select((o, e) => new { Order = o, EventName = e.EventName }) .FirstAsync(); if (order == null) throw Oops.Bah("订单不存在"); var registrationId = order.Order.RegistrationId; var items = await _itemRepository.AsQueryable().Where(x => x.RegistrationId == registrationId).ToListAsync(); var persons = await _personRepository.AsQueryable().Where(x => x.RegistrationId == registrationId).ToListAsync(); // Convert items and persons to Dto var itemDtos = items.Select(x => new OrderItemDto { ProjectName = x.ProjectName, // Assume stored or join GroupName = x.GroupName, Fee = x.TotalPrice }).ToList(); var personDtos = persons.Select(x => new OrderPersonDto { Name = x.Name, GroupName = x.GroupName }).ToList(); return new OrderDetailOutput { Id = order.Order.Id, OrderNo = order.Order.OrderNo, OrderType = order.Order.OrderType, OrderStatus = order.Order.OrderStatus, EventName = order.EventName, ProjectNames = string.Join(",", items.Select(x => x.ProjectName).Distinct()), GroupNames = string.Join(",", items.Select(x => x.GroupName).Distinct()), Items = itemDtos, Persons = personDtos, PaymentMethod = order.Order.PaymentMethod == 1 ? "线上" : "线下", PaymentProofUrl = order.Order.PaymentProofUrl, OriginalAmount = order.Order.OriginalAmount, DiscountAmount = order.Order.DiscountAmount, ActualAmount = order.Order.ActualAmount, CreatedTime = order.Order.CreatedTime }; } /// /// 更新订单状态 /// [DisplayName("更新订单状态")] [HttpPut("status")] public async Task UpdateStatus(UpdateOrderStatusInput input) { var order = await _orderRepository.GetByIdAsync(input.Id); if (order == null) throw Oops.Bah("订单不存在"); // Update Order order.OrderStatus = input.PaymentStatus == 1 ? OrderStatus.Paid : OrderStatus.Pending; // Assuming 1 is paid/success if (input.PaymentStatus == 1) { order.PaymentTime = DateTime.Now; } await _orderRepository.UpdateAsync(order); // Update Registration sync var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId); if (reg != null) { reg.PaymentStatus = input.PaymentStatus; reg.PaymentTime = order.PaymentTime; await _registrationRepository.UpdateAsync(reg); } } /// /// 获取建议退款金额 /// [DisplayName("获取建议退款金额")] [HttpGet("refund-amount")] public async Task 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; return await _refundConfigService.CalculateRefundAmount(order.ActualAmount, eventInfo.MatchStartTime); } /// /// 订单退款 /// [DisplayName("订单退款")] [HttpPost("refund")] [UnitOfWork] public async Task Refund(RefundOrderInput input) { var order = await _orderRepository.GetByIdAsync(input.Id); if (order == null) throw Oops.Bah("订单不存在"); if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款"); // 1. Create Refund Order var refundOrder = new YD_Event.Core.Entities.Order { Id = _idGenerator.NextId(), OrderNo = "REF" + DateTime.Now.ToString("yyyyMMddHHmmss"), OrderType = 2, // Refund PaymentMethod = order.PaymentMethod, RegistrationMode = order.RegistrationMode, MerchantId = order.MerchantId, EventId = order.EventId, RegistrationId = order.RegistrationId, RelatedOrderId = order.Id, // Link to original OriginalAmount = order.ActualAmount, ActualAmount = input.RefundAmount, // Refund Amount OrderStatus = OrderStatus.Refunded, // Success (Refunded) PaymentTime = DateTime.Now, Remark = input.Reason, CreatedTime = DateTime.Now, CreatedUserId = _idGenerator.NewLong() // System or current user }; await _orderRepository.InsertAsync(refundOrder); // 2. Update Registration Status var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId); if (reg != null) { reg.PaymentStatus = 3; // Refunded await _registrationRepository.UpdateAsync(reg); } } /// /// 删除订单 /// [DisplayName("删除订单")] [HttpPost("delete")] public async Task Delete([FromBody] List ids) { await _orderRepository.DeleteAsync(x => ids.Contains(x.Id)); } /// /// 导出订单 /// [DisplayName("导出订单")] [HttpGet("export")] public async Task Export([FromQuery] OrderQueryInput input) { var list = await _orderRepository.AsQueryable() .LeftJoin((o, e) => o.EventId == e.Id) .LeftJoin((o, e, m) => o.MerchantId == m.Id) .LeftJoin((o, e, m, r) => o.RegistrationId == r.Id) .LeftJoin((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) .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) => o.PaymentMethod == input.PaymentMethod) .WhereIF(input.RegistrationMode.HasValue, (o, e, m, r, ro) => o.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) .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 = o.PaymentMethod == 1 ? "线上" : "线下", RegistrationMode = o.RegistrationMode == 1 ? "个人" : "团队", ContactName = r.ContactName, ContactPhone = r.ContactPhone, OriginalAmount = o.OriginalAmount, DiscountAmount = o.DiscountAmount, ActualAmount = o.ActualAmount, OrderStatus = o.OrderStatus, CreatedTime = o.CreatedTime }) .ToListAsync(); // Calculate Summary decimal totalOriginal = list.Sum(x => x.OriginalAmount); decimal totalDiscount = list.Sum(x => x.DiscountAmount); decimal totalRefund = list.Where(x => x.OrderType == "退款").Sum(x => x.ActualAmount); decimal totalActual = list.Where(x => x.OrderType == "收入").Sum(x => x.ActualAmount); var sb = new StringBuilder(); // Summary Row sb.AppendLine($"汇总信息: 原金额:{totalOriginal}, 优惠金额:{totalDiscount}, 退款金额:{totalRefund}, 实付金额:{totalActual}"); sb.AppendLine("订单编号,单位名称,所属赛事,订单类型,关联订单,支付方式,报名方式,报名用户,报名账号,原金额,优惠金额,实付/退款金额,订单状态,生成时间"); foreach (var item in list) { sb.AppendLine($"{item.OrderNo},{item.UnitName},{item.EventName},{item.OrderType},{item.RelatedOrderNo},{item.PaymentMethod},{item.RegistrationMode},{item.ContactName},{item.ContactPhone},{item.OriginalAmount},{item.DiscountAmount},{item.ActualAmount},{item.OrderStatus},{item.CreatedTime}"); } var bytes = Encoding.UTF8.GetBytes(sb.ToString()); return new FileContentResult(bytes, "text/csv") { FileDownloadName = $"Orders_{DateTime.Now:yyyyMMddHHmmss}.csv" }; } }