修复bug

This commit is contained in:
马梓彭 2026-04-21 08:38:45 +08:00
parent f285c439a8
commit 493ead2d0f
8 changed files with 308 additions and 69 deletions

View File

@ -905,11 +905,7 @@ public class UploadVoucherInput
[Required]
public DateTime PayTime { get; set; }
/// <summary>
/// 支付渠道 (微信/支付宝/银行转账)
/// </summary>
[Required]
public string PayChannel { get; set; }
/// <summary>
/// 交易流水号 (可选)

View File

@ -38,7 +38,9 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
_eventInfoRepository = eventInfoRepository;
}
// 格式化时长显示(符号格式:X'X")
/// <summary>
/// 格式化时长显示(符号格式:X'X")
/// </summary>
private string FormatDurationSymbol(int totalSeconds)
{
if (totalSeconds <= 0)
@ -88,20 +90,19 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
// 按照 SortIndex 对 projects 排序
var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList();
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
// 统计各组别报名人数(统计所有非取消状态的报名,并且只统计运动员
var counts = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4 // 排除已取消的报名
&& !p.DeleteMark // 排除已删除的人员
&& p.PersonType == PersonType.Athlete // 只统计运动员
)
&& p.PersonType == PersonType.Athlete) // 只统计运动员
.GroupBy((p, r) => new { p.ProjectId, p.GroupName })
.Select((p, r) => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) })
.ToListAsync();
@ -109,9 +110,9 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
// 获取所有相关的人员和报名记录(用于统计团队数)
var personWithTeam = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4
&& !p.DeleteMark
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete
&& r.TeamName != null)
.Select((p, r) => new { p.ProjectId, p.GroupName, r.TeamName })
@ -136,7 +137,7 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
ProjectId = eventProj.Id,
ProjectName = proj.Name,
ProjectType = proj.ProjectType.Description(),
DurationSeconds = proj.DurationSeconds,
DurationSeconds = proj.GetTotalDurationInSeconds(),
VenueCount = proj.VenueCount,
SortIndex = proj.SortIndex,
Groups = new List<ArrangementGroupDto>()
@ -146,18 +147,18 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
foreach (var group in projGroups)
{
var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName);
dto.Groups.Add(new ArrangementGroupDto
{
GroupName = group.GroupName,
ParticipantCount = countObj?.Count ?? 0
dto.Groups.Add(new ArrangementGroupDto
{
GroupName = group.GroupName,
ParticipantCount = countObj?.Count ?? 0
});
}
// 根据项目类型计算总时长
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
int totalParticipants = dto.Groups.Sum(g => g.ParticipantCount);
int totalTeams = 0;
// 如果是团队项目,计算团队总数
if (proj.IsTeamProject())
{
@ -189,16 +190,16 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
// 团队项目:(队伍数 ÷ 场地数) × 项目时长
int teamCount = totalTeams > 0 ? totalTeams : 1;
int rounds = (int)Math.Ceiling((double)teamCount / venueCount);
totalDurationSeconds = rounds * proj.DurationSeconds;
totalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
}
else
{
// 个人项目:(报名人数 ÷ 场地数) × 项目时长
int participantCount = totalParticipants > 0 ? totalParticipants : 1;
int rounds = (int)Math.Ceiling((double)participantCount / venueCount);
totalDurationSeconds = rounds * proj.DurationSeconds;
totalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
}
// 设置总时长秒数和分钟数
dto.TotalDurationSeconds = totalDurationSeconds;
dto.TotalDurationMinutes = (int)Math.Ceiling((double)totalDurationSeconds / 60);
@ -223,13 +224,14 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
var ep = eventProjects.FirstOrDefault(x => x.Id == setting.ProjectId);
if (ep != null)
{
await _projectRepository.UpdateAsync(x => new YD_Event.Core.Entities.Project
{
VenueCount = setting.VenueCount,
SortIndex = setting.SortIndex,
DurationSeconds = setting.DurationSeconds,
DurationMinutes = setting.DurationSeconds / 60
}, x => x.Id == ep.ProjectId);
var project = await _projectRepository.GetByIdAsync(ep.ProjectId);
if (project != null)
{
project.VenueCount = setting.VenueCount;
project.SortIndex = setting.SortIndex;
project.SetTotalDurationInSeconds(setting.DurationSeconds);
await _projectRepository.UpdateAsync(project);
}
}
}
}
@ -244,7 +246,7 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
// 按照 SortIndex 对 projects 排序
var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList();
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
@ -252,9 +254,9 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
// 统计各组别报名人数
var counts = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4
&& !p.DeleteMark
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete)
.GroupBy((p, r) => new { p.ProjectId, p.GroupName })
.Select((p, r) => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) })
@ -263,9 +265,9 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
// 获取团队数
var personWithTeam = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4
&& !p.DeleteMark
.Where((p, r) => r.EventId == eventId
&& r.PaymentStatus != 4
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete
&& r.TeamName != null)
.Select((p, r) => new { p.ProjectId, p.GroupName, r.TeamName })
@ -278,27 +280,29 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
.ToList();
var result = new List<ArrangementPreviewOutput>();
// 2. 生成排期 - 按照排序后的项目顺序
// 2. 生成排期 - 按照排序后的项目顺序处理
foreach (var proj in sortedProjects)
{
var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id);
if (eventProj == null) continue;
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
// 获取参赛人员
var persons = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => p.ProjectId == eventProj.Id
&& r.PaymentStatus != 4
&& !p.DeleteMark
.Where((p, r) => p.ProjectId == eventProj.Id
&& r.PaymentStatus != 4
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete)
.OrderBy(p => p.GroupName)
.OrderBy(p => p.Id)
.Select((p, r) => new {
p.Name, p.GroupName, r.TeamName,
Duration = proj.DurationSeconds
.Select((p, r) => new {
p.Name,
p.GroupName,
r.TeamName,
Duration = proj.GetTotalDurationInSeconds()
})
.ToListAsync();
@ -326,13 +330,13 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
{
int teamCount = totalTeams > 0 ? totalTeams : 1;
int rounds = (int)Math.Ceiling((double)teamCount / venueCount);
projectTotalDurationSeconds = rounds * proj.DurationSeconds;
projectTotalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
}
else
{
int participantCount = totalParticipants > 0 ? totalParticipants : 1;
int rounds = (int)Math.Ceiling((double)participantCount / venueCount);
projectTotalDurationSeconds = rounds * proj.DurationSeconds;
projectTotalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
}
string projectTotalDuration = FormatDurationSymbol(projectTotalDurationSeconds);
@ -340,12 +344,12 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
// 分组逻辑
int currentRound = 1;
int currentVenue = 1;
foreach (var person in persons)
{
result.Add(new ArrangementPreviewOutput
{
TimeRange = $"{proj.DurationSeconds}''",
TimeRange = $"{proj.GetTotalDurationInSeconds()}''",
ProjectName = proj.Name,
ProjectType = proj.ProjectType.Description(),
GroupName = person.GroupName,

View File

@ -273,14 +273,12 @@ public class UserEventRecordService : IDynamicApiController, ITransient
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 仅 status = 待付款 允许上传
if (registration.PaymentStatus != 0) throw Oops.Bah("当前状态不允许上传凭证");
var uploadResult = await _fileService.Upload(input.File);
var fileInfo = uploadResult.First();
registration.PaymentProofUrl = fileInfo.Url;
registration.PaymentStatus = 0;
registration.UpdatedTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);

View File

@ -116,6 +116,16 @@ public class OrderListOutput
/// 退款时间
/// </summary>
public DateTime? RefundTime { get; set; }
/// <summary>
/// 关联原始订单的实付金额(仅退款订单时有效)
/// </summary>
public decimal? RelatedOrderActualAmount { get; set; }
/// <summary>
/// 是否为退款订单
/// </summary>
public bool IsRefundOrder { get; set; }
}
/// <summary>
@ -237,6 +247,16 @@ public class OrderDetailOutput
/// 联系人电话
/// </summary>
public string ContactPhone { get; set; }
/// <summary>
/// 关联原始订单的实付金额(仅退款订单时有效)
/// </summary>
public decimal? RelatedOrderActualAmount { get; set; }
/// <summary>
/// 是否为退款订单
/// </summary>
public bool IsRefundOrder { get; set; }
}
/// <summary>

View File

@ -231,7 +231,9 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
CreatedTime = o.CreatedTime,
PaymentTime = o.PaymentTime,
RefundAmount = o.OrderType == 2 ? o.ActualAmount : 0, // 如果是退款订单,ActualAmount即为退款金额
RefundTime = o.OrderType == 2 ? o.PaymentTime : null // 退款时间
RefundTime = o.OrderType == 2 ? o.PaymentTime : null, // 退款时间
RelatedOrderActualAmount = o.OrderType == 2 ? ro.ActualAmount : null, // 退款订单显示原始订单实付金额
IsRefundOrder = o.OrderType == 2
})
.ToPagedListAsync(input);
@ -250,7 +252,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
.LeftJoin<EventRegistration>((o, e, r) => o.RegistrationId == r.Id)
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, r, ro) => o.RelatedOrderId == ro.Id)
.Where(o => o.Id == id)
.Select((o, e, r, ro) => new { Order = o, EventName = e.EventName, Registration = r, RelatedOrderNo = ro.OrderNo });
.Select((o, e, r, ro) => new { Order = o, EventName = e.EventName, Registration = r, RelatedOrderNo = ro.OrderNo, RelatedOrderActualAmount = ro.ActualAmount });
var orderInfo = await query.FirstAsync();
@ -307,7 +309,9 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
CreatedTime = orderInfo.Order.CreatedTime,
RelatedOrderNo = orderInfo.RelatedOrderNo,
ContactName = orderInfo.Registration?.ContactName,
ContactPhone = orderInfo.Registration?.ContactPhone
ContactPhone = orderInfo.Registration?.ContactPhone,
RelatedOrderActualAmount = orderInfo.Order.OrderType == 2 ? orderInfo.RelatedOrderActualAmount : null,
IsRefundOrder = orderInfo.Order.OrderType == 2
};
}
@ -528,7 +532,9 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
o.OrderStatus == OrderStatus.Refunded ? "已退款" :
o.OrderStatus == OrderStatus.Shipped ? "已发货" :
o.OrderStatus == OrderStatus.Completed ? "已完成" : "",
CreatedTime = o.CreatedTime
CreatedTime = o.CreatedTime,
RelatedOrderActualAmount = o.OrderType == 2 ? ro.ActualAmount : null,
IsRefundOrder = o.OrderType == 2
})
.ToListAsync();
@ -559,7 +565,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
var totalActual = orders.Where(x => x.OrderType == "收入").Sum(x => x.ActualAmount);
// 汇总信息行
var summaryRange = worksheet.Cells[rowIndex, 1, rowIndex, 14];
var summaryRange = worksheet.Cells[rowIndex, 1, rowIndex, 15];
summaryRange.Merge = true;
summaryRange.Value = $"汇总信息: 原金额:{totalOriginal}, 优惠金额:{totalDiscount}, 退款金额:{totalRefund}, 实付金额:{totalActual}";
var summaryStyle = summaryRange.Style;
@ -576,7 +582,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
{
"订单编号", "单位名称", "所属赛事", "订单类型", "关联订单",
"支付方式", "报名方式", "报名用户", "报名账号", "原金额(元)",
"优惠金额(元)", "退款金额(元)", "实付金额(元)", "生成时间"
"优惠金额(元)", "退款金额(元)", "原始订单实付金额(元)", "实付金额(元)", "生成时间"
};
for (int i = 0; i < headers.Length; i++)
@ -600,6 +606,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
{
var refundAmount = item.OrderType == "退款" ? item.ActualAmount : 0;
var actualPayAmount = item.OrderType == "收入" ? item.ActualAmount : 0;
var relatedAmount = item.IsRefundOrder ? item.RelatedOrderActualAmount : null;
worksheet.Cells[rowIndex, 1].Value = item.OrderNo;
worksheet.Cells[rowIndex, 2].Value = item.UnitName;
@ -613,21 +620,23 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
worksheet.Cells[rowIndex, 10].Value = item.OriginalAmount;
worksheet.Cells[rowIndex, 11].Value = item.DiscountAmount;
worksheet.Cells[rowIndex, 12].Value = refundAmount;
worksheet.Cells[rowIndex, 13].Value = actualPayAmount;
worksheet.Cells[rowIndex, 14].Value = item.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss");
worksheet.Cells[rowIndex, 13].Value = relatedAmount;
worksheet.Cells[rowIndex, 14].Value = actualPayAmount;
worksheet.Cells[rowIndex, 15].Value = item.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss");
// 设置金额格式
worksheet.Cells[rowIndex, 10].Style.Numberformat.Format = "0.00";
worksheet.Cells[rowIndex, 11].Style.Numberformat.Format = "0.00";
worksheet.Cells[rowIndex, 12].Style.Numberformat.Format = "0.00";
worksheet.Cells[rowIndex, 13].Style.Numberformat.Format = "0.00";
worksheet.Cells[rowIndex, 14].Style.Numberformat.Format = "0.00";
SetDataRowStyle(worksheet, rowIndex, 14);
SetDataRowStyle(worksheet, rowIndex, 15);
rowIndex++;
}
// 设置列宽
var columnWidths = new double[] { 18, 15, 15, 10, 12, 10, 10, 12, 14, 12, 14, 14, 12, 18 };
var columnWidths = new double[] { 18, 15, 15, 10, 12, 10, 10, 12, 14, 12, 14, 14, 18, 12, 18 };
for (int i = 0; i < columnWidths.Length; i++)
{
worksheet.Column(i + 1).Width = columnWidths[i];

View File

@ -7276,11 +7276,6 @@
支付时间
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.UploadVoucherInput.PayChannel">
<summary>
支付渠道 (微信/支付宝/银行转账)
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.UploadVoucherInput.TransactionId">
<summary>
交易流水号 (可选)
@ -7766,6 +7761,11 @@
赛事编排服务
</summary>
</member>
<member name="M:YD_Event.Application.Event.EventArrangementService.FormatDurationSymbol(System.Int32)">
<summary>
格式化时长显示(符号格式:X'X")
</summary>
</member>
<member name="M:YD_Event.Application.Event.EventArrangementService.GetList(System.Int64)">
<summary>
获取编排项目列表
@ -9966,6 +9966,16 @@
退款时间
</summary>
</member>
<member name="P:YD_Event.Application.Order.Dtos.OrderListOutput.RelatedOrderActualAmount">
<summary>
关联原始订单的实付金额(仅退款订单时有效)
</summary>
</member>
<member name="P:YD_Event.Application.Order.Dtos.OrderListOutput.IsRefundOrder">
<summary>
是否为退款订单
</summary>
</member>
<member name="T:YD_Event.Application.Order.Dtos.OrderDetailOutput">
<summary>
订单详情输出
@ -10086,6 +10096,16 @@
联系人电话
</summary>
</member>
<member name="P:YD_Event.Application.Order.Dtos.OrderDetailOutput.RelatedOrderActualAmount">
<summary>
关联原始订单的实付金额(仅退款订单时有效)
</summary>
</member>
<member name="P:YD_Event.Application.Order.Dtos.OrderDetailOutput.IsRefundOrder">
<summary>
是否为退款订单
</summary>
</member>
<member name="T:YD_Event.Application.Order.Dtos.OrderItemDto">
<summary>
订单项目DTO

View File

@ -0,0 +1,144 @@
using YD_Event.Core.Entities;
namespace YD_Event.Core.Extensions;
/// <summary>
/// 项目时间处理扩展类
/// 用于统一处理 Project 实体的时间计算逻辑
/// </summary>
public static class ProjectTimeExtensions
{
/// <summary>
/// 获取项目的总时长(秒)
/// </summary>
/// <param name="project">项目实体</param>
/// <returns>总时长(秒)</returns>
/// <exception cref="ArgumentNullException">当 project 为 null 时抛出</exception>
public static int GetTotalDurationInSeconds(this Project project)
{
if (project == null)
throw new ArgumentNullException(nameof(project));
// 边界情况处理:确保值不小于 0
int minutes = Math.Max(0, project.DurationMinutes);
int seconds = Math.Max(0, project.DurationSeconds);
// 确保秒数不超过 59(一分钟内的秒数)
seconds = Math.Min(seconds, 59);
return minutes * 60 + seconds;
}
/// <summary>
/// 设置项目的总时长(秒)
/// 自动计算并更新 DurationMinutes 和 DurationSeconds
/// </summary>
/// <param name="project">项目实体</param>
/// <param name="totalSeconds">总时长(秒)</param>
/// <exception cref="ArgumentNullException">当 project 为 null 时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">当 totalSeconds 小于 0 时抛出</exception>
public static void SetTotalDurationInSeconds(this Project project, int totalSeconds)
{
if (project == null)
throw new ArgumentNullException(nameof(project));
if (totalSeconds < 0)
throw new ArgumentOutOfRangeException(nameof(totalSeconds), "总时长不能小于 0");
// 计算分钟和秒
project.DurationMinutes = totalSeconds / 60;
project.DurationSeconds = totalSeconds % 60;
}
/// <summary>
/// 标准化项目的时间字段
/// 确保 DurationSeconds 在 0-59 范围内,并正确处理负值
/// </summary>
/// <param name="project">项目实体</param>
/// <exception cref="ArgumentNullException">当 project 为 null 时抛出</exception>
public static void NormalizeDuration(this Project project)
{
if (project == null)
throw new ArgumentNullException(nameof(project));
// 先获取总秒数
int totalSeconds = project.GetTotalDurationInSeconds();
// 重新设置,确保标准化
project.SetTotalDurationInSeconds(totalSeconds);
}
/// <summary>
/// 获取格式化的时长显示字符串(例如:"2分30秒" 或 "30秒")
/// </summary>
/// <param name="project">项目实体</param>
/// <returns>格式化的时长字符串</returns>
/// <exception cref="ArgumentNullException">当 project 为 null 时抛出</exception>
public static string GetFormattedDuration(this Project project)
{
if (project == null)
throw new ArgumentNullException(nameof(project));
int totalSeconds = project.GetTotalDurationInSeconds();
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
if (minutes == 0)
{
return $"{seconds}秒";
}
else if (seconds == 0)
{
return $"{minutes}分";
}
else
{
return $"{minutes}分{seconds}秒";
}
}
/// <summary>
/// 获取符号格式的时长显示(例如:"2'30\"" 或 "30\"")
/// </summary>
/// <param name="project">项目实体</param>
/// <returns>符号格式的时长字符串</returns>
/// <exception cref="ArgumentNullException">当 project 为 null 时抛出</exception>
public static string GetSymbolFormattedDuration(this Project project)
{
if (project == null)
throw new ArgumentNullException(nameof(project));
int totalSeconds = project.GetTotalDurationInSeconds();
int hours = totalSeconds / 3600;
int remainingSeconds = totalSeconds % 3600;
int minutes = remainingSeconds / 60;
int seconds = remainingSeconds % 60;
if (hours == 0)
{
if (seconds == 0)
{
return $"{minutes}'";
}
else
{
string paddedSeconds = seconds.ToString().PadLeft(2, '0');
return $"{minutes}'{paddedSeconds}\"";
}
}
else
{
string result = $"{hours}h";
if (minutes > 0 || seconds > 0)
{
result += $"{minutes}'";
if (seconds > 0)
{
string paddedSeconds = seconds.ToString().PadLeft(2, '0');
result += $"{paddedSeconds}\"";
}
}
return result;
}
}
}

View File

@ -4755,6 +4755,54 @@
赞助单位
</summary>
</member>
<member name="T:YD_Event.Core.Extensions.ProjectTimeExtensions">
<summary>
项目时间处理扩展类
用于统一处理 Project 实体的时间计算逻辑
</summary>
</member>
<member name="M:YD_Event.Core.Extensions.ProjectTimeExtensions.GetTotalDurationInSeconds(YD_Event.Core.Entities.Project)">
<summary>
获取项目的总时长(秒)
</summary>
<param name="project">项目实体</param>
<returns>总时长(秒)</returns>
<exception cref="T:System.ArgumentNullException">当 project 为 null 时抛出</exception>
</member>
<member name="M:YD_Event.Core.Extensions.ProjectTimeExtensions.SetTotalDurationInSeconds(YD_Event.Core.Entities.Project,System.Int32)">
<summary>
设置项目的总时长(秒)
自动计算并更新 DurationMinutes 和 DurationSeconds
</summary>
<param name="project">项目实体</param>
<param name="totalSeconds">总时长(秒)</param>
<exception cref="T:System.ArgumentNullException">当 project 为 null 时抛出</exception>
<exception cref="T:System.ArgumentOutOfRangeException">当 totalSeconds 小于 0 时抛出</exception>
</member>
<member name="M:YD_Event.Core.Extensions.ProjectTimeExtensions.NormalizeDuration(YD_Event.Core.Entities.Project)">
<summary>
标准化项目的时间字段
确保 DurationSeconds 在 0-59 范围内,并正确处理负值
</summary>
<param name="project">项目实体</param>
<exception cref="T:System.ArgumentNullException">当 project 为 null 时抛出</exception>
</member>
<member name="M:YD_Event.Core.Extensions.ProjectTimeExtensions.GetFormattedDuration(YD_Event.Core.Entities.Project)">
<summary>
获取格式化的时长显示字符串(例如:"2分30秒" 或 "30秒")
</summary>
<param name="project">项目实体</param>
<returns>格式化的时长字符串</returns>
<exception cref="T:System.ArgumentNullException">当 project 为 null 时抛出</exception>
</member>
<member name="M:YD_Event.Core.Extensions.ProjectTimeExtensions.GetSymbolFormattedDuration(YD_Event.Core.Entities.Project)">
<summary>
获取符号格式的时长显示(例如:"2'30\"" 或 "30\"")
</summary>
<param name="project">项目实体</param>
<returns>符号格式的时长字符串</returns>
<exception cref="T:System.ArgumentNullException">当 project 为 null 时抛出</exception>
</member>
<member name="T:YD_Event.Core.Extensions.ProjectTypeExtensions">
<summary>
项目类型判断扩展方法