订单管理

This commit is contained in:
马梓彭 2026-04-09 20:49:36 +08:00
parent a9a0ecf0d5
commit daa5f7b973
10 changed files with 305 additions and 73 deletions

View File

@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
using YD_Event.Application.Client.Dtos; using YD_Event.Application.Client.Dtos;
using YD_Event.Core.Entities; using YD_Event.Core.Entities;
using YD_Event.Core.Enum; using YD_Event.Core.Enum;
using YD_Event.Core.Utils;
using YD_Event.Core.Infrastructure.WeChat; using YD_Event.Core.Infrastructure.WeChat;
using YD_Event.Core.Infrastructure.WeChat.Dto; using YD_Event.Core.Infrastructure.WeChat.Dto;
using YD_Event.Core.Options; using YD_Event.Core.Options;
@ -48,6 +49,7 @@ public class EventClientController : IDynamicApiController
private readonly ISqlSugarRepository<SysDistrict> _districtRepository; private readonly ISqlSugarRepository<SysDistrict> _districtRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository; private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository; private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IIdGenerator _idGenerator; private readonly IIdGenerator _idGenerator;
private readonly WeChatPayService _weChatPayService; private readonly WeChatPayService _weChatPayService;
@ -79,6 +81,7 @@ public class EventClientController : IDynamicApiController
ISqlSugarRepository<SysDistrict> districtRepository, ISqlSugarRepository<SysDistrict> districtRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository, ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository, ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
IIdGenerator idGenerator, IIdGenerator idGenerator,
WeChatPayService weChatPayService, WeChatPayService weChatPayService,
@ -109,6 +112,7 @@ public class EventClientController : IDynamicApiController
_districtRepository = districtRepository; _districtRepository = districtRepository;
_orderRepository = orderRepository; _orderRepository = orderRepository;
_merchantCertificationRepository = merchantCertificationRepository; _merchantCertificationRepository = merchantCertificationRepository;
_paymentRepository = paymentRepository;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_idGenerator = idGenerator; _idGenerator = idGenerator;
_weChatPayService = weChatPayService; _weChatPayService = weChatPayService;
@ -929,7 +933,7 @@ public class EventClientController : IDynamicApiController
AppId = configOptions.AppId, AppId = configOptions.AppId,
Description = $"{evt.EventName} - 报名费", Description = $"{evt.EventName} - 报名费",
NotifyUrl = configOptions.NotifyUrl, NotifyUrl = configOptions.NotifyUrl,
TotalAmount = (int)(reg.TotalAmount * 100), TotalAmount = AmountHelper.YuanToFen(reg.TotalAmount),
ExpireTime = DateTimeOffset.Now.AddMinutes(30), ExpireTime = DateTimeOffset.Now.AddMinutes(30),
OpenId = input.OpenId OpenId = input.OpenId
}; };
@ -1279,6 +1283,24 @@ public class EventClientController : IDynamicApiController
await _registrationRepository.UpdateAsync(reg); await _registrationRepository.UpdateAsync(reg);
_logger.LogInformation($"订单 {reg.OrderNo} 支付状态同步成功,微信交易号:{queryResult.TransactionId}"); _logger.LogInformation($"订单 {reg.OrderNo} 支付状态同步成功,微信交易号:{queryResult.TransactionId}");
// 创建支付记录
var payment = new EventRegistrationPayment
{
Id = _idGenerator.NewLong(),
RegistrationId = reg.Id,
TransactionNo = queryResult.TransactionId ?? "",
PaymentChannel = 1, // 微信支付
PaymentAmount = reg.TotalAmount,
PaymentTime = queryResult.SuccessTime?.DateTime ?? DateTime.Now,
PaymentStatus = 1, // 已支付
RefundStatus = 0, // 未退款
PayeeAccount = "", // 收款账户
CreatedUserId = userId,
CreatedTime = DateTime.Now
};
await _paymentRepository.InsertAsync(payment);
_logger.LogInformation($"支付记录创建成功,报名ID={reg.Id},交易号={queryResult.TransactionId}");
// 更新 Order 记录状态 // 更新 Order 记录状态
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo); var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo);
if (order != null) if (order != null)
@ -1412,6 +1434,24 @@ public class EventClientController : IDynamicApiController
await _registrationRepository.UpdateAsync(reg); await _registrationRepository.UpdateAsync(reg);
_logger.LogInformation($"订单 {reg.OrderNo} 支付成功,微信交易号:{notifyData.TransactionId}"); _logger.LogInformation($"订单 {reg.OrderNo} 支付成功,微信交易号:{notifyData.TransactionId}");
// 创建支付记录
var payment = new EventRegistrationPayment
{
Id = _idGenerator.NewLong(),
RegistrationId = reg.Id,
TransactionNo = notifyData.TransactionId ?? "",
PaymentChannel = 1, // 微信支付
PaymentAmount = reg.TotalAmount,
PaymentTime = notifyData.SuccessTime?.DateTime ?? DateTime.Now,
PaymentStatus = 1, // 已支付
RefundStatus = 0, // 未退款
PayeeAccount = "", // 收款账户
CreatedUserId = reg.CreatedUserId,
CreatedTime = DateTime.Now
};
await _paymentRepository.InsertAsync(payment);
_logger.LogInformation($"支付记录创建成功,报名ID={reg.Id},交易号={notifyData.TransactionId}");
// 更新 Order 记录状态 // 更新 Order 记录状态
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo); var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo);
if (order != null) if (order != null)

