420 lines
18 KiB
C#
420 lines
18 KiB
C#
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;
|
||
|
||
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) : base(orderRepository)
|
||
{
|
||
_orderRepository = orderRepository;
|
||
_registrationRepository = registrationRepository;
|
||
_itemRepository = itemRepository;
|
||
_personRepository = personRepository;
|
||
_paymentRepository = paymentRepository;
|
||
_eventRepository = eventRepository;
|
||
_merchantRepository = merchantRepository;
|
||
_refundConfigService = refundConfigService;
|
||
_idGenerator = idGenerator;
|
||
_authManager = authManager;
|
||
}
|
||
|
||
/// <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);
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 订单分页查询
|
||
/// </summary>
|
||
[DisplayName("订单分页查询")]
|
||
[HttpGet("page")]
|
||
public async Task<PageResult<OrderListOutput>> Page([FromQuery] OrderQueryInput input)
|
||
{
|
||
var userId = _authManager.UserId;
|
||
// 判断是否是商户账号
|
||
var merchant = await _merchantRepository.GetFirstAsync(m => m.CreatedUserId == userId);
|
||
|
||
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 (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;
|
||
}
|
||
|
||
/// <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,
|
||
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"
|
||
};
|
||
}
|
||
}
|