2026-08-27 16:09:05 +08:00

1087 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using YD_Event.Application.Auth;
using YD_Event.Application.Workbench.Dtos;
using YD_Event.Core.Entities;
namespace YD_Event.Application.Workbench;
/// <summary>
/// 打分系统工作台接口(供工作台管理端调用)
/// </summary>
[ApiDescriptionSettings("打分系统工作台")]
[Authorize]
public class WorkbenchController : IDynamicApiController
{
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
private readonly ISqlSugarRepository<EventVenue> _venueRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventProjectGroup> _groupRepository;
private readonly ISqlSugarRepository<EventSession> _sessionRepository;
private readonly ISqlSugarRepository<EventSessionEntry> _entryRepository;
private readonly ISqlSugarRepository<EventJudgeAssignment> _assignmentRepository;
private readonly ISqlSugarRepository<EventJudgeScoreSheet> _sheetRepository;
private readonly ISqlSugarRepository<EventScoreCorrection> _correctionRepository;
private readonly ISqlSugarRepository<EventResult> _resultRepository;
private readonly ISqlSugarRepository<EventResultVersion> _resultVersionRepository;
private readonly ISqlSugarRepository<EventResultScreenCast> _screenCastRepository;
private readonly ISqlSugarRepository<EventJudgeTask> _taskRepository;
private readonly ISqlSugarRepository<EventJudgeMessage> _messageRepository;
private readonly ISqlSugarRepository<Core.Entities.AppUser> _userRepository;
private readonly ISqlSugarRepository<Core.Entities.Referee> _refereeRepository;
private readonly ISqlSugarRepository<Core.Entities.Project> _projectRepository;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
public WorkbenchController(
ISqlSugarRepository<EventInfo> eventRepository,
ISqlSugarRepository<EventVenue> venueRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventProjectGroup> groupRepository,
ISqlSugarRepository<EventSession> sessionRepository,
ISqlSugarRepository<EventSessionEntry> entryRepository,
ISqlSugarRepository<EventJudgeAssignment> assignmentRepository,
ISqlSugarRepository<EventJudgeScoreSheet> sheetRepository,
ISqlSugarRepository<EventScoreCorrection> correctionRepository,
ISqlSugarRepository<EventResult> resultRepository,
ISqlSugarRepository<EventResultVersion> resultVersionRepository,
ISqlSugarRepository<EventResultScreenCast> screenCastRepository,
ISqlSugarRepository<EventJudgeTask> taskRepository,
ISqlSugarRepository<EventJudgeMessage> messageRepository,
ISqlSugarRepository<Core.Entities.AppUser> userRepository,
ISqlSugarRepository<Core.Entities.Referee> refereeRepository,
ISqlSugarRepository<Core.Entities.Project> projectRepository,
AuthManager authManager,
IIdGenerator idGenerator)
{
_eventRepository = eventRepository;
_venueRepository = venueRepository;
_eventProjectRepository = eventProjectRepository;
_groupRepository = groupRepository;
_sessionRepository = sessionRepository;
_entryRepository = entryRepository;
_assignmentRepository = assignmentRepository;
_sheetRepository = sheetRepository;
_correctionRepository = correctionRepository;
_resultRepository = resultRepository;
_resultVersionRepository = resultVersionRepository;
_screenCastRepository = screenCastRepository;
_taskRepository = taskRepository;
_messageRepository = messageRepository;
_userRepository = userRepository;
_refereeRepository = refereeRepository;
_projectRepository = projectRepository;
_authManager = authManager;
_idGenerator = idGenerator;
}
#region 场次管理
/// <summary>
/// 获取工作台场次列表
/// </summary>
[HttpGet("workbench/sessions")]
public async Task<List<WorkbenchSessionDto>> GetSessions([FromQuery] WorkbenchSessionQueryInput input)
{
var query = _sessionRepository.AsQueryable()
.Where(s => !s.DeleteMark && s.EventId == input.EventId);
if (input.VenueId.HasValue)
query = query.Where(s => s.VenueId == input.VenueId.Value);
if (input.EventProjectId.HasValue)
query = query.Where(s => s.EventProjectId == input.EventProjectId.Value);
if (input.SessionStatus.HasValue)
query = query.Where(s => s.SessionStatus == input.SessionStatus.Value);
if (!string.IsNullOrWhiteSpace(input.Keyword))
{
var keyword = input.Keyword.Trim();
query = query.Where(s => s.SessionNo.Contains(keyword) || (s.SessionName != null && s.SessionName.Contains(keyword)));
}
var sessions = await query
.OrderBy(s => s.StartTime)
.OrderBy(s => s.SessionNo)
.Select(s => new
{
s.Id,
s.SessionNo,
s.SessionName,
s.EventId,
s.VenueId,
s.EventProjectId,
s.EventProjectGroupId,
s.StartTime,
s.EndTime,
s.SessionStatus,
s.CurrentEntryId
})
.ToListAsync();
if (!sessions.Any())
return new List<WorkbenchSessionDto>();
var eventIds = sessions.Select(s => s.EventId).Distinct().ToList();
var venueIds = sessions.Select(s => s.VenueId).Distinct().ToList();
var projectIds = sessions.Select(s => s.EventProjectId).Distinct().ToList();
var groupIds = sessions.Where(s => s.EventProjectGroupId.HasValue).Select(s => s.EventProjectGroupId!.Value).Distinct().ToList();
var sessionIds = sessions.Select(s => s.Id).ToList();
var events = await _eventRepository.AsQueryable()
.Where(e => eventIds.Contains(e.Id))
.Select(e => new { e.Id, e.EventName })
.ToListAsync();
var venues = await _venueRepository.AsQueryable()
.Where(v => venueIds.Contains(v.Id))
.Select(v => new { v.Id, v.VenueName })
.ToListAsync();
var eventProjects = await _eventProjectRepository.AsQueryable()
.Where(ep => projectIds.Contains(ep.Id))
.Select(ep => new { ep.Id, ep.ProjectId })
.ToListAsync();
var projectDict = (await _projectRepository.AsQueryable()
.Where(p => eventProjects.Select(ep => ep.ProjectId).Contains(p.Id))
.Select(p => new { p.Id, p.Name })
.ToListAsync())
.ToDictionary(p => p.Id, p => p.Name);
var groups = await _groupRepository.AsQueryable()
.Where(g => groupIds.Contains(g.Id))
.Select(g => new { g.Id, g.GroupName })
.ToListAsync();
var entryCounts = await _entryRepository.AsQueryable()
.Where(e => !e.DeleteMark && sessionIds.Contains(e.SessionId))
.GroupBy(e => e.SessionId)
.Select(e => new
{
SessionId = e.SessionId,
TotalCount = SqlFunc.AggregateCount(e.Id),
CompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF(e.EntryStatus == 40, 1, 0))
})
.ToListAsync();
var resultCounts = await _resultRepository.AsQueryable()
.InnerJoin<EventSessionEntry>((r, e) => r.SessionEntryId == e.Id)
.Where((r, e) => !r.DeleteMark && !e.DeleteMark && sessionIds.Contains(e.SessionId) && r.ResultStatus == 20)
.GroupBy((r, e) => e.SessionId)
.Select((r, e) => new
{
SessionId = e.SessionId,
ConfirmedCount = SqlFunc.AggregateCount(r.Id)
})
.ToListAsync();
var eventDict = events.ToDictionary(e => e.Id, e => e.EventName);
var venueDict = venues.ToDictionary(v => v.Id, v => v.VenueName);
var eventProjectDict = eventProjects.ToDictionary(ep => ep.Id, ep => projectDict.GetValueOrDefault(ep.ProjectId, ""));
var groupDict = groups.ToDictionary(g => g.Id, g => g.GroupName);
var entryCountDict = entryCounts.ToDictionary(ec => ec.SessionId);
var resultCountDict = resultCounts.ToDictionary(rc => rc.SessionId);
return sessions.Select(s => new WorkbenchSessionDto
{
SessionId = s.Id,
SessionNo = s.SessionNo,
SessionName = s.SessionName,
EventName = eventDict.GetValueOrDefault(s.EventId, ""),
ProjectName = eventProjectDict.GetValueOrDefault(s.EventProjectId, ""),
VenueName = venueDict.GetValueOrDefault(s.VenueId, ""),
GroupName = s.EventProjectGroupId.HasValue ? groupDict.GetValueOrDefault(s.EventProjectGroupId.Value) : null,
StartTime = s.StartTime,
EndTime = s.EndTime,
SessionStatus = s.SessionStatus,
SessionStatusName = GetSessionStatusName(s.SessionStatus),
CurrentEntryId = s.CurrentEntryId,
TotalEntries = entryCountDict.TryGetValue(s.Id, out var ec) ? ec.TotalCount : 0,
CompletedEntries = entryCountDict.TryGetValue(s.Id, out var ec2) ? ec2.CompletedCount : 0,
ConfirmedResults = resultCountDict.TryGetValue(s.Id, out var rc) ? rc.ConfirmedCount : 0
}).ToList();
}
/// <summary>
/// 获取场次的出场记录列表
/// </summary>
[HttpGet("workbench/sessions/{sessionId}/entries")]
public async Task<List<WorkbenchEntryDetailDto>> GetSessionEntries(long sessionId, [FromQuery] WorkbenchEntryQueryInput input)
{
var query = _entryRepository.AsQueryable()
.Where(e => !e.DeleteMark && e.SessionId == sessionId);
if (input.EntryStatus.HasValue)
query = query.Where(e => e.EntryStatus == input.EntryStatus.Value);
if (!string.IsNullOrWhiteSpace(input.Keyword))
{
var keyword = input.Keyword.Trim();
query = query.Where(e => e.EntryNo.Contains(keyword) || e.ParticipantName.Contains(keyword) ||
(e.TeamName != null && e.TeamName.Contains(keyword)));
}
var entries = await query
.OrderBy(e => e.PlayOrder)
.Select(e => new
{
e.Id,
e.EntryNo,
e.PlayOrder,
e.ParticipantName,
e.TeamName,
e.GroupName,
e.EntryStatus,
e.IsRematch
})
.ToListAsync();
if (!entries.Any())
return new List<WorkbenchEntryDetailDto>();
var entryIds = entries.Select(e => e.Id).ToList();
var results = await _resultRepository.AsQueryable()
.Where(r => !r.DeleteMark && entryIds.Contains(r.SessionEntryId!.Value))
.Select(r => new
{
r.Id,
r.SessionEntryId,
r.DifficultyScore,
r.PerformanceScore,
r.RequiredScore,
r.DeductionScore,
r.TotalScore,
r.Score,
r.IsDisqualified,
r.DisqualifyReasonCode,
r.ResultStatus,
r.ResultVersion,
r.Rank
})
.ToListAsync();
if (input.ResultStatus.HasValue)
{
var filteredEntryIds = results.Where(r => r.ResultStatus == input.ResultStatus.Value).Select(r => r.SessionEntryId).ToHashSet();
entries = entries.Where(e => filteredEntryIds.Contains(e.Id)).ToList();
entryIds = entries.Select(e => e.Id).ToList();
}
var sheets = await _sheetRepository.AsQueryable()
.Where(s => !s.DeleteMark && entryIds.Contains(s.SessionEntryId) && s.IsCurrent)
.OrderBy(s => s.JudgeRole)
.ToListAsync();
var refereeIds = sheets.Select(s => s.RefereeId).Distinct().ToList();
var refereeMap = (await _refereeRepository.AsQueryable()
.Where(r => refereeIds.Contains(r.Id))
.Select(r => new { r.Id, r.Name })
.ToListAsync())
.ToDictionary(r => r.Id, r => r.Name);
var pendingCorrections = await _correctionRepository.AsQueryable()
.Where(c => !c.DeleteMark && entryIds.Contains(c.SessionEntryId) && c.CorrectionStatus < 20)
.Select(c => c.OriginalScoreSheetId)
.ToListAsync();
var pendingCorrectionSet = pendingCorrections.ToHashSet();
var resultDict = results.ToDictionary(r => r.SessionEntryId!.Value);
return entries.Select(e =>
{
var result = resultDict.GetValueOrDefault(e.Id);
var entrySheets = sheets.Where(s => s.SessionEntryId == e.Id).ToList();
return new WorkbenchEntryDetailDto
{
SessionEntryId = e.Id,
EntryNo = e.EntryNo,
PlayOrder = e.PlayOrder,
ParticipantName = e.ParticipantName,
TeamName = e.TeamName,
GroupName = e.GroupName,
EntryStatus = e.EntryStatus,
EntryStatusName = GetEntryStatusName(e.EntryStatus),
IsRematch = e.IsRematch,
Result = result != null ? new WorkbenchEntryResultDto
{
ResultId = result.Id,
DifficultyScore = result.DifficultyScore,
PerformanceScore = result.PerformanceScore,
RequiredScore = result.RequiredScore,
DeductionScore = result.DeductionScore,
TotalScore = result.TotalScore,
ScoreText = result.Score,
IsDisqualified = result.IsDisqualified,
DisqualifyReason = result.DisqualifyReasonCode,
ResultStatus = result.ResultStatus,
ResultStatusName = GetResultStatusName(result.ResultStatus),
ResultVersion = result.ResultVersion,
Rank = result.Rank
} : null,
JudgeScores = entrySheets.Select(sheet => new WorkbenchJudgeScoreDto
{
ScoreSheetId = sheet.Id,
JudgeRole = sheet.JudgeRole,
JudgeRoleType = sheet.JudgeRoleType,
JudgeRoleName = GetJudgeRoleTypeName(sheet.JudgeRoleType),
JudgeName = refereeMap.GetValueOrDefault(sheet.RefereeId, ""),
SheetStatus = sheet.SheetStatus,
SheetStatusName = GetSheetStatusName(sheet.SheetStatus),
DifficultyScore = sheet.DifficultyScore,
PerformanceScore = sheet.PerformanceScore,
RequiredScore = sheet.RequiredScore,
DeductionScore = sheet.DeductionScore,
TotalScore = sheet.TotalScore,
IsDisqualified = sheet.IsDisqualified,
SubmittedTime = sheet.SubmittedTime,
HasPendingCorrection = pendingCorrectionSet.Contains(sheet.Id)
}).ToList()
};
}).ToList();
}
#endregion
#region 成绩投屏
/// <summary>
/// 投出成绩到大屏
/// </summary>
[HttpPost("workbench/screencast")]
public async Task<ResultScreenCastOutput> CastResult([FromBody] ResultScreenCastInput input)
{
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
if (input.CastType == 1)
{
if (!input.SessionEntryId.HasValue || !input.EventResultId.HasValue)
throw Oops.Bah("投出单个成绩时,必须提供出场记录ID和成绩ID");
var entry = await _entryRepository.GetFirstAsync(e => e.Id == input.SessionEntryId.Value && !e.DeleteMark)
?? throw Oops.Bah("出场记录不存在");
var result = await _resultRepository.GetFirstAsync(r => r.Id == input.EventResultId.Value && !r.DeleteMark)
?? throw Oops.Bah("成绩记录不存在");
var session = await _sessionRepository.GetByIdAsync(entry.SessionId)
?? throw Oops.Bah("场次不存在");
var payload = new
{
EntryNo = entry.EntryNo,
ParticipantName = entry.ParticipantName,
TeamName = entry.TeamName,
GroupName = entry.GroupName,
DifficultyScore = result.DifficultyScore,
PerformanceScore = result.PerformanceScore,
RequiredScore = result.RequiredScore,
DeductionScore = result.DeductionScore,
TotalScore = result.TotalScore,
Score = result.Score,
IsDisqualified = result.IsDisqualified,
Rank = result.Rank
};
await _screenCastRepository.AsUpdateable()
.SetColumns(sc => sc.CastStatus == 20)
.Where(sc => sc.EventId == input.EventId &&
(!input.VenueId.HasValue || sc.VenueId == input.VenueId.Value) &&
sc.CastStatus == 0)
.ExecuteCommandAsync();
var screenCast = new EventResultScreenCast
{
Id = _idGenerator.NextId(),
EventId = input.EventId,
CastType = 1,
VenueId = input.VenueId,
EventProjectId = session.EventProjectId,
EventProjectGroupId = session.EventProjectGroupId,
SessionId = session.Id,
SessionEntryId = entry.Id,
EventResultId = result.Id,
ResultVersion = input.ResultVersion ?? result.ResultVersion,
PayloadJson = System.Text.Json.JsonSerializer.Serialize(payload),
CastStatus = 0,
CastTime = now,
CreatedUserId = currentUserId,
CreatedTime = now
};
await _screenCastRepository.InsertAsync(screenCast);
return new ResultScreenCastOutput
{
ScreenCastId = screenCast.Id,
CastType = 1,
CastTime = now,
PayloadJson = screenCast.PayloadJson
};
}
else if (input.CastType == 2)
{
if (!input.EventProjectId.HasValue || !input.EventProjectGroupId.HasValue)
throw Oops.Bah("投出排名时,必须提供项目ID和组别ID");
var results = await _resultRepository.AsQueryable()
.InnerJoin<EventSessionEntry>((r, e) => r.SessionEntryId == e.Id)
.InnerJoin<EventSession>((r, e, s) => e.SessionId == s.Id)
.Where((r, e, s) => !r.DeleteMark && !e.DeleteMark && !s.DeleteMark &&
r.EventId == input.EventId &&
s.EventProjectId == input.EventProjectId.Value &&
(s.EventProjectGroupId == input.EventProjectGroupId.Value ||
e.GroupName == SqlFunc.Subqueryable<EventProjectGroup>()
.Where(g => g.Id == input.EventProjectGroupId.Value)
.Select(g => g.GroupName)) &&
r.ResultStatus >= 20)
.OrderBy((r, e, s) => r.Rank)
.Select((r, e, s) => new
{
r.Id,
e.EntryNo,
e.ParticipantName,
e.TeamName,
e.GroupName,
r.TotalScore,
r.Score,
r.Rank,
r.IsDisqualified
})
.ToListAsync();
var payload = results.Select(r => new
{
r.EntryNo,
r.ParticipantName,
r.TeamName,
r.GroupName,
r.TotalScore,
r.Score,
r.Rank,
r.IsDisqualified
}).ToList();
await _screenCastRepository.AsUpdateable()
.SetColumns(sc => sc.CastStatus == 20)
.Where(sc => sc.EventId == input.EventId &&
(!input.VenueId.HasValue || sc.VenueId == input.VenueId.Value) &&
sc.CastStatus == 0)
.ExecuteCommandAsync();
var screenCast = new EventResultScreenCast
{
Id = _idGenerator.NextId(),
EventId = input.EventId,
CastType = 2,
VenueId = input.VenueId,
EventProjectId = input.EventProjectId.Value,
EventProjectGroupId = input.EventProjectGroupId.Value,
SessionId = null,
SessionEntryId = null,
EventResultId = null,
ResultVersion = null,
PayloadJson = System.Text.Json.JsonSerializer.Serialize(payload),
CastStatus = 0,
CastTime = now,
CreatedUserId = currentUserId,
CreatedTime = now
};
await _screenCastRepository.InsertAsync(screenCast);
return new ResultScreenCastOutput
{
ScreenCastId = screenCast.Id,
CastType = 2,
CastTime = now,
PayloadJson = screenCast.PayloadJson
};
}
else
{
throw Oops.Bah("无效的投屏类型");
}
}
/// <summary>
/// 撤回投屏内容
/// </summary>
[HttpPost("workbench/screencast/withdraw")]
public async Task WithdrawScreenCast([FromBody] WithdrawScreenCastInput input)
{
var screenCast = await _screenCastRepository.GetByIdAsync(input.ScreenCastId)
?? throw Oops.Bah("投屏记录不存在");
if (screenCast.CastStatus != 0)
throw Oops.Bah("该投屏已被撤回或替换,无法再次撤回");
screenCast.CastStatus = 10;
screenCast.UpdatedUserId = _authManager.UserId;
screenCast.UpdatedTime = DateTime.Now;
await _screenCastRepository.UpdateAsync(screenCast);
}
/// <summary>
/// 获取当前投屏内容
/// </summary>
[HttpGet("workbench/screencast/current")]
public async Task<CurrentScreenCastDto?> GetCurrentScreenCast([FromQuery] GetCurrentScreenCastInput input)
{
var query = _screenCastRepository.AsQueryable()
.Where(sc => sc.EventId == input.EventId && sc.CastStatus == 0);
if (input.VenueId.HasValue)
query = query.Where(sc => sc.VenueId == input.VenueId.Value);
var screenCast = await query
.OrderByDescending(sc => sc.CastTime)
.FirstAsync();
if (screenCast == null)
return null;
return new CurrentScreenCastDto
{
ScreenCastId = screenCast.Id,
CastType = screenCast.CastType,
CastStatus = screenCast.CastStatus,
CastTime = screenCast.CastTime,
PayloadJson = screenCast.PayloadJson
};
}
#endregion
#region 成绩修正审批
/// <summary>
/// 获取成绩修正申请列表
/// </summary>
[HttpGet("workbench/corrections")]
public async Task<List<ScoreCorrectionListDto>> GetCorrections([FromQuery] ScoreCorrectionQueryInput input)
{
var query = _correctionRepository.AsQueryable()
.Where(c => !c.DeleteMark && c.EventId == input.EventId);
if (input.CorrectionStatus.HasValue)
query = query.Where(c => c.CorrectionStatus == input.CorrectionStatus.Value);
if (!string.IsNullOrWhiteSpace(input.Keyword))
{
var keyword = input.Keyword.Trim();
query = query.Where(c => c.ApplyReason.Contains(keyword));
}
var corrections = await query
.OrderByDescending(c => c.CreatedTime)
.ToListAsync();
if (!corrections.Any())
return new List<ScoreCorrectionListDto>();
var sessionIds = corrections.Select(c => c.SessionId).Distinct().ToList();
var entryIds = corrections.Select(c => c.SessionEntryId).Distinct().ToList();
var applyUserIds = corrections.Select(c => c.ApplyUserId).Distinct().ToList();
var approveUserIds = corrections.Where(c => c.ApproveUserId.HasValue).Select(c => c.ApproveUserId!.Value).Distinct().ToList();
var sessions = (await _sessionRepository.AsQueryable()
.Where(s => sessionIds.Contains(s.Id))
.Select(s => new { s.Id, s.SessionNo })
.ToListAsync())
.ToDictionary(s => s.Id, s => s.SessionNo);
var entries = (await _entryRepository.AsQueryable()
.Where(e => entryIds.Contains(e.Id))
.Select(e => new { e.Id, e.EntryNo, e.ParticipantName })
.ToListAsync())
.ToDictionary(e => e.Id);
var eventInfo = await _eventRepository.GetFirstAsync(e => e.Id == input.EventId);
var allUserIds = applyUserIds.Concat(approveUserIds).Distinct().ToList();
var users = (await _userRepository.AsQueryable()
.Where(u => allUserIds.Contains(u.Id))
.Select(u => new { u.Id, u.Name })
.ToListAsync())
.ToDictionary(u => u.Id, u => u.Name);
return corrections.Select(c =>
{
var entry = entries.GetValueOrDefault(c.SessionEntryId);
return new ScoreCorrectionListDto
{
CorrectionId = c.Id,
EventName = eventInfo?.EventName ?? "",
SessionNo = sessions.GetValueOrDefault(c.SessionId, ""),
EntryNo = entry?.EntryNo ?? "",
ParticipantName = entry?.ParticipantName ?? "",
JudgeRole = c.JudgeRole,
ApplyUserName = users.GetValueOrDefault(c.ApplyUserId, ""),
ApplyReason = c.ApplyReason,
CreatedTime = c.CreatedTime,
CorrectionStatus = c.CorrectionStatus,
CorrectionStatusName = GetCorrectionStatusName(c.CorrectionStatus),
ApproveUserName = c.ApproveUserId.HasValue ? users.GetValueOrDefault(c.ApproveUserId.Value, "") : null,
ApproveTime = c.ApproveTime,
CompletedTime = c.CompletedTime
};
}).ToList();
}
/// <summary>
/// 获取成绩修正申请详情
/// </summary>
[HttpGet("workbench/corrections/{correctionId}")]
public async Task<ScoreCorrectionDetailDto> GetCorrectionDetail(long correctionId)
{
var correction = await _correctionRepository.GetFirstAsync(c => c.Id == correctionId && !c.DeleteMark)
?? throw Oops.Bah("修正申请不存在");
var session = await _sessionRepository.GetByIdAsync(correction.SessionId)
?? throw Oops.Bah("场次不存在");
var entry = await _entryRepository.GetByIdAsync(correction.SessionEntryId)
?? throw Oops.Bah("出场记录不存在");
var originalSheet = await _sheetRepository.GetByIdAsync(correction.OriginalScoreSheetId)
?? throw Oops.Bah("原评分单不存在");
EventJudgeScoreSheet? newSheet = null;
if (correction.NewScoreSheetId.HasValue)
{
newSheet = await _sheetRepository.GetByIdAsync(correction.NewScoreSheetId.Value);
}
var referee = await _refereeRepository.GetByIdAsync(originalSheet.RefereeId);
var eventInfo = await _eventRepository.GetByIdAsync(correction.EventId);
var venue = await _venueRepository.GetByIdAsync(session.VenueId);
var eventProject = await _eventProjectRepository.GetByIdAsync(session.EventProjectId);
var project = eventProject != null ? await _projectRepository.GetByIdAsync(eventProject.ProjectId) : null;
var applyUser = await _userRepository.GetByIdAsync(correction.ApplyUserId);
var approveUser = correction.ApproveUserId.HasValue
? await _userRepository.GetByIdAsync(correction.ApproveUserId.Value)
: null;
var result = await _resultRepository.GetFirstAsync(r => r.SessionEntryId == entry.Id && !r.DeleteMark);
return new ScoreCorrectionDetailDto
{
CorrectionId = correction.Id,
Session = new WorkbenchSessionDto
{
SessionId = session.Id,
SessionNo = session.SessionNo,
SessionName = session.SessionName,
EventName = eventInfo?.EventName ?? "",
VenueName = venue?.VenueName ?? "",
ProjectName = project?.Name ?? "",
StartTime = session.StartTime,
SessionStatus = session.SessionStatus,
SessionStatusName = GetSessionStatusName(session.SessionStatus)
},
Entry = new WorkbenchEntryDetailDto
{
SessionEntryId = entry.Id,
EntryNo = entry.EntryNo,
PlayOrder = entry.PlayOrder,
ParticipantName = entry.ParticipantName,
TeamName = entry.TeamName,
GroupName = entry.GroupName,
EntryStatus = entry.EntryStatus,
EntryStatusName = GetEntryStatusName(entry.EntryStatus),
IsRematch = entry.IsRematch,
Result = result != null ? new WorkbenchEntryResultDto
{
ResultId = result.Id,
DifficultyScore = result.DifficultyScore,
PerformanceScore = result.PerformanceScore,
RequiredScore = result.RequiredScore,
DeductionScore = result.DeductionScore,
TotalScore = result.TotalScore,
ScoreText = result.Score,
IsDisqualified = result.IsDisqualified,
ResultStatus = result.ResultStatus,
ResultStatusName = GetResultStatusName(result.ResultStatus),
ResultVersion = result.ResultVersion
} : null
},
OriginalScore = new WorkbenchJudgeScoreDto
{
ScoreSheetId = originalSheet.Id,
JudgeRole = originalSheet.JudgeRole,
JudgeRoleType = originalSheet.JudgeRoleType,
JudgeRoleName = GetJudgeRoleTypeName(originalSheet.JudgeRoleType),
JudgeName = referee?.Name ?? "",
SheetStatus = originalSheet.SheetStatus,
SheetStatusName = GetSheetStatusName(originalSheet.SheetStatus),
DifficultyScore = originalSheet.DifficultyScore,
PerformanceScore = originalSheet.PerformanceScore,
RequiredScore = originalSheet.RequiredScore,
DeductionScore = originalSheet.DeductionScore,
TotalScore = originalSheet.TotalScore,
IsDisqualified = originalSheet.IsDisqualified,
SubmittedTime = originalSheet.SubmittedTime,
HasPendingCorrection = false
},
NewScore = newSheet != null ? new WorkbenchJudgeScoreDto
{
ScoreSheetId = newSheet.Id,
JudgeRole = newSheet.JudgeRole,
JudgeRoleType = newSheet.JudgeRoleType,
JudgeRoleName = GetJudgeRoleTypeName(newSheet.JudgeRoleType),
JudgeName = referee?.Name ?? "",
SheetStatus = newSheet.SheetStatus,
SheetStatusName = GetSheetStatusName(newSheet.SheetStatus),
DifficultyScore = newSheet.DifficultyScore,
PerformanceScore = newSheet.PerformanceScore,
RequiredScore = newSheet.RequiredScore,
DeductionScore = newSheet.DeductionScore,
TotalScore = newSheet.TotalScore,
IsDisqualified = newSheet.IsDisqualified,
SubmittedTime = newSheet.SubmittedTime,
HasPendingCorrection = false
} : null,
ApplyUserName = applyUser?.Name ?? "",
ApplyReason = correction.ApplyReason,
CreatedTime = correction.CreatedTime,
CorrectionStatus = correction.CorrectionStatus,
ApproveUserName = approveUser?.Name,
ApproveRemark = correction.ApproveRemark,
ApproveTime = correction.ApproveTime,
CompletedTime = correction.CompletedTime
};
}
/// <summary>
/// 审批成绩修正申请
/// </summary>
[HttpPost("workbench/corrections/approve")]
public async Task ApproveCorrection([FromBody] ApproveScoreCorrectionInput input)
{
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var correction = await _correctionRepository.GetFirstAsync(c => c.Id == input.CorrectionId && !c.DeleteMark)
?? throw Oops.Bah("修正申请不存在");
if (correction.CorrectionStatus != 0)
throw Oops.Bah("该申请已被处理,无法重复审批");
correction.ApproveUserId = currentUserId;
correction.ApproveRemark = input.ApproveRemark;
correction.ApproveTime = now;
correction.CorrectionStatus = input.IsApproved ? 10 : 20;
correction.UpdatedUserId = currentUserId;
correction.UpdatedTime = now;
await _correctionRepository.UpdateAsync(correction);
if (input.IsApproved)
{
var assignment = await _assignmentRepository.GetByIdAsync(correction.JudgeAssignmentId);
var task = new EventJudgeTask
{
Id = _idGenerator.NextId(),
EventId = correction.EventId,
SessionId = correction.SessionId,
SessionEntryId = correction.SessionEntryId,
JudgeAssignmentId = correction.JudgeAssignmentId,
EventRefereeId = assignment?.EventRefereeId ?? 0,
RefereeId = assignment?.RefereeId ?? 0,
JudgeUserId = assignment?.JudgeUserId,
JudgeRole = assignment?.JudgeRole ?? "",
JudgeRoleType = assignment?.JudgeRoleType ?? "",
TaskSnapshotJson = "{}",
TaskType = 1,
TaskStatus = 0,
CreatedUserId = currentUserId,
CreatedTime = now
};
await _taskRepository.InsertAsync(task);
correction.CorrectionTaskId = task.Id;
await _correctionRepository.UpdateAsync(correction);
if (assignment?.JudgeUserId != null)
{
var message = new EventJudgeMessage
{
Id = _idGenerator.NextId(),
EventId = correction.EventId,
ReceiverUserId = assignment.JudgeUserId.Value,
RefereeId = assignment.RefereeId,
MessageType = 1,
Title = "成绩修正任务通知",
Content = $"您有一条成绩修正任务需要处理,申请原因:{correction.ApplyReason}",
RelationType = "ScoreCorrection",
RelationId = correction.Id,
IsRead = false,
CreatedUserId = currentUserId,
CreatedTime = now
};
await _messageRepository.InsertAsync(message);
}
}
}
#endregion
#region 成绩管理
/// <summary>
/// 确认成绩
/// </summary>
[HttpPost("workbench/results/confirm")]
public async Task ConfirmResult([FromBody] ConfirmResultInput input)
{
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var result = await _resultRepository.GetFirstAsync(r => r.Id == input.ResultId && !r.DeleteMark)
?? throw Oops.Bah("成绩记录不存在");
if (result.ResultStatus == 90)
throw Oops.Bah("该成绩已作废");
if (result.ResultStatus != 10)
throw Oops.Bah("只能确认待复核状态的成绩");
result.ResultStatus = 20;
result.UpdatedUserId = currentUserId;
result.UpdatedTime = now;
await _resultRepository.UpdateAsync(result);
}
/// <summary>
/// 批量确认成绩
/// </summary>
[HttpPost("workbench/results/batch-confirm")]
public async Task BatchConfirmResults([FromBody] BatchConfirmResultInput input)
{
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var query = _resultRepository.AsQueryable()
.InnerJoin<EventSessionEntry>((r, e) => r.SessionEntryId == e.Id)
.Where((r, e) => !r.DeleteMark && !e.DeleteMark && e.SessionId == input.SessionId && r.ResultStatus == 10);
if (input.ResultIds != null && input.ResultIds.Any())
{
query = query.Where((r, e) => input.ResultIds.Contains(r.Id));
}
var results = await query.Select((r, e) => r).ToListAsync();
if (!results.Any())
throw Oops.Bah("没有可确认的成绩");
foreach (var result in results)
{
result.ResultStatus = 20;
result.UpdatedUserId = currentUserId;
result.UpdatedTime = now;
}
await _resultRepository.UpdateRangeAsync(results);
}
/// <summary>
/// 创建重赛
/// </summary>
[HttpPost("workbench/entries/rematch")]
public async Task<CreateRematchOutput> CreateRematch([FromBody] CreateRematchInput input)
{
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var originalEntry = await _entryRepository.GetFirstAsync(e => e.Id == input.OriginalSessionEntryId && !e.DeleteMark)
?? throw Oops.Bah("原出场记录不存在");
var session = await _sessionRepository.GetByIdAsync(originalEntry.SessionId)
?? throw Oops.Bah("场次不存在");
var maxPlayOrder = await _entryRepository.AsQueryable()
.Where(e => !e.DeleteMark && e.SessionId == originalEntry.SessionId)
.MaxAsync(e => e.PlayOrder);
var newPlayOrder = maxPlayOrder + 1;
originalEntry.IsVoided = true;
originalEntry.VoidReason = $"重赛原因:{input.RematchReason}";
originalEntry.UpdatedUserId = currentUserId;
originalEntry.UpdatedTime = now;
await _entryRepository.UpdateAsync(originalEntry);
var result = await _resultRepository.GetFirstAsync(r => r.SessionEntryId == originalEntry.Id && !r.DeleteMark);
if (result != null)
{
result.ResultStatus = 90;
result.UpdatedUserId = currentUserId;
result.UpdatedTime = now;
await _resultRepository.UpdateAsync(result);
}
var newEntry = new EventSessionEntry
{
Id = _idGenerator.NextId(),
SessionId = originalEntry.SessionId,
ParticipationRecordId = originalEntry.ParticipationRecordId,
RegistrationItemId = originalEntry.RegistrationItemId,
SourceType = originalEntry.SourceType,
EntryNo = $"{originalEntry.EntryNo}-R",
PlayOrder = newPlayOrder,
EntryStatus = 0,
IsRematch = true,
RematchFromEntryId = originalEntry.Id,
IsVoided = false,
ParticipantName = originalEntry.ParticipantName,
TeamName = originalEntry.TeamName,
GroupName = originalEntry.GroupName,
Version = 1,
Status = Core.Enum.AvailabilityStatus.Enable,
DeleteMark = false,
CreatedUserId = currentUserId,
CreatedTime = now
};
await _entryRepository.InsertAsync(newEntry);
return new CreateRematchOutput
{
NewSessionEntryId = newEntry.Id,
NewEntryNo = newEntry.EntryNo,
NewPlayOrder = newEntry.PlayOrder
};
}
/// <summary>
/// 作废成绩
/// </summary>
[HttpPost("workbench/results/void")]
public async Task VoidResult([FromBody] VoidResultInput input)
{
var currentUserId = _authManager.UserId;
var now = DateTime.Now;
var entry = await _entryRepository.GetFirstAsync(e => e.Id == input.SessionEntryId && !e.DeleteMark)
?? throw Oops.Bah("出场记录不存在");
entry.IsVoided = true;
entry.VoidReason = input.VoidReason;
entry.UpdatedUserId = currentUserId;
entry.UpdatedTime = now;
await _entryRepository.UpdateAsync(entry);
var result = await _resultRepository.GetFirstAsync(r => r.SessionEntryId == entry.Id && !r.DeleteMark);
if (result != null)
{
result.ResultStatus = 90;
result.UpdatedUserId = currentUserId;
result.UpdatedTime = now;
await _resultRepository.UpdateAsync(result);
}
}
#endregion
#region 辅助方法
private static string GetSessionStatusName(int status)
{
return status switch
{
0 => "草稿",
10 => "就绪",
20 => "检录中",
30 => "进行中",
40 => "暂停",
50 => "待确认",
60 => "已结束",
90 => "已取消",
_ => "未知"
};
}
private static string GetEntryStatusName(int status)
{
return status switch
{
0 => "待出场",
10 => "已检录",
20 => "已叫号",
30 => "进行中",
40 => "已完成",
80 => "退赛",
90 => "取消比赛资格",
_ => "未知"
};
}
private static string GetResultStatusName(int status)
{
return status switch
{
0 => "计算中",
10 => "待复核",
20 => "已确认",
30 => "已投屏",
40 => "已更正",
90 => "已作废",
_ => "未知"
};
}
private static string GetSheetStatusName(int status)
{
return status switch
{
0 => "草稿",
10 => "已提交",
20 => "已确认",
30 => "已被修正",
90 => "已作废",
_ => "未知"
};
}
private static string GetCorrectionStatusName(int status)
{
return status switch
{
0 => "已申请",
10 => "已批准",
20 => "已拒绝",
30 => "裁判修改中",
40 => "已完成",
90 => "已取消",
_ => "未知"
};
}
private static string GetJudgeRoleTypeName(string roleType)
{
return roleType switch
{
"DP" => "难度裁判(DP)",
"DM" => "难度裁判(DM)",
"DR" => "难度裁判(DR)",
"P" => "表现裁判",
"T" => "主裁",
_ => roleType
};
}
#endregion
}