View File

@ -2,6 +2,7 @@ using YD_Event.Application.Event.Dtos;
using YD_Event.Application.Project.Dtos; using YD_Event.Application.Project.Dtos;
using YD_Event.Core.Entities; using YD_Event.Core.Entities;
using YD_Event.Core.Enum; using YD_Event.Core.Enum;
using YD_Event.Core.Utils;
using MiniExcelLibs; using MiniExcelLibs;
using SqlSugar; using SqlSugar;
using Mapster; using Mapster;
@ -402,7 +403,7 @@ public class EventService : BaseService<EventInfo>, ITransient
EventId = eventInfo.Id, EventId = eventInfo.Id,
ProjectId = projInput.ProjectId, ProjectId = projInput.ProjectId,
GenderType = projInput.GenderType, GenderType = projInput.GenderType,
Price = (int)(projInput.Fee * 100), Price = AmountHelper.YuanToFen(projInput.Fee),
CreatedTime = DateTime.Now CreatedTime = DateTime.Now
}; };
await _eventProjectRepository.InsertAsync(eventProject); await _eventProjectRepository.InsertAsync(eventProject);
@ -576,7 +577,7 @@ public class EventService : BaseService<EventInfo>, ITransient
EventId = eventInfo.Id, EventId = eventInfo.Id,
ProjectId = projInput.ProjectId, ProjectId = projInput.ProjectId,
GenderType = projInput.GenderType, GenderType = projInput.GenderType,
Price = (int)(projInput.Fee * 100), Price = AmountHelper.YuanToFen(projInput.Fee),
CreatedTime = DateTime.Now CreatedTime = DateTime.Now
}; };
await _eventProjectRepository.InsertAsync(eventProject); await _eventProjectRepository.InsertAsync(eventProject);
@ -938,7 +939,7 @@ public class EventService : BaseService<EventInfo>, ITransient
EventId = input.EventId, EventId = input.EventId,
ProjectId = projInput.ProjectId, ProjectId = projInput.ProjectId,
GenderType = projInput.GenderType, GenderType = projInput.GenderType,
Price = (int)(projInput.Fee * 100), Price = AmountHelper.YuanToFen(projInput.Fee),
CreatedTime = DateTime.Now CreatedTime = DateTime.Now
}; };
await _eventProjectRepository.InsertAsync(eventProject); await _eventProjectRepository.InsertAsync(eventProject);
@ -1033,7 +1034,7 @@ public class EventService : BaseService<EventInfo>, ITransient
} }
// 格式验证:最多保留两位小数 // 格式验证:最多保留两位小数
if (Math.Round(input.FeeConfig.Price, 2) != input.FeeConfig.Price) if (!AmountHelper.IsValidAmount(input.FeeConfig.Price))
{ {
throw new InvalidOperationException("价格格式不正确,最多保留两位小数"); throw new InvalidOperationException("价格格式不正确,最多保留两位小数");
} }
@ -1059,7 +1060,7 @@ public class EventService : BaseService<EventInfo>, ITransient
} }
// 格式验证:最多保留两位小数 // 格式验证:最多保留两位小数
if (Math.Round(feeInput.Fee, 2) != feeInput.Fee) if (!AmountHelper.IsValidAmount(feeInput.Fee))
{ {
throw new InvalidOperationException("项目单价格式不正确,最多保留两位小数"); throw new InvalidOperationException("项目单价格式不正确,最多保留两位小数");
} }
@ -1067,7 +1068,7 @@ public class EventService : BaseService<EventInfo>, ITransient
var proj = eventProjects.FirstOrDefault(p => p.Id == feeInput.ProjectId); var proj = eventProjects.FirstOrDefault(p => p.Id == feeInput.ProjectId);
if (proj != null) if (proj != null)
{ {
proj.Price = (int)(feeInput.Fee * 100); proj.Price = AmountHelper.YuanToFen(feeInput.Fee);
proj.UpdatedTime = DateTime.Now; proj.UpdatedTime = DateTime.Now;
await _eventProjectRepository.UpdateAsync(proj); await _eventProjectRepository.UpdateAsync(proj);
} }

