diff --git a/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs b/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs index 2a8bc12..c9a271f 100644 --- a/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs +++ b/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs @@ -135,6 +135,11 @@ public class JudgeSessionDto /// 我已提交评分的出场数 /// public int ScoredCount { get; set; } + + /// + /// 场次在赛事编排中的顺序 + /// + public int ArrangementOrder { get; set; } } /// @@ -157,6 +162,11 @@ public class JudgeEntryDto /// public int PlayOrder { get; set; } + /// + /// 秩序册中的全局出场顺序;非秩序册数据为0 + /// + public long ScheduleOrder { get; set; } + /// /// 参赛者/参赛队名称 /// @@ -643,6 +653,11 @@ public class SaveJudgeScoreInput /// 各项评分录入值 /// public List Items { get; set; } = new(); + + /// + /// 主裁花样赛扣分系数(各扣分项合计值,不是最终得分系数 M) + /// + public decimal? DeductionCoefficient { get; set; } } /// @@ -660,6 +675,11 @@ public class SubmitJudgeScoreInput /// public List Items { get; set; } = new(); + /// + /// 主裁花样赛扣分系数(各扣分项合计值,不是最终得分系数 M) + /// + public decimal? DeductionCoefficient { get; set; } + /// /// 幂等键,取评分表接口返回值。弱网重试时同一幂等键只会提交一次。 /// diff --git a/YD_Event.Application/Judge/JudgeScoringController.cs b/YD_Event.Application/Judge/JudgeScoringController.cs index b5bf8d1..ed96ea6 100644 --- a/YD_Event.Application/Judge/JudgeScoringController.cs +++ b/YD_Event.Application/Judge/JudgeScoringController.cs @@ -162,14 +162,14 @@ public class JudgeScoringController : IDynamicApiController .ToListAsync()).ToDictionary(x => x.Id); sessions = sessions - .OrderBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject) + .OrderBy(x => GetArrangementSequence(x.SessionNo)) + .ThenBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject) && projects.TryGetValue(eventProject.ProjectId, out var project) ? project.SortIndex : int.MaxValue) .ThenBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject) ? eventProject.ProjectId : long.MaxValue) - .ThenBy(x => GetArrangementSequence(x.SessionNo)) .ThenBy(x => venues.TryGetValue(x.VenueId, out var venue) ? venue.Sort : int.MaxValue) .ThenBy(x => x.SessionNo) .ThenBy(x => x.Id) @@ -216,7 +216,11 @@ public class JudgeScoringController : IDynamicApiController JudgeRoleType = roleType, JudgeRoleName = slot?.Name ?? JudgeRoleSchemaService.RoleName(roleType), EntryCount = entries.Count(x => x.SessionId == session.Id), - ScoredCount = mySheets.Count(x => x.SessionId == session.Id) + ScoredCount = mySheets.Count(x => x.SessionId == session.Id), + ArrangementOrder = GetArrangementSequence(session.SessionNo) is var order + && order < int.MaxValue + ? order + : 0 }); } return rows; @@ -266,7 +270,10 @@ public class JudgeScoringController : IDynamicApiController var config = ScoreRuleConfigService.Parse( (await _ruleRepository.GetFirstAsync(x => x.Id == session.ScoreRuleId))?.RuleConfigJson); - var entries = (await _entryRepository.AsQueryable().Where(x => x.SessionId == sessionId).ToListAsync()) + var entries = (await _entryRepository.AsQueryable() + .Where(x => x.SessionId == sessionId && !x.IsVoided && + x.Status == AvailabilityStatus.Enable && !x.DeleteMark) + .ToListAsync()) .OrderBy(x => x.PlayOrder).ThenBy(x => x.Id).ToList(); if (entries.Count == 0) return new List(); var entryIds = entries.Select(x => x.Id).ToList(); @@ -289,6 +296,7 @@ public class JudgeScoringController : IDynamicApiController SessionEntryId = entry.Id, EntryNo = entry.EntryNo, PlayOrder = entry.PlayOrder, + ScheduleOrder = entry.SourceType == 1 ? entry.Id : 0, ParticipantName = string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!, GroupName = entry.GroupName, EntryStatus = entry.EntryStatus, @@ -364,7 +372,11 @@ public class JudgeScoringController : IDynamicApiController ProjectName = ctx.Project.Name, RuleName = ctx.Rule.RuleName, GroupName = entry.GroupName, - SessionVenue = $"{ctx.Session.SessionName ?? ctx.Session.SessionNo}-{venue?.VenueName ?? "-"}", + SessionVenue = BuildSessionVenue( + ctx.Session, + ctx.Project.Name, + entry.GroupName, + venue?.VenueName ?? "-"), EntryNo = entry.EntryNo, ParticipantName = string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!, MemberNames = members, @@ -440,6 +452,7 @@ public class JudgeScoringController : IDynamicApiController throw Oops.Bah("已提交评分,如需修改请先申请成绩修正"); var calculation = ScoreSheetCalculationService.Calculate(definitions, ToInputValues(input.Items), ctx.Config, false); + ValidateDeductionCoefficient(input.DeductionCoefficient, calculation, ctx.Config, roleType); var task = await EnsureTaskAsync(entry, ctx, assignment, roleType, submitted == null ? 0 : TaskTypeCorrection); var draft = await _sheetRepository.GetFirstAsync(x => x.SessionEntryId == entry.Id && x.JudgeAssignmentId == assignment.Id && x.SheetStatus == SheetStatusDraft); @@ -497,6 +510,7 @@ public class JudgeScoringController : IDynamicApiController // 正式提交才校验必填项与取值范围。 var calculation = ScoreSheetCalculationService.Calculate(definitions, ToInputValues(input.Items), ctx.Config, true); + ValidateDeductionCoefficient(input.DeductionCoefficient, calculation, ctx.Config, roleType); var task = await EnsureTaskAsync(entry, ctx, assignment, roleType, correction == null ? 0 : TaskTypeCorrection); var draft = await _sheetRepository.GetFirstAsync(x => x.SessionEntryId == entry.Id && x.JudgeAssignmentId == assignment.Id && x.SheetStatus == SheetStatusDraft); @@ -1145,6 +1159,31 @@ public class JudgeScoringController : IDynamicApiController CountValue = x.CountValue }).ToList(); + /// + /// 校验主裁端上传的扣分系数。最终得分系数 M 仍由平台按 1-扣分系数计算, + /// 这里不信任客户端结果,只用于确认页面明细与上传系数属于同一评分快照。 + /// + private static void ValidateDeductionCoefficient(decimal? uploaded, + ScoreSheetCalculationResult calculation, ScoreRuleConfig config, string roleType) + { + if (uploaded == null) return; // 兼容尚未升级的裁判端。 + if (!config.IsFactorTotal || !string.Equals( + JudgeRoleSchemaService.NormalizeRoleType(roleType), + JudgeRoleSchemaService.RoleChief, + StringComparison.OrdinalIgnoreCase)) + throw Oops.Bah("当前评分表不支持上传扣分系数"); + + if (uploaded < 0m || uploaded > 1m) + throw Oops.Bah("扣分系数必须在0至1之间"); + + // 明细行保留了递进扣分的未舍入小计,用它校验 0.125 等三位小数系数。 + var expected = calculation.Lines + .Where(x => x.ScoreDimension == ScoreDimensionCode.Deduction) + .Sum(x => x.Subtotal); + if (Math.Abs(uploaded.Value - expected) > 0.0005m) + throw Oops.Bah($"扣分系数与扣分明细不一致,应为{expected:0.###}"); + } + /// /// 组装合计预览 /// @@ -1275,6 +1314,43 @@ public class JudgeScoringController : IDynamicApiController : 0; } + /// + /// 构建裁判端展示用的“场次-场地”,去除秩序册导入时写入场次名的项目和组别信息。 + /// + private static string BuildSessionVenue( + EventSession session, + string projectName, + string groupName, + string venueName) + { + var sessionName = session.SessionName ?? string.Empty; + foreach (var redundant in new[] { projectName, groupName, venueName }) + { + if (!string.IsNullOrWhiteSpace(redundant)) + sessionName = sessionName.Replace( + redundant, + string.Empty, + StringComparison.OrdinalIgnoreCase); + } + + sessionName = Regex.Replace( + sessionName, + @"^[\s·|/_—–-]+|[\s·|/_—–-]+$", + string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(sessionName)) + { + var arrangementOrder = GetArrangementSequence(session.SessionNo); + sessionName = arrangementOrder > 0 && arrangementOrder < int.MaxValue + ? $"第{arrangementOrder}场" + : session.SessionNo; + } + + return string.IsNullOrWhiteSpace(venueName) + || sessionName.Contains(venueName, StringComparison.OrdinalIgnoreCase) + ? sessionName + : $"{sessionName}-{venueName}"; + } + /// /// 获取用于裁判场次选择的赛事实时状态,避免后台状态同步延迟导致比赛中场次不可见。 ///