赛事管理bug

This commit is contained in:
马梓彭 2026-04-07 09:04:38 +08:00
parent 2f053f5f11
commit 239fb777f3
6 changed files with 337 additions and 108 deletions

View File

@ -61,6 +61,26 @@ public class EventExportInput : EventQueryInput
public List<string>? ExportFields { get; set; }
}
/// <summary>
/// 赛事导出数据
/// </summary>
public class EventExportData
{
public long Id { get; set; }
public string EventName { get; set; } = "";
public EventFormat EventType { get; set; }
public ChargeMode ChargeMode { get; set; }
public RegistrationType RegistrationType { get; set; }
public EventStatus EventStatus { get; set; }
public DateTime RegistrationStartTime { get; set; }
public DateTime RegistrationEndTime { get; set; }
public DateTime? MaterialUploadEndTime { get; set; }
public DateTime MatchStartTime { get; set; }
public DateTime MatchEndTime { get; set; }
public string? DetailAddress { get; set; }
public string? MerchantName { get; set; }
}
/// <summary>
/// 赛事列表输出
/// </summary>
@ -448,6 +468,11 @@ public class EventFeeConfigInput
/// 银行账号 (显示字段)
/// </summary>
public string? AccountNumber { get; set; }
/// <summary>
/// 营业执照图片URL (显示字段)
/// </summary>
public string? BusinessLicenseUrl { get; set; }
}
/// <summary>

View File

