From 239fb777f35ab10c1fc51243574ddc8cb1959d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Tue, 7 Apr 2026 09:04:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=9B=E4=BA=8B=E7=AE=A1=E7=90=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YD_Event.Application/Event/Dtos/EventDtos.cs | 25 +++ .../Event/EventArrangementService.cs | 19 +- YD_Event.Application/Event/EventService.cs | 203 ++++++++++++------ .../Event/UserEventRegistrationService.cs | 119 +++++++++- YD_Event.Application/Order/OrderService.cs | 59 ++--- YD_Event.Application/YD_Event.Application.xml | 20 ++ 6 files changed, 337 insertions(+), 108 deletions(-) diff --git a/YD_Event.Application/Event/Dtos/EventDtos.cs b/YD_Event.Application/Event/Dtos/EventDtos.cs index 1b60bb2..5fc8c93 100644 --- a/YD_Event.Application/Event/Dtos/EventDtos.cs +++ b/YD_Event.Application/Event/Dtos/EventDtos.cs @@ -61,6 +61,26 @@ public class EventExportInput : EventQueryInput public List? ExportFields { get; set; } } +/// +/// 赛事导出数据 +/// +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; } +} + /// /// 赛事列表输出 /// @@ -448,6 +468,11 @@ public class EventFeeConfigInput /// 银行账号 (显示字段) /// public string? AccountNumber { get; set; } + + /// + /// 营业执照图片URL (显示字段) + /// + public string? BusinessLicenseUrl { get; set; } } /// diff --git a/YD_Event.Application/Event/EventArrangementService.cs b/YD_Event.Application/Event/EventArrangementService.cs index 39b1aa2..73c79f8 100644 --- a/YD_Event.Application/Event/EventArrangementService.cs +++ b/YD_Event.Application/Event/EventArrangementService.cs @@ -57,12 +57,16 @@ public class EventArrangementService : BaseService x.ProjectTypeId).Distinct().ToList(); var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id)); - // 统计各组别报名人数(仅统计已支付或审核通过的) + // 统计各组别报名人数(统计所有非取消状态的报名,并且只统计运动员 var counts = await _personRepository.AsQueryable() .InnerJoin((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(); @@ -155,10 +159,13 @@ public class EventArrangementService : BaseService 0 ? proj.VenueCount : 1; // 获取参赛人员(排序:组别 -> ID) - // 放宽查询条件,只要不是取消状态的报名都包含进来 + // 查询条件:不是取消状态的报名,并且只统计运动员 var persons = await _personRepository.AsQueryable() .InnerJoin((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 { diff --git a/YD_Event.Application/Event/EventService.cs b/YD_Event.Application/Event/EventService.cs index 9b8027f..aba3f6b 100644 --- a/YD_Event.Application/Event/EventService.cs +++ b/YD_Event.Application/Event/EventService.cs @@ -156,9 +156,6 @@ public class EventService : BaseService, ITransient output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt()).ToList(); } - // 2. 自定义字段配置 (JSON) - // output.CustomConfigJson 已自动映射 - // 3. 获取区域名称和Code if (output.ProvinceId > 0) { @@ -179,24 +176,6 @@ public class EventService : BaseService, 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, ITransient if (feeConfig != null) { output.FeeConfig = feeConfig.Adapt(); - // 获取商户详情(使用 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, 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, 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, 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, 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, ITransient var eventIds = list.Select(x => x.Id).ToList(); var units = await _unitRepository.GetListAsync(x => eventIds.Contains(x.EventId)); - var exportData = new List>(); - 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 GetValue)> { - var row = new Dictionary(); - var fields = input.ExportFields ?? new List(); - 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(); + 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(); + } + + /// + /// 获取枚举描述 + /// + 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(); } /// @@ -957,6 +1005,31 @@ public class EventService : BaseService, 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) { diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs index fd0577e..4d609d1 100644 --- a/YD_Event.Application/Event/UserEventRegistrationService.cs +++ b/YD_Event.Application/Event/UserEventRegistrationService.cs @@ -478,6 +478,20 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient } } + // 验证3:调用检查报名限制API,确保所有限制都满足 + var checkInput = new RegistrationLimitCheckInput + { + EventId = input.EventId, + MemberIds = input.MemberIds ?? new List(), + GroupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List() + }; + 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(); 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 }); } diff --git a/YD_Event.Application/Order/OrderService.cs b/YD_Event.Application/Order/OrderService.cs index 5d0ffe2..e23aa66 100644 --- a/YD_Event.Application/Order/OrderService.cs +++ b/YD_Event.Application/Order/OrderService.cs @@ -61,15 +61,16 @@ public class OrderService : BaseService, ITransien [UnitOfWork] public async Task Create(CreateOrderInput input) { - // 1. Create Registration + // 1. 创建报名记录 var entity = input.Adapt(); 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, ITransien await _registrationRepository.InsertAsync(entity); - // 2. Create Items + // 2. 创建报名项目 var createdItems = new List(); if (input.Items != null && input.Items.Any()) { @@ -100,7 +101,7 @@ public class OrderService : BaseService, ITransien createdItems = items; } - // 3. Create Persons + // 3. 创建报名人员 if (input.Persons != null && input.Persons.Any()) { var persons = input.Persons.Adapt>(); @@ -109,20 +110,20 @@ public class OrderService : BaseService, 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, 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, ITransien .LeftJoin((o, e) => o.EventId == e.Id) .LeftJoin((o, e, m) => o.MerchantId == m.Id) .LeftJoin((o, e, m, r) => o.RegistrationId == r.Id) - .LeftJoin((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) // Join self for Related Order No + .LeftJoin((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, 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, 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, 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); } } diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index 9740918..3e3e2f7 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -3541,6 +3541,11 @@ 导出字段列表(如:赛事名称,赛事类型...) + + + 赛事导出数据 + + 赛事列表输出 @@ -3911,6 +3916,11 @@ 银行账号 (显示字段) + + + 营业执照图片URL (显示字段) + + 更新赛事输入 @@ -6272,6 +6282,11 @@ + + + 验证赛事时间逻辑 + + 新增赛事 @@ -6293,6 +6308,11 @@ + + + 获取枚举描述 + + 删除赛事