View File

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using YD_Event.Application.Event.Dtos; using YD_Event.Application.Event.Dtos;
using YD_Event.Core.Entities; using YD_Event.Core.Entities;
using YD_Event.Core.Enum; using YD_Event.Core.Enum;
using YD_Event.Core.Utils;
using SqlSugar; using SqlSugar;
using Mapster; using Mapster;
using YD_Event.Application.Auth; using YD_Event.Application.Auth;
@ -519,8 +520,8 @@ public class UserEventRecordService : IDynamicApiController, ITransient
{ {
OutTradeNo = registration.OrderNo, OutTradeNo = registration.OrderNo,
OutRefundNo = outRefundNo, OutRefundNo = outRefundNo,
RefundAmount = (int)(registration.TotalAmount * 100), // 转分 RefundAmount = AmountHelper.YuanToFen(registration.TotalAmount), // 转分
TotalAmount = (int)(registration.TotalAmount * 100), TotalAmount = AmountHelper.YuanToFen(registration.TotalAmount),
Reason = input.CancelReason Reason = input.CancelReason
}; };

View File

@ -7,6 +7,7 @@ using YD_Event.Application.Project;
using YD_Event.Core.Entities; using YD_Event.Core.Entities;
using YD_Event.Core.Enum; using YD_Event.Core.Enum;
using YD_Event.Core.Domain.Pricing; using YD_Event.Core.Domain.Pricing;
using YD_Event.Core.Utils;
using SqlSugar; using SqlSugar;
using Mapster; using Mapster;
using YD_Event.Application.Auth; using YD_Event.Application.Auth;
@ -234,25 +235,16 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
.Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable) .Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable)
.ToListAsync(); .ToListAsync();
var completionStats = await _projectService.GetProjectStatisticsBatchAsync(
projectIds,
ProjectService.ProjectStatisticType.CompletionCount);
output.Projects = eventProjects.Select(ep => output.Projects = eventProjects.Select(ep =>
{ {
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
var completionCount = 0;
if (p != null && completionStats.TryGetValue(p.Id, out var count))
{
completionCount = count;
}
var dto = new UserEventProjectListOutput var dto = new UserEventProjectListOutput
{ {
Id = ep.Id, Id = ep.Id,
Name = p?.Name ?? "", Name = p?.Name ?? "",
Fee = ep.Price / 100m, Fee = ep.Price / 100m,
CompletionCount = completionCount CompletionCount = p?.CompletionCount ?? 0
}; };
if (p != null) if (p != null)
@ -330,18 +322,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
.Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable) .Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable)
.ToListAsync(); .ToListAsync();
var completionStats = await _projectService.GetProjectStatisticsBatchAsync(
projectIds,
ProjectService.ProjectStatisticType.CompletionCount);
return eventProjects.Select(ep => return eventProjects.Select(ep =>
{ {
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
var completionCount = 0;
if (p != null && completionStats.TryGetValue(p.Id, out var count))
{
completionCount = count;
}
var dto = new UserEventProjectListOutput var dto = new UserEventProjectListOutput
{ {
@ -349,7 +332,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
Name = p?.Name ?? "", Name = p?.Name ?? "",
Fee = ep.Price / 100m, Fee = ep.Price / 100m,
TeamSize = 1, TeamSize = 1,
CompletionCount = completionCount CompletionCount = p?.CompletionCount ?? 0
}; };
if (p != null) if (p != null)
@ -939,7 +922,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
AppId = configOptions.AppId, AppId = configOptions.AppId,
Description = $"{eventInfo.EventName} - 报名费", Description = $"{eventInfo.EventName} - 报名费",
NotifyUrl = configOptions.NotifyUrl, NotifyUrl = configOptions.NotifyUrl,
TotalAmount = (int)(finalAmount * 100), // 单位为分,使用优惠后的金额 TotalAmount = AmountHelper.YuanToFen(finalAmount), // 单位为分,使用优惠后的金额
ExpireTime = DateTimeOffset.Now.AddMinutes(5) ExpireTime = DateTimeOffset.Now.AddMinutes(5)
}; };

View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace YD_Event.Application.Order.Dtos;
public class UpdatePaymentStatusInput
{
/// <summary>
/// 订单ID列表(批量更新)
/// </summary>
[Required]
public List<long> Ids { get; set; }
/// <summary>
/// 支付状态:0-未支付,1-已支付,3-已退款
/// </summary>
[Required]
public int PaymentStatus { get; set; }
}

View File

@ -136,7 +136,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
Id = _idGenerator.NextId(), Id = _idGenerator.NextId(),
OrderNo = entity.OrderNo, OrderNo = entity.OrderNo,
OrderType = 1, // 收入 OrderType = 1, // 收入
PaymentMethod = 1, // 默认线上支付 PaymentMethod = 2, // 默认线上支付 (1=线下, 2=线上)
RegistrationMode = 2, // 后台管理页面只做团队报名 RegistrationMode = 2, // 后台管理页面只做团队报名
MerchantId = eventInfo?.MerchantId, // 设置商户ID(支付商户) MerchantId = eventInfo?.MerchantId, // 设置商户ID(支付商户)
MerchantCertificationId = certification?.Id, // 设置单位认证ID MerchantCertificationId = certification?.Id, // 设置单位认证ID
@ -202,7 +202,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
EventName = e.EventName, EventName = e.EventName,
OrderType = o.OrderType == 1 ? "收入" : "退款", OrderType = o.OrderType == 1 ? "收入" : "退款",
RelatedOrderNo = ro.OrderNo, RelatedOrderNo = ro.OrderNo,
PaymentMethod = o.PaymentMethod == 1 ? "线上" : "线下", PaymentMethod = o.PaymentMethod == 2 ? "线上" : "线下",
PaymentProofUrl = o.PaymentProofUrl, PaymentProofUrl = o.PaymentProofUrl,
RegistrationMode = o.RegistrationMode == 1 ? "个人" : "团队", RegistrationMode = o.RegistrationMode == 1 ? "个人" : "团队",
ContactName = r.ContactName, ContactName = r.ContactName,
@ -282,30 +282,60 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
} }
/// <summary> /// <summary>
/// 更新订单状态 /// 批量更新订单支付状态
/// </summary> /// </summary>
[DisplayName("更新订单状态")] [DisplayName("批量更新订单支付状态")]
[HttpPut("status")] [HttpPut("payment-status")]
public async Task UpdateStatus(UpdateOrderStatusInput input) public async Task UpdatePaymentStatus(UpdatePaymentStatusInput input)
{ {
var order = await _orderRepository.GetByIdAsync(input.Id); if (input.Ids == null || !input.Ids.Any())
if (order == null) throw Oops.Bah("订单不存在");
// 更新订单
order.OrderStatus = input.PaymentStatus == 1 ? OrderStatus.Paid : OrderStatus.Pending; // 假设1表示支付成功
if (input.PaymentStatus == 1)
{ {
order.PaymentTime = DateTime.Now; throw Oops.Bah("请选择要更新的订单");
} }
await _orderRepository.UpdateAsync(order);
// 同步更新报名记录 var orders = await _orderRepository.AsQueryable()
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId); .Where(o => input.Ids.Contains(o.Id))
if (reg != null) .ToListAsync();
if (!orders.Any())
{ {
reg.PaymentStatus = input.PaymentStatus; throw Oops.Bah("未找到订单");
reg.PaymentTime = order.PaymentTime; }
await _registrationRepository.UpdateAsync(reg);
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
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);
}
} }
} }
@ -338,6 +368,9 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款"); if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
// 1. 创建退款订单 // 1. 创建退款订单
var refundOrder = new YD_Event.Core.Entities.Order var refundOrder = new YD_Event.Core.Entities.Order
{ {
@ -354,30 +387,66 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
OriginalAmount = order.ActualAmount, OriginalAmount = order.ActualAmount,
ActualAmount = input.RefundAmount, // 退款金额 ActualAmount = input.RefundAmount, // 退款金额
OrderStatus = OrderStatus.Refunded, // 退款成功 OrderStatus = OrderStatus.Refunded, // 退款成功
PaymentTime = DateTime.Now, PaymentTime = now,
Remark = input.Reason, Remark = input.Reason,
CreatedTime = DateTime.Now, CreatedTime = now,
CreatedUserId = _idGenerator.NewLong() // 系统或当前用户 CreatedUserId = currentUserId
}; };
await _orderRepository.InsertAsync(refundOrder); await _orderRepository.InsertAsync(refundOrder);
// 2. 更新报名状态 // 2. 更新原订单状态
order.OrderStatus = OrderStatus.Refunded;
order.UpdatedTime = now;
order.UpdatedUserId = currentUserId;
await _orderRepository.UpdateAsync(order);
// 3. 更新报名状态
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId); var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
if (reg != null) if (reg != null)
{ {
reg.PaymentStatus = 3; // 已退款 reg.PaymentStatus = 3; // 已退款
reg.UpdatedTime = now;
reg.UpdatedUserId = currentUserId;
await _registrationRepository.UpdateAsync(reg); await _registrationRepository.UpdateAsync(reg);
} }
// 4. 更新支付记录(如果有)
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == order.RegistrationId);
if (payment != null)
{
payment.RefundStatus = 1; // 已退款
payment.UpdatedTime = now;
payment.UpdatedUserId = currentUserId;
await _paymentRepository.UpdateAsync(payment);
}
} }
/// <summary> /// <summary>
/// 删除订单 /// 删除订单(软删除)
/// </summary> /// </summary>
[DisplayName("删除订单")] [DisplayName("删除订单")]
[HttpPost("delete")] [HttpPost("delete")]
public async Task Delete([FromBody] List<long> ids) public async Task Delete([FromBody] List<long> ids)
{ {
await _orderRepository.DeleteAsync(x => ids.Contains(x.Id)); if (ids == null || !ids.Any())
{
throw Oops.Bah("请选择要删除的订单");
}
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var orders = await _orderRepository.AsQueryable()
.Where(o => ids.Contains(o.Id))
.ToListAsync();
foreach (var order in orders)
{
order.DeleteMark = true;
order.UpdatedTime = now;
order.UpdatedUserId = currentUserId;
await _orderRepository.UpdateAsync(order);
}
} }
/// <summary> /// <summary>
@ -415,7 +484,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
EventName = e.EventName, EventName = e.EventName,
OrderType = o.OrderType == 1 ? "收入" : "退款", OrderType = o.OrderType == 1 ? "收入" : "退款",
RelatedOrderNo = ro.OrderNo, RelatedOrderNo = ro.OrderNo,
PaymentMethod = o.PaymentMethod == 1 ? "线上" : "线下", PaymentMethod = o.PaymentMethod == 2 ? "线上" : "线下",
RegistrationMode = o.RegistrationMode == 1 ? "个人" : "团队", RegistrationMode = o.RegistrationMode == 1 ? "个人" : "团队",
ContactName = r.ContactName, ContactName = r.ContactName,
ContactPhone = r.ContactPhone, ContactPhone = r.ContactPhone,

