Merge branch 'main' of http://8.153.108.90:3000/YD/YD_Event.Admin
This commit is contained in:
commit
93567c00b8
@ -187,7 +187,7 @@ public class ArrangementPreviewOutput
|
|||||||
public string UnitName { get; set; }
|
public string UnitName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 场次索引
|
/// 场次索引(同一赛事内按项目顺序全局连续)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SessionIndex { get; set; }
|
public int SessionIndex { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,7 @@ namespace YD_Event.Application.Event;
|
|||||||
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
|
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
|
||||||
|
|
||||||
// 按照 SortIndex 对 projects 排序
|
// 按照 SortIndex 对 projects 排序
|
||||||
var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList();
|
var sortedProjects = projects.OrderBy(x => x.SortIndex).ThenBy(x => x.Id).ToList();
|
||||||
|
|
||||||
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
||||||
|
|
||||||
@ -238,14 +238,24 @@ namespace YD_Event.Application.Event;
|
|||||||
{
|
{
|
||||||
// 团队项目:(队伍数 ÷ 场地数) × 项目时长
|
// 团队项目:(队伍数 ÷ 场地数) × 项目时长
|
||||||
int teamCount = totalTeams > 0 ? totalTeams : 1;
|
int teamCount = totalTeams > 0 ? totalTeams : 1;
|
||||||
int rounds = (int)Math.Ceiling((double)teamCount / venueCount);
|
var groupLoads = projGroups
|
||||||
|
.Select(group => teamCounts.FirstOrDefault(x =>
|
||||||
|
x.ProjectId == eventProj.Id && x.GroupName == group.GroupName)?.Count ?? 0);
|
||||||
|
int rounds = IsFreestyleProject(proj.ProjectType)
|
||||||
|
? CalculateBalancedVenueLoad(groupLoads, venueCount)
|
||||||
|
: (int)Math.Ceiling((double)teamCount / venueCount);
|
||||||
totalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
totalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 个人项目:(报名人数 ÷ 场地数) × 项目时长
|
// 个人项目:(报名人数 ÷ 场地数) × 项目时长
|
||||||
int participantCount = totalParticipants > 0 ? totalParticipants : 1;
|
int participantCount = totalParticipants > 0 ? totalParticipants : 1;
|
||||||
int rounds = (int)Math.Ceiling((double)participantCount / venueCount);
|
var groupLoads = projGroups
|
||||||
|
.Select(group => counts.FirstOrDefault(x =>
|
||||||
|
x.ProjectId == eventProj.Id && x.GroupName == group.GroupName)?.Count ?? 0);
|
||||||
|
int rounds = IsFreestyleProject(proj.ProjectType)
|
||||||
|
? CalculateBalancedVenueLoad(groupLoads, venueCount)
|
||||||
|
: (int)Math.Ceiling((double)participantCount / venueCount);
|
||||||
totalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
totalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +307,7 @@ namespace YD_Event.Application.Event;
|
|||||||
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
|
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
|
||||||
|
|
||||||
// 按照 SortIndex 对 projects 排序
|
// 按照 SortIndex 对 projects 排序
|
||||||
var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList();
|
var sortedProjects = projects.OrderBy(x => x.SortIndex).ThenBy(x => x.Id).ToList();
|
||||||
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
||||||
|
|
||||||
// 统计各组别报名人数
|
// 统计各组别报名人数
|
||||||
@ -333,6 +343,7 @@ namespace YD_Event.Application.Event;
|
|||||||
var result = new List<ArrangementPreviewOutput>();
|
var result = new List<ArrangementPreviewOutput>();
|
||||||
|
|
||||||
// 2. 生成排期 - 按照排序后的项目顺序处理
|
// 2. 生成排期 - 按照排序后的项目顺序处理
|
||||||
|
var nextSessionIndex = 1;
|
||||||
foreach (var proj in sortedProjects)
|
foreach (var proj in sortedProjects)
|
||||||
{
|
{
|
||||||
var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id);
|
var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id);
|
||||||
@ -419,20 +430,29 @@ namespace YD_Event.Application.Event;
|
|||||||
if (proj.IsTeamProject())
|
if (proj.IsTeamProject())
|
||||||
{
|
{
|
||||||
int teamCount = totalTeams > 0 ? totalTeams : 1;
|
int teamCount = totalTeams > 0 ? totalTeams : 1;
|
||||||
int rounds = (int)Math.Ceiling((double)teamCount / venueCount);
|
var groupLoads = projGroups
|
||||||
|
.Select(group => teamCounts.FirstOrDefault(x =>
|
||||||
|
x.ProjectId == eventProj.Id && x.GroupName == group.GroupName)?.Count ?? 0);
|
||||||
|
int rounds = IsFreestyleProject(proj.ProjectType)
|
||||||
|
? CalculateBalancedVenueLoad(groupLoads, venueCount)
|
||||||
|
: (int)Math.Ceiling((double)teamCount / venueCount);
|
||||||
projectTotalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
projectTotalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int participantCount = totalParticipants > 0 ? totalParticipants : 1;
|
int participantCount = totalParticipants > 0 ? totalParticipants : 1;
|
||||||
int rounds = (int)Math.Ceiling((double)participantCount / venueCount);
|
var groupLoads = projGroups
|
||||||
|
.Select(group => counts.FirstOrDefault(x =>
|
||||||
|
x.ProjectId == eventProj.Id && x.GroupName == group.GroupName)?.Count ?? 0);
|
||||||
|
int rounds = IsFreestyleProject(proj.ProjectType)
|
||||||
|
? CalculateBalancedVenueLoad(groupLoads, venueCount)
|
||||||
|
: (int)Math.Ceiling((double)participantCount / venueCount);
|
||||||
projectTotalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
projectTotalDurationSeconds = rounds * proj.GetTotalDurationInSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
string projectTotalDuration = FormatDurationSymbol(projectTotalDurationSeconds);
|
string projectTotalDuration = FormatDurationSymbol(projectTotalDurationSeconds);
|
||||||
|
|
||||||
int currentRound = 1;
|
var projectEntries = new List<ArrangementPreviewOutput>();
|
||||||
int currentVenue = 1;
|
|
||||||
|
|
||||||
foreach (var group in projGroups)
|
foreach (var group in projGroups)
|
||||||
{
|
{
|
||||||
@ -454,7 +474,7 @@ namespace YD_Event.Application.Event;
|
|||||||
var firstPerson = teamGroup.First();
|
var firstPerson = teamGroup.First();
|
||||||
string displayUnitName = GetDisplayUnitName(firstPerson);
|
string displayUnitName = GetDisplayUnitName(firstPerson);
|
||||||
|
|
||||||
result.Add(new ArrangementPreviewOutput
|
projectEntries.Add(new ArrangementPreviewOutput
|
||||||
{
|
{
|
||||||
EventProjectId = eventProj.Id,
|
EventProjectId = eventProj.Id,
|
||||||
ProjectId = proj.Id,
|
ProjectId = proj.Id,
|
||||||
@ -470,18 +490,9 @@ namespace YD_Event.Application.Event;
|
|||||||
TeamName = teamGroup.Key.TeamName,
|
TeamName = teamGroup.Key.TeamName,
|
||||||
AthleteName = combinedAthleteNames,
|
AthleteName = combinedAthleteNames,
|
||||||
UnitName = displayUnitName,
|
UnitName = displayUnitName,
|
||||||
SessionIndex = currentRound,
|
|
||||||
VenueIndex = currentVenue,
|
|
||||||
ProjectTotalDurationSeconds = projectTotalDurationSeconds,
|
ProjectTotalDurationSeconds = projectTotalDurationSeconds,
|
||||||
ProjectTotalDuration = projectTotalDuration
|
ProjectTotalDuration = projectTotalDuration
|
||||||
});
|
});
|
||||||
|
|
||||||
currentVenue++;
|
|
||||||
if (currentVenue > venueCount)
|
|
||||||
{
|
|
||||||
currentVenue = 1;
|
|
||||||
currentRound++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -490,7 +501,7 @@ namespace YD_Event.Application.Event;
|
|||||||
{
|
{
|
||||||
string displayUnitName = GetDisplayUnitName(person);
|
string displayUnitName = GetDisplayUnitName(person);
|
||||||
|
|
||||||
result.Add(new ArrangementPreviewOutput
|
projectEntries.Add(new ArrangementPreviewOutput
|
||||||
{
|
{
|
||||||
EventProjectId = eventProj.Id,
|
EventProjectId = eventProj.Id,
|
||||||
ProjectId = proj.Id,
|
ProjectId = proj.Id,
|
||||||
@ -504,26 +515,173 @@ namespace YD_Event.Application.Event;
|
|||||||
TeamName = person.TeamName,
|
TeamName = person.TeamName,
|
||||||
AthleteName = person.Name,
|
AthleteName = person.Name,
|
||||||
UnitName = displayUnitName,
|
UnitName = displayUnitName,
|
||||||
SessionIndex = currentRound,
|
|
||||||
VenueIndex = currentVenue,
|
|
||||||
ProjectTotalDurationSeconds = projectTotalDurationSeconds,
|
ProjectTotalDurationSeconds = projectTotalDurationSeconds,
|
||||||
ProjectTotalDuration = projectTotalDuration
|
ProjectTotalDuration = projectTotalDuration
|
||||||
});
|
});
|
||||||
|
|
||||||
currentVenue++;
|
|
||||||
if (currentVenue > venueCount)
|
|
||||||
{
|
|
||||||
currentVenue = 1;
|
|
||||||
currentRound++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!projectEntries.Any()) continue;
|
||||||
|
|
||||||
|
if (IsFreestyleProject(proj.ProjectType))
|
||||||
|
{
|
||||||
|
AssignFreestyleVenues(projectEntries, projGroups, venueCount, nextSessionIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AssignSpeedSessions(projectEntries, venueCount, nextSessionIndex, eventId, eventProj.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.AddRange(projectEntries
|
||||||
|
.OrderBy(x => x.SessionIndex)
|
||||||
|
.ThenBy(x => x.VenueIndex));
|
||||||
|
nextSessionIndex = projectEntries.Max(x => x.SessionIndex) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 速度项目按组别交叉单位,再依次填入同一场次的各场地。
|
||||||
|
/// 随机种子由赛事和赛事项目确定,避免预览、导出与确认结果反复变化。
|
||||||
|
/// </summary>
|
||||||
|
private static void AssignSpeedSessions(
|
||||||
|
List<ArrangementPreviewOutput> entries,
|
||||||
|
int venueCount,
|
||||||
|
int firstSessionIndex,
|
||||||
|
long eventId,
|
||||||
|
long eventProjectId)
|
||||||
|
{
|
||||||
|
var random = new Random(CreateStableRandomSeed(eventId, eventProjectId));
|
||||||
|
var orderedEntries = new List<ArrangementPreviewOutput>(entries.Count);
|
||||||
|
string? lastUnitKey = null;
|
||||||
|
|
||||||
|
foreach (var groupEntries in entries.GroupBy(x => x.GroupName))
|
||||||
|
{
|
||||||
|
var unitQueues = groupEntries
|
||||||
|
.GroupBy(GetArrangementUnitKey)
|
||||||
|
.ToDictionary(
|
||||||
|
x => x.Key,
|
||||||
|
x => new Queue<ArrangementPreviewOutput>(x.OrderBy(_ => random.Next())));
|
||||||
|
|
||||||
|
while (unitQueues.Count > 0)
|
||||||
|
{
|
||||||
|
var candidates = unitQueues
|
||||||
|
.Where(x => x.Key != lastUnitKey)
|
||||||
|
.ToList();
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
candidates = unitQueues.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxRemaining = candidates.Max(x => x.Value.Count);
|
||||||
|
var preferredCandidates = candidates
|
||||||
|
.Where(x => x.Value.Count == maxRemaining)
|
||||||
|
.ToList();
|
||||||
|
var selected = preferredCandidates[random.Next(preferredCandidates.Count)];
|
||||||
|
|
||||||
|
orderedEntries.Add(selected.Value.Dequeue());
|
||||||
|
lastUnitKey = selected.Key;
|
||||||
|
if (selected.Value.Count == 0)
|
||||||
|
{
|
||||||
|
unitQueues.Remove(selected.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index < orderedEntries.Count; index++)
|
||||||
|
{
|
||||||
|
orderedEntries[index].SessionIndex = firstSessionIndex + index / venueCount;
|
||||||
|
orderedEntries[index].VenueIndex = index % venueCount + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 花样项目以整个组别为不可拆分单元分配场地,优先将大组分配给当前负载最小的场地。
|
||||||
|
/// 同一场地内的组别串行,且每个组别的所有出场记录连续。
|
||||||
|
/// </summary>
|
||||||
|
private static void AssignFreestyleVenues(
|
||||||
|
List<ArrangementPreviewOutput> entries,
|
||||||
|
IReadOnlyCollection<ProjectGroup> projectGroups,
|
||||||
|
int venueCount,
|
||||||
|
int firstSessionIndex)
|
||||||
|
{
|
||||||
|
var groupOrder = projectGroups
|
||||||
|
.Select((group, index) => new { group.GroupName, Index = index })
|
||||||
|
.ToDictionary(x => x.GroupName, x => x.Index);
|
||||||
|
var groups = entries
|
||||||
|
.GroupBy(x => x.GroupName)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
GroupName = x.Key,
|
||||||
|
Entries = x.ToList(),
|
||||||
|
Order = groupOrder.GetValueOrDefault(x.Key, int.MaxValue)
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.Entries.Count)
|
||||||
|
.ThenBy(x => x.Order)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var venueLoads = new int[venueCount];
|
||||||
|
var venueGroups = Enumerable.Range(0, venueCount)
|
||||||
|
.ToDictionary(x => x, _ => new List<(int Order, List<ArrangementPreviewOutput> Entries)>());
|
||||||
|
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
var venueIndex = Enumerable.Range(0, venueCount)
|
||||||
|
.OrderBy(x => venueLoads[x])
|
||||||
|
.ThenBy(x => x)
|
||||||
|
.First();
|
||||||
|
venueGroups[venueIndex].Add((group.Order, group.Entries));
|
||||||
|
venueLoads[venueIndex] += group.Entries.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var venue in venueGroups)
|
||||||
|
{
|
||||||
|
var sessionIndex = firstSessionIndex;
|
||||||
|
foreach (var group in venue.Value.OrderBy(x => x.Order))
|
||||||
|
{
|
||||||
|
foreach (var entry in group.Entries)
|
||||||
|
{
|
||||||
|
entry.VenueIndex = venue.Key + 1;
|
||||||
|
entry.SessionIndex = sessionIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFreestyleProject(ProjectTypeEnum projectType)
|
||||||
|
{
|
||||||
|
return projectType is ProjectTypeEnum.PersonalFreestyle or ProjectTypeEnum.TeamFreestyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetArrangementUnitKey(ArrangementPreviewOutput entry)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(entry.UnitName)
|
||||||
|
? entry.UnitName.Trim()
|
||||||
|
: $"__ENTRY_{entry.RegistrationItemId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CalculateBalancedVenueLoad(IEnumerable<int> groupLoads, int venueCount)
|
||||||
|
{
|
||||||
|
var venueLoads = new int[venueCount];
|
||||||
|
foreach (var groupLoad in groupLoads.Where(x => x > 0).OrderByDescending(x => x))
|
||||||
|
{
|
||||||
|
var venueIndex = Enumerable.Range(0, venueCount)
|
||||||
|
.OrderBy(x => venueLoads[x])
|
||||||
|
.ThenBy(x => x)
|
||||||
|
.First();
|
||||||
|
venueLoads[venueIndex] += groupLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
return venueLoads.Max();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CreateStableRandomSeed(long eventId, long eventProjectId)
|
||||||
|
{
|
||||||
|
return unchecked((int)(eventId ^ (eventId >> 32) ^ eventProjectId ^ (eventProjectId >> 32)));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>确认编排并固化场地、场次及出场记录。</summary>
|
/// <summary>确认编排并固化场地、场次及出场记录。</summary>
|
||||||
[HttpPost("confirm/{eventId}"), DisplayName("确认赛事编排"), UnitOfWork]
|
[HttpPost("confirm/{eventId}"), DisplayName("确认赛事编排"), UnitOfWork]
|
||||||
public async Task<ConfirmArrangementOutput> Confirm(long eventId)
|
public async Task<ConfirmArrangementOutput> Confirm(long eventId)
|
||||||
@ -557,13 +715,35 @@ namespace YD_Event.Application.Event;
|
|||||||
var sessions = new List<EventSession>(); var entries = new List<EventSessionEntry>();
|
var sessions = new List<EventSession>(); var entries = new List<EventSessionEntry>();
|
||||||
var participationRecords = await _participationRepository.AsQueryable().Where(x => x.EventId == eventId).ToListAsync();
|
var participationRecords = await _participationRepository.AsQueryable().Where(x => x.EventId == eventId).ToListAsync();
|
||||||
var projectMap = projects.ToDictionary(x => x.Id);
|
var projectMap = projects.ToDictionary(x => x.Id);
|
||||||
foreach (var group in preview.GroupBy(x => new { x.EventProjectId, x.VenueIndex }))
|
var sessionGroups = preview
|
||||||
|
.GroupBy(x => new
|
||||||
|
{
|
||||||
|
x.EventProjectId,
|
||||||
|
x.VenueIndex,
|
||||||
|
EventProjectGroupId = IsFreestyleProject(projectMap[x.ProjectId].ProjectType)
|
||||||
|
? x.EventProjectGroupId
|
||||||
|
: null,
|
||||||
|
GroupName = IsFreestyleProject(projectMap[x.ProjectId].ProjectType)
|
||||||
|
? x.GroupName
|
||||||
|
: null
|
||||||
|
})
|
||||||
|
.OrderBy(x => projectMap[x.First().ProjectId].SortIndex)
|
||||||
|
.ThenBy(x => x.Min(item => item.SessionIndex))
|
||||||
|
.ThenBy(x => x.Key.VenueIndex);
|
||||||
|
foreach (var group in sessionGroups)
|
||||||
{
|
{
|
||||||
var first = group.First(); var project = projectMap[first.ProjectId]; var venue = venues.First(x => x.Sort == group.Key.VenueIndex);
|
var first = group.First(); var project = projectMap[first.ProjectId]; var venue = venues.First(x => x.Sort == group.Key.VenueIndex);
|
||||||
|
var isFreestyle = IsFreestyleProject(project.ProjectType);
|
||||||
var session = new EventSession { Id = _idGenerator.NextId(), EventId = eventId, EventProjectId = group.Key.EventProjectId,
|
var session = new EventSession { Id = _idGenerator.NextId(), EventId = eventId, EventProjectId = group.Key.EventProjectId,
|
||||||
EventProjectGroupId = group.Select(x => x.EventProjectGroupId).Distinct().Count() == 1 ? first.EventProjectGroupId : null,
|
EventProjectGroupId = group.Key.EventProjectGroupId,
|
||||||
VenueId = venue.Id, ScoreRuleId = project.ScoreRuleId!.Value, SessionNo = $"EP{group.Key.EventProjectId}-{group.Key.VenueIndex:00}",
|
VenueId = venue.Id, ScoreRuleId = project.ScoreRuleId!.Value,
|
||||||
SessionName = $"{project.Name}-{venue.VenueName}", SessionStatus = 0, Version = 1, Status = AvailabilityStatus.Enable,
|
SessionNo = isFreestyle
|
||||||
|
? $"EP{group.Key.EventProjectId}-{group.Key.VenueIndex:00}-{group.Min(x => x.SessionIndex):000}"
|
||||||
|
: $"EP{group.Key.EventProjectId}-{group.Key.VenueIndex:00}",
|
||||||
|
SessionName = isFreestyle
|
||||||
|
? $"{project.Name}-{first.GroupName}-{venue.VenueName}"
|
||||||
|
: $"{project.Name}-{venue.VenueName}",
|
||||||
|
SessionStatus = 0, Version = 1, Status = AvailabilityStatus.Enable,
|
||||||
CreatedUserId = userId, CreatedTime = now };
|
CreatedUserId = userId, CreatedTime = now };
|
||||||
sessions.Add(session);
|
sessions.Add(session);
|
||||||
var order = 1;
|
var order = 1;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using YD_Event.Application.Auth;
|
using YD_Event.Application.Auth;
|
||||||
using YD_Event.Application.Scoring;
|
using YD_Event.Application.Scoring;
|
||||||
|
using YD_Event.Application.Scoring.Dtos;
|
||||||
using YD_Event.Application.Workbench.Dtos;
|
using YD_Event.Application.Workbench.Dtos;
|
||||||
using YD_Event.Core.Entities;
|
using YD_Event.Core.Entities;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@ -683,7 +684,7 @@ public class WorkbenchController : IDynamicApiController
|
|||||||
?? throw Oops.Bah("所选组别不属于当前赛事项目");
|
?? throw Oops.Bah("所选组别不属于当前赛事项目");
|
||||||
var groupName = eventProjectGroup.GroupName;
|
var groupName = eventProjectGroup.GroupName;
|
||||||
|
|
||||||
var results = await _resultRepository.AsQueryable()
|
var rankingQuery = _resultRepository.AsQueryable()
|
||||||
.InnerJoin<EventSessionEntry>((r, e) => r.SessionEntryId == e.Id)
|
.InnerJoin<EventSessionEntry>((r, e) => r.SessionEntryId == e.Id)
|
||||||
.InnerJoin<EventSession>((r, e, s) => e.SessionId == s.Id)
|
.InnerJoin<EventSession>((r, e, s) => e.SessionId == s.Id)
|
||||||
.Where((r, e, s) => !r.DeleteMark && !e.DeleteMark && !s.DeleteMark &&
|
.Where((r, e, s) => !r.DeleteMark && !e.DeleteMark && !s.DeleteMark &&
|
||||||
@ -693,19 +694,34 @@ public class WorkbenchController : IDynamicApiController
|
|||||||
e.GroupName == groupName) &&
|
e.GroupName == groupName) &&
|
||||||
// 只投屏有效成绩(已确认20/已投屏30/已更正40),排除DQ成绩
|
// 只投屏有效成绩(已确认20/已投屏30/已更正40),排除DQ成绩
|
||||||
(r.ResultStatus == 20 || r.ResultStatus == 30 || r.ResultStatus == 40) &&
|
(r.ResultStatus == 20 || r.ResultStatus == 30 || r.ResultStatus == 40) &&
|
||||||
!r.IsDisqualified)
|
!r.IsDisqualified);
|
||||||
|
|
||||||
|
// 排名按大屏对应场地隔离,避免同项目同组别在多个场地比赛时串榜。
|
||||||
|
// 使用显式分支和值类型局部变量,避免 nullable 条件被 SqlSugar 翻译成非法 SQL。
|
||||||
|
if (input.VenueId.HasValue)
|
||||||
|
{
|
||||||
|
var venueId = input.VenueId.Value;
|
||||||
|
rankingQuery = rankingQuery.Where((r, e, s) => s.VenueId == venueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = await rankingQuery
|
||||||
.OrderBy((r, e, s) => r.Rank)
|
.OrderBy((r, e, s) => r.Rank)
|
||||||
.Select((r, e, s) => new
|
.Select((r, e, s) => new
|
||||||
{
|
{
|
||||||
r.Id,
|
r.Id,
|
||||||
r.ResultVersion,
|
r.ResultVersion,
|
||||||
|
SessionEntryId = e.Id,
|
||||||
|
s.ScoreRuleId,
|
||||||
e.EntryNo,
|
e.EntryNo,
|
||||||
e.ParticipantName,
|
e.ParticipantName,
|
||||||
e.TeamName,
|
e.TeamName,
|
||||||
e.GroupName,
|
e.GroupName,
|
||||||
|
r.DifficultyScore,
|
||||||
|
r.PerformanceScore,
|
||||||
|
r.RequiredScore,
|
||||||
|
r.DeductionScore,
|
||||||
r.TotalScore,
|
r.TotalScore,
|
||||||
r.Score,
|
r.Score,
|
||||||
r.Rank,
|
|
||||||
r.IsDisqualified
|
r.IsDisqualified
|
||||||
})
|
})
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@ -713,7 +729,38 @@ public class WorkbenchController : IDynamicApiController
|
|||||||
if (!results.Any())
|
if (!results.Any())
|
||||||
throw Oops.Bah("当前项目组别没有可投屏的已确认成绩");
|
throw Oops.Bah("当前项目组别没有可投屏的已确认成绩");
|
||||||
|
|
||||||
var payload = results.Select(r => new
|
// 落库名次可能是成绩刚生成时连同“待复核”成绩一起计算的。
|
||||||
|
// 投屏时必须以上面已经过状态筛选的有效成绩重新排名,避免待复核成绩隐形占位。
|
||||||
|
var scoreRuleId = results.Select(r => r.ScoreRuleId).First();
|
||||||
|
var scoreRule = await _scoreRuleRepository.GetByIdAsync(scoreRuleId);
|
||||||
|
var rankCandidates = results
|
||||||
|
.GroupBy(r => r.SessionEntryId)
|
||||||
|
.Select(group =>
|
||||||
|
{
|
||||||
|
var row = group.First();
|
||||||
|
return new RankCandidate
|
||||||
|
{
|
||||||
|
SessionEntryId = group.Key,
|
||||||
|
TotalScore = row.TotalScore ?? 0m,
|
||||||
|
DifficultyScore = row.DifficultyScore,
|
||||||
|
PerformanceScore = row.PerformanceScore,
|
||||||
|
RequiredScore = row.RequiredScore,
|
||||||
|
DeductionScore = row.DeductionScore,
|
||||||
|
IsDisqualified = row.IsDisqualified,
|
||||||
|
IsRanked = row.TotalScore.HasValue
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
EntryResultCalculationService.AssignRanks(rankCandidates,
|
||||||
|
ScoreRuleConfigService.Parse(scoreRule?.RuleConfigJson));
|
||||||
|
var screenRankByEntry = rankCandidates.ToDictionary(x => x.SessionEntryId, x => x.Rank);
|
||||||
|
|
||||||
|
var payload = results
|
||||||
|
.OrderBy(r => screenRankByEntry[r.SessionEntryId] == 0
|
||||||
|
? int.MaxValue
|
||||||
|
: screenRankByEntry[r.SessionEntryId])
|
||||||
|
.ThenBy(r => r.EntryNo)
|
||||||
|
.Select(r => new
|
||||||
{
|
{
|
||||||
r.EntryNo,
|
r.EntryNo,
|
||||||
r.ParticipantName,
|
r.ParticipantName,
|
||||||
@ -721,7 +768,7 @@ public class WorkbenchController : IDynamicApiController
|
|||||||
r.GroupName,
|
r.GroupName,
|
||||||
r.TotalScore,
|
r.TotalScore,
|
||||||
r.Score,
|
r.Score,
|
||||||
r.Rank,
|
Rank = screenRankByEntry[r.SessionEntryId],
|
||||||
r.IsDisqualified
|
r.IsDisqualified
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
|||||||
@ -4876,7 +4876,7 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="P:YD_Event.Application.Event.Dtos.ArrangementPreviewOutput.SessionIndex">
|
<member name="P:YD_Event.Application.Event.Dtos.ArrangementPreviewOutput.SessionIndex">
|
||||||
<summary>
|
<summary>
|
||||||
场次索引
|
场次索引(同一赛事内按项目顺序全局连续)
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="P:YD_Event.Application.Event.Dtos.ArrangementPreviewOutput.VenueIndex">
|
<member name="P:YD_Event.Application.Event.Dtos.ArrangementPreviewOutput.VenueIndex">
|
||||||
@ -9463,6 +9463,18 @@
|
|||||||
编排预览
|
编排预览
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:YD_Event.Application.Event.EventArrangementService.AssignSpeedSessions(System.Collections.Generic.List{YD_Event.Application.Event.Dtos.ArrangementPreviewOutput},System.Int32,System.Int32,System.Int64,System.Int64)">
|
||||||
|
<summary>
|
||||||
|
速度项目按组别交叉单位,再依次填入同一场次的各场地。
|
||||||
|
随机种子由赛事和赛事项目确定,避免预览、导出与确认结果反复变化。
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:YD_Event.Application.Event.EventArrangementService.AssignFreestyleVenues(System.Collections.Generic.List{YD_Event.Application.Event.Dtos.ArrangementPreviewOutput},System.Collections.Generic.IReadOnlyCollection{YD_Event.Core.Entities.ProjectGroup},System.Int32,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
花样项目以整个组别为不可拆分单元分配场地,优先将大组分配给当前负载最小的场地。
|
||||||
|
同一场地内的组别串行,且每个组别的所有出场记录连续。
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="M:YD_Event.Application.Event.EventArrangementService.Confirm(System.Int64)">
|
<member name="M:YD_Event.Application.Event.EventArrangementService.Confirm(System.Int64)">
|
||||||
<summary>确认编排并固化场地、场次及出场记录。</summary>
|
<summary>确认编排并固化场地、场次及出场记录。</summary>
|
||||||
</member>
|
</member>
|
||||||
|
|||||||
@ -54,7 +54,6 @@
|
|||||||
"dbconfig": {
|
"dbconfig": {
|
||||||
"ConnectionStrings": [
|
"ConnectionStrings": [
|
||||||
"8.153.108.90,Password=yd708090,SyncTimeout=15000,ConnectTimeout=15000,ResponseTimeout=15000,defaultDatabase=7,poolsize=100,ssl=false,writeBuffer=10240,idleTimeout=30000,connectRetry=3,asyncPipeline=false"
|
"8.153.108.90,Password=yd708090,SyncTimeout=15000,ConnectTimeout=15000,ResponseTimeout=15000,defaultDatabase=7,poolsize=100,ssl=false,writeBuffer=10240,idleTimeout=30000,connectRetry=3,asyncPipeline=false"
|
||||||
//"8.153.108.90,Password=yd708090,SyncTimeout=15000,ConnectTimeout=15000,ResponseTimeout=15000,defaultDatabase=8,poolsize=100,ssl=false,writeBuffer=10240,idleTimeout=30000,connectRetry=3,asyncPipeline=false"
|
|
||||||
],
|
],
|
||||||
//"Sentinels": [
|
//"Sentinels": [
|
||||||
// "192.169.1.10:26379",
|
// "192.169.1.10:26379",
|
||||||
|
|||||||
@ -2128,7 +2128,7 @@
|
|||||||
"ParentId": 1086,
|
"ParentId": 1086,
|
||||||
"RouteName": "scoringCast",
|
"RouteName": "scoringCast",
|
||||||
"Path": "/scoring/cast",
|
"Path": "/scoring/cast",
|
||||||
"Component": "scoring/workbench/index",
|
"Component": "scoring/screen/index",
|
||||||
"Redirect": "",
|
"Redirect": "",
|
||||||
"Icon": "ele-DataLine",
|
"Icon": "ele-DataLine",
|
||||||
"IsIframe": "0",
|
"IsIframe": "0",
|
||||||
@ -2138,7 +2138,7 @@
|
|||||||
"IsFixed": "0",
|
"IsFixed": "0",
|
||||||
"Status": 0,
|
"Status": 0,
|
||||||
"Sort": 4,
|
"Sort": 4,
|
||||||
"Remark": "成绩投屏操作台",
|
"Remark": "成绩投屏大屏",
|
||||||
"DeleteMark": false,
|
"DeleteMark": false,
|
||||||
"CreatedUserId": 1000,
|
"CreatedUserId": 1000,
|
||||||
"CreatedTime": "2023-06-01T00:00:00"
|
"CreatedTime": "2023-06-01T00:00:00"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user