@ -57,12 +57,16 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
var typeIds = projects.Select(x => x.ProjectTypeId).Distinct().ToList();
var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id));
// 统计各组别报名人数(仅统计已支付或审核通过的)
// 统计各组别报名人数(统计所有非取消状态的报名,并且只统计运动员
var counts = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId && (r.PaymentStatus == 1 || r.RegistrationStatus == 2))
.GroupBy(p => new { p.ProjectId, p.GroupName })
.Select(p => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) })
.Where((p, r) => r.EventId == eventId
&& r.RegistrationStatus != 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) })
.ToListAsync();
var result = new List<ArrangementProjectListOutput>();
@ -155,10 +159,13 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
// 获取参赛人员(排序:组别 -> ID)
// 放宽查询条件,只要不是取消状态的报名都包含进来
// 查询条件:不是取消状态的报名,并且只统计运动员
var persons = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => p.ProjectId == eventProj.Id && r.RegistrationStatus != 4)
.Where((p, r) => p.ProjectId == eventProj.Id
&& r.RegistrationStatus != 4
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete)
.OrderBy(p => p.GroupName)
.OrderBy(p => p.Id)
.Select((p, r) => new {

View File

@ -156,9 +156,6 @@ public class EventService : BaseService<EventInfo>, ITransient
output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt<EventUnitDto>()).ToList();
}
// 2. 自定义字段配置 (JSON)
// output.CustomConfigJson 已自动映射
// 3. 获取区域名称和Code
if (output.ProvinceId > 0)
{
@ -179,24 +176,6 @@ public class EventService : BaseService<EventInfo>, ITransient
output.DistrictName = d?.DistrictName;
}
// 4. 获取商户信息并填充到FeeConfig
if (eventInfo.MerchantId.HasValue && eventInfo.MerchantId.Value > 0)
{
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
if (merchant != null)
{
if (output.FeeConfig == null)
{
output.FeeConfig = new EventFeeConfigInput();
}
output.FeeConfig.MerchantId = merchant.Id;
output.FeeConfig.MerchantName = merchant.MerchantName;
output.FeeConfig.CreditCode = merchant.CreditCode;
output.FeeConfig.BankName = merchant.BankName;
output.FeeConfig.AccountNumber = merchant.AccountNumber;
}
}
// 4. 获取项目列表 (Step 3)
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id);
if (eventProjects.Any())
@ -271,7 +250,7 @@ public class EventService : BaseService<EventInfo>, ITransient
if (feeConfig != null)
{
output.FeeConfig = feeConfig.Adapt<EventFeeConfigInput>();
// 获取商户详情(使用 EventInfo.MerchantId)
// 获取商户详情
if (eventInfo.MerchantId.HasValue && eventInfo.MerchantId.Value > 0)
{
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
@ -281,6 +260,8 @@ public class EventService : BaseService<EventInfo>, ITransient
output.FeeConfig.CreditCode = merchant.CreditCode;
output.FeeConfig.BankName = merchant.BankName;
output.FeeConfig.AccountNumber = merchant.AccountNumber;
output.FeeConfig.MerchantId = merchant.Id;
output.FeeConfig.BusinessLicenseUrl = merchant.BusinessLicenseUrl;
}
}
}
@ -371,9 +352,9 @@ public class EventService : BaseService<EventInfo>, ITransient
eventInfo.Status = AvailabilityStatus.Enable;
// 保存商户ID到EventInfo表
if (input.FeeConfig != null && input.FeeConfig.MerchantId.HasValue)
if (input.FeeConfig != null)
{
eventInfo.MerchantId = input.FeeConfig.MerchantId.Value;
eventInfo.MerchantId = input.FeeConfig.MerchantId;
}
// 自动生成二维码URL (模拟)
@ -534,9 +515,9 @@ public class EventService : BaseService<EventInfo>, ITransient
eventInfo.DistrictId = districtId;
// 保存商户ID到EventInfo表
if (input.FeeConfig != null && input.FeeConfig.MerchantId.HasValue)
if (input.FeeConfig != null)
{
eventInfo.MerchantId = input.FeeConfig.MerchantId.Value;
eventInfo.MerchantId = input.FeeConfig.MerchantId;
}
eventInfo.UpdatedTime = DateTime.Now;
@ -679,20 +660,20 @@ public class EventService : BaseService<EventInfo>, ITransient
.WhereIF(input.RegistrationEndTime.HasValue, e => e.RegistrationEndTime <= input.RegistrationEndTime)
.WhereIF(input.MatchStartTime.HasValue, e => e.MatchStartTime >= input.MatchStartTime)
.WhereIF(input.MatchEndTime.HasValue, e => e.MatchEndTime <= input.MatchEndTime)
.Select((e, f, m) => new
.Select((e, f, m) => new EventExportData
{
e.Id,
e.EventName,
e.EventType,
e.ChargeMode,
e.RegistrationType,
e.EventStatus,
e.RegistrationStartTime,
e.RegistrationEndTime,
e.MaterialUploadEndTime,
e.MatchStartTime,
e.MatchEndTime,
e.DetailAddress,
Id = e.Id,
EventName = e.EventName,
EventType = e.EventType,
ChargeMode = e.ChargeMode,
RegistrationType = e.RegistrationType,
EventStatus = e.EventStatus,
RegistrationStartTime = e.RegistrationStartTime,
RegistrationEndTime = e.RegistrationEndTime,
MaterialUploadEndTime = e.MaterialUploadEndTime,
MatchStartTime = e.MatchStartTime,
MatchEndTime = e.MatchEndTime,
DetailAddress = e.DetailAddress,
MerchantName = m.MerchantName
});
@ -700,40 +681,107 @@ public class EventService : BaseService<EventInfo>, ITransient
var eventIds = list.Select(x => x.Id).ToList();
var units = await _unitRepository.GetListAsync(x => eventIds.Contains(x.EventId));
var exportData = new List<Dictionary<string, object>>();
foreach (var item in list)
// 使用 EPPlus 进行导出
OfficeOpenXml.ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
using var package = new OfficeOpenXml.ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add("赛事列表");
// 定义所有可用的列
var allColumns = new List<(string Header, Func<EventExportData, string> GetValue)>
{
var row = new Dictionary<string, object>();
var fields = input.ExportFields ?? new List<string>();
bool all = fields.Count == 0;
("赛事名称", item => item.EventName ?? ""),
("赛事类型", item => GetEnumDescription(item.EventType)),
("收费模式", item => GetEnumDescription(item.ChargeMode)),
("收款商户", item => item.MerchantName ?? ""),
("报名方式", item => GetEnumDescription(item.RegistrationType)),
("赛事状态", item => GetEnumDescription(item.EventStatus)),
("报名起止时间", item => $"{item.RegistrationStartTime:yyyy-MM-dd HH:mm} - {item.RegistrationEndTime:yyyy-MM-dd HH:mm}"),
("上传素材截至时间", item => item.MaterialUploadEndTime.HasValue ? item.MaterialUploadEndTime.Value.ToString("yyyy-MM-dd HH:mm") : ""),
("比赛起止时间", item => $"{item.MatchStartTime:yyyy-MM-dd HH:mm} - {item.MatchEndTime:yyyy-MM-dd HH:mm}"),
("赛事地址", item => item.DetailAddress ?? ""),
("主办单位", item => ""), // 后面填充
("承办单位", item => ""), // 后面填充
("协办单位", item => ""), // 后面填充
("赞助单位", item => "") // 后面填充
};
// 确定要导出的列
var selectedFields = input.ExportFields ?? new List<string>();
bool exportAll = selectedFields.Count == 0;
var columnsToExport = allColumns
.Where(col => exportAll || selectedFields.Contains(col.Header))
.ToList();
if (columnsToExport.Count == 0)
{
columnsToExport = allColumns;
}
// 写入表头
for (int i = 0; i < columnsToExport.Count; i++)
{
worksheet.Cells[1, i + 1].Value = columnsToExport[i].Header;
}
// 设置表头样式
using (var headerRange = worksheet.Cells[1, 1, 1, columnsToExport.Count])
{
headerRange.Style.Font.Bold = true;
headerRange.Style.Font.Size = 11;
headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
headerRange.Style.VerticalAlignment = OfficeOpenXml.Style.ExcelVerticalAlignment.Center;
headerRange.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
headerRange.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);
}
// 写入数据行
for (int rowIndex = 0; rowIndex < list.Count; rowIndex++)
{
var item = list[rowIndex];
var eventUnits = units.Where(u => u.EventId == item.Id).ToList();
var hostNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.Host).Select(u => u.UnitName));
var organizerNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.Organizer).Select(u => u.UnitName));
var coOrganizerNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.CoOrganizer).Select(u => u.UnitName));
var sponsorNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.Sponsor).Select(u => u.UnitName));
if (all || fields.Contains("赛事名称")) row["赛事名称"] = item.EventName;
if (all || fields.Contains("赛事类型")) row["赛事类型"] = item.EventType.ToString(); // TODO: Enum Description
if (all || fields.Contains("收费模式")) row["收费模式"] = item.ChargeMode.ToString();
if (all || fields.Contains("收款商户")) row["收款商户"] = item.MerchantName;
if (all || fields.Contains("报名方式")) row["报名方式"] = item.RegistrationType.ToString();
if (all || fields.Contains("赛事状态")) row["赛事状态"] = item.EventStatus.ToString();
if (all || fields.Contains("报名起止时间")) row["报名起止时间"] = $"{item.RegistrationStartTime:yyyy-MM-dd HH:mm} - {item.RegistrationEndTime:yyyy-MM-dd HH:mm}";
if (all || fields.Contains("上传素材截至时间")) row["上传素材截至时间"] = item.MaterialUploadEndTime?.ToString("yyyy-MM-dd HH:mm");
if (all || fields.Contains("比赛起止时间")) row["比赛起止时间"] = $"{item.MatchStartTime:yyyy-MM-dd HH:mm} - {item.MatchEndTime:yyyy-MM-dd HH:mm}";
if (all || fields.Contains("赛事地址")) row["赛事地址"] = item.DetailAddress;
if (all || fields.Contains("主办单位")) row["主办单位"] = hostNames;
if (all || fields.Contains("承办单位")) row["承办单位"] = organizerNames;
if (all || fields.Contains("协办单位")) row["协办单位"] = coOrganizerNames;
if (all || fields.Contains("赞助单位")) row["赞助单位"] = sponsorNames;
exportData.Add(row);
for (int colIndex = 0; colIndex < columnsToExport.Count; colIndex++)
{
var column = columnsToExport[colIndex];
string cellValue = column.GetValue(item);
// 补充参与单位数据
if (column.Header == "主办单位") cellValue = hostNames;
else if (column.Header == "承办单位") cellValue = organizerNames;
else if (column.Header == "协办单位") cellValue = coOrganizerNames;
else if (column.Header == "赞助单位") cellValue = sponsorNames;
worksheet.Cells[rowIndex + 2, colIndex + 1].Value = cellValue;
}
}
using var stream = new MemoryStream();
stream.SaveAs(exportData);
return stream.ToArray();
// 自动调整列宽
worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
return await package.GetAsByteArrayAsync();
}
/// <summary>
/// 获取枚举描述
/// </summary>
private string GetEnumDescription(object enumValue)
{
if (enumValue == null) return "";
var type = enumValue.GetType();
if (!type.IsEnum) return enumValue.ToString();
var fieldInfo = type.GetField(enumValue.ToString());
if (fieldInfo == null) return enumValue.ToString();
var descriptionAttribute = (System.ComponentModel.DescriptionAttribute)
System.Attribute.GetCustomAttribute(fieldInfo, typeof(System.ComponentModel.DescriptionAttribute));
return descriptionAttribute != null ? descriptionAttribute.Description : enumValue.ToString();
}
/// <summary>
@ -957,6 +1005,31 @@ public class EventService : BaseService<EventInfo>, ITransient
throw new InvalidOperationException("请先完成项目配置");
}
// 验证非免费模式下的必填参数
if (input.FeeConfig != null && input.FeeConfig.ChargeMode != ChargeMode.Free)
{
// 验证收款商户
if (!input.FeeConfig.MerchantId.HasValue || input.FeeConfig.MerchantId.Value <= 0)
{
throw new InvalidOperationException("收费模式为非免费时,必须选择收款商户");
}
// 验证定价(赛事)和单价(赛事)模式下的价格
if (input.FeeConfig.ChargeMode == ChargeMode.FixedPrice || input.FeeConfig.ChargeMode == ChargeMode.UnitPriceEvent)
{
if (input.FeeConfig.Price <= 0)
{
throw new InvalidOperationException("收费模式为“定价(赛事)”或“单价(赛事)”时,价格必须大于0");
}
// 格式验证:最多保留两位小数
if (Math.Round(input.FeeConfig.Price, 2) != input.FeeConfig.Price)
{
throw new InvalidOperationException("价格格式不正确,最多保留两位小数");
}
}
}
// 验证单价(项目)模式下的项目单价
if (input.FeeConfig != null && input.FeeConfig.ChargeMode == ChargeMode.UnitPriceProject)
{

View File

@ -478,6 +478,20 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
}
}
// 验证3:调用检查报名限制API,确保所有限制都满足
var checkInput = new RegistrationLimitCheckInput
{
EventId = input.EventId,
MemberIds = input.MemberIds ?? new List<long>(),
GroupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>()
};
var checkResult = await CheckRegistrationLimit(checkInput);
if (checkResult.HasLimit)
{
var firstLimit = checkResult.LimitDetails.FirstOrDefault();
throw Oops.Bah(firstLimit?.LimitMessage ?? "报名限制检查失败");
}
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark);
if (registration == null)
@ -493,7 +507,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
ContactName = userName,
ContactPhone = userMobile,
OrderNo = "",
RegistrationMode = 1
RegistrationMode = 2 // PC端只做团队报名
};
await _registrationRepository.InsertAsync(registration);
}
@ -1222,14 +1236,75 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
};
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId);
if (limit != null)
// 获取该赛事的项目级限报配置
var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
// 获取所有赛事项目,用于关联
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId);
// 获取所有基础项目信息(用于获取项目名称)
var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && !x.DeleteMark);
// 获取所有赛事项目组
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
// 检查项目级限报(每个项目下的报名队伍数量)
if (input.GroupIds != null && input.GroupIds.Any())
{
foreach (var groupId in input.GroupIds)
{
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == groupId);
if (eventProjectGroup == null) continue;
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
if (eventProject == null) continue;
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject.ProjectId);
if (projectLimit == null) continue;
// 统计该项目下已报名的队伍数量
var currentTeams = await _registrationItemRepository.AsQueryable()
.Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark)
.Select(x => x.RegistrationId)
.Distinct()
.CountAsync();
if (currentTeams >= projectLimit.LimitCount)
{
var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
var projectName = project?.Name ?? "未知项目";
result.HasLimit = true;
result.LimitDetails.Add(new RegistrationLimitDetail
{
MemberId = 0,
MemberName = eventProjectGroup.GroupName ?? "未知组别",
LimitType = "ProjectLimit",
LimitMessage = $"项目\"{projectName}\"的组别\"{eventProjectGroup.GroupName}\"已报名{currentTeams}队,达到限报{projectLimit.LimitCount}队"
});
}
}
}
// 检查个人项目限报和团队项目限报
if (limit != null && input.MemberIds != null && input.MemberIds.Any())
{
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
// 先查询该赛事的所有报名记录
var eventRegistrations = await _registrationRepository.GetListAsync(x =>
x.EventId == input.EventId &&
x.RegistrationStatus != 4 &&
!x.DeleteMark);
var eventRegistrationIds = eventRegistrations.Select(r => r.Id).ToList();
// 只查询该赛事的报名人员
var registrationPersons = await _registrationPersonRepository.GetListAsync(x =>
eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark);
foreach (var member in members)
{
var registrationPersons = await _registrationPersonRepository.GetListAsync(x =>
!x.DeleteMark);
var matchedPersons = registrationPersons.Where(x => x.IdCard == member.IdCardNumber && !string.IsNullOrEmpty(x.IdCard)).ToList();
var registrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList();
@ -1239,7 +1314,11 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
x.RegistrationStatus != 4 &&
!x.DeleteMark);
if (registrations.Count() >= limit.IndividualProjectLimit)
// 统计该队员已报名的项目数量(不区分个人/团队)
var registeredProjectCount = registrations.Count();
// 检查个人项目限报
if (registeredProjectCount >= limit.IndividualProjectLimit)
{
result.HasLimit = true;
result.LimitDetails.Add(new RegistrationLimitDetail
@ -1247,7 +1326,20 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
MemberId = member.Id,
MemberName = member.Name ?? member.NickName ?? "未知",
LimitType = "IndividualLimit",
LimitMessage = $"该队员已报名{registrations.Count()}个项目,达到个人报名限制{limit.IndividualProjectLimit}个"
LimitMessage = $"该队员已报名{registeredProjectCount}个项目,达到个人报名限制{limit.IndividualProjectLimit}个"
});
}
// 检查团队项目限报(PC端只做团队报名)
if (registeredProjectCount >= limit.TeamProjectLimit)
{
result.HasLimit = true;
result.LimitDetails.Add(new RegistrationLimitDetail
{
MemberId = member.Id,
MemberName = member.Name ?? member.NickName ?? "未知",
LimitType = "TeamLimit",
LimitMessage = $"该队员已报名{registeredProjectCount}个团队项目,达到团队报名限制{limit.TeamProjectLimit}个"
});
}
}
@ -1266,6 +1358,12 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
var result = new List<GroupTeamLimitOutput>();
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark);
// 获取该赛事的项目级限报配置
var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark);
// 获取所有赛事项目,用于关联 ProjectId
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
foreach (var eventProjectGroup in eventProjectGroups)
{
@ -1275,10 +1373,15 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
.Distinct()
.CountAsync();
// 查找该组别的项目限报配置
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject?.ProjectId);
var maxTeams = projectLimit?.LimitCount ?? 2; // 默认2队
result.Add(new GroupTeamLimitOutput
{
GroupId = eventProjectGroup.Id,
MaxTeams = 2,
MaxTeams = maxTeams,
CurrentTeams = currentTeams
});
}