View File

@ -70,19 +70,6 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
}) })
.ToPagedListAsync(input); .ToPagedListAsync(input);
var projectIds = result.Rows.Select(x => x.Id).ToList();
if (projectIds.Any())
{
var stats = await GetProjectStatisticsBatchAsync(projectIds, ProjectStatisticType.CompletionCount);
foreach (var item in result.Rows)
{
if (stats.TryGetValue(item.Id, out var count))
{
item.CompletionCount = count;
}
}
}
return result; return result;
} }
@ -181,8 +168,6 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id); var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id);
dto.Groups = groups.Adapt<List<ProjectGroupDto>>(); dto.Groups = groups.Adapt<List<ProjectGroupDto>>();
dto.CompletionCount = await GetProjectStatisticAsync(id, ProjectStatisticType.CompletionCount);
return dto; return dto;
} }

View File

@ -8,10 +8,13 @@
<ItemGroup> <ItemGroup>
<Compile Remove="Blog\**" /> <Compile Remove="Blog\**" />
<Compile Remove="Config\**" /> <Compile Remove="Config\**" />
<Compile Remove="File\**" />
<EmbeddedResource Remove="Blog\**" /> <EmbeddedResource Remove="Blog\**" />
<EmbeddedResource Remove="Config\**" /> <EmbeddedResource Remove="Config\**" />
<EmbeddedResource Remove="File\**" />
<None Remove="Blog\**" /> <None Remove="Blog\**" />
<None Remove="Config\**" /> <None Remove="Config\**" />
<None Remove="File\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="applicationsettings.json" /> <None Remove="applicationsettings.json" />
@ -25,7 +28,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Auth\Dtos\" /> <Folder Include="Auth\Dtos\" />
<Folder Include="Certificate\Dtos\" /> <Folder Include="Certificate\Dtos\" />
<Folder Include="File\Dtos\" />
<Folder Include="Logging\Dtos\" /> <Folder Include="Logging\Dtos\" />
<Folder Include="Menu\Dtos\" /> <Folder Include="Menu\Dtos\" />
<Folder Include="Organization\Dtos\" /> <Folder Include="Organization\Dtos\" />

