一个单位报名一个比赛可以报多个队伍
This commit is contained in:
parent
6982ba8ac5
commit
1669913e1a
@ -452,6 +452,11 @@ public class CancelPreviewOutput
|
||||
/// 是否需要填写收款人信息(线下支付且已支付时需要)
|
||||
/// </summary>
|
||||
public bool NeedPayeeInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许部分取消(多团队合并报名仅允许整单取消)
|
||||
/// </summary>
|
||||
public bool AllowPartialCancel { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -827,6 +832,21 @@ public class CancelExecuteOutput
|
||||
/// </summary>
|
||||
public class RegistrationItemWithMembersDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 来源团队ID
|
||||
/// </summary>
|
||||
public long? TeamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源团队名称
|
||||
/// </summary>
|
||||
public string? TeamName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 队伍序号
|
||||
/// </summary>
|
||||
public int? TeamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报名项目ID
|
||||
/// </summary>
|
||||
|
||||
@ -352,6 +352,21 @@ public class UserEventProjectGroupOutput
|
||||
/// </summary>
|
||||
public class UserEventProjectListOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 来源团队ID(报名预览时返回)
|
||||
/// </summary>
|
||||
public long? TeamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源团队名称(报名预览时返回)
|
||||
/// </summary>
|
||||
public string? TeamName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 队伍序号(报名预览时返回)
|
||||
/// </summary>
|
||||
public int? TeamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 项目ID
|
||||
/// </summary>
|
||||
@ -743,10 +758,9 @@ public class UserSubmitRegistrationInput
|
||||
public long? CouponId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 团队ID (必填)
|
||||
/// 团队ID(旧版单团队结构,保留兼容)
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long TeamId { get; set; }
|
||||
public long? TeamId { get; set; }
|
||||
}
|
||||
|
||||
#region 参赛记录相关 DTOs
|
||||
@ -1389,6 +1403,11 @@ public class EventLimitDetail
|
||||
/// </summary>
|
||||
public class GroupTeamAssignment
|
||||
{
|
||||
/// <summary>
|
||||
/// 来源团队ID
|
||||
/// </summary>
|
||||
public long? TeamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 团队序号(例如:1, 2, 3)
|
||||
/// </summary>
|
||||
|
||||
@ -137,18 +137,32 @@ namespace YD_Event.Application.Event;
|
||||
// 获取所有相关的人员和报名记录(用于统计团队数)
|
||||
var personWithTeam = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => r.EventId == eventId
|
||||
.InnerJoin<EventRegistrationItem>((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<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => r.EventId == eventId
|
||||
.InnerJoin<EventRegistrationItem>((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<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => p.ProjectId == eventProj.Id
|
||||
.InnerJoin<EventRegistrationItem>((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,
|
||||
|
||||
@ -1019,18 +1019,19 @@ public class EventRegistrationService : BaseService<EventRegistration>, ITransie
|
||||
// 统计符合条件的所有运动员(与赛事编排页面保持一致的筛选规则)
|
||||
var athletes = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => r.EventId == eventId
|
||||
.InnerJoin<EventRegistrationItem>((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,
|
||||
|
||||
@ -133,7 +133,8 @@ public class EventScoreService : BaseService<EventResult>, ITransient
|
||||
.LeftJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.LeftJoin<EventResult>((p, r, res) => p.Id == res.RegistrationPersonId)
|
||||
.LeftJoin<EventCertificate>((p, r, res, c) => res.Id == c.ResultId)
|
||||
.Where((p, r, res, c) => r.EventId == input.EventId
|
||||
.LeftJoin<EventRegistrationItem>((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<EventResult>, 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<EventResult>, 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<EventResult>, 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<EventResult>, ITransient
|
||||
// 步骤2:批量预加载该赛事的所有人员数据(包含团队信息)
|
||||
var allPersonsWithTeam = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => r.EventId == eventId
|
||||
.InnerJoin<EventRegistrationItem>((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<EventResult>, ITransient
|
||||
p.GroupName,
|
||||
p.RegistrationId,
|
||||
p.ProjectId,
|
||||
r.TeamName
|
||||
TeamName = item.TeamName ?? r.TeamName
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
@ -833,8 +836,9 @@ public class EventScoreService : BaseService<EventResult>, ITransient
|
||||
// 步骤3:批量预加载该赛事的所有人员数据(包含团队信息)
|
||||
var allPersonsWithTeam = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => r.EventId == input.EventId && !p.DeleteMark)
|
||||
.Select((p, r) => new {
|
||||
.InnerJoin<EventRegistrationItem>((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<EventResult>, ITransient
|
||||
p.RegistrationItemId,
|
||||
p.EventProjectGroupId,
|
||||
p.DeleteMark,
|
||||
r.TeamName,
|
||||
TeamName = item.TeamName ?? r.TeamName,
|
||||
r.MerchantCertificationId,
|
||||
Person = p
|
||||
})
|
||||
|
||||
@ -654,6 +654,50 @@ public class UnifiedRegistrationService : ITransient
|
||||
return persons;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据已保存的报名明细重建统一报名结构,用于支付确认前再次校验报名限制。
|
||||
/// 同一项目组别的多个报名明细会还原为多个参赛队伍,避免少算队伍数量。
|
||||
/// </summary>
|
||||
private static List<UnifiedRegistrationItemInput> BuildUnifiedItemsFromStoredDetails(
|
||||
IEnumerable<EventRegistrationItem> items,
|
||||
IEnumerable<EventRegistrationPerson> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取人员证件号码(根据认证类型返回身份证号或护照号)
|
||||
/// </summary>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断报名单是否包含多个来源团队。
|
||||
/// 来源团队与同一项目下的一队、二队不是同一概念,以报名项的 TeamId 为准。
|
||||
/// </summary>
|
||||
private static bool IsMultiSourceTeamRegistration(IEnumerable<EventRegistrationItem> items)
|
||||
{
|
||||
return items
|
||||
.Where(x => !x.DeleteMark && x.TeamId.HasValue)
|
||||
.Select(x => x.TeamId!.Value)
|
||||
.Distinct()
|
||||
.Take(2)
|
||||
.Count() > 1;
|
||||
}
|
||||
|
||||
public async Task<decimal> 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<EventRegistrationItem>().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)
|
||||
{
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -507,6 +507,121 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
return idCard.Substring(0, 6) + "********" + idCard.Substring(idCard.Length - 4);
|
||||
}
|
||||
|
||||
private async Task<Dictionary<long, YD_Event.Core.Entities.Team>> GetAccessibleTeamsAsync(
|
||||
long userId,
|
||||
IEnumerable<long> teamIds)
|
||||
{
|
||||
var distinctTeamIds = teamIds.Where(id => id > 0).Distinct().ToList();
|
||||
if (!distinctTeamIds.Any())
|
||||
{
|
||||
throw Oops.Bah("请至少选择一个团队");
|
||||
}
|
||||
|
||||
var teams = await _db.Queryable<YD_Event.Core.Entities.Team>()
|
||||
.Where(team => distinctTeamIds.Contains(team.Id) && !team.DeleteMark)
|
||||
.ToListAsync();
|
||||
if (teams.Count != distinctTeamIds.Count)
|
||||
{
|
||||
throw Oops.Bah("部分团队不存在或已被删除,请刷新后重试");
|
||||
}
|
||||
|
||||
var certification = await _db.Queryable<MerchantCertification>()
|
||||
.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<long> 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<TeamMember>()
|
||||
.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<YD_Event.Core.Entities.Team> 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<GroupTeamAssignment> 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("同一团队的队伍序号不能重复");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2 分配队员接口 - 提交报名(含保存草稿)
|
||||
/// </summary>
|
||||
@ -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<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId && !t.DeleteMark);
|
||||
if (team == null) throw Oops.Bah("团队不存在");
|
||||
|
||||
// 收集所有成员ID
|
||||
var allMemberIds = new List<long>();
|
||||
if (input.GroupMembers != null)
|
||||
var groupMembers = input.GroupMembers ?? new List<UserGroupMemberInput>();
|
||||
var submittedAssignments = new List<(long TeamId, List<long> 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<long>());
|
||||
throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
|
||||
}
|
||||
|
||||
submittedAssignments.Add((teamId.Value, teamAssignment.MemberIds ?? new List<long>()));
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!input.TeamId.HasValue || input.TeamId.Value <= 0)
|
||||
{
|
||||
// 向后兼容单团队模式
|
||||
allMemberIds.AddRange(gm.MemberIds ?? new List<long>());
|
||||
throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
|
||||
}
|
||||
|
||||
submittedAssignments.Add((input.TeamId.Value, groupMember.MemberIds ?? new List<long>()));
|
||||
}
|
||||
}
|
||||
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<long>()));
|
||||
}
|
||||
|
||||
allMemberIds = allMemberIds.Distinct().ToList();
|
||||
|
||||
// 验证:一次报名只能选择单个团队内的运动员
|
||||
if (allMemberIds.Count > 0)
|
||||
{
|
||||
var teamMembers = await _db.Queryable<YD_Event.Core.Entities.TeamMember>()
|
||||
.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<long>();
|
||||
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<UnifiedRegistrationItemInput>(),
|
||||
@ -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<UnifiedPersonInput>()
|
||||
};
|
||||
|
||||
// 检查是多团队模式还是单团队模式
|
||||
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<GroupTeamAssignment>
|
||||
{
|
||||
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<long>()
|
||||
}
|
||||
};
|
||||
|
||||
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<long>()).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<GroupMemberAssignment>();
|
||||
var submittedAssignments = new List<(long TeamId, List<long> MemberIds)>();
|
||||
foreach (var groupMember in groupMembers)
|
||||
{
|
||||
var team = await _db.Queryable<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId.Value && !t.DeleteMark);
|
||||
if (team != null)
|
||||
if (groupMember.Teams?.Any() == true)
|
||||
{
|
||||
teamName = team.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有成员信息
|
||||
var allMemberIds = new List<long>();
|
||||
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<long>());
|
||||
throw Oops.Bah("报名队伍信息不完整,请重新选择团队");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 向后兼容单团队模式
|
||||
allMemberIds.AddRange(gm.MemberIds ?? new List<long>());
|
||||
|
||||
submittedAssignments.Add((teamId.Value, teamAssignment.MemberIds ?? new List<long>()));
|
||||
}
|
||||
}
|
||||
else if (input.TeamId.HasValue && input.TeamId.Value > 0)
|
||||
{
|
||||
submittedAssignments.Add((input.TeamId.Value, groupMember.MemberIds ?? new List<long>()));
|
||||
}
|
||||
}
|
||||
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<long>()));
|
||||
}
|
||||
|
||||
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<long>();
|
||||
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<long>();
|
||||
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<GroupMemberAssignment>())
|
||||
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<UnifiedPersonInput>()
|
||||
};
|
||||
|
||||
// 检查是多团队模式还是单团队模式
|
||||
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<GroupTeamAssignment>
|
||||
{
|
||||
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<long>()
|
||||
}
|
||||
};
|
||||
|
||||
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<long>()).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<UnifiedTeamInput>
|
||||
{
|
||||
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()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,24 @@ public class EventRegistrationItem : Entity<long>, ISoftDelete, ICreatedUserId,
|
||||
/// </summary>
|
||||
public long? EventProjectGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报名时选择的来源团队ID
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public long? TeamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报名时的来源团队名称快照
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 64, IsNullable = true)]
|
||||
public string? TeamName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源团队在当前项目组别下的队伍序号(1队、2队等)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? TeamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参赛项目名称
|
||||
/// </summary>
|
||||
@ -92,4 +110,4 @@ public class EventRegistrationItem : Entity<long>, ISoftDelete, ICreatedUserId,
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
42
二期需求/dbo.sql
42
二期需求/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'),
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;未匹配历史团队记录已逐条确认
|
||||
- 单位账号可同时选择两支团队,同项目分别分配运动员并生成一笔订单
|
||||
- 多团队订单只允许整单取消,排期及报名详情显示各自来源团队名称
|
||||
- 裁判报名、赛事编排和检录接口正常
|
||||
- 裁判端可以获取任务并提交评分
|
||||
- 成绩计算、成绩修正和投屏接口正常
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user