View File

@ -61,15 +61,16 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
[UnitOfWork]
public async Task<string> Create(CreateOrderInput input)
{
// 1. Create Registration
// 1. 创建报名记录
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.OrderNo = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(1000, 9999); // 简单生成
entity.RegistrationStatus = 0; // 待提交
entity.PaymentStatus = 0; // 未支付
entity.RegistrationTime = DateTime.Now;
entity.RegistrationMode = 2; // 后台管理页面只做团队报名
// Calculate Total
// 计算总金额
decimal totalAmount = 0;
int totalPerson = 0;
if (input.Items != null)
@ -85,7 +86,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
await _registrationRepository.InsertAsync(entity);
// 2. Create Items
// 2. 创建报名项目
var createdItems = new List<EventRegistrationItem>();
if (input.Items != null && input.Items.Any())
{
@ -100,7 +101,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
createdItems = items;
}
// 3. Create Persons
// 3. 创建报名人员
if (input.Persons != null && input.Persons.Any())
{
var persons = input.Persons.Adapt<List<EventRegistrationPerson>>();
@ -109,20 +110,20 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
x.Id = _idGenerator.NextId();
x.RegistrationId = entity.Id;
// Link to Item based on ProjectId and GroupName
// 根据项目ID和组别名称关联到报名项目
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)
// 4. 创建订单(收入)
// 获取赛事关联的商户ID
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
@ -134,17 +135,17 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
{
Id = _idGenerator.NextId(),
OrderNo = entity.OrderNo,
OrderType = 1, // Income
PaymentMethod = 1, // Default Online? Or unknown yet.
RegistrationMode = input.RegistrationMode,
OrderType = 1, // 收入
PaymentMethod = 1, // 默认线上支付
RegistrationMode = 2, // 后台管理页面只做团队报名
MerchantId = eventInfo?.MerchantId, // 设置商户ID(支付商户)
MerchantCertificationId = certification?.Id, // 设置单位认证ID
EventId = input.EventId,
RegistrationId = entity.Id,
OriginalAmount = totalAmount,
ActualAmount = totalAmount, // Before discount
ActualAmount = totalAmount, // 折扣前金额
DiscountAmount = 0,
OrderStatus = OrderStatus.Pending, // Pending
OrderStatus = OrderStatus.Pending, // 待支付
CreatedTime = DateTime.Now,
CreatedUserId = entity.CreatedUserId
};
@ -169,7 +170,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
.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
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) // 自连接获取关联订单编号
// 默认只显示已完成支付流程的订单(已支付、已发货、已完成、已退款)
.WhereIF(!input.OrderStatus.HasValue, (o, e, m, r, ro) =>
o.OrderStatus == OrderStatus.Paid ||
@ -246,10 +247,10 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
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
// 将项目和人员转换为DTO
var itemDtos = items.Select(x => new OrderItemDto
{
ProjectName = x.ProjectName, // Assume stored or join
ProjectName = x.ProjectName, // 假设已存储或通过关联获取
GroupName = x.GroupName,
Fee = x.TotalPrice
}).ToList();
@ -290,15 +291,15 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
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
// 更新订单
order.OrderStatus = input.PaymentStatus == 1 ? OrderStatus.Paid : OrderStatus.Pending; // 假设1表示支付成功
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)
{
@ -337,34 +338,34 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
// 1. Create Refund Order
// 1. 创建退款订单
var refundOrder = new YD_Event.Core.Entities.Order
{
Id = _idGenerator.NextId(),
OrderNo = "REF" + DateTime.Now.ToString("yyyyMMddHHmmss"),
OrderType = 2, // Refund
OrderType = 2, // 退款
PaymentMethod = order.PaymentMethod,
RegistrationMode = order.RegistrationMode,
MerchantId = order.MerchantId,
MerchantCertificationId = order.MerchantCertificationId, // 继承原订单的单位认证ID
EventId = order.EventId,
RegistrationId = order.RegistrationId,
RelatedOrderId = order.Id, // Link to original
RelatedOrderId = order.Id, // 关联原订单
OriginalAmount = order.ActualAmount,
ActualAmount = input.RefundAmount, // Refund Amount
OrderStatus = OrderStatus.Refunded, // Success (Refunded)
ActualAmount = input.RefundAmount, // 退款金额
OrderStatus = OrderStatus.Refunded, // 退款成功
PaymentTime = DateTime.Now,
Remark = input.Reason,
CreatedTime = DateTime.Now,
CreatedUserId = _idGenerator.NewLong() // System or current user
CreatedUserId = _idGenerator.NewLong() // 系统或当前用户
};
await _orderRepository.InsertAsync(refundOrder);
// 2. Update Registration Status
// 2. 更新报名状态
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
if (reg != null)
{
reg.PaymentStatus = 3; // Refunded
reg.PaymentStatus = 3; // 已退款
await _registrationRepository.UpdateAsync(reg);
}
}

