523 lines
25 KiB
C#
523 lines
25 KiB
C#
using YD_Event.Application.Auth;
|
||
using YD_Event.Application.Scoring.Dtos;
|
||
|
||
namespace YD_Event.Application.Scoring;
|
||
|
||
/// <summary>
|
||
/// 裁判评分与成绩计算(后台记分台)
|
||
/// </summary>
|
||
[ApiDescriptionSettings("打分系统")]
|
||
public class JudgeScoreService : IDynamicApiController, ITransient
|
||
{
|
||
/// <summary>
|
||
/// 修正状态:0-已申请
|
||
/// </summary>
|
||
private const int CorrectionStatusApplied = 0;
|
||
|
||
/// <summary>
|
||
/// 修正状态:10-已批准
|
||
/// </summary>
|
||
private const int CorrectionStatusApproved = 10;
|
||
|
||
/// <summary>
|
||
/// 修正状态:20-已拒绝
|
||
/// </summary>
|
||
private const int CorrectionStatusRejected = 20;
|
||
|
||
/// <summary>
|
||
/// 修正状态:30-裁判修改中
|
||
/// </summary>
|
||
private const int CorrectionStatusEditing = 30;
|
||
|
||
/// <summary>
|
||
/// 修正状态:40-已完成
|
||
/// </summary>
|
||
private const int CorrectionStatusCompleted = 40;
|
||
|
||
/// <summary>
|
||
/// 修正状态:90-已取消
|
||
/// </summary>
|
||
private const int CorrectionStatusCancelled = 90;
|
||
|
||
/// <summary>
|
||
/// 评分单状态:20-已确认
|
||
/// </summary>
|
||
private const int SheetStatusConfirmed = 20;
|
||
|
||
/// <summary>
|
||
/// 场次状态:60-已结束
|
||
/// </summary>
|
||
private const int SessionStatusFinished = 60;
|
||
|
||
/// <summary>
|
||
/// 消息类型:1-成绩修正提醒
|
||
/// </summary>
|
||
private const int MessageTypeCorrection = 1;
|
||
|
||
private readonly ISqlSugarRepository<EventSession> _sessionRepository;
|
||
private readonly ISqlSugarRepository<EventSessionEntry> _entryRepository;
|
||
private readonly ISqlSugarRepository<EventVenue> _venueRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.ScoreRule> _ruleRepository;
|
||
private readonly ISqlSugarRepository<EventJudgeAssignment> _assignmentRepository;
|
||
private readonly ISqlSugarRepository<EventJudgeScoreSheet> _sheetRepository;
|
||
private readonly ISqlSugarRepository<EventScoreCorrection> _correctionRepository;
|
||
private readonly ISqlSugarRepository<EventJudgeMessage> _messageRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Referee> _refereeRepository;
|
||
private readonly ISqlSugarRepository<EventResult> _resultRepository;
|
||
private readonly EventScoreCalculationService _calculationService;
|
||
private readonly IIdGenerator _idGenerator;
|
||
private readonly AuthManager _authManager;
|
||
|
||
public JudgeScoreService(ISqlSugarRepository<EventSession> sessionRepository,
|
||
ISqlSugarRepository<EventSessionEntry> entryRepository, ISqlSugarRepository<EventVenue> venueRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.ScoreRule> ruleRepository,
|
||
ISqlSugarRepository<EventJudgeAssignment> assignmentRepository,
|
||
ISqlSugarRepository<EventJudgeScoreSheet> sheetRepository,
|
||
ISqlSugarRepository<EventScoreCorrection> correctionRepository,
|
||
ISqlSugarRepository<EventJudgeMessage> messageRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Referee> refereeRepository,
|
||
ISqlSugarRepository<EventResult> resultRepository,
|
||
EventScoreCalculationService calculationService, IIdGenerator idGenerator, AuthManager authManager)
|
||
{
|
||
_sessionRepository = sessionRepository; _entryRepository = entryRepository; _venueRepository = venueRepository;
|
||
_ruleRepository = ruleRepository; _assignmentRepository = assignmentRepository; _sheetRepository = sheetRepository;
|
||
_correctionRepository = correctionRepository; _messageRepository = messageRepository;
|
||
_refereeRepository = refereeRepository; _resultRepository = resultRepository;
|
||
_calculationService = calculationService; _idGenerator = idGenerator; _authManager = authManager;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取场次记分台。按当前已提交的评分单实时算分,不写库,可随时刷新查看进度。
|
||
/// </summary>
|
||
[HttpGet("judge-scores/board/{sessionId}")]
|
||
[DisplayName("获取场次记分台")]
|
||
public async Task<JudgeScoreBoardOutput> GetBoard(long sessionId)
|
||
{
|
||
var ctx = await _calculationService.LoadContextAsync(sessionId);
|
||
var outcomes = await _calculationService.EvaluateSessionAsync(sessionId, ctx);
|
||
return await BuildBoardAsync(ctx, outcomes);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重算整场成绩。所有应到裁判都已提交的出场写入成绩并按规则重排名次,未评满的出场只返回进度。
|
||
/// </summary>
|
||
[HttpPost("judge-scores/recalculate/{sessionId}")]
|
||
[DisplayName("重算场次成绩")]
|
||
[UnitOfWork]
|
||
public async Task<JudgeScoreBoardOutput> Recalculate(long sessionId)
|
||
{
|
||
var ctx = await _calculationService.LoadContextAsync(sessionId);
|
||
var outcomes = await _calculationService.RecalculateSessionAsync(sessionId,
|
||
EventScoreCalculationService.SourceTypeNormal, _authManager.UserId, ctx);
|
||
return await BuildBoardAsync(ctx, outcomes);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 成绩排名查询。按项目、组别筛选,名次为 0 的(未评满、退赛、取消资格)排在最后。
|
||
/// </summary>
|
||
[HttpGet("judge-scores/ranking")]
|
||
[DisplayName("成绩排名查询")]
|
||
public async Task<List<JudgeRankingRowOutput>> GetRanking([FromQuery] JudgeRankingQueryInput input)
|
||
{
|
||
if (input.EventId <= 0) throw Oops.Bah("请指定赛事");
|
||
var results = await _resultRepository.AsQueryable()
|
||
.Where(x => x.EventId == input.EventId && x.Status == AvailabilityStatus.Enable)
|
||
.WhereIF(input.ProjectId.HasValue, x => x.ProjectId == input.ProjectId!.Value)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.GroupName), x => x.GroupName == input.GroupName)
|
||
.WhereIF(input.ResultStatus.HasValue, x => x.ResultStatus == input.ResultStatus!.Value)
|
||
.ToListAsync();
|
||
|
||
return results
|
||
.OrderBy(x => x.ProjectId).ThenBy(x => x.GroupName)
|
||
// 名次 0 表示尚未参与排名,统一沉到组内末尾,避免占据第一名的位置。
|
||
.ThenBy(x => x.Rank == 0 ? int.MaxValue : x.Rank)
|
||
.ThenByDescending(x => x.TotalScore ?? 0m).ThenBy(x => x.ParticipantName)
|
||
.Select(x => new JudgeRankingRowOutput
|
||
{
|
||
ResultId = x.Id,
|
||
SessionEntryId = x.SessionEntryId,
|
||
ProjectId = x.ProjectId,
|
||
ProjectTypeName = x.ProjectTypeName ?? string.Empty,
|
||
GroupName = x.GroupName ?? string.Empty,
|
||
ParticipantName = x.ParticipantName ?? string.Empty,
|
||
Rank = x.Rank,
|
||
TotalScore = x.Score ?? string.Empty,
|
||
DifficultyScore = x.DifficultyScore,
|
||
PerformanceScore = x.PerformanceScore,
|
||
RequiredScore = x.RequiredScore,
|
||
DeductionScore = x.DeductionScore,
|
||
IsDisqualified = x.IsDisqualified,
|
||
ResultStatus = x.ResultStatus,
|
||
ResultStatusName = ResultStatusName(x.ResultStatus),
|
||
ResultVersion = x.ResultVersion,
|
||
ReviewUserId = x.ReviewUserId,
|
||
ReviewTime = x.ReviewTime
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认场次成绩。仅在全场都已评满时确认,确认后裁判端不能再自行申请修正。
|
||
/// </summary>
|
||
[HttpPost("judge-scores/confirm/{sessionId}")]
|
||
[DisplayName("确认场次成绩")]
|
||
[UnitOfWork]
|
||
public async Task<ConfirmSessionScoreOutput> ConfirmSession(long sessionId)
|
||
{
|
||
var session = await _sessionRepository.GetFirstAsync(x => x.Id == sessionId);
|
||
if (session == null) throw Oops.Bah("比赛场次不存在");
|
||
var ctx = await _calculationService.LoadContextAsync(session);
|
||
|
||
// 先只算不写:确认被拒时不留下任何写库痕迹,也不会把已确认的成绩打回待复核。
|
||
var preview = await _calculationService.EvaluateSessionAsync(sessionId, ctx);
|
||
var output = new ConfirmSessionScoreOutput
|
||
{
|
||
PendingEntries = preview.Where(x => !x.IsSkipped && !x.IsCompleted).Select(x => x.EntryNo).ToList()
|
||
};
|
||
// 有出场没评满就不确认,否则先确认的成绩会挡住后到的评分。
|
||
if (output.PendingEntries.Count > 0) return output;
|
||
|
||
var outcomes = await _calculationService.RecalculateSessionAsync(sessionId,
|
||
EventScoreCalculationService.SourceTypeNormal, _authManager.UserId, ctx);
|
||
|
||
var entryIds = outcomes.Select(x => (long?)x.SessionEntryId).ToList();
|
||
var results = await _resultRepository.GetListAsync(x => entryIds.Contains(x.SessionEntryId)
|
||
&& x.ResultStatus == EventScoreCalculationService.ResultStatusPendingReview);
|
||
var now = DateTime.Now;
|
||
foreach (var result in results)
|
||
{
|
||
result.ResultStatus = EventScoreCalculationService.ResultStatusConfirmed;
|
||
result.ReviewUserId = _authManager.UserId;
|
||
result.ReviewTime = now;
|
||
result.UpdatedUserId = _authManager.UserId;
|
||
result.UpdatedTime = now;
|
||
}
|
||
if (results.Count > 0) await _resultRepository.UpdateRangeAsync(results);
|
||
output.ConfirmedCount = results.Count;
|
||
|
||
// 评分单一并置为已确认,裁判端据此禁用修正入口。
|
||
var plainIds = outcomes.Select(x => x.SessionEntryId).ToList();
|
||
var sheets = await _sheetRepository.GetListAsync(x => plainIds.Contains(x.SessionEntryId)
|
||
&& x.IsCurrent && x.SheetStatus == EventScoreCalculationService.SheetStatusSubmitted);
|
||
foreach (var sheet in sheets)
|
||
{
|
||
sheet.SheetStatus = SheetStatusConfirmed;
|
||
sheet.UpdatedUserId = _authManager.UserId;
|
||
sheet.UpdatedTime = now;
|
||
}
|
||
if (sheets.Count > 0) await _sheetRepository.UpdateRangeAsync(sheets);
|
||
|
||
if (session.SessionStatus != SessionStatusFinished)
|
||
{
|
||
session.SessionStatus = SessionStatusFinished;
|
||
session.Version++;
|
||
session.EndTime ??= now;
|
||
session.UpdatedUserId = _authManager.UserId;
|
||
session.UpdatedTime = now;
|
||
await _sessionRepository.UpdateAsync(session);
|
||
}
|
||
output.SessionFinished = true;
|
||
return output;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取成绩修正申请列表
|
||
/// </summary>
|
||
[HttpGet("judge-scores/corrections/{eventId}")]
|
||
[DisplayName("获取成绩修正申请列表")]
|
||
public async Task<List<JudgeCorrectionOutput>> GetCorrections(long eventId, [FromQuery] int? correctionStatus = null)
|
||
{
|
||
var corrections = (await _correctionRepository.AsQueryable()
|
||
.Where(x => x.EventId == eventId)
|
||
.WhereIF(correctionStatus.HasValue, x => x.CorrectionStatus == correctionStatus!.Value)
|
||
.ToListAsync())
|
||
.OrderByDescending(x => x.CreatedTime).ToList();
|
||
if (corrections.Count == 0) return new List<JudgeCorrectionOutput>();
|
||
|
||
var sessionIds = corrections.Select(x => x.SessionId).Distinct().ToList();
|
||
var sessions = await _sessionRepository.GetListAsync(x => sessionIds.Contains(x.Id));
|
||
var ruleIds = sessions.Select(x => x.ScoreRuleId).Distinct().ToList();
|
||
var rules = await _ruleRepository.GetListAsync(x => ruleIds.Contains(x.Id));
|
||
// 每个场次的小数位取自各自的计分规则,避免跨规则显示口径不一致。
|
||
var configs = sessions.GroupBy(x => x.Id).ToDictionary(x => x.Key,
|
||
x => ScoreRuleConfigService.Parse(rules.FirstOrDefault(r => r.Id == x.First().ScoreRuleId)?.RuleConfigJson));
|
||
|
||
var entryIds = corrections.Select(x => x.SessionEntryId).Distinct().ToList();
|
||
var entries = await _entryRepository.GetListAsync(x => entryIds.Contains(x.Id));
|
||
var sheetIds = corrections.Select(x => x.OriginalScoreSheetId)
|
||
.Concat(corrections.Where(x => x.NewScoreSheetId.HasValue).Select(x => x.NewScoreSheetId!.Value))
|
||
.Distinct().ToList();
|
||
var sheets = await _sheetRepository.GetListAsync(x => sheetIds.Contains(x.Id));
|
||
var assignmentIds = corrections.Select(x => x.JudgeAssignmentId).Distinct().ToList();
|
||
var assignments = await _assignmentRepository.GetListAsync(x => assignmentIds.Contains(x.Id));
|
||
var refereeIds = assignments.Select(x => x.RefereeId).Distinct().ToList();
|
||
var referees = await _refereeRepository.GetListAsync(x => refereeIds.Contains(x.Id));
|
||
|
||
return corrections.Select(correction =>
|
||
{
|
||
var session = sessions.FirstOrDefault(x => x.Id == correction.SessionId);
|
||
var entry = entries.FirstOrDefault(x => x.Id == correction.SessionEntryId);
|
||
var original = sheets.FirstOrDefault(x => x.Id == correction.OriginalScoreSheetId);
|
||
var updated = correction.NewScoreSheetId.HasValue
|
||
? sheets.FirstOrDefault(x => x.Id == correction.NewScoreSheetId!.Value)
|
||
: null;
|
||
var assignment = assignments.FirstOrDefault(x => x.Id == correction.JudgeAssignmentId);
|
||
var referee = assignment == null ? null : referees.FirstOrDefault(x => x.Id == assignment.RefereeId);
|
||
var config = configs.TryGetValue(correction.SessionId, out var found) ? found : ScoreRuleConfigService.Parse(null);
|
||
return new JudgeCorrectionOutput
|
||
{
|
||
Id = correction.Id,
|
||
SessionId = correction.SessionId,
|
||
SessionName = session == null ? string.Empty : session.SessionName ?? session.SessionNo,
|
||
SessionEntryId = correction.SessionEntryId,
|
||
EntryNo = entry?.EntryNo ?? string.Empty,
|
||
ParticipantName = entry == null
|
||
? string.Empty
|
||
: string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName!,
|
||
OriginalScoreSheetId = correction.OriginalScoreSheetId,
|
||
NewScoreSheetId = correction.NewScoreSheetId,
|
||
JudgeRole = correction.JudgeRole,
|
||
RefereeName = referee?.Name ?? string.Empty,
|
||
OriginalScore = original == null ? string.Empty : config.Format(original.TotalScore),
|
||
NewScore = updated == null ? null : config.Format(updated.TotalScore),
|
||
ApplyReason = correction.ApplyReason,
|
||
ApproveRemark = correction.ApproveRemark,
|
||
CorrectionStatus = correction.CorrectionStatus,
|
||
CorrectionStatusName = CorrectionStatusName(correction.CorrectionStatus),
|
||
CreatedTime = correction.CreatedTime,
|
||
ApproveTime = correction.ApproveTime,
|
||
CompletedTime = correction.CompletedTime
|
||
};
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后台发起成绩修正。发起后该裁判可重新提交这张评分单,原评分在新评分提交时才失效。
|
||
/// </summary>
|
||
[HttpPost("judge-scores/corrections/initiate")]
|
||
[DisplayName("发起成绩修正")]
|
||
[UnitOfWork]
|
||
public async Task<long> InitiateCorrection([FromBody] InitiateJudgeCorrectionInput input)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(input.Reason)) throw Oops.Bah("请填写修正原因");
|
||
var sheet = await _sheetRepository.GetFirstAsync(x => x.Id == input.ScoreSheetId);
|
||
if (sheet == null) throw Oops.Bah("评分单不存在");
|
||
if (!sheet.IsCurrent || sheet.SheetStatus < EventScoreCalculationService.SheetStatusSubmitted)
|
||
throw Oops.Bah("该评分单不是当前生效的已提交评分,无需修正");
|
||
if (await HasOpenCorrectionAsync(sheet.Id)) throw Oops.Bah("该评分单已有修正流程在处理中");
|
||
|
||
var now = DateTime.Now;
|
||
var correction = new EventScoreCorrection
|
||
{
|
||
Id = _idGenerator.NextId(),
|
||
EventId = sheet.EventId,
|
||
SessionId = sheet.SessionId,
|
||
SessionEntryId = sheet.SessionEntryId,
|
||
OriginalScoreSheetId = sheet.Id,
|
||
JudgeAssignmentId = sheet.JudgeAssignmentId,
|
||
JudgeRole = sheet.JudgeRole,
|
||
ApplyUserId = _authManager.UserId,
|
||
ApplyReason = input.Reason.Trim(),
|
||
// 后台发起本身即代表已批准,裁判端可直接重新评分。
|
||
CorrectionStatus = CorrectionStatusApproved,
|
||
ApproveUserId = _authManager.UserId,
|
||
ApproveRemark = "赛事管理员发起",
|
||
ApproveTime = now,
|
||
CreatedUserId = _authManager.UserId,
|
||
CreatedTime = now
|
||
};
|
||
await _correctionRepository.InsertAsync(correction);
|
||
await NotifyRefereeAsync(correction, sheet, "成绩修正通知",
|
||
$"请对{sheet.JudgeRole}号位的评分重新提交,原因:{correction.ApplyReason}");
|
||
return correction.Id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 审批裁判提交的成绩修正申请
|
||
/// </summary>
|
||
[HttpPost("judge-scores/corrections/approve")]
|
||
[DisplayName("审批成绩修正申请")]
|
||
[UnitOfWork]
|
||
public async Task ApproveCorrection([FromBody] ApproveJudgeCorrectionInput input)
|
||
{
|
||
var correction = await _correctionRepository.GetFirstAsync(x => x.Id == input.CorrectionId);
|
||
if (correction == null) throw Oops.Bah("修正申请不存在");
|
||
if (correction.CorrectionStatus != CorrectionStatusApplied)
|
||
throw Oops.Bah($"该申请当前为“{CorrectionStatusName(correction.CorrectionStatus)}”,不能重复审批");
|
||
var sheet = await _sheetRepository.GetFirstAsync(x => x.Id == correction.OriginalScoreSheetId);
|
||
if (sheet == null) throw Oops.Bah("被修正的评分单不存在");
|
||
|
||
var now = DateTime.Now;
|
||
correction.CorrectionStatus = input.Approved ? CorrectionStatusApproved : CorrectionStatusRejected;
|
||
correction.ApproveUserId = _authManager.UserId;
|
||
correction.ApproveRemark = input.ApproveRemark;
|
||
correction.ApproveTime = now;
|
||
if (!input.Approved) correction.CompletedTime = now;
|
||
correction.UpdatedUserId = _authManager.UserId;
|
||
correction.UpdatedTime = now;
|
||
await _correctionRepository.UpdateAsync(correction);
|
||
|
||
await NotifyRefereeAsync(correction, sheet,
|
||
input.Approved ? "成绩修正申请已通过" : "成绩修正申请被驳回",
|
||
input.Approved
|
||
? $"{sheet.JudgeRole}号位的修正申请已通过,请重新提交评分。"
|
||
: $"{sheet.JudgeRole}号位的修正申请未通过。{input.ApproveRemark}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组装记分台数据,名次与成绩状态取已落库的成绩
|
||
/// </summary>
|
||
private async Task<JudgeScoreBoardOutput> BuildBoardAsync(ScoringContext ctx, List<EntryCalculationOutcome> outcomes)
|
||
{
|
||
var venue = await _venueRepository.GetFirstAsync(x => x.Id == ctx.Session.VenueId);
|
||
var output = new JudgeScoreBoardOutput
|
||
{
|
||
SessionId = ctx.Session.Id,
|
||
EventId = ctx.Session.EventId,
|
||
SessionName = ctx.Session.SessionName ?? ctx.Session.SessionNo,
|
||
VenueName = venue?.VenueName ?? string.Empty,
|
||
ProjectName = ctx.Project.Name,
|
||
RuleName = ctx.Rule.RuleName,
|
||
Formula = EntryResultCalculationService.BuildFormula(ctx.Config),
|
||
SessionStatus = ctx.Session.SessionStatus,
|
||
EntryCount = outcomes.Count,
|
||
CompletedCount = outcomes.Count(x => x.IsCompleted)
|
||
};
|
||
if (outcomes.Count == 0) return output;
|
||
|
||
var entryIds = outcomes.Select(x => (long?)x.SessionEntryId).ToList();
|
||
var results = await _resultRepository.GetListAsync(x => entryIds.Contains(x.SessionEntryId)
|
||
&& x.ResultStatus != EventScoreCalculationService.ResultStatusVoided);
|
||
// 一个出场(队伍)的多名运动员共享同一名次,取任意一条即可。
|
||
var resultByEntry = results.Where(x => x.SessionEntryId.HasValue)
|
||
.GroupBy(x => x.SessionEntryId!.Value).ToDictionary(x => x.Key, x => x.First());
|
||
|
||
foreach (var outcome in outcomes)
|
||
{
|
||
resultByEntry.TryGetValue(outcome.SessionEntryId, out var result);
|
||
var entry = new JudgeScoreBoardEntryOutput
|
||
{
|
||
SessionEntryId = outcome.SessionEntryId,
|
||
EntryNo = outcome.EntryNo,
|
||
ParticipantName = outcome.ParticipantName,
|
||
GroupName = outcome.GroupName,
|
||
IsCompleted = outcome.IsCompleted,
|
||
IsSkipped = outcome.IsSkipped,
|
||
SkipReason = outcome.SkipReason,
|
||
IsDisqualified = outcome.IsDisqualified,
|
||
TotalScore = outcome.IsDisqualified
|
||
? "取消资格"
|
||
: outcome.IsCompleted ? ctx.Config.Format(outcome.TotalScore) : string.Empty,
|
||
Rank = result?.Rank ?? 0,
|
||
ResultStatus = result?.ResultStatus ?? 0,
|
||
ResultStatusName = ResultStatusName(result?.ResultStatus ?? 0),
|
||
Progress = outcome.Roles.Select(role => new JudgeBoardRoleProgressOutput
|
||
{
|
||
RoleType = role.RoleType, RoleName = role.RoleName, Symbol = role.Symbol,
|
||
ExpectedCount = role.ExpectedCount, SubmittedCount = role.SubmittedCount,
|
||
PendingJudges = role.PendingJudges
|
||
}).ToList()
|
||
};
|
||
if (outcome.Aggregate != null)
|
||
{
|
||
entry.Symbols = outcome.Aggregate.Symbols.Select(symbol => new JudgeSymbolScoreOutput
|
||
{
|
||
Symbol = symbol.Symbol, Label = symbol.Label, RoleType = symbol.RoleType,
|
||
Aggregate = symbol.Aggregate, Sign = symbol.Sign, Value = ctx.Config.Format(symbol.Value),
|
||
Judges = symbol.Judges.Select(judge => new JudgeBoardSheetScoreOutput
|
||
{
|
||
ScoreSheetId = judge.ScoreSheetId, RefereeId = judge.RefereeId,
|
||
RefereeName = judge.JudgeName ?? string.Empty, JudgeRole = judge.JudgeRole,
|
||
JudgeRoleType = judge.JudgeRoleType, Value = ctx.Config.Format(judge.Value),
|
||
IsDisqualified = judge.IsDisqualified
|
||
}).ToList()
|
||
}).ToList();
|
||
}
|
||
output.Entries.Add(entry);
|
||
}
|
||
|
||
// 出场状态不在计算结果里,单独回填,前端据此区分退赛与未上场。
|
||
var plainIds = outcomes.Select(x => x.SessionEntryId).ToList();
|
||
var entries = await _entryRepository.GetListAsync(x => plainIds.Contains(x.Id));
|
||
foreach (var row in output.Entries)
|
||
{
|
||
var source = entries.FirstOrDefault(x => x.Id == row.SessionEntryId);
|
||
if (source == null) continue;
|
||
row.EntryStatus = source.EntryStatus;
|
||
row.PlayOrder = source.PlayOrder;
|
||
}
|
||
return output;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该评分单是否已有未结束的修正流程
|
||
/// </summary>
|
||
private async Task<bool> HasOpenCorrectionAsync(long scoreSheetId) =>
|
||
await _correctionRepository.IsAnyAsync(x => x.OriginalScoreSheetId == scoreSheetId
|
||
&& (x.CorrectionStatus == CorrectionStatusApplied
|
||
|| x.CorrectionStatus == CorrectionStatusApproved
|
||
|| x.CorrectionStatus == CorrectionStatusEditing));
|
||
|
||
/// <summary>
|
||
/// 给被修正的裁判发一条站内消息,裁判端在消息列表里看到修正提醒
|
||
/// </summary>
|
||
private async Task NotifyRefereeAsync(EventScoreCorrection correction, EventJudgeScoreSheet sheet,
|
||
string title, string content)
|
||
{
|
||
var receiverUserId = sheet.JudgeUserId;
|
||
if (!receiverUserId.HasValue)
|
||
{
|
||
var assignment = await _assignmentRepository.GetFirstAsync(x => x.Id == correction.JudgeAssignmentId);
|
||
receiverUserId = assignment?.JudgeUserId;
|
||
}
|
||
if (!receiverUserId.HasValue)
|
||
{
|
||
var referee = await _refereeRepository.GetFirstAsync(x => x.Id == sheet.RefereeId);
|
||
receiverUserId = referee?.AppUserId;
|
||
}
|
||
// 裁判还没绑定小程序账号时消息无处投递,此时只留修正记录,不阻断主流程。
|
||
if (!receiverUserId.HasValue) return;
|
||
|
||
await _messageRepository.InsertAsync(new EventJudgeMessage
|
||
{
|
||
Id = _idGenerator.NextId(),
|
||
EventId = correction.EventId,
|
||
ReceiverUserId = receiverUserId.Value,
|
||
RefereeId = sheet.RefereeId,
|
||
MessageType = MessageTypeCorrection,
|
||
Title = title,
|
||
Content = content.Length > 512 ? content[..512] : content,
|
||
RelationType = nameof(EventScoreCorrection),
|
||
RelationId = correction.Id,
|
||
IsRead = false,
|
||
CreatedUserId = _authManager.UserId,
|
||
CreatedTime = DateTime.Now
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 成绩状态名称
|
||
/// </summary>
|
||
private static string ResultStatusName(int status) => status switch
|
||
{
|
||
0 => "未生成",
|
||
EventScoreCalculationService.ResultStatusPendingReview => "待复核",
|
||
EventScoreCalculationService.ResultStatusConfirmed => "已确认",
|
||
EventScoreCalculationService.ResultStatusVoided => "已作废",
|
||
_ => status.ToString()
|
||
};
|
||
|
||
/// <summary>
|
||
/// 修正状态名称
|
||
/// </summary>
|
||
private static string CorrectionStatusName(int status) => status switch
|
||
{
|
||
CorrectionStatusApplied => "已申请",
|
||
CorrectionStatusApproved => "已批准",
|
||
CorrectionStatusRejected => "已拒绝",
|
||
CorrectionStatusEditing => "裁判修改中",
|
||
CorrectionStatusCompleted => "已完成",
|
||
CorrectionStatusCancelled => "已取消",
|
||
_ => status.ToString()
|
||
};
|
||
}
|