diff --git a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
index e4a746a..6e93843 100644
--- a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
+++ b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
@@ -905,11 +905,7 @@ public class UploadVoucherInput
[Required]
public DateTime PayTime { get; set; }
- ///
- /// 支付渠道 (微信/支付宝/银行转账)
- ///
- [Required]
- public string PayChannel { get; set; }
+
///
/// 交易流水号 (可选)
diff --git a/YD_Event.Application/Event/EventArrangementService.cs b/YD_Event.Application/Event/EventArrangementService.cs
index ce86e51..80fffc6 100644
--- a/YD_Event.Application/Event/EventArrangementService.cs
+++ b/YD_Event.Application/Event/EventArrangementService.cs
@@ -38,7 +38,9 @@ public class EventArrangementService : BaseService
+ /// 格式化时长显示(符号格式:X'X")
+ ///
private string FormatDurationSymbol(int totalSeconds)
{
if (totalSeconds <= 0)
@@ -88,20 +90,19 @@ public class EventArrangementService : BaseService 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((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((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()
@@ -146,18 +147,18 @@ public class EventArrangementService : BaseService 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 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 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 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((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((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();
-
- // 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((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 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 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);
diff --git a/YD_Event.Application/Order/Dtos/OrderListOutput.cs b/YD_Event.Application/Order/Dtos/OrderListOutput.cs
index 3c41889..ab5c715 100644
--- a/YD_Event.Application/Order/Dtos/OrderListOutput.cs
+++ b/YD_Event.Application/Order/Dtos/OrderListOutput.cs
@@ -116,6 +116,16 @@ public class OrderListOutput
/// 退款时间
///
public DateTime? RefundTime { get; set; }
+
+ ///
+ /// 关联原始订单的实付金额(仅退款订单时有效)
+ ///
+ public decimal? RelatedOrderActualAmount { get; set; }
+
+ ///
+ /// 是否为退款订单
+ ///
+ public bool IsRefundOrder { get; set; }
}
///
@@ -237,6 +247,16 @@ public class OrderDetailOutput
/// 联系人电话
///
public string ContactPhone { get; set; }
+
+ ///
+ /// 关联原始订单的实付金额(仅退款订单时有效)
+ ///
+ public decimal? RelatedOrderActualAmount { get; set; }
+
+ ///
+ /// 是否为退款订单
+ ///
+ public bool IsRefundOrder { get; set; }
}
///
diff --git a/YD_Event.Application/Order/OrderService.cs b/YD_Event.Application/Order/OrderService.cs
index 5108507..fda4b3a 100644
--- a/YD_Event.Application/Order/OrderService.cs
+++ b/YD_Event.Application/Order/OrderService.cs
@@ -231,7 +231,9 @@ public class OrderService : BaseService, 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, ITransien
.LeftJoin((o, e, r) => o.RegistrationId == r.Id)
.LeftJoin((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, 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, 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, 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, ITransien
{
"订单编号", "单位名称", "所属赛事", "订单类型", "关联订单",
"支付方式", "报名方式", "报名用户", "报名账号", "原金额(元)",
- "优惠金额(元)", "退款金额(元)", "实付金额(元)", "生成时间"
+ "优惠金额(元)", "退款金额(元)", "原始订单实付金额(元)", "实付金额(元)", "生成时间"
};
for (int i = 0; i < headers.Length; i++)
@@ -600,6 +606,7 @@ public class OrderService : BaseService, 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, 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];
diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml
index d21143d..8206cec 100644
--- a/YD_Event.Application/YD_Event.Application.xml
+++ b/YD_Event.Application/YD_Event.Application.xml
@@ -7276,11 +7276,6 @@
支付时间
-
-
- 支付渠道 (微信/支付宝/银行转账)
-
-
交易流水号 (可选)
@@ -7766,6 +7761,11 @@
赛事编排服务
+
+
+ 格式化时长显示(符号格式:X'X")
+
+
获取编排项目列表
@@ -9966,6 +9966,16 @@
退款时间
+
+
+ 关联原始订单的实付金额(仅退款订单时有效)
+
+
+
+
+ 是否为退款订单
+
+
订单详情输出
@@ -10086,6 +10096,16 @@
联系人电话
+
+
+ 关联原始订单的实付金额(仅退款订单时有效)
+
+
+
+
+ 是否为退款订单
+
+
订单项目DTO
diff --git a/YD_Event.Core/Extensions/ProjectTimeExtensions.cs b/YD_Event.Core/Extensions/ProjectTimeExtensions.cs
new file mode 100644
index 0000000..1c2cb9f
--- /dev/null
+++ b/YD_Event.Core/Extensions/ProjectTimeExtensions.cs
@@ -0,0 +1,144 @@
+using YD_Event.Core.Entities;
+
+namespace YD_Event.Core.Extensions;
+
+///
+/// 项目时间处理扩展类
+/// 用于统一处理 Project 实体的时间计算逻辑
+///
+public static class ProjectTimeExtensions
+{
+ ///
+ /// 获取项目的总时长(秒)
+ ///
+ /// 项目实体
+ /// 总时长(秒)
+ /// 当 project 为 null 时抛出
+ 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;
+ }
+
+ ///
+ /// 设置项目的总时长(秒)
+ /// 自动计算并更新 DurationMinutes 和 DurationSeconds
+ ///
+ /// 项目实体
+ /// 总时长(秒)
+ /// 当 project 为 null 时抛出
+ /// 当 totalSeconds 小于 0 时抛出
+ 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;
+ }
+
+ ///
+ /// 标准化项目的时间字段
+ /// 确保 DurationSeconds 在 0-59 范围内,并正确处理负值
+ ///
+ /// 项目实体
+ /// 当 project 为 null 时抛出
+ public static void NormalizeDuration(this Project project)
+ {
+ if (project == null)
+ throw new ArgumentNullException(nameof(project));
+
+ // 先获取总秒数
+ int totalSeconds = project.GetTotalDurationInSeconds();
+
+ // 重新设置,确保标准化
+ project.SetTotalDurationInSeconds(totalSeconds);
+ }
+
+ ///
+ /// 获取格式化的时长显示字符串(例如:"2分30秒" 或 "30秒")
+ ///
+ /// 项目实体
+ /// 格式化的时长字符串
+ /// 当 project 为 null 时抛出
+ 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}秒";
+ }
+ }
+
+ ///
+ /// 获取符号格式的时长显示(例如:"2'30\"" 或 "30\"")
+ ///
+ /// 项目实体
+ /// 符号格式的时长字符串
+ /// 当 project 为 null 时抛出
+ 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;
+ }
+ }
+}
diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml
index 15bde8d..06e8120 100644
--- a/YD_Event.Core/YD_Event.Core.xml
+++ b/YD_Event.Core/YD_Event.Core.xml
@@ -4755,6 +4755,54 @@
赞助单位
+
+
+ 项目时间处理扩展类
+ 用于统一处理 Project 实体的时间计算逻辑
+
+
+
+
+ 获取项目的总时长(秒)
+
+ 项目实体
+ 总时长(秒)
+ 当 project 为 null 时抛出
+
+
+
+ 设置项目的总时长(秒)
+ 自动计算并更新 DurationMinutes 和 DurationSeconds
+
+ 项目实体
+ 总时长(秒)
+ 当 project 为 null 时抛出
+ 当 totalSeconds 小于 0 时抛出
+
+
+
+ 标准化项目的时间字段
+ 确保 DurationSeconds 在 0-59 范围内,并正确处理负值
+
+ 项目实体
+ 当 project 为 null 时抛出
+
+
+
+ 获取格式化的时长显示字符串(例如:"2分30秒" 或 "30秒")
+
+ 项目实体
+ 格式化的时长字符串
+ 当 project 为 null 时抛出
+
+
+
+ 获取符号格式的时长显示(例如:"2'30\"" 或 "30\"")
+
+ 项目实体
+ 符号格式的时长字符串
+ 当 project 为 null 时抛出
+
项目类型判断扩展方法