2026-01-17 12:52:58 +08:00
|
|
|
using System.Text;
|
|
|
|
|
using YD_Event.Application.Order.Dtos;
|
|
|
|
|
using YD_Event.Core.Entities;
|
|
|
|
|
using YD_Event.Core.Enum;
|
2026-03-02 09:54:52 +08:00
|
|
|
using YD_Event.Application.Refund;
|
2026-01-17 12:52:58 +08:00
|
|
|
|
|
|
|
|
namespace YD_Event.Application.Order;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 订单管理服务
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class OrderService : BaseService<EventRegistration>, ITransient
|
|
|
|
|
{
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistration> _repository;
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationItem> _itemRepository;
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
2026-03-02 09:54:52 +08:00
|
|
|
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
|
|
|
|
private readonly RefundConfigService _refundConfigService;
|
2026-01-17 12:52:58 +08:00
|
|
|
private readonly IIdGenerator _idGenerator;
|
|
|
|
|
|
|
|
|
|
public OrderService(ISqlSugarRepository<EventRegistration> repository,
|
|
|
|
|
ISqlSugarRepository<EventRegistrationItem> itemRepository,
|
|
|
|
|
ISqlSugarRepository<EventRegistrationPerson> personRepository,
|
|
|
|
|
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
2026-03-02 09:54:52 +08:00
|
|
|
ISqlSugarRepository<EventInfo> eventRepository,
|
|
|
|
|
RefundConfigService refundConfigService,
|
2026-01-17 12:52:58 +08:00
|
|
|
IIdGenerator idGenerator) : base(repository)
|
|
|
|
|
{
|
|
|
|
|
_repository = repository;
|
|
|
|
|
_itemRepository = itemRepository;
|
|
|
|
|
_personRepository = personRepository;
|
|
|
|
|
_paymentRepository = paymentRepository;
|
2026-03-02 09:54:52 +08:00
|
|
|
_eventRepository = eventRepository;
|
|
|
|
|
_refundConfigService = refundConfigService;
|
2026-01-17 12:52:58 +08:00
|
|
|
_idGenerator = idGenerator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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 _repository.InsertAsync(entity);
|
|
|
|
|
|
|
|
|
|
// 2. Create Items
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
});
|
|
|
|
|
await _personRepository.InsertRangeAsync(persons);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entity.OrderNo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 订单分页查询
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("订单分页查询")]
|
|
|
|
|
[HttpGet]
|
|
|
|
|
public async Task<PageResult<EventRegistration>> Page([FromQuery] OrderQueryInput input)
|
|
|
|
|
{
|
|
|
|
|
return await _repository.AsQueryable()
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderNo), x => x.OrderNo.Contains(input.OrderNo))
|
|
|
|
|
.WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId)
|
|
|
|
|
.WhereIF(input.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus)
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), x => x.ContactPhone.Contains(input.ContactPhone))
|
|
|
|
|
.WhereIF(input.StartTime.HasValue, x => x.CreatedTime >= input.StartTime)
|
|
|
|
|
.WhereIF(input.EndTime.HasValue, x => x.CreatedTime <= input.EndTime)
|
|
|
|
|
.OrderByDescending(x => x.CreatedTime)
|
|
|
|
|
.ToPagedListAsync(input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取订单详情
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("获取订单详情")]
|
|
|
|
|
[HttpGet("detail")]
|
|
|
|
|
public async Task<EventRegistration> Detail([FromQuery] long id)
|
|
|
|
|
{
|
|
|
|
|
var entity = await _repository.GetByIdAsync(id);
|
|
|
|
|
if (entity == null) throw Oops.Bah("订单不存在");
|
|
|
|
|
|
|
|
|
|
entity.Items = await _itemRepository.AsQueryable().Where(x => x.RegistrationId == id).ToListAsync();
|
|
|
|
|
entity.Persons = await _personRepository.AsQueryable().Where(x => x.RegistrationId == id).ToListAsync();
|
|
|
|
|
entity.Payment = await _paymentRepository.AsQueryable().Where(x => x.RegistrationId == id).FirstAsync();
|
|
|
|
|
|
|
|
|
|
return entity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 更新订单状态
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("更新订单状态")]
|
|
|
|
|
[HttpPut("status")]
|
|
|
|
|
public async Task UpdateStatus(UpdateOrderStatusInput input)
|
|
|
|
|
{
|
|
|
|
|
var entity = await _repository.GetByIdAsync(input.Id);
|
|
|
|
|
if (entity == null) throw Oops.Bah("订单不存在");
|
|
|
|
|
|
|
|
|
|
entity.PaymentStatus = input.PaymentStatus;
|
|
|
|
|
if (input.PaymentStatus == 1) // Paid
|
|
|
|
|
{
|
|
|
|
|
entity.PaymentTime = DateTime.Now;
|
|
|
|
|
}
|
|
|
|
|
await _repository.UpdateAsync(entity);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 09:54:52 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// 获取建议退款金额
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("获取建议退款金额")]
|
|
|
|
|
[HttpGet("refund-amount")]
|
|
|
|
|
public async Task<decimal> GetRefundAmount([FromQuery] long orderId)
|
|
|
|
|
{
|
|
|
|
|
var order = await _repository.GetByIdAsync(orderId);
|
|
|
|
|
if (order == null) throw Oops.Bah("订单不存在");
|
|
|
|
|
|
|
|
|
|
var eventInfo = await _eventRepository.GetByIdAsync(order.EventId);
|
|
|
|
|
if (eventInfo == null) return order.TotalAmount; // 无法获取赛事时间,默认全额
|
|
|
|
|
|
|
|
|
|
return await _refundConfigService.CalculateRefundAmount(order.TotalAmount, eventInfo.MatchStartTime);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 12:52:58 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// 订单退款
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("订单退款")]
|
|
|
|
|
[HttpPost("refund")]
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
public async Task Refund(RefundOrderInput input)
|
|
|
|
|
{
|
|
|
|
|
var entity = await _repository.GetByIdAsync(input.Id);
|
|
|
|
|
if (entity == null) throw Oops.Bah("订单不存在");
|
|
|
|
|
|
|
|
|
|
if (entity.PaymentStatus != 1) throw Oops.Bah("订单未支付,无法退款");
|
|
|
|
|
|
|
|
|
|
// Update Registration Status
|
|
|
|
|
entity.PaymentStatus = 3; // Refunded
|
|
|
|
|
await _repository.UpdateAsync(entity);
|
|
|
|
|
|
|
|
|
|
// Update Payment Record
|
|
|
|
|
var payment = await _paymentRepository.AsQueryable().Where(x => x.RegistrationId == entity.Id).FirstAsync();
|
|
|
|
|
if (payment != null)
|
|
|
|
|
{
|
|
|
|
|
payment.RefundStatus = 2; // Refunded
|
|
|
|
|
payment.RefundAmount = input.RefundAmount;
|
|
|
|
|
payment.RefundReason = input.Reason;
|
|
|
|
|
payment.RefundTime = DateTime.Now;
|
|
|
|
|
await _paymentRepository.UpdateAsync(payment);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Create a payment record if missing (should not happen in real scenario if flow is correct)
|
|
|
|
|
payment = new EventRegistrationPayment
|
|
|
|
|
{
|
|
|
|
|
Id = _idGenerator.NextId(),
|
|
|
|
|
RegistrationId = entity.Id,
|
|
|
|
|
RefundStatus = 2,
|
|
|
|
|
RefundAmount = input.RefundAmount,
|
|
|
|
|
RefundReason = input.Reason,
|
|
|
|
|
RefundTime = DateTime.Now,
|
|
|
|
|
TransactionNo = "REF" + DateTime.Now.ToString("yyyyMMddHHmmss")
|
|
|
|
|
};
|
|
|
|
|
await _paymentRepository.InsertAsync(payment);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 删除订单
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("删除订单")]
|
|
|
|
|
[HttpPost("delete")]
|
|
|
|
|
public async Task Delete([FromBody] List<long> ids)
|
|
|
|
|
{
|
|
|
|
|
await _repository.DeleteAsync(x => ids.Contains(x.Id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 导出订单
|
|
|
|
|
/// </summary>
|
|
|
|
|
[DisplayName("导出订单")]
|
|
|
|
|
[HttpGet("export")]
|
|
|
|
|
public async Task<IActionResult> Export([FromQuery] OrderQueryInput input)
|
|
|
|
|
{
|
|
|
|
|
var list = await _repository.AsQueryable()
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderNo), x => x.OrderNo.Contains(input.OrderNo))
|
|
|
|
|
.WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId)
|
|
|
|
|
.OrderByDescending(x => x.CreatedTime)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
// Since we cannot use external libraries easily without knowing what's installed,
|
|
|
|
|
// we return a CSV string as a file.
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine("订单编号,赛事ID,报名人,联系电话,总金额,状态,创建时间");
|
|
|
|
|
foreach (var item in list)
|
|
|
|
|
{
|
|
|
|
|
sb.AppendLine($"{item.OrderNo},{item.EventId},{item.ContactName},{item.ContactPhone},{item.TotalAmount},{item.PaymentStatus},{item.CreatedTime}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bytes = Encoding.UTF8.GetBytes(sb.ToString());
|
|
|
|
|
return new FileContentResult(bytes, "text/csv")
|
|
|
|
|
{
|
|
|
|
|
FileDownloadName = $"Orders_{DateTime.Now:yyyyMMddHHmmss}.csv"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|