修复“单位报名的个人项目被合并、重复打分”问题
This commit is contained in:
parent
e632ab0d40
commit
50192c152b
@ -514,7 +514,9 @@ namespace YD_Event.Application.Event;
|
|||||||
ProjectName = proj.Name,
|
ProjectName = proj.Name,
|
||||||
ProjectType = proj.ProjectType.Description(),
|
ProjectType = proj.ProjectType.Description(),
|
||||||
GroupName = person.GroupName,
|
GroupName = person.GroupName,
|
||||||
TeamName = person.TeamName,
|
// 个人项目的出场单元是报名人,单位报名时携带的队伍/单位名不能当成团队名。
|
||||||
|
// 否则裁判端会优先显示该名称,导致同单位的多名运动员看起来是重复出场。
|
||||||
|
TeamName = null,
|
||||||
AthleteName = person.Name,
|
AthleteName = person.Name,
|
||||||
UnitName = displayUnitName,
|
UnitName = displayUnitName,
|
||||||
ProjectTotalDurationSeconds = projectTotalDurationSeconds,
|
ProjectTotalDurationSeconds = projectTotalDurationSeconds,
|
||||||
|
|||||||
@ -210,7 +210,7 @@ public class EventScoreCalculationService : ITransient
|
|||||||
|
|
||||||
if (persist)
|
if (persist)
|
||||||
{
|
{
|
||||||
var members = await LoadEntryMembersAsync(entries);
|
var members = await LoadEntryMembersAsync(entries, ctx.Scope);
|
||||||
foreach (var entry in written)
|
foreach (var entry in written)
|
||||||
{
|
{
|
||||||
var outcome = outcomes.First(x => x.SessionEntryId == entry.Id);
|
var outcome = outcomes.First(x => x.SessionEntryId == entry.Id);
|
||||||
@ -237,7 +237,7 @@ public class EventScoreCalculationService : ITransient
|
|||||||
var outcome = new EntryCalculationOutcome
|
var outcome = new EntryCalculationOutcome
|
||||||
{
|
{
|
||||||
SessionEntryId = entry.Id, EntryNo = entry.EntryNo,
|
SessionEntryId = entry.Id, EntryNo = entry.EntryNo,
|
||||||
ParticipantName = string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!,
|
ParticipantName = DisplayParticipantName(entry, ctx.Scope),
|
||||||
GroupName = entry.GroupName
|
GroupName = entry.GroupName
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -311,6 +311,15 @@ public class EventScoreCalculationService : ITransient
|
|||||||
var userId = operatorUserId ?? 0;
|
var userId = operatorUserId ?? 0;
|
||||||
|
|
||||||
var personIds = athletes.Select(x => x.Id).ToList();
|
var personIds = athletes.Select(x => x.Id).ToList();
|
||||||
|
if (!isTeam)
|
||||||
|
{
|
||||||
|
// 修复旧逻辑已经造成的串成绩:个人项一个出场号只允许归属当前报名人。
|
||||||
|
// 重算时作废同出场号下误写给其他人的成绩,并保留版本留痕。
|
||||||
|
var mismatchedResults = await _resultRepository.GetListAsync(x => x.SessionEntryId == entry.Id
|
||||||
|
&& x.ResultStatus != ResultStatusVoided && !personIds.Contains(x.RegistrationPersonId));
|
||||||
|
await VoidResultsAsync(mismatchedResults, operatorUserId);
|
||||||
|
}
|
||||||
|
|
||||||
// 同一名运动员可能因重赛等原因在同一项目中产生多条出场记录,
|
// 同一名运动员可能因重赛等原因在同一项目中产生多条出场记录,
|
||||||
// 成绩必须以“出场 + 运动员”为业务键更新,不能覆盖其他出场的历史成绩。
|
// 成绩必须以“出场 + 运动员”为业务键更新,不能覆盖其他出场的历史成绩。
|
||||||
var existing = await _resultRepository.GetListAsync(x => x.EventId == ctx.Session.EventId
|
var existing = await _resultRepository.GetListAsync(x => x.EventId == ctx.Session.EventId
|
||||||
@ -407,6 +416,14 @@ public class EventScoreCalculationService : ITransient
|
|||||||
private async Task VoidResultAsync(EventSessionEntry entry, long? operatorUserId)
|
private async Task VoidResultAsync(EventSessionEntry entry, long? operatorUserId)
|
||||||
{
|
{
|
||||||
var results = await _resultRepository.GetListAsync(x => x.SessionEntryId == entry.Id && x.ResultStatus != ResultStatusVoided);
|
var results = await _resultRepository.GetListAsync(x => x.SessionEntryId == entry.Id && x.ResultStatus != ResultStatusVoided);
|
||||||
|
await VoidResultsAsync(results, operatorUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 作废指定成绩及其当前版本,历史版本保留留痕。
|
||||||
|
/// </summary>
|
||||||
|
private async Task VoidResultsAsync(List<EventResult> results, long? operatorUserId)
|
||||||
|
{
|
||||||
if (results.Count == 0) return;
|
if (results.Count == 0) return;
|
||||||
var now = DateTime.Now;
|
var now = DateTime.Now;
|
||||||
foreach (var result in results)
|
foreach (var result in results)
|
||||||
@ -493,7 +510,8 @@ public class EventScoreCalculationService : ITransient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 取各出场对应的运动员名单。集体项目取同一报名项下的全部运动员,个人项目只有一人。
|
/// 取各出场对应的运动员名单。集体项目取同一报名项下的全部运动员,个人项目只有一人。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<Dictionary<long, List<EventRegistrationPerson>>> LoadEntryMembersAsync(List<EventSessionEntry> entries)
|
private async Task<Dictionary<long, List<EventRegistrationPerson>>> LoadEntryMembersAsync(
|
||||||
|
List<EventSessionEntry> entries, string scope)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<long, List<EventRegistrationPerson>>();
|
var result = new Dictionary<long, List<EventRegistrationPerson>>();
|
||||||
var itemIds = entries.Where(x => x.RegistrationItemId.HasValue).Select(x => x.RegistrationItemId!.Value).Distinct().ToList();
|
var itemIds = entries.Where(x => x.RegistrationItemId.HasValue).Select(x => x.RegistrationItemId!.Value).Distinct().ToList();
|
||||||
@ -505,7 +523,10 @@ public class EventScoreCalculationService : ITransient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 少数出场只关联了参赛记录(如现场补录),按参赛记录反查报名人。
|
// 少数出场只关联了参赛记录(如现场补录),按参赛记录反查报名人。
|
||||||
var recordIds = entries.Where(x => !x.RegistrationItemId.HasValue && x.ParticipationRecordId.HasValue)
|
// 个人项即使有 RegistrationItemId,也必须优先按 ParticipationRecordId 定位到唯一报名人。
|
||||||
|
// 单位报名会让多名个人项运动员共享一个 RegistrationItemId。
|
||||||
|
var recordIds = entries.Where(x => x.ParticipationRecordId.HasValue
|
||||||
|
&& (scope == ScoreScope.Personal || !x.RegistrationItemId.HasValue))
|
||||||
.Select(x => x.ParticipationRecordId!.Value).Distinct().ToList();
|
.Select(x => x.ParticipationRecordId!.Value).Distinct().ToList();
|
||||||
var personByRecord = new Dictionary<long, EventRegistrationPerson>();
|
var personByRecord = new Dictionary<long, EventRegistrationPerson>();
|
||||||
if (recordIds.Count > 0)
|
if (recordIds.Count > 0)
|
||||||
@ -523,7 +544,23 @@ public class EventScoreCalculationService : ITransient
|
|||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var members = new List<EventRegistrationPerson>();
|
var members = new List<EventRegistrationPerson>();
|
||||||
if (entry.RegistrationItemId.HasValue && personsByItem.TryGetValue(entry.RegistrationItemId.Value, out var candidates))
|
if (scope == ScoreScope.Personal)
|
||||||
|
{
|
||||||
|
if (entry.ParticipationRecordId.HasValue
|
||||||
|
&& personByRecord.TryGetValue(entry.ParticipationRecordId.Value, out var personal))
|
||||||
|
{
|
||||||
|
members.Add(personal);
|
||||||
|
}
|
||||||
|
else if (entry.RegistrationItemId.HasValue
|
||||||
|
&& personsByItem.TryGetValue(entry.RegistrationItemId.Value, out var personalCandidates))
|
||||||
|
{
|
||||||
|
// 只为无有效参赛记录的历史数据做姓名精确回退,绝不将整个报名项写成一个人的成绩。
|
||||||
|
var matched = personalCandidates.FirstOrDefault(x =>
|
||||||
|
string.Equals(x.Name, entry.ParticipantName, StringComparison.Ordinal));
|
||||||
|
if (matched != null) members.Add(matched);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entry.RegistrationItemId.HasValue && personsByItem.TryGetValue(entry.RegistrationItemId.Value, out var candidates))
|
||||||
{
|
{
|
||||||
// 报名人里可能同时含教练、领队,成绩只算运动员;没有标记运动员的历史数据全部计入。
|
// 报名人里可能同时含教练、领队,成绩只算运动员;没有标记运动员的历史数据全部计入。
|
||||||
members = candidates.Where(x => x.PersonType == PersonType.Athlete).OrderBy(x => x.Name).ToList();
|
members = candidates.Where(x => x.PersonType == PersonType.Athlete).OrderBy(x => x.Name).ToList();
|
||||||
@ -537,4 +574,12 @@ public class EventScoreCalculationService : ITransient
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 个人项始终显示出场人快照,只有集体项才使用队名。
|
||||||
|
/// </summary>
|
||||||
|
private static string DisplayParticipantName(EventSessionEntry entry, string scope) =>
|
||||||
|
scope == ScoreScope.Team && !string.IsNullOrWhiteSpace(entry.TeamName)
|
||||||
|
? entry.TeamName
|
||||||
|
: entry.ParticipantName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
private readonly ISqlSugarRepository<EventJudgeScoreAttachment> _attachmentRepository;
|
private readonly ISqlSugarRepository<EventJudgeScoreAttachment> _attachmentRepository;
|
||||||
private readonly ISqlSugarRepository<EventResult> _resultRepository;
|
private readonly ISqlSugarRepository<EventResult> _resultRepository;
|
||||||
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
|
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
|
||||||
|
private readonly ISqlSugarRepository<EventParticipationRecord> _participationRepository;
|
||||||
private readonly EventScoreCalculationService _calculationService;
|
private readonly EventScoreCalculationService _calculationService;
|
||||||
private readonly AuthManager _authManager;
|
private readonly AuthManager _authManager;
|
||||||
private readonly IIdGenerator _idGenerator;
|
private readonly IIdGenerator _idGenerator;
|
||||||
@ -65,6 +66,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
ISqlSugarRepository<EventJudgeScoreAttachment> attachmentRepository,
|
ISqlSugarRepository<EventJudgeScoreAttachment> attachmentRepository,
|
||||||
ISqlSugarRepository<EventResult> resultRepository,
|
ISqlSugarRepository<EventResult> resultRepository,
|
||||||
ISqlSugarRepository<EventRegistrationPerson> personRepository,
|
ISqlSugarRepository<EventRegistrationPerson> personRepository,
|
||||||
|
ISqlSugarRepository<EventParticipationRecord> participationRepository,
|
||||||
EventScoreCalculationService calculationService,
|
EventScoreCalculationService calculationService,
|
||||||
AuthManager authManager,
|
AuthManager authManager,
|
||||||
IIdGenerator idGenerator,
|
IIdGenerator idGenerator,
|
||||||
@ -88,6 +90,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
_attachmentRepository = attachmentRepository;
|
_attachmentRepository = attachmentRepository;
|
||||||
_resultRepository = resultRepository;
|
_resultRepository = resultRepository;
|
||||||
_personRepository = personRepository;
|
_personRepository = personRepository;
|
||||||
|
_participationRepository = participationRepository;
|
||||||
_calculationService = calculationService;
|
_calculationService = calculationService;
|
||||||
_authManager = authManager;
|
_authManager = authManager;
|
||||||
_idGenerator = idGenerator;
|
_idGenerator = idGenerator;
|
||||||
@ -268,8 +271,8 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
var assignment = await GetMyAssignmentAsync(sessionId, referee.Id);
|
var assignment = await GetMyAssignmentAsync(sessionId, referee.Id);
|
||||||
var session = await _sessionRepository.GetFirstAsync(x => x.Id == sessionId);
|
var session = await _sessionRepository.GetFirstAsync(x => x.Id == sessionId);
|
||||||
if (session == null) throw Oops.Bah("比赛场次不存在");
|
if (session == null) throw Oops.Bah("比赛场次不存在");
|
||||||
var config = ScoreRuleConfigService.Parse(
|
var ctx = await _calculationService.LoadContextAsync(sessionId);
|
||||||
(await _ruleRepository.GetFirstAsync(x => x.Id == session.ScoreRuleId))?.RuleConfigJson);
|
var config = ctx.Config;
|
||||||
|
|
||||||
var entries = (await _entryRepository.AsQueryable()
|
var entries = (await _entryRepository.AsQueryable()
|
||||||
.Where(x => x.SessionId == sessionId && !x.IsVoided &&
|
.Where(x => x.SessionId == sessionId && !x.IsVoided &&
|
||||||
@ -298,7 +301,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
EntryNo = entry.EntryNo,
|
EntryNo = entry.EntryNo,
|
||||||
PlayOrder = entry.PlayOrder,
|
PlayOrder = entry.PlayOrder,
|
||||||
ScheduleOrder = entry.SourceType == 1 ? entry.Id : 0,
|
ScheduleOrder = entry.SourceType == 1 ? entry.Id : 0,
|
||||||
ParticipantName = string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!,
|
ParticipantName = DisplayParticipantName(entry, ctx.Scope),
|
||||||
GroupName = entry.GroupName,
|
GroupName = entry.GroupName,
|
||||||
EntryStatus = entry.EntryStatus,
|
EntryStatus = entry.EntryStatus,
|
||||||
IsRematch = entry.IsRematch,
|
IsRematch = entry.IsRematch,
|
||||||
@ -348,7 +351,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
var stageAllowsScoring = CompetitionStagePolicy.CanScore(ctx.Session.SessionStatus, eventInfo.EventStatus);
|
var stageAllowsScoring = CompetitionStagePolicy.CanScore(ctx.Session.SessionStatus, eventInfo.EventStatus);
|
||||||
var scoreSheetAllowsEditing = submitted == null || correction != null;
|
var scoreSheetAllowsEditing = submitted == null || correction != null;
|
||||||
var canEdit = stageAllowsScoring && scoreSheetAllowsEditing;
|
var canEdit = stageAllowsScoring && scoreSheetAllowsEditing;
|
||||||
var members = await LoadMemberNamesAsync(entry);
|
var members = await LoadMemberNamesAsync(entry, ctx.Scope);
|
||||||
var venue = await _venueRepository.GetFirstAsync(x => x.Id == ctx.Session.VenueId);
|
var venue = await _venueRepository.GetFirstAsync(x => x.Id == ctx.Session.VenueId);
|
||||||
var nextVersion = (submitted?.SheetVersion ?? 0) + 1;
|
var nextVersion = (submitted?.SheetVersion ?? 0) + 1;
|
||||||
|
|
||||||
@ -379,7 +382,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
entry.GroupName,
|
entry.GroupName,
|
||||||
venue?.VenueName ?? "-"),
|
venue?.VenueName ?? "-"),
|
||||||
EntryNo = entry.EntryNo,
|
EntryNo = entry.EntryNo,
|
||||||
ParticipantName = string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!,
|
ParticipantName = DisplayParticipantName(entry, ctx.Scope),
|
||||||
MemberNames = members,
|
MemberNames = members,
|
||||||
JudgeRole = assignment.JudgeRole,
|
JudgeRole = assignment.JudgeRole,
|
||||||
JudgeRoleType = roleType,
|
JudgeRoleType = roleType,
|
||||||
@ -661,8 +664,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
ScoreSheetId = sheet.Id,
|
ScoreSheetId = sheet.Id,
|
||||||
SessionEntryId = sheet.SessionEntryId,
|
SessionEntryId = sheet.SessionEntryId,
|
||||||
EntryNo = entry?.EntryNo ?? string.Empty,
|
EntryNo = entry?.EntryNo ?? string.Empty,
|
||||||
ParticipantName = entry == null ? string.Empty
|
ParticipantName = entry == null ? string.Empty : DisplayParticipantName(entry, ctx.Scope),
|
||||||
: string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!,
|
|
||||||
MyScore = ctx.Config.Format(sheet.TotalScore),
|
MyScore = ctx.Config.Format(sheet.TotalScore),
|
||||||
SubmittedTime = sheet.SubmittedTime,
|
SubmittedTime = sheet.SubmittedTime,
|
||||||
CorrectionStatus = status,
|
CorrectionStatus = status,
|
||||||
@ -948,8 +950,28 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 取出场对应的运动员姓名,用于评分表表头展示
|
/// 取出场对应的运动员姓名,用于评分表表头展示
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<List<string>> LoadMemberNamesAsync(EventSessionEntry entry)
|
private async Task<List<string>> LoadMemberNamesAsync(EventSessionEntry entry, string scope)
|
||||||
{
|
{
|
||||||
|
// 单位可以在同一报名项下填报多名个人项运动员。
|
||||||
|
// 个人项必须通过出场记录关联的参赛记录精确取一人,不能按 RegistrationItemId 整体取人。
|
||||||
|
if (scope == ScoreScope.Personal)
|
||||||
|
{
|
||||||
|
if (entry.ParticipationRecordId.HasValue)
|
||||||
|
{
|
||||||
|
var record = await _participationRepository.GetFirstAsync(x => x.Id == entry.ParticipationRecordId.Value);
|
||||||
|
if (record != null)
|
||||||
|
{
|
||||||
|
var person = await _personRepository.GetFirstAsync(x => x.Id == record.RegistrationPersonId);
|
||||||
|
if (person != null) return new List<string> { person.Name };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 历史异常关联不扩大到同报名项的其他人,使用出场快照保证不串人。
|
||||||
|
return string.IsNullOrWhiteSpace(entry.ParticipantName)
|
||||||
|
? new List<string>()
|
||||||
|
: new List<string> { entry.ParticipantName };
|
||||||
|
}
|
||||||
|
|
||||||
if (!entry.RegistrationItemId.HasValue) return new List<string>();
|
if (!entry.RegistrationItemId.HasValue) return new List<string>();
|
||||||
var persons = await _personRepository.GetListAsync(x => x.RegistrationItemId == entry.RegistrationItemId.Value);
|
var persons = await _personRepository.GetListAsync(x => x.RegistrationItemId == entry.RegistrationItemId.Value);
|
||||||
var athletes = persons.Where(x => x.PersonType == PersonType.Athlete).ToList();
|
var athletes = persons.Where(x => x.PersonType == PersonType.Athlete).ToList();
|
||||||
@ -957,6 +979,15 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
return athletes.OrderBy(x => x.Name).Select(x => x.Name).ToList();
|
return athletes.OrderBy(x => x.Name).Select(x => x.Name).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 个人项始终显示出场人快照;只有集体项才优先显示队名。
|
||||||
|
/// 兼容修复前已经编排、TeamName 被误写为单位名的历史出场记录。
|
||||||
|
/// </summary>
|
||||||
|
private static string DisplayParticipantName(EventSessionEntry entry, string scope) =>
|
||||||
|
scope == ScoreScope.Team && !string.IsNullOrWhiteSpace(entry.TeamName)
|
||||||
|
? entry.TeamName
|
||||||
|
: entry.ParticipantName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 该评分单是否有正在处理中的修正申请
|
/// 该评分单是否有正在处理中的修正申请
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1025,7 +1056,7 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
sessionName = ctx.Session.SessionName ?? ctx.Session.SessionNo,
|
sessionName = ctx.Session.SessionName ?? ctx.Session.SessionNo,
|
||||||
entryNo = entry.EntryNo,
|
entryNo = entry.EntryNo,
|
||||||
groupName = entry.GroupName,
|
groupName = entry.GroupName,
|
||||||
participantName = string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName,
|
participantName = DisplayParticipantName(entry, ctx.Scope),
|
||||||
judgeRole = assignment.JudgeRole,
|
judgeRole = assignment.JudgeRole,
|
||||||
judgeRoleType = roleType,
|
judgeRoleType = roleType,
|
||||||
scope = ctx.Scope
|
scope = ctx.Scope
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user