View File

@ -0,0 +1,79 @@
namespace YD_Event.Core.Utils;
/// <summary>
/// 金额处理工具类
/// 统一处理货币单位转换、精度控制和四舍五入规则
/// </summary>
public static class AmountHelper
{
/// <summary>
/// 金额精度小数位数
/// </summary>
private const int DecimalPlaces = 2;
/// <summary>
/// 将元转换为分
/// </summary>
/// <param name="amount">金额(元)</param>
/// <returns>金额(分)</returns>
public static int YuanToFen(decimal amount)
{
// 使用四舍五入到最近的整数,使用银行家舍入法(四舍六入五成双)
// 这里先确保精度,然后乘以100,再四舍五入
var roundedAmount = Math.Round(amount, DecimalPlaces, MidpointRounding.AwayFromZero);
return (int)Math.Round(roundedAmount * 100, MidpointRounding.AwayFromZero);
}
/// <summary>
/// 将分转换为元
/// </summary>
/// <param name="amount">金额(分)</param>
/// <returns>金额(元)</returns>
public static decimal FenToYuan(int amount)
{
return amount / 100m;
}
/// <summary>
/// 将分转换为元(可为空)
/// </summary>
/// <param name="amount">金额(分)</param>
/// <returns>金额(元)</returns>
public static decimal? FenToYuan(int? amount)
{
if (!amount.HasValue) return null;
return amount.Value / 100m;
}
/// <summary>
/// 四舍五入金额到指定小数位
/// </summary>
/// <param name="amount">金额</param>
/// <param name="decimalPlaces">小数位数,默认2位</param>
/// <returns>四舍五入后的金额</returns>
public static decimal Round(decimal amount, int decimalPlaces = DecimalPlaces)
{
return Math.Round(amount, decimalPlaces, MidpointRounding.AwayFromZero);
}
/// <summary>
/// 验证金额格式是否正确(最多保留2位小数)
/// </summary>
/// <param name="amount">金额</param>
/// <returns>是否有效</returns>
public static bool IsValidAmount(decimal amount)
{
var rounded = Math.Round(amount, DecimalPlaces, MidpointRounding.AwayFromZero);
return rounded == amount;
}
/// <summary>
/// 验证金额是否大于0且格式正确
/// </summary>
/// <param name="amount">金额</param>
/// <returns>是否有效</returns>
public static bool IsValidPositiveAmount(decimal amount)
{
return amount > 0 && IsValidAmount(amount);
}
}