View File

@ -3541,6 +3541,11 @@
导出字段列表(如:赛事名称,赛事类型...)
</summary>
</member>
<member name="T:YD_Event.Application.Event.Dtos.EventExportData">
<summary>
赛事导出数据
</summary>
</member>
<member name="T:YD_Event.Application.Event.Dtos.EventListOutput">
<summary>
赛事列表输出
@ -3911,6 +3916,11 @@
银行账号 (显示字段)
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventFeeConfigInput.BusinessLicenseUrl">
<summary>
营业执照图片URL (显示字段)
</summary>
</member>
<member name="T:YD_Event.Application.Event.Dtos.UpdateEventInput">
<summary>
更新赛事输入
@ -6272,6 +6282,11 @@
<param name="id"></param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.Event.EventService.ValidateEventTime(System.DateTime,System.DateTime,System.DateTime,System.DateTime)">
<summary>
验证赛事时间逻辑
</summary>
</member>
<member name="M:YD_Event.Application.Event.EventService.Add(YD_Event.Application.Event.Dtos.AddEventInput)">
<summary>
新增赛事
@ -6293,6 +6308,11 @@
<param name="input"></param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.Event.EventService.GetEnumDescription(System.Object)">
<summary>
获取枚举描述
</summary>
</member>
<member name="M:YD_Event.Application.Event.EventService.Delete(YD_Event.Core.Shared.KeyDto)">
<summary>
删除赛事