裁判报名分配限制人数
This commit is contained in:
parent
33a289568e
commit
de1609e2f1
@ -36,10 +36,31 @@ public class JudgeAssignmentProjectOutput
|
||||
public long EventProjectId { get; set; }
|
||||
public string ProjectName { get; set; } = null!;
|
||||
public string RuleName { get; set; } = null!;
|
||||
public string JudgeRoleSchemaJson { get; set; } = "[]";
|
||||
/// <summary>裁判大类分组(难度裁判、表现裁判、主裁),由计分规则的裁判席位配置解析而来。</summary>
|
||||
public List<JudgeRoleSectionOutput> JudgeRoleSections { get; set; } = new();
|
||||
public List<JudgeAssignmentSessionOutput> Sessions { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>裁判大类分组。</summary>
|
||||
public class JudgeRoleSectionOutput
|
||||
{
|
||||
public string Title { get; set; } = null!;
|
||||
public List<JudgeRoleSlotOutput> Slots { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>裁判类型(如 DP 力量体操、DM 多摇、P 表现、T 主裁),一个类型可分配多名裁判。</summary>
|
||||
public class JudgeRoleSlotOutput
|
||||
{
|
||||
public string Code { get; set; } = null!;
|
||||
public string Name { get; set; } = null!;
|
||||
/// <summary>该类型至少需要分配的裁判人数。</summary>
|
||||
public int MinCount { get; set; }
|
||||
/// <summary>该类型最多可分配的裁判人数,null 表示不限人数(成绩按平均值计算)。</summary>
|
||||
public int? MaxCount { get; set; }
|
||||
/// <summary>执裁该类型所需具备的裁判角色位标记。</summary>
|
||||
public int RoleFlag { get; set; }
|
||||
}
|
||||
|
||||
public class JudgeAssignmentSessionOutput
|
||||
{
|
||||
public long SessionId { get; set; }
|
||||
@ -52,7 +73,10 @@ public class JudgeAssignmentSessionOutput
|
||||
public class JudgeAssignmentItemOutput
|
||||
{
|
||||
public long EventRefereeId { get; set; }
|
||||
/// <summary>裁判席位编码(同一场次内唯一,如 DP1、DP2、T)。</summary>
|
||||
public string JudgeRole { get; set; } = null!;
|
||||
/// <summary>裁判类型编码(如 DP、DM、DR、P、T),对应计分规则中的裁判类型。</summary>
|
||||
public string JudgeRoleType { get; set; } = null!;
|
||||
public string JudgeRoleName { get; set; } = null!;
|
||||
public long RefereeId { get; set; }
|
||||
public string RefereeName { get; set; } = null!;
|
||||
@ -68,6 +92,7 @@ public class SaveJudgeAssignmentsInput
|
||||
|
||||
public class SaveJudgeAssignmentItem
|
||||
{
|
||||
/// <summary>裁判类型编码;同一类型可重复出现,表示该类型分配了多名裁判。</summary>
|
||||
public string JudgeRole { get; set; } = null!;
|
||||
public long EventRefereeId { get; set; }
|
||||
public long RefereeId { get; set; }
|
||||
|
||||
@ -63,10 +63,22 @@ public class JudgeAssignmentService : IDynamicApiController, ITransient
|
||||
{
|
||||
var project = projects.FirstOrDefault(x => x.Id == ep.ProjectId); if (project == null) continue;
|
||||
var rule = rules.FirstOrDefault(x => x.Id == project.ScoreRuleId);
|
||||
var group = new JudgeAssignmentProjectOutput { EventProjectId = ep.Id, ProjectName = project.Name, RuleName = rule?.RuleName ?? "未配置计分规则", JudgeRoleSchemaJson = rule?.JudgeRoleSchemaJson ?? "[]" };
|
||||
var sections = ParseRoleSections(rule?.JudgeRoleSchemaJson);
|
||||
var slots = sections.SelectMany(x => x.Slots).ToList();
|
||||
var slotMap = slots.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase);
|
||||
var slotOrder = slots.Select((slot, index) => (slot.Code, index)).ToDictionary(x => x.Code, x => x.index, StringComparer.OrdinalIgnoreCase);
|
||||
var group = new JudgeAssignmentProjectOutput { EventProjectId = ep.Id, ProjectName = project.Name, RuleName = rule?.RuleName ?? "未配置计分规则", JudgeRoleSections = sections };
|
||||
foreach (var session in sessions.Where(x => x.EventProjectId == ep.Id).OrderBy(x => venueMap.GetValueOrDefault(x.VenueId)?.Sort))
|
||||
{
|
||||
var items = assignments.Where(x => x.SessionId == session.Id).Select(x => { var referee = referees.FirstOrDefault(r => r.Id == x.RefereeId); return new JudgeAssignmentItemOutput { EventRefereeId = x.EventRefereeId, JudgeRole = x.JudgeRole, JudgeRoleName = x.JudgeRole, RefereeId = x.RefereeId, RefereeName = referee?.Name ?? "-", Mobile = referee?.Mobile ?? "" }; }).ToList();
|
||||
var items = assignments.Where(x => x.SessionId == session.Id).Select(x =>
|
||||
{
|
||||
var referee = referees.FirstOrDefault(r => r.Id == x.RefereeId);
|
||||
var roleType = string.IsNullOrWhiteSpace(x.JudgeRoleType) ? NormalizeRoleType(x.JudgeRole) : x.JudgeRoleType!;
|
||||
return new JudgeAssignmentItemOutput { EventRefereeId = x.EventRefereeId, JudgeRole = x.JudgeRole, JudgeRoleType = roleType,
|
||||
JudgeRoleName = slotMap.GetValueOrDefault(roleType)?.Name ?? roleType, RefereeId = x.RefereeId,
|
||||
RefereeName = referee?.Name ?? "-", Mobile = referee?.Mobile ?? "" };
|
||||
}).OrderBy(x => slotOrder.TryGetValue(x.JudgeRoleType, out var order) ? order : int.MaxValue)
|
||||
.ThenBy(x => x.JudgeRole, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
group.Sessions.Add(new JudgeAssignmentSessionOutput { SessionId = session.Id, SessionName = session.SessionName ?? session.SessionNo,
|
||||
VenueName = venueMap.GetValueOrDefault(session.VenueId)?.VenueName ?? "-", Assigned = items.Count > 0, Assignments = items });
|
||||
}
|
||||
@ -90,35 +102,57 @@ public class JudgeAssignmentService : IDynamicApiController, ITransient
|
||||
if (await _assignmentRepository.IsAnyAsync(x => x.SessionId == input.SessionId && x.AssignmentStatus == 1))
|
||||
throw Oops.Bah("裁判分配已提交,不能再次修改");
|
||||
if (input.Assignments.Any(x => string.IsNullOrWhiteSpace(x.JudgeRole) || (x.EventRefereeId <= 0 && x.RefereeId <= 0)))
|
||||
throw Oops.Bah("裁判岗位未完整分配");
|
||||
throw Oops.Bah("存在未选择裁判的分配记录");
|
||||
var rule = await _ruleRepository.GetByIdAsync(session.ScoreRuleId);
|
||||
var requiredRoles = JArray.Parse(rule?.JudgeRoleSchemaJson ?? "[]").SelectMany(x => x["slots"] ?? new JArray()).Select(x => x["code"]?.ToString()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
|
||||
if (requiredRoles.Count == 0 || requiredRoles.Except(input.Assignments.Select(x => x.JudgeRole)).Any() || input.Assignments.Any(x => !requiredRoles.Contains(x.JudgeRole)))
|
||||
throw Oops.Bah("请完成计分规则要求的全部裁判岗位分配");
|
||||
if (input.Assignments.GroupBy(x => x.JudgeRole).Any(x => x.Count() > 1)) throw Oops.Bah("同一岗位只能分配一名裁判");
|
||||
if (input.Assignments.GroupBy(x => x.EventRefereeId > 0 ? x.EventRefereeId : x.RefereeId).Any(x => x.Count() > 1))
|
||||
throw Oops.Bah("同一场次一名裁判只能分配一个岗位");
|
||||
var candidates = await GetEventReferees(input.EventId);
|
||||
var slots = ParseRoleSections(rule?.JudgeRoleSchemaJson).SelectMany(x => x.Slots).ToList();
|
||||
if (slots.Count == 0) throw Oops.Bah("当前项目的计分规则未配置裁判类型");
|
||||
var slotMap = slots.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 同一裁判类型可分配多名裁判,先按类型归组再校验人数。
|
||||
var grouped = new Dictionary<string, List<SaveJudgeAssignmentItem>>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var item in input.Assignments)
|
||||
{
|
||||
var candidate = item.EventRefereeId > 0
|
||||
? candidates.FirstOrDefault(x => x.EventRefereeId == item.EventRefereeId)
|
||||
: candidates.FirstOrDefault(x => x.RefereeId == item.RefereeId);
|
||||
if (candidate == null) throw Oops.Bah("存在尚未关联到当前赛事的裁判");
|
||||
item.EventRefereeId = candidate.EventRefereeId;
|
||||
item.RefereeId = candidate.RefereeId;
|
||||
var requiredFlag = RoleFlag(item.JudgeRole);
|
||||
if (requiredFlag != 0 && (candidate.Roles & requiredFlag) == 0) throw Oops.Bah($"裁判{candidate.Name}不具备岗位{item.JudgeRole}资格");
|
||||
var roleType = slotMap.TryGetValue(item.JudgeRole, out var matched) ? matched.Code : NormalizeRoleType(item.JudgeRole);
|
||||
if (!slotMap.ContainsKey(roleType)) throw Oops.Bah($"裁判类型{item.JudgeRole}不属于当前项目的计分规则");
|
||||
if (!grouped.TryGetValue(roleType, out var list)) grouped[roleType] = list = new List<SaveJudgeAssignmentItem>();
|
||||
list.Add(item);
|
||||
}
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
var count = grouped.TryGetValue(slot.Code, out var list) ? list.Count : 0;
|
||||
if (count < slot.MinCount) throw Oops.Bah($"{slot.Name}至少需要分配 {slot.MinCount} 名裁判");
|
||||
if (slot.MaxCount.HasValue && count > slot.MaxCount.Value) throw Oops.Bah($"{slot.Name}最多只能分配 {slot.MaxCount.Value} 名裁判");
|
||||
}
|
||||
|
||||
var candidates = await GetEventReferees(input.EventId);
|
||||
var seats = new List<(JudgeRoleSlotOutput Slot, string SeatCode, EventRefereeOutput Candidate)>();
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (!grouped.TryGetValue(slot.Code, out var list)) continue;
|
||||
var seatNo = 0;
|
||||
foreach (var item in list)
|
||||
{
|
||||
var candidate = item.EventRefereeId > 0
|
||||
? candidates.FirstOrDefault(x => x.EventRefereeId == item.EventRefereeId)
|
||||
: candidates.FirstOrDefault(x => x.RefereeId == item.RefereeId);
|
||||
if (candidate == null) throw Oops.Bah("存在尚未关联到当前赛事的裁判");
|
||||
if (slot.RoleFlag != 0 && (candidate.Roles & slot.RoleFlag) == 0) throw Oops.Bah($"裁判{candidate.Name}不具备{slot.Name}资格");
|
||||
seatNo++;
|
||||
seats.Add((slot, slot.MaxCount == 1 ? slot.Code : $"{slot.Code}{seatNo}", candidate));
|
||||
}
|
||||
}
|
||||
var repeated = seats.GroupBy(x => x.Candidate.EventRefereeId).FirstOrDefault(x => x.Count() > 1);
|
||||
if (repeated != null) throw Oops.Bah($"裁判{repeated.First().Candidate.Name}在同一场次只能担任一个裁判席位");
|
||||
|
||||
await _assignmentRepository.SoftDeleteAsync(x => x.SessionId == input.SessionId && x.AssignmentStatus == 0);
|
||||
var now = DateTime.Now;
|
||||
var refereeIds = input.Assignments.Select(x => x.RefereeId).Distinct().ToList();
|
||||
var refereeIds = seats.Select(x => x.Candidate.RefereeId).Distinct().ToList();
|
||||
var refereeUsers = (await _refereeRepository.AsQueryable()
|
||||
.Where(x => refereeIds.Contains(x.Id)).ToListAsync())
|
||||
.ToDictionary(x => x.Id, x => x.AppUserId);
|
||||
await _assignmentRepository.InsertRangeAsync(input.Assignments.Select(x => new EventJudgeAssignment { Id = _idGenerator.NextId(), EventId = input.EventId,
|
||||
SessionId = input.SessionId, EventRefereeId = x.EventRefereeId, RefereeId = x.RefereeId,
|
||||
JudgeUserId = refereeUsers.GetValueOrDefault(x.RefereeId), JudgeRole = x.JudgeRole, AssignmentStatus = 0,
|
||||
await _assignmentRepository.InsertRangeAsync(seats.Select(x => new EventJudgeAssignment { Id = _idGenerator.NextId(), EventId = input.EventId,
|
||||
SessionId = input.SessionId, EventRefereeId = x.Candidate.EventRefereeId, RefereeId = x.Candidate.RefereeId,
|
||||
JudgeUserId = refereeUsers.GetValueOrDefault(x.Candidate.RefereeId), JudgeRole = x.SeatCode, JudgeRoleType = x.Slot.Code, AssignmentStatus = 0,
|
||||
Status = AvailabilityStatus.Enable, CreatedUserId = _authManager.UserId, CreatedTime = now }).ToList());
|
||||
}
|
||||
|
||||
@ -176,7 +210,51 @@ public class JudgeAssignmentService : IDynamicApiController, ITransient
|
||||
};
|
||||
}
|
||||
|
||||
private static int RoleFlag(string role) => role switch
|
||||
/// <summary>
|
||||
/// 解析计分规则的裁判席位配置。
|
||||
/// 配置中的每个 slot 代表一个裁判类型(DP、DM、DR、P、T 等),可分配多名裁判;
|
||||
/// 主裁固定 1 人,其余类型分配时不限人数(成绩按平均值计算),配置里显式给出 min/max 时以配置为准。
|
||||
/// </summary>
|
||||
private static List<JudgeRoleSectionOutput> ParseRoleSections(string? schemaJson)
|
||||
{
|
||||
var sections = new List<JudgeRoleSectionOutput>();
|
||||
if (string.IsNullOrWhiteSpace(schemaJson)) return sections;
|
||||
JArray array;
|
||||
try { array = JArray.Parse(schemaJson); } catch { return sections; }
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var node in array)
|
||||
{
|
||||
var section = new JudgeRoleSectionOutput { Title = node["title"]?.ToString() ?? node["name"]?.ToString() ?? "裁判" };
|
||||
foreach (var slotNode in node["slots"] as JArray ?? new JArray())
|
||||
{
|
||||
var code = slotNode["code"]?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(code)) continue;
|
||||
var roleType = NormalizeRoleType(code);
|
||||
// 同一类型在配置里重复出现(如历史 DP1/DP2 席位)时只保留一条,人数改由分配时决定。
|
||||
if (!seen.Add(roleType)) continue;
|
||||
section.Slots.Add(new JudgeRoleSlotOutput
|
||||
{
|
||||
Code = roleType,
|
||||
Name = slotNode["name"]?.ToString() ?? roleType,
|
||||
MinCount = Math.Max(slotNode["min"]?.Value<int?>() ?? 1, 0),
|
||||
MaxCount = slotNode["max"]?.Value<int?>() ?? (roleType == "T" ? (int?)1 : null),
|
||||
RoleFlag = RoleFlag(roleType)
|
||||
});
|
||||
}
|
||||
if (section.Slots.Count > 0) sections.Add(section);
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
/// <summary>把席位编码归一为裁判类型编码,兼容 DP1、P2 这类带席位序号的历史编码。</summary>
|
||||
private static string NormalizeRoleType(string? code)
|
||||
{
|
||||
var trimmed = (code ?? string.Empty).Trim().ToUpperInvariant();
|
||||
var letters = new string(trimmed.TakeWhile(ch => !char.IsDigit(ch)).ToArray());
|
||||
return letters.Length > 0 ? letters : trimmed;
|
||||
}
|
||||
|
||||
private static int RoleFlag(string roleType) => roleType switch
|
||||
{
|
||||
"T" => (int)RefereeRole.Chief,
|
||||
"P" => (int)RefereeRole.Performance,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user