2026-03-30 17:06:12 +08:00

430 lines
19 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 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;
/// <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 RefundConfigService _refundConfigService;
private readonly IIdGenerator _idGenerator;
private readonly AuthManager _authManager;
private readonly ISqlSugarClient _db;
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,
RefundConfigService refundConfigService,
IIdGenerator idGenerator,
AuthManager authManager,
ISqlSugarClient db) : base(orderRepository)
{
_orderRepository = orderRepository;
_registrationRepository = registrationRepository;
_itemRepository = itemRepository;
_personRepository = personRepository;
_paymentRepository = paymentRepository;
_eventRepository = eventRepository;
_merchantRepository = merchantRepository;
_refundConfigService = refundConfigService;
_idGenerator = idGenerator;
_authManager = authManager;
_db = db;
}
/// <summary>
/// 创建订单
/// </summary>
[DisplayName("创建订单")]
[HttpPost("create")]
[UnitOfWork]
public async Task<string> Create(CreateOrderInput input)
{
// 1. Create Registration
var entity = input.Adapt<EventRegistration>();
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<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. Create Persons
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;
// 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);
// 获取当前用户的单位认证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, // Income
PaymentMethod = 1, // Default Online? Or unknown yet.
RegistrationMode = input.RegistrationMode,
MerchantId = eventInfo?.MerchantId, // 设置商户ID(支付商户)
MerchantCertificationId = certification?.Id, // 设置单位认证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;
}
/// <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) // 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 (certification != null && !_authManager.IsSuperAdmin)
{
query = query.Where((o, e, m, r, ro) => o.MerchantCertificationId == certification.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;
}
/// <summary>
/// 获取订单详情
/// </summary>
[DisplayName("获取订单详情")]
[HttpGet("detail")]
public async Task<OrderDetailOutput> Detail([FromQuery] long id)
{
var order = await _orderRepository.AsQueryable()
.LeftJoin<EventInfo>((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
};
}
/// <summary>
/// 更新订单状态
/// </summary>
[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);
}
}
/// <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;
return await _refundConfigService.CalculateRefundAmount(order.ActualAmount, eventInfo.MatchStartTime);
}
/// <summary>
/// 订单退款
/// </summary>
[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,
MerchantCertificationId = order.MerchantCertificationId, // 继承原订单的单位认证ID
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);
}
}
/// <summary>
/// 删除订单
/// </summary>
[DisplayName("删除订单")]
[HttpPost("delete")]
public async Task Delete([FromBody] List<long> ids)
{
await _orderRepository.DeleteAsync(x => ids.Contains(x.Id));
}
/// <summary>
/// 导出订单
/// </summary>
[DisplayName("导出订单")]
[HttpGet("export")]
public async Task<IActionResult> Export([FromQuery] OrderQueryInput input)
{
var list = await _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(!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"
};
}
}