View File

@ -5811,6 +5811,60 @@
子选项 子选项
</summary> </summary>
</member> </member>
<member name="T:YD_Event.Core.Utils.AmountHelper">
<summary>
金额处理工具类
统一处理货币单位转换、精度控制和四舍五入规则
</summary>
</member>
<member name="F:YD_Event.Core.Utils.AmountHelper.DecimalPlaces">
<summary>
金额精度小数位数
</summary>
</member>
<member name="M:YD_Event.Core.Utils.AmountHelper.YuanToFen(System.Decimal)">
<summary>
将元转换为分
</summary>
<param name="amount">金额(元)</param>
<returns>金额(分)</returns>
</member>
<member name="M:YD_Event.Core.Utils.AmountHelper.FenToYuan(System.Int32)">
<summary>
将分转换为元
</summary>
<param name="amount">金额(分)</param>
<returns>金额(元)</returns>
</member>
<member name="M:YD_Event.Core.Utils.AmountHelper.FenToYuan(System.Nullable{System.Int32})">
<summary>
将分转换为元(可为空)
</summary>
<param name="amount">金额(分)</param>
<returns>金额(元)</returns>
</member>
<member name="M:YD_Event.Core.Utils.AmountHelper.Round(System.Decimal,System.Int32)">
<summary>
四舍五入金额到指定小数位
</summary>
<param name="amount">金额</param>
<param name="decimalPlaces">小数位数,默认2位</param>
<returns>四舍五入后的金额</returns>
</member>
<member name="M:YD_Event.Core.Utils.AmountHelper.IsValidAmount(System.Decimal)">
<summary>
验证金额格式是否正确(最多保留2位小数)
</summary>
<param name="amount">金额</param>
<returns>是否有效</returns>
</member>
<member name="M:YD_Event.Core.Utils.AmountHelper.IsValidPositiveAmount(System.Decimal)">
<summary>
验证金额是否大于0且格式正确
</summary>
<param name="amount">金额</param>
<returns>是否有效</returns>
</member>
<member name="F:Easy.Core.EnumExtension.EnumDic"> <member name="F:Easy.Core.EnumExtension.EnumDic">
<summary> <summary>
缓存 缓存