diff --git a/YD_Event.Application/Event/Dtos/EventRecordDtos.cs b/YD_Event.Application/Event/Dtos/EventRecordDtos.cs
index 84b140d..ee4a915 100644
--- a/YD_Event.Application/Event/Dtos/EventRecordDtos.cs
+++ b/YD_Event.Application/Event/Dtos/EventRecordDtos.cs
@@ -452,6 +452,11 @@ public class CancelPreviewOutput
/// 是否需要填写收款人信息(线下支付且已支付时需要)
///
public bool NeedPayeeInfo { get; set; }
+
+ ///
+ /// 是否允许部分取消(多团队合并报名仅允许整单取消)
+ ///
+ public bool AllowPartialCancel { get; set; } = true;
}
///
@@ -827,6 +832,21 @@ public class CancelExecuteOutput
///
public class RegistrationItemWithMembersDto
{
+ ///
+ /// 来源团队ID
+ ///
+ public long? TeamId { get; set; }
+
+ ///
+ /// 来源团队名称
+ ///
+ public string? TeamName { get; set; }
+
+ ///
+ /// 队伍序号
+ ///
+ public int? TeamIndex { get; set; }
+
///
/// 报名项目ID
///
diff --git a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
index c36303d..1d17b77 100644
--- a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
+++ b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
@@ -352,6 +352,21 @@ public class UserEventProjectGroupOutput
///
public class UserEventProjectListOutput
{
+ ///
+ /// 来源团队ID(报名预览时返回)
+ ///
+ public long? TeamId { get; set; }
+
+ ///
+ /// 来源团队名称(报名预览时返回)
+ ///
+ public string? TeamName { get; set; }
+
+ ///
+ /// 队伍序号(报名预览时返回)
+ ///
+ public int? TeamIndex { get; set; }
+
///
/// 项目ID
///
@@ -743,10 +758,9 @@ public class UserSubmitRegistrationInput
public long? CouponId { get; set; }
///
- /// 团队ID (必填)
+ /// 团队ID(旧版单团队结构,保留兼容)
///
- [Required]
- public long TeamId { get; set; }
+ public long? TeamId { get; set; }
}
#region 参赛记录相关 DTOs
@@ -1389,6 +1403,11 @@ public class EventLimitDetail
///
public class GroupTeamAssignment
{
+ ///
+ /// 来源团队ID
+ ///
+ public long? TeamId { get; set; }
+
///
/// 团队序号(例如:1, 2, 3)
///
diff --git a/YD_Event.Application/Event/EventArrangementService.cs b/YD_Event.Application/Event/EventArrangementService.cs
index 08d6954..7895dc5 100644
--- a/YD_Event.Application/Event/EventArrangementService.cs
+++ b/YD_Event.Application/Event/EventArrangementService.cs
@@ -137,18 +137,32 @@ namespace YD_Event.Application.Event;
// 获取所有相关的人员和报名记录(用于统计团队数)
var personWithTeam = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
- .Where((p, r) => r.EventId == eventId
+ .InnerJoin((p, r, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, item) => r.EventId == eventId
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)))
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete
- && r.TeamName != null)
- .Select((p, r) => new { p.ProjectId, p.GroupName, r.TeamName, p.RegistrationItemId })
+ && (item.TeamName != null || r.TeamName != null))
+ .Select((p, r, item) => new
+ {
+ p.ProjectId,
+ p.GroupName,
+ ItemTeamName = item.TeamName,
+ RegistrationTeamName = r.TeamName,
+ p.RegistrationItemId
+ })
.ToListAsync();
// 在内存中统计团队数(增加 RegistrationItemId 作为分组条件,与 Preview 方法保持一致)
var teamCounts = personWithTeam
- .GroupBy(x => new { x.ProjectId, x.GroupName, x.TeamName, x.RegistrationItemId })
+ .GroupBy(x => new
+ {
+ x.ProjectId,
+ x.GroupName,
+ TeamName = x.ItemTeamName ?? x.RegistrationTeamName,
+ x.RegistrationItemId
+ })
.GroupBy(g => new { g.Key.ProjectId, g.Key.GroupName })
.Select(g => new { g.Key.ProjectId, g.Key.GroupName, Count = g.Count() })
.ToList();
@@ -325,17 +339,31 @@ namespace YD_Event.Application.Event;
// 获取团队数
var personWithTeam = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
- .Where((p, r) => r.EventId == eventId
+ .InnerJoin((p, r, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, item) => r.EventId == eventId
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)))
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete
- && r.TeamName != null)
- .Select((p, r) => new { p.ProjectId, p.GroupName, r.TeamName, p.RegistrationItemId })
+ && (item.TeamName != null || r.TeamName != null))
+ .Select((p, r, item) => new
+ {
+ p.ProjectId,
+ p.GroupName,
+ ItemTeamName = item.TeamName,
+ RegistrationTeamName = r.TeamName,
+ p.RegistrationItemId
+ })
.ToListAsync();
var teamCounts = personWithTeam
- .GroupBy(x => new { x.ProjectId, x.GroupName, x.TeamName, x.RegistrationItemId })
+ .GroupBy(x => new
+ {
+ x.ProjectId,
+ x.GroupName,
+ TeamName = x.ItemTeamName ?? x.RegistrationTeamName,
+ x.RegistrationItemId
+ })
.GroupBy(g => new { g.Key.ProjectId, g.Key.GroupName })
.Select(g => new { g.Key.ProjectId, g.Key.GroupName, Count = g.Count() })
.ToList();
@@ -357,19 +385,21 @@ namespace YD_Event.Application.Event;
// 第一步:先查询主要数据和相关的 MerchantCertificationId
var basicPersons = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
- .Where((p, r) => p.ProjectId == eventProj.Id
+ .InnerJoin((p, r, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, item) => p.ProjectId == eventProj.Id
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)))
&& !p.DeleteMark
&& p.PersonType == PersonType.Athlete)
- .Select((p, r) => new {
+ .Select((p, r, item) => new {
Id = p.Id,
RegistrationItemId = p.RegistrationItemId,
p.UserId,
p.Name,
p.GroupName,
p.EventProjectGroupId,
- r.TeamName,
+ ItemTeamName = item.TeamName,
+ RegistrationTeamName = r.TeamName,
r.MerchantCertificationId,
Duration = projTotalDuration
})
@@ -398,7 +428,7 @@ namespace YD_Event.Application.Event;
bp.Name,
bp.GroupName,
bp.EventProjectGroupId,
- bp.TeamName,
+ TeamName = bp.ItemTeamName ?? bp.RegistrationTeamName,
MerchantName = bp.MerchantCertificationId.HasValue && merchantDict.ContainsKey(bp.MerchantCertificationId.Value)
? merchantDict[bp.MerchantCertificationId.Value].MerchantName
: null,
diff --git a/YD_Event.Application/Event/EventRegistrationService.cs b/YD_Event.Application/Event/EventRegistrationService.cs
index 5564870..1e651ca 100644
--- a/YD_Event.Application/Event/EventRegistrationService.cs
+++ b/YD_Event.Application/Event/EventRegistrationService.cs
@@ -1019,18 +1019,19 @@ public class EventRegistrationService : BaseService, ITransie
// 统计符合条件的所有运动员(与赛事编排页面保持一致的筛选规则)
var athletes = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
- .Where((p, r) => r.EventId == eventId
+ .InnerJoin((p, r, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, item) => r.EventId == eventId
&& !r.DeleteMark // 排除软删除的报名记录
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)))
&& !p.DeleteMark // 排除已删除的人员
&& p.PersonType == PersonType.Athlete) // 只统计运动员
- .Select((p, r) => new
+ .Select((p, r, item) => new
{
PersonId = p.Id,
EventProjectId = p.ProjectId,
GroupName = p.GroupName,
- TeamName = r.TeamName,
+ TeamName = item.TeamName ?? r.TeamName,
RegistrationItemId = p.RegistrationItemId,
UserId = p.UserId,
RegistrationId = r.Id,
diff --git a/YD_Event.Application/Event/EventScoreService.cs b/YD_Event.Application/Event/EventScoreService.cs
index 11e34a8..abb37b7 100644
--- a/YD_Event.Application/Event/EventScoreService.cs
+++ b/YD_Event.Application/Event/EventScoreService.cs
@@ -133,7 +133,8 @@ public class EventScoreService : BaseService, ITransient
.LeftJoin((p, r) => p.RegistrationId == r.Id)
.LeftJoin((p, r, res) => p.Id == res.RegistrationPersonId)
.LeftJoin((p, r, res, c) => res.Id == c.ResultId)
- .Where((p, r, res, c) => r.EventId == input.EventId
+ .LeftJoin((p, r, res, c, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, res, c, item) => r.EventId == input.EventId
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)))
&& (p.PersonType == PersonType.Athlete || p.PersonType == PersonType.User));
@@ -141,31 +142,32 @@ public class EventScoreService : BaseService, ITransient
// 应用筛选条件
if (input.ProjectId.HasValue)
{
- baseQuery = baseQuery.Where((p, r, res, c) => p.ProjectId == input.ProjectId.Value);
+ baseQuery = baseQuery.Where((p, r, res, c, item) => p.ProjectId == input.ProjectId.Value);
}
if (input.EventProjectGroupId.HasValue)
{
- baseQuery = baseQuery.Where((p, r, res, c) => p.EventProjectGroupId == input.EventProjectGroupId.Value);
+ baseQuery = baseQuery.Where((p, r, res, c, item) => p.EventProjectGroupId == input.EventProjectGroupId.Value);
}
else if (!string.IsNullOrWhiteSpace(input.GroupName))
{
- baseQuery = baseQuery.Where((p, r, res, c) => p.GroupName == input.GroupName);
+ baseQuery = baseQuery.Where((p, r, res, c, item) => p.GroupName == input.GroupName);
}
if (!string.IsNullOrWhiteSpace(input.Name))
{
- baseQuery = baseQuery.Where((p, r, res, c) => p.Name.Contains(input.Name));
+ baseQuery = baseQuery.Where((p, r, res, c, item) => p.Name.Contains(input.Name));
}
if (!string.IsNullOrWhiteSpace(input.UnitName))
{
- baseQuery = baseQuery.Where((p, r, res, c) => r.TeamName.Contains(input.UnitName));
+ baseQuery = baseQuery.Where((p, r, res, c, item) =>
+ (item.TeamName ?? r.TeamName ?? string.Empty).Contains(input.UnitName));
}
if (input.Gender.HasValue)
{
- baseQuery = baseQuery.Where((p, r, res, c) => p.Gender == input.Gender.Value);
+ baseQuery = baseQuery.Where((p, r, res, c, item) => p.Gender == input.Gender.Value);
}
// 步骤1.5:获取所有相关的单位认证信息
@@ -193,8 +195,8 @@ public class EventScoreService : BaseService, ITransient
// 查询基本数据到内存
var baseData = await baseQuery
- .OrderBy((p, r, res, c) => res.Rank)
- .Select((p, r, res, c) => new
+ .OrderBy((p, r, res, c, item) => res.Rank)
+ .Select((p, r, res, c, item) => new
{
Id = p.Id,
Name = p.Name,
@@ -208,7 +210,7 @@ public class EventScoreService : BaseService, ITransient
CertificateId = c.Id,
EventProjectId = p.ProjectId, // 注意:这里 p.ProjectId 实际上是 EventProject 的 Id
GroupName = p.GroupName ?? string.Empty,
- TeamName = r.TeamName ?? string.Empty,
+ TeamName = item.TeamName ?? r.TeamName ?? string.Empty,
RegistrationItemId = p.RegistrationItemId // 增加报名项目唯一码,用于分组
})
.ToListAsync();
@@ -557,10 +559,11 @@ public class EventScoreService : BaseService, ITransient
// 步骤2:批量预加载该赛事的所有人员数据(包含团队信息)
var allPersonsWithTeam = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
- .Where((p, r) => r.EventId == eventId
+ .InnerJoin((p, r, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, item) => r.EventId == eventId
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1))))
- .Select((p, r) => new {
+ .Select((p, r, item) => new {
p.Id,
p.RegistrationItemId,
p.Name,
@@ -568,7 +571,7 @@ public class EventScoreService : BaseService, ITransient
p.GroupName,
p.RegistrationId,
p.ProjectId,
- r.TeamName
+ TeamName = item.TeamName ?? r.TeamName
})
.ToListAsync();
@@ -833,8 +836,9 @@ public class EventScoreService : BaseService, ITransient
// 步骤3:批量预加载该赛事的所有人员数据(包含团队信息)
var allPersonsWithTeam = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
- .Where((p, r) => r.EventId == input.EventId && !p.DeleteMark)
- .Select((p, r) => new {
+ .InnerJoin((p, r, item) => p.RegistrationItemId == item.Id)
+ .Where((p, r, item) => r.EventId == input.EventId && !p.DeleteMark)
+ .Select((p, r, item) => new {
p.Id,
p.PersonType,
p.UserId,
@@ -846,7 +850,7 @@ public class EventScoreService : BaseService, ITransient
p.RegistrationItemId,
p.EventProjectGroupId,
p.DeleteMark,
- r.TeamName,
+ TeamName = item.TeamName ?? r.TeamName,
r.MerchantCertificationId,
Person = p
})
diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs
index 306f28c..f37cae7 100644
--- a/YD_Event.Application/Event/UnifiedRegistrationService.cs
+++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs
@@ -654,6 +654,50 @@ public class UnifiedRegistrationService : ITransient
return persons;
}
+ ///
+ /// 根据已保存的报名明细重建统一报名结构,用于支付确认前再次校验报名限制。
+ /// 同一项目组别的多个报名明细会还原为多个参赛队伍,避免少算队伍数量。
+ ///
+ private static List BuildUnifiedItemsFromStoredDetails(
+ IEnumerable items,
+ IEnumerable persons,
+ string? fallbackTeamName)
+ {
+ var activePersons = persons.Where(person => !person.DeleteMark).ToList();
+
+ return items
+ .Where(item => !item.DeleteMark)
+ .GroupBy(item => new { item.ProjectId, item.EventProjectGroupId })
+ .Select(itemGroup => new UnifiedRegistrationItemInput
+ {
+ ProjectId = itemGroup.Key.ProjectId,
+ GroupId = itemGroup.Key.EventProjectGroupId,
+ Teams = itemGroup
+ .OrderBy(item => item.TeamIndex ?? int.MaxValue)
+ .ThenBy(item => item.Id)
+ .Select((item, index) => new UnifiedTeamInput
+ {
+ TeamId = item.TeamId,
+ TeamName = item.TeamName ?? fallbackTeamName,
+ TeamIndex = item.TeamIndex.GetValueOrDefault(index + 1),
+ Persons = activePersons
+ .Where(person => person.RegistrationItemId == item.Id)
+ .Select(person => new UnifiedPersonInput
+ {
+ UserId = person.UserId,
+ Name = person.Name,
+ Gender = person.Gender,
+ IdCardNumber = person.IdCard,
+ BirthDate = person.BirthDate,
+ Mobile = person.Phone
+ })
+ .ToList()
+ })
+ .ToList()
+ })
+ .ToList();
+ }
+
///
/// 获取人员证件号码(根据认证类型返回身份证号或护照号)
///
@@ -824,6 +868,11 @@ public class UnifiedRegistrationService : ITransient
RegistrationId = registration.Id,
ProjectId = item.ProjectId,
EventProjectGroupId = item.GroupId,
+ TeamId = team.TeamId,
+ TeamName = team.TeamName,
+ TeamIndex = baseProject?.IsTeamProject() == true
+ ? (team.TeamIndex > 0 ? team.TeamIndex : 1)
+ : null,
ProjectName = baseProject?.Name ?? "",
GroupName = groupName,
ProjectTypeName = baseProject?.ProjectType.Description() ?? "",
@@ -1275,24 +1324,7 @@ public class UnifiedRegistrationService : ITransient
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registration.Id);
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id);
- unifiedInput.Items = items.Select(item =>
- {
- var itemPersons = persons.Where(p => p.RegistrationItemId == item.Id).ToList();
- return new UnifiedRegistrationItemInput
- {
- ProjectId = item.ProjectId,
- GroupId = item.EventProjectGroupId,
- Persons = itemPersons.Select(p => new UnifiedPersonInput
- {
- UserId = p.UserId,
- Name = p.Name,
- Gender = p.Gender,
- IdCardNumber = p.IdCard,
- BirthDate = p.BirthDate,
- Mobile = p.Phone
- }).ToList()
- };
- }).ToList();
+ unifiedInput.Items = BuildUnifiedItemsFromStoredDetails(items, persons, registration.TeamName);
var limitCheck = await CheckRegistrationLimitAsync(unifiedInput, registration.Id);
if (limitCheck.HasLimit)
@@ -2492,24 +2524,7 @@ public class UnifiedRegistrationService : ITransient
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == reg.Id);
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == reg.Id);
- unifiedInput.Items = items.Select(item =>
- {
- var itemPersons = persons.Where(p => p.RegistrationItemId == item.Id).ToList();
- return new UnifiedRegistrationItemInput
- {
- ProjectId = item.ProjectId,
- GroupId = item.EventProjectGroupId,
- Persons = itemPersons.Select(p => new UnifiedPersonInput
- {
- UserId = p.UserId,
- Name = p.Name,
- Gender = p.Gender,
- IdCardNumber = p.IdCard,
- BirthDate = p.BirthDate,
- Mobile = p.Phone
- }).ToList()
- };
- }).ToList();
+ unifiedInput.Items = BuildUnifiedItemsFromStoredDetails(items, persons, reg.TeamName);
var limitCheck = await CheckRegistrationLimitAsync(unifiedInput, reg.Id);
if (limitCheck.HasLimit)
@@ -2991,6 +3006,20 @@ public class UnifiedRegistrationService : ITransient
return roundedAmount;
}
+ ///
+ /// 判断报名单是否包含多个来源团队。
+ /// 来源团队与同一项目下的一队、二队不是同一概念,以报名项的 TeamId 为准。
+ ///
+ private static bool IsMultiSourceTeamRegistration(IEnumerable items)
+ {
+ return items
+ .Where(x => !x.DeleteMark && x.TeamId.HasValue)
+ .Select(x => x.TeamId!.Value)
+ .Distinct()
+ .Take(2)
+ .Count() > 1;
+ }
+
public async Task CalculateRefundAmountAsync(decimal totalAmount, DateTime eventStartTime)
{
var refundAmount = await _refundConfigService.CalculateRefundAmount(totalAmount, eventStartTime);
@@ -3117,7 +3146,8 @@ public class UnifiedRegistrationService : ITransient
CurrentRefundAmount = refundableAmount,
PaymentMethod = registration.PaymentMethod,
PaymentStatus = registration.PaymentStatus,
- NeedPayeeInfo = registration.PaymentMethod == 1 && registration.PaymentStatus == 1
+ NeedPayeeInfo = registration.PaymentMethod == 1 && registration.PaymentStatus == 1,
+ AllowPartialCancel = !IsMultiSourceTeamRegistration(items)
};
foreach (var item in items.Where(x => !x.DeleteMark))
@@ -3189,7 +3219,7 @@ public class UnifiedRegistrationService : ITransient
};
// 处理该项目组别下的所有报名项,每个报名项作为一个独立的团队
- int teamIndex = 0;
+ int fallbackTeamIndex = 1;
foreach (var item in groupGroup)
{
// 获取该报名项目下的所有队员 - 只获取未被软删除的
@@ -3198,10 +3228,11 @@ public class UnifiedRegistrationService : ITransient
// 每个报名项作为一个独立的团队条目
var teamDto = new CancelPreviewTeamDto
{
- TeamName = "",
- TeamIndex = teamIndex++,
+ TeamName = item.TeamName ?? registration.TeamName ?? "",
+ TeamIndex = item.TeamIndex ?? fallbackTeamIndex,
RegistrationItemId = item.Id
};
+ fallbackTeamIndex++;
foreach (var person in itemPersons)
{
@@ -3279,6 +3310,11 @@ public class UnifiedRegistrationService : ITransient
var items = await _db.Queryable().Where(x => x.RegistrationId == input.RegistrationId).ToListAsync();
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == input.RegistrationId);
+ if (input.CancelType == 1 && IsMultiSourceTeamRegistration(items))
+ {
+ throw Oops.Bah("多团队报名仅支持整单取消");
+ }
+
// 查询订单获取实际支付金额(考虑优惠券抵扣)
var order = await _orderRepository.GetFirstAsync(x => x.RegistrationId == input.RegistrationId && x.OrderType == 1 && !x.DeleteMark);
var actualPaidAmount = order?.ActualAmount ?? registration.TotalAmount;
@@ -3655,6 +3691,11 @@ public class UnifiedRegistrationService : ITransient
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registration.Id);
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id);
+ if (input.CancelType == 1 && IsMultiSourceTeamRegistration(items))
+ {
+ throw Oops.Bah("多团队报名仅支持整单取消");
+ }
+
// 计算实际退款金额
// 【重要】始终信任后端计算结果,前端计算只用于展示
// 不接受前端传递的退款金额,避免资金风险
@@ -4130,6 +4171,12 @@ public class UnifiedRegistrationService : ITransient
{
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
+
+ if (IsMultiSourceTeamRegistration(items))
+ {
+ throw Oops.Bah("多团队报名仅支持整单取消");
+ }
+
var totalPersons = persons.Count;
if (totalPersons == 0)
{
diff --git a/YD_Event.Application/Event/UserEventRecordService.cs b/YD_Event.Application/Event/UserEventRecordService.cs
index 8c27dde..4b66e03 100644
--- a/YD_Event.Application/Event/UserEventRecordService.cs
+++ b/YD_Event.Application/Event/UserEventRecordService.cs
@@ -713,6 +713,9 @@ public class UserEventRecordService : IDynamicApiController, ITransient
result.Items.Add(new RegistrationItemWithMembersDto
{
+ TeamId = item.TeamId,
+ TeamName = item.TeamName ?? registration.TeamName,
+ TeamIndex = item.TeamIndex,
RegistrationItemId = item.Id,
ProjectId = item.ProjectId,
EventProjectGroupId = item.EventProjectGroupId ?? 0,
@@ -775,6 +778,11 @@ public class UserEventRecordService : IDynamicApiController, ITransient
// 获取报名项目信息
var registrationItems = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == input.RegistrationId);
+ if (registrationItems.Where(item => item.TeamId.HasValue).Select(item => item.TeamId).Distinct().Count() > 1)
+ {
+ throw Oops.Bah("多团队报名不支持单独调整运动员,请取消整单后重新报名");
+ }
+
var eventProjectIds = registrationItems.Select(x => x.ProjectId).Distinct().ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId));
diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs
index 254eb88..7405e92 100644
--- a/YD_Event.Application/Event/UserEventRegistrationService.cs
+++ b/YD_Event.Application/Event/UserEventRegistrationService.cs
@@ -507,6 +507,121 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
return idCard.Substring(0, 6) + "********" + idCard.Substring(idCard.Length - 4);
}
+ private async Task> GetAccessibleTeamsAsync(
+ long userId,
+ IEnumerable teamIds)
+ {
+ var distinctTeamIds = teamIds.Where(id => id > 0).Distinct().ToList();
+ if (!distinctTeamIds.Any())
+ {
+ throw Oops.Bah("请至少选择一个团队");
+ }
+
+ var teams = await _db.Queryable()
+ .Where(team => distinctTeamIds.Contains(team.Id) && !team.DeleteMark)
+ .ToListAsync();
+ if (teams.Count != distinctTeamIds.Count)
+ {
+ throw Oops.Bah("部分团队不存在或已被删除,请刷新后重试");
+ }
+
+ var certification = await _db.Queryable()
+ .FirstAsync(item => item.UserId == userId && !item.DeleteMark);
+
+ var hasUnauthorizedTeam = certification != null
+ ? teams.Any(team => team.MerchantCertificationId != certification.Id)
+ : teams.Any(team => team.CreatedUserId != userId || team.MerchantCertificationId != null);
+ if (hasUnauthorizedTeam)
+ {
+ throw Oops.Bah("无权使用所选团队报名");
+ }
+
+ return teams.ToDictionary(team => team.Id);
+ }
+
+ private async Task ValidateTeamMemberAssignmentsAsync(
+ IEnumerable<(long TeamId, List MemberIds)> assignments)
+ {
+ var normalizedAssignments = assignments
+ .Select(assignment => (
+ assignment.TeamId,
+ MemberIds: assignment.MemberIds.Where(id => id > 0).Distinct().ToList()))
+ .ToList();
+ var allMemberIds = normalizedAssignments.SelectMany(assignment => assignment.MemberIds).Distinct().ToList();
+ if (!allMemberIds.Any())
+ {
+ return;
+ }
+
+ var conflictingMember = normalizedAssignments
+ .SelectMany(assignment => assignment.MemberIds.Select(memberId => new { assignment.TeamId, MemberId = memberId }))
+ .GroupBy(item => item.MemberId)
+ .FirstOrDefault(group => group.Select(item => item.TeamId).Distinct().Count() > 1);
+ if (conflictingMember != null)
+ {
+ throw Oops.Bah("同一名运动员不能代表多个团队报名同一场赛事");
+ }
+
+ var teamIds = normalizedAssignments.Select(assignment => assignment.TeamId).Distinct().ToList();
+ var teamMembers = await _db.Queryable()
+ .Where(member => teamIds.Contains(member.TeamId)
+ && allMemberIds.Contains(member.UserId)
+ && member.PersonType == PersonType.Athlete
+ && !member.DeleteMark)
+ .ToListAsync();
+ var teamMemberKeys = teamMembers
+ .Select(member => (member.TeamId, member.UserId))
+ .ToHashSet();
+
+ var hasInvalidMember = normalizedAssignments.Any(assignment =>
+ assignment.MemberIds.Any(memberId => !teamMemberKeys.Contains((assignment.TeamId, memberId))));
+ if (hasInvalidMember)
+ {
+ throw Oops.Bah("部分运动员不属于对应团队,请刷新团队成员后重试");
+ }
+ }
+
+ private static string BuildTeamNameSummary(IReadOnlyCollection teams)
+ {
+ var teamNames = teams.Select(team => team.Name).Where(name => !string.IsNullOrWhiteSpace(name)).Distinct().ToList();
+ return teamNames.Count switch
+ {
+ 0 => "",
+ 1 => teamNames[0],
+ _ => $"{teamNames[0]}等{teamNames.Count}支队伍"
+ };
+ }
+
+ private static void ValidateTeamAssignmentStructure(
+ IEnumerable assignments,
+ long? fallbackTeamId)
+ {
+ var normalizedAssignments = assignments
+ .Select(assignment => new
+ {
+ TeamId = assignment.TeamId ?? fallbackTeamId,
+ assignment.TeamIndex
+ })
+ .ToList();
+
+ if (normalizedAssignments.Any(assignment => !assignment.TeamId.HasValue || assignment.TeamId.Value <= 0))
+ {
+ throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
+ }
+
+ if (normalizedAssignments.Any(assignment => assignment.TeamIndex <= 0))
+ {
+ throw Oops.Bah("队伍序号无效,请刷新后重试");
+ }
+
+ if (normalizedAssignments
+ .GroupBy(assignment => new { assignment.TeamId, assignment.TeamIndex })
+ .Any(group => group.Count() > 1))
+ {
+ throw Oops.Bah("同一团队的队伍序号不能重复");
+ }
+ }
+
///
/// 3.2 分配队员接口 - 提交报名(含保存草稿)
///
@@ -524,82 +639,71 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
if (eventInfo == null) throw Oops.Bah("赛事不存在");
- // 获取团队信息
- var team = await _db.Queryable().FirstAsync(t => t.Id == input.TeamId && !t.DeleteMark);
- if (team == null) throw Oops.Bah("团队不存在");
-
- // 收集所有成员ID
- var allMemberIds = new List();
- if (input.GroupMembers != null)
+ var groupMembers = input.GroupMembers ?? new List();
+ var submittedAssignments = new List<(long TeamId, List MemberIds)>();
+ foreach (var groupMember in groupMembers)
{
- foreach (var gm in input.GroupMembers)
+ if (groupMember.Teams?.Any() == true)
{
- // 检查是否有多团队数据
- if (gm.Teams != null && gm.Teams.Count > 0)
+ ValidateTeamAssignmentStructure(groupMember.Teams, input.TeamId);
+ foreach (var teamAssignment in groupMember.Teams)
{
- foreach (var t in gm.Teams)
+ var teamId = teamAssignment.TeamId ?? input.TeamId;
+ if (!teamId.HasValue || teamId.Value <= 0)
{
- allMemberIds.AddRange(t.MemberIds ?? new List());
+ throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
}
+
+ submittedAssignments.Add((teamId.Value, teamAssignment.MemberIds ?? new List()));
}
- else
+ }
+ else
+ {
+ if (!input.TeamId.HasValue || input.TeamId.Value <= 0)
{
- // 向后兼容单团队模式
- allMemberIds.AddRange(gm.MemberIds ?? new List());
+ throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
}
+
+ submittedAssignments.Add((input.TeamId.Value, groupMember.MemberIds ?? new List()));
}
}
- else if (input.MemberIds != null)
+
+ if (!submittedAssignments.Any() && input.TeamId.HasValue && input.TeamId.Value > 0)
{
- // 向后兼容旧结构
- allMemberIds.AddRange(input.MemberIds);
+ submittedAssignments.Add((input.TeamId.Value, input.MemberIds ?? new List()));
}
-
- allMemberIds = allMemberIds.Distinct().ToList();
- // 验证:一次报名只能选择单个团队内的运动员
- if (allMemberIds.Count > 0)
- {
- var teamMembers = await _db.Queryable()
- .Where(tm => allMemberIds.Contains(tm.UserId))
- .Where(tm => !tm.DeleteMark)
- .ToListAsync();
+ var selectedTeamMap = await GetAccessibleTeamsAsync(userId, submittedAssignments.Select(item => item.TeamId));
+ await ValidateTeamMemberAssignmentsAsync(submittedAssignments);
- if (teamMembers == null || teamMembers.Count == 0)
- {
- throw Oops.Bah("未找到队员信息");
- }
-
- var userTeamMap = teamMembers
- .GroupBy(tm => tm.UserId)
- .ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).ToList());
-
- var missingUsers = allMemberIds.Except(userTeamMap.Keys).ToList();
- if (missingUsers.Any())
- {
- throw Oops.Bah("部分队员未找到团队信息");
- }
-
- var allBelongToTeam = userTeamMap.All(ut => ut.Value.Contains(input.TeamId));
- if (!allBelongToTeam)
- {
- throw Oops.Bah("所有选择的运动员必须隶属于同一个团队");
- }
- }
+ var allMemberIds = submittedAssignments
+ .SelectMany(item => item.MemberIds)
+ .Where(id => id > 0)
+ .Distinct()
+ .ToList();
// 获取成员信息
var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id));
+ if (members.Count != allMemberIds.Count)
+ {
+ throw Oops.Bah("部分运动员不存在或已被删除,请刷新后重试");
+ }
// 获取所有组别信息
- var groupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List();
- var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
+ var groupIds = groupMembers.Select(group => group.GroupId).Distinct().ToList();
+ var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x =>
+ groupIds.Contains(x.Id) && x.EventId == input.EventId && !x.DeleteMark);
+ if (eventProjectGroups.Count != groupIds.Count)
+ {
+ throw Oops.Bah("部分报名组别不存在或不属于当前赛事,请刷新后重试");
+ }
// 构建统一报名输入
var unifiedInput = new UnifiedRegistrationInput
{
EventId = input.EventId,
RegistrationMode = 2, // PC端只做团队报名
- TeamName = team.Name,
+ TeamName = BuildTeamNameSummary(selectedTeamMap.Values.ToList()),
ContactName = userName,
ContactPhone = userMobile,
Items = new List(),
@@ -608,9 +712,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
};
// 填充项目和人员信息
- if (input.GroupMembers != null && input.GroupMembers.Any())
+ if (groupMembers.Any())
{
- foreach (var groupMember in input.GroupMembers)
+ foreach (var groupMember in groupMembers)
{
var eventProjectGroup = eventProjectGroups.FirstOrDefault(g => g.Id == groupMember.GroupId);
if (eventProjectGroup == null) continue;
@@ -624,49 +728,45 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
Persons = new List()
};
- // 检查是多团队模式还是单团队模式
- var hasMultipleTeams = groupMember.Teams != null && groupMember.Teams.Count > 0;
-
- if (hasMultipleTeams)
- {
- // ========== 多团队模式 ==========
- foreach (var t in groupMember.Teams)
+ var teamAssignments = groupMember.Teams?.Any() == true
+ ? groupMember.Teams
+ : new List
{
- var teamMembersList = members.Where(m => t.MemberIds != null && t.MemberIds.Contains(m.Id)).ToList();
- item.Teams.Add(new UnifiedTeamInput
+ new()
{
- TeamIndex = t.TeamIndex,
- Persons = teamMembersList.Select(m => new UnifiedPersonInput
- {
- UserId = m.Id,
- Name = m.Name ?? "",
- IdCardNumber = m.IdCardNumber ?? "",
- PassportNumber = m.PassportNumber,
- AuthType = m.AuthType,
- Gender = m.Gender,
- Mobile = m.Mobile,
- BirthDate = m.BirthDate ?? DateTime.MinValue
- }).ToList()
- });
- }
- }
- else
+ TeamId = input.TeamId,
+ TeamIndex = 1,
+ MemberIds = groupMember.MemberIds ?? new List()
+ }
+ };
+
+ foreach (var teamAssignment in teamAssignments)
{
- // ========== 单团队模式(向后兼容) ==========
- var groupMembersList = members.Where(m => groupMember.MemberIds != null && groupMember.MemberIds.Contains(m.Id)).ToList();
- if (!groupMembersList.Any()) continue;
-
- item.Persons = groupMembersList.Select(m => new UnifiedPersonInput
+ var teamId = teamAssignment.TeamId ?? input.TeamId;
+ if (!teamId.HasValue || !selectedTeamMap.TryGetValue(teamId.Value, out var selectedTeam))
{
- UserId = m.Id,
- Name = m.Name ?? "",
- IdCardNumber = m.IdCardNumber ?? "",
- PassportNumber = m.PassportNumber,
- AuthType = m.AuthType,
- Gender = m.Gender,
- Mobile = m.Mobile,
- BirthDate = m.BirthDate ?? DateTime.MinValue
- }).ToList();
+ throw Oops.Bah("报名队伍信息无效,请重新选择团队");
+ }
+
+ var assignmentMemberIds = (teamAssignment.MemberIds ?? new List()).Distinct().ToHashSet();
+ var teamMembersList = members.Where(member => assignmentMemberIds.Contains(member.Id)).ToList();
+ item.Teams.Add(new UnifiedTeamInput
+ {
+ TeamId = selectedTeam.Id,
+ TeamName = selectedTeam.Name,
+ TeamIndex = teamAssignment.TeamIndex > 0 ? teamAssignment.TeamIndex : 1,
+ Persons = teamMembersList.Select(member => new UnifiedPersonInput
+ {
+ UserId = member.Id,
+ Name = member.Name ?? "",
+ IdCardNumber = member.IdCardNumber ?? "",
+ PassportNumber = member.PassportNumber,
+ AuthType = member.AuthType,
+ Gender = member.Gender,
+ Mobile = member.Mobile,
+ BirthDate = member.BirthDate ?? DateTime.MinValue
+ }).ToList()
+ });
}
unifiedInput.Items.Add(item);
@@ -711,6 +811,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
return new UserEventProjectListOutput
{
+ TeamId = i.TeamId,
+ TeamName = i.TeamName ?? registration.TeamName,
+ TeamIndex = i.TeamIndex,
Id = i.ProjectId,
Name = i.ProjectName,
GroupName = i.GroupName,
@@ -1179,52 +1282,60 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
string userName = sysUser.Name;
string userMobile = sysUser.Mobile ?? "";
- // 获取团队信息 - 优先使用新结构的 TeamId
- string teamName = "临时团队";
- if (input.TeamId.HasValue)
+ var groupMembers = input.GroupMembers ?? new List();
+ var submittedAssignments = new List<(long TeamId, List MemberIds)>();
+ foreach (var groupMember in groupMembers)
{
- var team = await _db.Queryable().FirstAsync(t => t.Id == input.TeamId.Value && !t.DeleteMark);
- if (team != null)
+ if (groupMember.Teams?.Any() == true)
{
- teamName = team.Name;
- }
- }
-
- // 获取所有成员信息
- var allMemberIds = new List();
- if (input.GroupMembers != null)
- {
- foreach (var gm in input.GroupMembers)
- {
- // 检查是否有多团队数据
- if (gm.Teams != null && gm.Teams.Count > 0)
+ ValidateTeamAssignmentStructure(groupMember.Teams, input.TeamId);
+ foreach (var teamAssignment in groupMember.Teams)
{
- foreach (var team in gm.Teams)
+ var teamId = teamAssignment.TeamId ?? input.TeamId;
+ if (!teamId.HasValue || teamId.Value <= 0)
{
- allMemberIds.AddRange(team.MemberIds ?? new List());
+ throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
}
- }
- else
- {
- // 向后兼容单团队模式
- allMemberIds.AddRange(gm.MemberIds ?? new List());
+
+ submittedAssignments.Add((teamId.Value, teamAssignment.MemberIds ?? new List()));
}
}
+ else if (input.TeamId.HasValue && input.TeamId.Value > 0)
+ {
+ submittedAssignments.Add((input.TeamId.Value, groupMember.MemberIds ?? new List()));
+ }
}
- else if (input.MemberIds != null)
+
+ if (!submittedAssignments.Any() && input.TeamId.HasValue && input.TeamId.Value > 0)
{
- // 向后兼容旧结构
- allMemberIds.AddRange(input.MemberIds);
+ submittedAssignments.Add((input.TeamId.Value, input.MemberIds ?? new List()));
}
-
- allMemberIds = allMemberIds.Distinct().ToList();
+
+ var selectedTeamMap = await GetAccessibleTeamsAsync(userId, submittedAssignments.Select(item => item.TeamId));
+ await ValidateTeamMemberAssignmentsAsync(submittedAssignments);
+ var allMemberIds = submittedAssignments
+ .SelectMany(item => item.MemberIds)
+ .Where(id => id > 0)
+ .Distinct()
+ .ToList();
// 获取成员信息
var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id));
+ if (members.Count != allMemberIds.Count)
+ {
+ throw Oops.Bah("部分运动员不存在或已被删除,请刷新后重试");
+ }
// 获取组别信息
- var groupIds = input.GroupMembers?.Select(g => g.GroupId).ToList() ?? input.GroupIds ?? new List();
- var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
+ var groupIds = groupMembers.Any()
+ ? groupMembers.Select(group => group.GroupId).Distinct().ToList()
+ : input.GroupIds?.Distinct().ToList() ?? new List();
+ var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x =>
+ groupIds.Contains(x.Id) && x.EventId == input.EventId && !x.DeleteMark);
+ if (eventProjectGroups.Count != groupIds.Count)
+ {
+ throw Oops.Bah("部分报名组别不存在或不属于当前赛事,请刷新后重试");
+ }
// 获取项目信息
var eventProjectIds = eventProjectGroups.Select(epg => epg.EventProjectId).Distinct().ToList();
@@ -1235,7 +1346,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
{
EventId = input.EventId,
RegistrationMode = 2, // PC端团队报名
- TeamName = teamName,
+ TeamName = BuildTeamNameSummary(selectedTeamMap.Values.ToList()),
ContactName = userName,
ContactPhone = userMobile,
CreatedUserId = userId,
@@ -1244,7 +1355,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
};
// 为每个组别添加项目项
- foreach (var groupMember in input.GroupMembers ?? new List())
+ foreach (var groupMember in groupMembers)
{
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == groupMember.GroupId);
if (eventProjectGroup == null) continue;
@@ -1261,54 +1372,52 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
Persons = new List()
};
- // 检查是多团队模式还是单团队模式
- var hasMultipleTeams = groupMember.Teams != null && groupMember.Teams.Count > 0;
-
- if (hasMultipleTeams)
- {
- // ========== 多团队模式 ==========
- foreach (var team in groupMember.Teams)
+ var teamAssignments = groupMember.Teams?.Any() == true
+ ? groupMember.Teams
+ : new List
{
- var teamMembers = members.Where(m => team.MemberIds != null && team.MemberIds.Contains(m.Id)).ToList();
- item.Teams.Add(new UnifiedTeamInput
+ new()
{
- TeamIndex = team.TeamIndex,
- Persons = teamMembers.Select(m => new UnifiedPersonInput
- {
- UserId = m.Id,
- Name = m.Name ?? "",
- IdCardNumber = m.IdCardNumber ?? "",
- PassportNumber = m.PassportNumber,
- AuthType = m.AuthType,
- Gender = m.Gender,
- Mobile = m.Mobile,
- BirthDate = m.BirthDate ?? DateTime.MinValue
- }).ToList()
- });
- }
- }
- else
+ TeamId = input.TeamId,
+ TeamIndex = 1,
+ MemberIds = groupMember.MemberIds ?? new List()
+ }
+ };
+
+ foreach (var teamAssignment in teamAssignments)
{
- // ========== 单团队模式(向后兼容) ==========
- var groupMembersList = members.Where(m => groupMember.MemberIds != null && groupMember.MemberIds.Contains(m.Id)).ToList();
- item.Persons = groupMembersList.Select(m => new UnifiedPersonInput
+ var teamId = teamAssignment.TeamId ?? input.TeamId;
+ if (!teamId.HasValue || !selectedTeamMap.TryGetValue(teamId.Value, out var selectedTeam))
{
- UserId = m.Id,
- Name = m.Name ?? "",
- IdCardNumber = m.IdCardNumber ?? "",
- PassportNumber = m.PassportNumber,
- AuthType = m.AuthType,
- Gender = m.Gender,
- Mobile = m.Mobile,
- BirthDate = m.BirthDate ?? DateTime.MinValue
- }).ToList();
+ throw Oops.Bah("报名队伍信息无效,请重新选择团队");
+ }
+
+ var assignmentMemberIds = (teamAssignment.MemberIds ?? new List()).Distinct().ToHashSet();
+ var teamMembers = members.Where(member => assignmentMemberIds.Contains(member.Id)).ToList();
+ item.Teams.Add(new UnifiedTeamInput
+ {
+ TeamId = selectedTeam.Id,
+ TeamName = selectedTeam.Name,
+ TeamIndex = teamAssignment.TeamIndex > 0 ? teamAssignment.TeamIndex : 1,
+ Persons = teamMembers.Select(member => new UnifiedPersonInput
+ {
+ UserId = member.Id,
+ Name = member.Name ?? "",
+ IdCardNumber = member.IdCardNumber ?? "",
+ PassportNumber = member.PassportNumber,
+ AuthType = member.AuthType,
+ Gender = member.Gender,
+ Mobile = member.Mobile,
+ BirthDate = member.BirthDate ?? DateTime.MinValue
+ }).ToList()
+ });
}
unifiedInput.Items.Add(item);
}
// 处理旧结构(向后兼容)
- if ((input.GroupMembers == null || !input.GroupMembers.Any()) && input.GroupIds != null && input.MemberIds != null)
+ if (!groupMembers.Any() && input.GroupIds != null && input.MemberIds != null && input.TeamId.HasValue)
{
foreach (var groupId in input.GroupIds)
{
@@ -1325,17 +1434,26 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
ProjectId = eventProject.Id,
GroupId = groupId,
GroupName = eventProjectGroup.GroupName,
- Persons = groupMembersList.Select(m => new UnifiedPersonInput
+ Teams = new List
{
- UserId = m.Id,
- Name = m.Name ?? "",
- IdCardNumber = m.IdCardNumber ?? "",
- PassportNumber = m.PassportNumber,
- AuthType = m.AuthType,
- Gender = m.Gender,
- Mobile = m.Mobile,
- BirthDate = m.BirthDate ?? DateTime.MinValue
- }).ToList()
+ new()
+ {
+ TeamId = input.TeamId.Value,
+ TeamName = selectedTeamMap[input.TeamId.Value].Name,
+ TeamIndex = 1,
+ Persons = groupMembersList.Select(member => new UnifiedPersonInput
+ {
+ UserId = member.Id,
+ Name = member.Name ?? "",
+ IdCardNumber = member.IdCardNumber ?? "",
+ PassportNumber = member.PassportNumber,
+ AuthType = member.AuthType,
+ Gender = member.Gender,
+ Mobile = member.Mobile,
+ BirthDate = member.BirthDate ?? DateTime.MinValue
+ }).ToList()
+ }
+ }
});
}
}
diff --git a/YD_Event.Core/Entities/EventRegistrationItem.cs b/YD_Event.Core/Entities/EventRegistrationItem.cs
index 7e0bd64..16b7e37 100644
--- a/YD_Event.Core/Entities/EventRegistrationItem.cs
+++ b/YD_Event.Core/Entities/EventRegistrationItem.cs
@@ -23,6 +23,24 @@ public class EventRegistrationItem : Entity, ISoftDelete, ICreatedUserId,
///
public long? EventProjectGroupId { get; set; }
+ ///
+ /// 报名时选择的来源团队ID
+ ///
+ [SugarColumn(IsNullable = true)]
+ public long? TeamId { get; set; }
+
+ ///
+ /// 报名时的来源团队名称快照
+ ///
+ [SugarColumn(Length = 64, IsNullable = true)]
+ public string? TeamName { get; set; }
+
+ ///
+ /// 来源团队在当前项目组别下的队伍序号(1队、2队等)
+ ///
+ [SugarColumn(IsNullable = true)]
+ public int? TeamIndex { get; set; }
+
///
/// 参赛项目名称
///
@@ -92,4 +110,4 @@ public class EventRegistrationItem : Entity, ISoftDelete, ICreatedUserId,
/// 更新时间
///
public DateTime? UpdatedTime { get; set; }
-}
\ No newline at end of file
+}
diff --git a/二期需求/dbo.sql b/二期需求/dbo.sql
index 8effb2d..2df1aab 100644
--- a/二期需求/dbo.sql
+++ b/二期需求/dbo.sql
@@ -635,6 +635,41 @@ BEGIN TRY
IF COL_LENGTH('dbo.EventInfo','RefereeRegistrationCode') IS NULL ALTER TABLE dbo.EventInfo ADD RefereeRegistrationCode NVARCHAR(32) NULL; -- 裁判报名邀请码
/* EventRegistrationLimit 报名限额:裁判名额 RefereeLimit,默认0 */
IF COL_LENGTH('dbo.EventRegistrationLimit','RefereeLimit') IS NULL ALTER TABLE dbo.EventRegistrationLimit ADD RefereeLimit INT NOT NULL CONSTRAINT DF_EventRegistrationLimit_RefereeLimit DEFAULT(0) WITH VALUES; -- 裁判名额限制(默认0)
+ /* EventRegistrationItem 报名项目明细:支持一个单位在同一报名订单中选择多个来源团队。 */
+ IF COL_LENGTH(N'dbo.EventRegistrationItem', N'TeamId') IS NULL ALTER TABLE dbo.EventRegistrationItem ADD TeamId BIGINT NULL;
+ IF COL_LENGTH(N'dbo.EventRegistrationItem', N'TeamName') IS NULL ALTER TABLE dbo.EventRegistrationItem ADD TeamName NVARCHAR(64) NULL;
+ IF COL_LENGTH(N'dbo.EventRegistrationItem', N'TeamIndex') IS NULL ALTER TABLE dbo.EventRegistrationItem ADD TeamIndex INT NULL;
+ ;WITH TeamCandidates AS (
+ SELECT item.Id AS RegistrationItemId,team.Id AS TeamId,
+ COUNT_BIG(1) OVER(PARTITION BY item.Id) AS CandidateCount
+ FROM dbo.EventRegistrationItem item
+ INNER JOIN dbo.EventRegistration registration ON registration.Id=item.RegistrationId
+ INNER JOIN dbo.Team team
+ ON LTRIM(RTRIM(team.Name))=LTRIM(RTRIM(registration.TeamName))
+ AND ((registration.MerchantCertificationId IS NOT NULL AND team.MerchantCertificationId=registration.MerchantCertificationId)
+ OR team.CreatedUserId=registration.CreatedUserId)
+ WHERE item.TeamId IS NULL AND registration.RegistrationMode=2
+ AND NULLIF(LTRIM(RTRIM(registration.TeamName)),N'') IS NOT NULL
+ )
+ UPDATE item SET TeamId=candidate.TeamId
+ FROM dbo.EventRegistrationItem item
+ INNER JOIN TeamCandidates candidate ON candidate.RegistrationItemId=item.Id AND candidate.CandidateCount=1;
+ UPDATE item SET TeamName=LEFT(COALESCE(NULLIF(LTRIM(RTRIM(registration.TeamName)),N''),team.Name),64)
+ FROM dbo.EventRegistrationItem item
+ INNER JOIN dbo.EventRegistration registration ON registration.Id=item.RegistrationId
+ LEFT JOIN dbo.Team team ON team.Id=item.TeamId
+ WHERE item.TeamName IS NULL
+ AND (NULLIF(LTRIM(RTRIM(registration.TeamName)),N'') IS NOT NULL OR team.Id IS NOT NULL);
+ ;WITH OrderedTeamItems AS (
+ SELECT item.Id,ROW_NUMBER() OVER(PARTITION BY item.RegistrationId,item.ProjectId,
+ ISNULL(item.EventProjectGroupId,0),ISNULL(item.TeamId,0) ORDER BY item.CreatedTime,item.Id) AS RepairedTeamIndex
+ FROM dbo.EventRegistrationItem item
+ LEFT JOIN dbo.EventProject eventProject ON eventProject.Id=item.ProjectId
+ LEFT JOIN dbo.Project project ON project.Id=eventProject.ProjectId
+ WHERE item.TeamIndex IS NULL AND (project.ProjectType IN(1005,1006) OR item.ProjectTypeName LIKE N'%团队%')
+ )
+ UPDATE item SET TeamIndex=ordered.RepairedTeamIndex
+ FROM dbo.EventRegistrationItem item INNER JOIN OrderedTeamItems ordered ON ordered.Id=item.Id;
/* Project 项目:绑定计分规则 ScoreRuleId -> ScoreRule.Id */
IF COL_LENGTH('dbo.Project','ScoreRuleId') IS NULL ALTER TABLE dbo.Project ADD ScoreRuleId BIGINT NULL; -- 项目绑定的计分规则Id(ScoreRule.Id)
/* SysUser 扩展为“单位账号”:UnitName 单位名称;UnitPlatform 所属平台 0-系统用户 1-报名平台 2-打分系统,默认0 */
@@ -673,11 +708,17 @@ BEGIN TRY
IF OBJECT_ID('CK_ScoreRule_Version','C') IS NULL ALTER TABLE dbo.ScoreRule WITH CHECK ADD CONSTRAINT CK_ScoreRule_Version CHECK(RuleVersion>=1);
IF OBJECT_ID('CK_EventJudgeScoreSheet_Version','C') IS NULL ALTER TABLE dbo.EventJudgeScoreSheet WITH CHECK ADD CONSTRAINT CK_EventJudgeScoreSheet_Version CHECK(RuleVersion>=1 AND SheetVersion>=1);
IF OBJECT_ID('CK_EventScoreCorrection_Applicant','C') IS NULL ALTER TABLE dbo.EventScoreCorrection WITH CHECK ADD CONSTRAINT CK_EventScoreCorrection_Applicant CHECK((ApplyUserId IS NOT NULL AND ApplyRefereeId IS NULL) OR (ApplyUserId IS NULL AND ApplyRefereeId IS NOT NULL));
+ IF OBJECT_ID(N'dbo.CK_EventRegistrationItem_TeamIndex',N'C') IS NULL ALTER TABLE dbo.EventRegistrationItem WITH CHECK ADD CONSTRAINT CK_EventRegistrationItem_TeamIndex CHECK(TeamIndex IS NULL OR TeamIndex>0);
+ IF EXISTS(SELECT 1 FROM dbo.EventRegistrationItem item LEFT JOIN dbo.Team team ON team.Id=item.TeamId WHERE item.TeamId IS NOT NULL AND team.Id IS NULL) THROW 51024,N'EventRegistrationItem 存在无效 TeamId,停止升级。',1;
/* 3.2 外键:二期表引用一期表主键、以及二期表之间的引用关系 */
IF OBJECT_ID(N'dbo.[FK_RefereeApplication_Event]','F') IS NULL ALTER TABLE dbo.[RefereeApplication] WITH CHECK ADD CONSTRAINT [FK_RefereeApplication_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_RefereeApplication_AppUser]','F') IS NULL ALTER TABLE dbo.[RefereeApplication] WITH CHECK ADD CONSTRAINT [FK_RefereeApplication_AppUser] FOREIGN KEY([RefereeUserId]) REFERENCES dbo.[AppUser]([Id]);
IF OBJECT_ID(N'dbo.[FK_CheckinTask_Event]','F') IS NULL ALTER TABLE dbo.[CheckinTask] WITH CHECK ADD CONSTRAINT [FK_CheckinTask_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_CheckinRecord_Task]','F') IS NULL ALTER TABLE dbo.[CheckinRecord] WITH CHECK ADD CONSTRAINT [FK_CheckinRecord_Task] FOREIGN KEY([TaskId]) REFERENCES dbo.[CheckinTask]([Id]);
+ IF OBJECT_ID(N'dbo.[FK_EventRegistrationItem_Team]','F') IS NULL ALTER TABLE dbo.[EventRegistrationItem] WITH CHECK ADD CONSTRAINT [FK_EventRegistrationItem_Team] FOREIGN KEY([TeamId]) REFERENCES dbo.[Team]([Id]);
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventRegistrationItem]') AND name=N'IX_EventRegistrationItem_RegistrationId_TeamId') CREATE NONCLUSTERED INDEX [IX_EventRegistrationItem_RegistrationId_TeamId] ON dbo.[EventRegistrationItem]([RegistrationId],[TeamId]) INCLUDE([EventProjectGroupId],[TeamIndex]) WHERE [TeamId] IS NOT NULL;
+ IF EXISTS(SELECT 1 FROM dbo.EventRegistrationItem WHERE TeamId IS NOT NULL AND TeamIndex IS NOT NULL AND DeleteMark=0 GROUP BY RegistrationId,ProjectId,EventProjectGroupId,TeamId,TeamIndex HAVING COUNT_BIG(1)>1) THROW 51025,N'EventRegistrationItem 存在重复的来源团队队伍序号,停止创建唯一索引。',1;
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventRegistrationItem]') AND name=N'UX_EventRegistrationItem_SourceTeamSquad_Active') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventRegistrationItem_SourceTeamSquad_Active] ON dbo.[EventRegistrationItem]([RegistrationId],[ProjectId],[EventProjectGroupId],[TeamId],[TeamIndex]) WHERE [TeamId] IS NOT NULL AND [TeamIndex] IS NOT NULL AND [DeleteMark]=0;
IF OBJECT_ID(N'dbo.[FK_Referee_AppUser]','F') IS NULL ALTER TABLE dbo.[Referee] WITH CHECK ADD CONSTRAINT [FK_Referee_AppUser] FOREIGN KEY([AppUserId]) REFERENCES dbo.[AppUser]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventReferee_Event]','F') IS NULL ALTER TABLE dbo.[EventReferee] WITH CHECK ADD CONSTRAINT [FK_EventReferee_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventReferee_Referee]','F') IS NULL ALTER TABLE dbo.[EventReferee] WITH CHECK ADD CONSTRAINT [FK_EventReferee_Referee] FOREIGN KEY([RefereeId]) REFERENCES dbo.[Referee]([Id]);
@@ -1470,6 +1511,7 @@ SELECT r.RuleCode,COUNT(i.Id) AS ItemCount FROM dbo.ScoreRule r LEFT JOIN dbo.Sc
(N'CertificateTemplate',N'UpdatedUserId'),(N'CertificateTemplate',N'UpdatedTime'),
(N'CertificatePlaceholder',N'CreatedUserId'),(N'CertificatePlaceholder',N'CreatedTime'),(N'CertificatePlaceholder',N'UpdatedUserId'),(N'CertificatePlaceholder',N'UpdatedTime'),
(N'EventInfo',N'RefereeRegistrationCode'),(N'EventRegistrationLimit',N'RefereeLimit'),
+ (N'EventRegistrationItem',N'TeamId'),(N'EventRegistrationItem',N'TeamName'),(N'EventRegistrationItem',N'TeamIndex'),
(N'Project',N'ScoreRuleId'),(N'SysUser',N'UnitName'),(N'SysUser',N'UnitPlatform'),
(N'EventResult',N'SessionId'),(N'EventResult',N'SessionEntryId'),(N'EventResult',N'TotalScore'),(N'EventResult',N'ResultVersion'),(N'EventResult',N'ReviewUserId'),(N'EventResult',N'ReviewTime'),
(N'EventReferee',N'RefereeApplicationId'),(N'EventVenue',N'VenueCode'),(N'EventJudgeMessage',N'ReceiverUserId'),
diff --git a/二期需求/上线/dbo_alter.sql b/二期需求/上线/dbo_alter.sql
index b0f26eb..949be01 100644
--- a/二期需求/上线/dbo_alter.sql
+++ b/二期需求/上线/dbo_alter.sql
@@ -58,6 +58,68 @@ BEGIN TRY
IF COL_LENGTH('dbo.EventInfo','RefereeRegistrationCode') IS NULL ALTER TABLE dbo.EventInfo ADD RefereeRegistrationCode NVARCHAR(32) NULL; -- 裁判报名邀请码
/* EventRegistrationLimit 报名限额:裁判名额 RefereeLimit,默认0 */
IF COL_LENGTH('dbo.EventRegistrationLimit','RefereeLimit') IS NULL ALTER TABLE dbo.EventRegistrationLimit ADD RefereeLimit INT NOT NULL CONSTRAINT DF_EventRegistrationLimit_RefereeLimit DEFAULT(0) WITH VALUES; -- 裁判名额限制(默认0)
+ /* EventRegistrationItem 报名项目明细:支持一个单位在同一报名订单中选择多个来源团队。 */
+ IF COL_LENGTH(N'dbo.EventRegistrationItem', N'TeamId') IS NULL ALTER TABLE dbo.EventRegistrationItem ADD TeamId BIGINT NULL;
+ IF COL_LENGTH(N'dbo.EventRegistrationItem', N'TeamName') IS NULL ALTER TABLE dbo.EventRegistrationItem ADD TeamName NVARCHAR(64) NULL;
+ IF COL_LENGTH(N'dbo.EventRegistrationItem', N'TeamIndex') IS NULL ALTER TABLE dbo.EventRegistrationItem ADD TeamIndex INT NULL;
+
+ /* 历史 TeamId 仅在单位/创建账号 + 团队名称唯一匹配时回填,歧义数据保持 NULL。 */
+ ;WITH TeamCandidates AS (
+ SELECT
+ item.Id AS RegistrationItemId,
+ team.Id AS TeamId,
+ COUNT_BIG(1) OVER (PARTITION BY item.Id) AS CandidateCount
+ FROM dbo.EventRegistrationItem AS item
+ INNER JOIN dbo.EventRegistration AS registration ON registration.Id = item.RegistrationId
+ INNER JOIN dbo.Team AS team
+ ON LTRIM(RTRIM(team.Name)) = LTRIM(RTRIM(registration.TeamName))
+ AND (
+ (registration.MerchantCertificationId IS NOT NULL
+ AND team.MerchantCertificationId = registration.MerchantCertificationId)
+ OR team.CreatedUserId = registration.CreatedUserId
+ )
+ WHERE item.TeamId IS NULL
+ AND registration.RegistrationMode = 2
+ AND NULLIF(LTRIM(RTRIM(registration.TeamName)), N'') IS NOT NULL
+ )
+ UPDATE item
+ SET item.TeamId = candidate.TeamId
+ FROM dbo.EventRegistrationItem AS item
+ INNER JOIN TeamCandidates AS candidate
+ ON candidate.RegistrationItemId = item.Id
+ AND candidate.CandidateCount = 1;
+
+ /* 团队名称为报名快照,优先保留报名主表历史名称。 */
+ UPDATE item
+ SET item.TeamName = LEFT(COALESCE(
+ NULLIF(LTRIM(RTRIM(registration.TeamName)), N''),
+ team.Name
+ ), 64)
+ FROM dbo.EventRegistrationItem AS item
+ INNER JOIN dbo.EventRegistration AS registration ON registration.Id = item.RegistrationId
+ LEFT JOIN dbo.Team AS team ON team.Id = item.TeamId
+ WHERE item.TeamName IS NULL
+ AND (NULLIF(LTRIM(RTRIM(registration.TeamName)), N'') IS NOT NULL OR team.Id IS NOT NULL);
+
+ /* 1005=团队速度、1006=团队花样;按原明细创建顺序重建一队、二队序号。 */
+ ;WITH OrderedTeamItems AS (
+ SELECT
+ item.Id,
+ ROW_NUMBER() OVER (
+ PARTITION BY item.RegistrationId, item.ProjectId,
+ ISNULL(item.EventProjectGroupId, 0), ISNULL(item.TeamId, 0)
+ ORDER BY item.CreatedTime, item.Id
+ ) AS RepairedTeamIndex
+ FROM dbo.EventRegistrationItem AS item
+ LEFT JOIN dbo.EventProject AS eventProject ON eventProject.Id = item.ProjectId
+ LEFT JOIN dbo.Project AS project ON project.Id = eventProject.ProjectId
+ WHERE item.TeamIndex IS NULL
+ AND (project.ProjectType IN (1005, 1006) OR item.ProjectTypeName LIKE N'%团队%')
+ )
+ UPDATE item
+ SET item.TeamIndex = ordered.RepairedTeamIndex
+ FROM dbo.EventRegistrationItem AS item
+ INNER JOIN OrderedTeamItems AS ordered ON ordered.Id = item.Id;
/* Project 项目:绑定计分规则 ScoreRuleId -> ScoreRule.Id */
IF COL_LENGTH('dbo.Project','ScoreRuleId') IS NULL ALTER TABLE dbo.Project ADD ScoreRuleId BIGINT NULL; -- 项目绑定的计分规则Id(ScoreRule.Id)
/* SysUser 扩展为“单位账号”:UnitName 单位名称;UnitPlatform 所属平台 0-系统用户 1-报名平台 2-打分系统,默认0 */
@@ -93,11 +155,17 @@ BEGIN TRY
IF OBJECT_ID('CK_ScoreRule_Version','C') IS NULL ALTER TABLE dbo.ScoreRule WITH CHECK ADD CONSTRAINT CK_ScoreRule_Version CHECK(RuleVersion>=1);
IF OBJECT_ID('CK_EventJudgeScoreSheet_Version','C') IS NULL ALTER TABLE dbo.EventJudgeScoreSheet WITH CHECK ADD CONSTRAINT CK_EventJudgeScoreSheet_Version CHECK(RuleVersion>=1 AND SheetVersion>=1);
IF OBJECT_ID('CK_EventScoreCorrection_Applicant','C') IS NULL ALTER TABLE dbo.EventScoreCorrection WITH CHECK ADD CONSTRAINT CK_EventScoreCorrection_Applicant CHECK((ApplyUserId IS NOT NULL AND ApplyRefereeId IS NULL) OR (ApplyUserId IS NULL AND ApplyRefereeId IS NOT NULL));
+ IF OBJECT_ID(N'dbo.CK_EventRegistrationItem_TeamIndex', N'C') IS NULL ALTER TABLE dbo.EventRegistrationItem WITH CHECK ADD CONSTRAINT CK_EventRegistrationItem_TeamIndex CHECK(TeamIndex IS NULL OR TeamIndex>0);
+ IF EXISTS(SELECT 1 FROM dbo.EventRegistrationItem item LEFT JOIN dbo.Team team ON team.Id=item.TeamId WHERE item.TeamId IS NOT NULL AND team.Id IS NULL) THROW 51024,N'EventRegistrationItem 存在无效 TeamId,停止升级。',1;
/* 外键:二期表引用一期表及二期表。 */
IF OBJECT_ID(N'dbo.[FK_RefereeApplication_Event]','F') IS NULL ALTER TABLE dbo.[RefereeApplication] WITH CHECK ADD CONSTRAINT [FK_RefereeApplication_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_RefereeApplication_AppUser]','F') IS NULL ALTER TABLE dbo.[RefereeApplication] WITH CHECK ADD CONSTRAINT [FK_RefereeApplication_AppUser] FOREIGN KEY([RefereeUserId]) REFERENCES dbo.[AppUser]([Id]);
IF OBJECT_ID(N'dbo.[FK_CheckinTask_Event]','F') IS NULL ALTER TABLE dbo.[CheckinTask] WITH CHECK ADD CONSTRAINT [FK_CheckinTask_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_CheckinRecord_Task]','F') IS NULL ALTER TABLE dbo.[CheckinRecord] WITH CHECK ADD CONSTRAINT [FK_CheckinRecord_Task] FOREIGN KEY([TaskId]) REFERENCES dbo.[CheckinTask]([Id]);
+ IF OBJECT_ID(N'dbo.[FK_EventRegistrationItem_Team]','F') IS NULL ALTER TABLE dbo.[EventRegistrationItem] WITH CHECK ADD CONSTRAINT [FK_EventRegistrationItem_Team] FOREIGN KEY([TeamId]) REFERENCES dbo.[Team]([Id]);
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventRegistrationItem]') AND name=N'IX_EventRegistrationItem_RegistrationId_TeamId') CREATE NONCLUSTERED INDEX [IX_EventRegistrationItem_RegistrationId_TeamId] ON dbo.[EventRegistrationItem]([RegistrationId],[TeamId]) INCLUDE([EventProjectGroupId],[TeamIndex]) WHERE [TeamId] IS NOT NULL;
+ IF EXISTS(SELECT 1 FROM dbo.EventRegistrationItem WHERE TeamId IS NOT NULL AND TeamIndex IS NOT NULL AND DeleteMark=0 GROUP BY RegistrationId,ProjectId,EventProjectGroupId,TeamId,TeamIndex HAVING COUNT_BIG(1)>1) THROW 51025,N'EventRegistrationItem 存在重复的来源团队队伍序号,停止创建唯一索引。',1;
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventRegistrationItem]') AND name=N'UX_EventRegistrationItem_SourceTeamSquad_Active') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventRegistrationItem_SourceTeamSquad_Active] ON dbo.[EventRegistrationItem]([RegistrationId],[ProjectId],[EventProjectGroupId],[TeamId],[TeamIndex]) WHERE [TeamId] IS NOT NULL AND [TeamIndex] IS NOT NULL AND [DeleteMark]=0;
IF OBJECT_ID(N'dbo.[FK_Referee_AppUser]','F') IS NULL ALTER TABLE dbo.[Referee] WITH CHECK ADD CONSTRAINT [FK_Referee_AppUser] FOREIGN KEY([AppUserId]) REFERENCES dbo.[AppUser]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventReferee_Event]','F') IS NULL ALTER TABLE dbo.[EventReferee] WITH CHECK ADD CONSTRAINT [FK_EventReferee_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventReferee_Referee]','F') IS NULL ALTER TABLE dbo.[EventReferee] WITH CHECK ADD CONSTRAINT [FK_EventReferee_Referee] FOREIGN KEY([RefereeId]) REFERENCES dbo.[Referee]([Id]);
@@ -268,6 +336,32 @@ SELECT r.RuleCode,COUNT(i.Id) AS ItemCount FROM dbo.ScoreRule r LEFT JOIN dbo.Sc
COMMIT TRANSACTION;
PRINT N'二期 ALTER 脚本执行成功。';
+
+ /* 多团队报名升级结果:未匹配记录允许保留为空,但上线前必须人工核对。 */
+ SELECT
+ item.Id AS RegistrationItemId,
+ item.RegistrationId,
+ registration.TeamName AS HistoricalTeamName,
+ registration.MerchantCertificationId,
+ registration.CreatedUserId,
+ item.ProjectName,
+ item.GroupName,
+ item.TeamIndex
+ FROM dbo.EventRegistrationItem item
+ INNER JOIN dbo.EventRegistration registration ON registration.Id=item.RegistrationId
+ WHERE item.TeamId IS NULL AND registration.RegistrationMode=2 AND item.DeleteMark=0
+ ORDER BY item.RegistrationId,item.ProjectId,item.Id;
+
+ SELECT
+ (SELECT COUNT_BIG(1) FROM dbo.EventRegistrationItem item LEFT JOIN dbo.Team team ON team.Id=item.TeamId WHERE item.TeamId IS NOT NULL AND team.Id IS NULL) AS InvalidTeamIdCount,
+ (SELECT COUNT_BIG(1) FROM dbo.EventRegistrationItem WHERE TeamIndex IS NOT NULL AND TeamIndex<=0) AS InvalidTeamIndexCount,
+ (SELECT COUNT_BIG(1) FROM (
+ SELECT RegistrationId,ProjectId,EventProjectGroupId,TeamId,TeamIndex
+ FROM dbo.EventRegistrationItem
+ WHERE TeamId IS NOT NULL AND TeamIndex IS NOT NULL AND DeleteMark=0
+ GROUP BY RegistrationId,ProjectId,EventProjectGroupId,TeamId,TeamIndex
+ HAVING COUNT_BIG(1)>1
+ ) duplicateItems) AS DuplicateSquadCount;
END TRY
BEGIN CATCH
DECLARE @ErrorNumber INT=ERROR_NUMBER(),@ErrorLine INT=ERROR_LINE(),@ErrorMessage NVARCHAR(4000)=ERROR_MESSAGE();
diff --git a/二期需求/上线/二期上线准备与执行清单.md b/二期需求/上线/二期上线准备与执行清单.md
index 3ff8bd7..1417ae0 100644
--- a/二期需求/上线/二期上线准备与执行清单.md
+++ b/二期需求/上线/二期上线准备与执行清单.md
@@ -11,7 +11,7 @@
生产 SQL 已拆成三份,必须按以下顺序执行:
1. `二期需求/上线/dbo_add.sql`:创建二期新增表。
-2. `二期需求/上线/dbo_alter.sql`:修改一期表,补充约束、外键、索引、视图、存储过程和数据库角色。
+2. `二期需求/上线/dbo_alter.sql`:修改一期表,补充约束、外键、索引、视图、存储过程和数据库角色;其中已经包含单位多团队报名的字段升级及历史数据修复。
3. `二期需求/上线/dbo_insert.sql`:写入或更新计分规则、评分项、角色和菜单,并执行最终校验。
三个脚本各自使用事务。任意一个失败都应停止上线,处理完成后从失败的脚本重新执行,不要跳过执行顺序。
@@ -20,6 +20,14 @@
原完整脚本 `二期需求/dbo.sql` 继续保留,用于留档和整体核对;实际分步上线使用“上线”目录中的三份脚本。
+多团队报名相关脚本说明:
+
+- `20261012_event_registration_multi_team_test_database.sql`:先在测试库执行,增加报名明细的 `TeamId/TeamName/TeamIndex`,自动修复能够唯一匹配的历史报名,并输出未匹配记录和候选团队。
+- `20261012_event_registration_multi_team_unmatched_repair_template.sql`:对测试脚本仍未匹配的历史报名,人工确认 `RegistrationId -> TeamId` 后定向修复;脚本会校验团队归属并拒绝覆盖已有不同团队。
+- `20261012_event_registration_multi_team.sql`:仅在需要单独发布该功能或单独演练时使用;正式二期完整上线时不要额外重复安排,它的内容已经合并进 `dbo_alter.sql`。
+- 历史 `TeamId` 只按“报名时团队名称 + 单位认证/创建账号归属”唯一匹配;候选不唯一时保持为空,由人工依据脚本结果集确认,禁止按名称随意选一条。
+- 历史团队项目的 `TeamIndex` 按原报名明细创建顺序恢复;个人项目保持为空。
+
执行前需要确认最新计分项已经同步进 `dbo_insert.sql`。当前 `scoreruleitem.txt` 后续调整过规定元素配置,不能漏到生产库。
脚本由 DBA 审核后在正式库执行。最后确认 `dbo_insert.sql` 输出的表、字段、种子和角色校验全部通过。
@@ -49,6 +57,9 @@ dotnet publish YD_Event.Web.Entry\YD_Event.Web.Entry.csproj -c Release -o <发
## 5. 上线后验证
- 后台服务启动正常,日志无数据库或配置错误
+- 测试库脚本最后结果中的 `InvalidTeamIdCount`、`InvalidTeamIndexCount`、`DuplicateSquadCount` 均为 0;未匹配历史团队记录已逐条确认
+- 单位账号可同时选择两支团队,同项目分别分配运动员并生成一笔订单
+- 多团队订单只允许整单取消,排期及报名详情显示各自来源团队名称
- 裁判报名、赛事编排和检录接口正常
- 裁判端可以获取任务并提交评分
- 成绩计算、成绩修正和投屏接口正常