using YD_Event.Application.Auth;
using YD_Event.Application.Workbench.Dtos;
using YD_Event.Core.Entities;
using Newtonsoft.Json.Linq;
namespace YD_Event.Application.Workbench;
///
/// 打分系统工作台接口(供工作台管理端调用)
///
[ApiDescriptionSettings("打分系统")]
[Authorize]
public class WorkbenchController : IDynamicApiController
{
private readonly ISqlSugarRepository _eventRepository;
private readonly ISqlSugarRepository _venueRepository;
private readonly ISqlSugarRepository _eventProjectRepository;
private readonly ISqlSugarRepository _groupRepository;
private readonly ISqlSugarRepository _sessionRepository;
private readonly ISqlSugarRepository _entryRepository;
private readonly ISqlSugarRepository _assignmentRepository;
private readonly ISqlSugarRepository _sheetRepository;
private readonly ISqlSugarRepository _correctionRepository;
private readonly ISqlSugarRepository _resultRepository;
private readonly ISqlSugarRepository _resultVersionRepository;
private readonly ISqlSugarRepository _screenCastRepository;
private readonly ISqlSugarRepository _taskRepository;
private readonly ISqlSugarRepository _messageRepository;
private readonly ISqlSugarRepository _userRepository;
private readonly ISqlSugarRepository _refereeRepository;
private readonly ISqlSugarRepository _projectRepository;
private readonly ISqlSugarRepository _scoreDetailRepository;
private readonly ISqlSugarRepository _operationLogRepository;
private readonly ISqlSugarRepository _sysUserRepository;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
public WorkbenchController(
ISqlSugarRepository eventRepository,
ISqlSugarRepository venueRepository,
ISqlSugarRepository eventProjectRepository,
ISqlSugarRepository groupRepository,
ISqlSugarRepository sessionRepository,
ISqlSugarRepository entryRepository,
ISqlSugarRepository assignmentRepository,
ISqlSugarRepository sheetRepository,
ISqlSugarRepository correctionRepository,
ISqlSugarRepository resultRepository,
ISqlSugarRepository resultVersionRepository,
ISqlSugarRepository screenCastRepository,
ISqlSugarRepository taskRepository,
ISqlSugarRepository messageRepository,
ISqlSugarRepository userRepository,
ISqlSugarRepository refereeRepository,
ISqlSugarRepository projectRepository,
ISqlSugarRepository scoreDetailRepository,
ISqlSugarRepository operationLogRepository,
ISqlSugarRepository sysUserRepository,
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;
_scoreDetailRepository = scoreDetailRepository;
_operationLogRepository = operationLogRepository;
_sysUserRepository = sysUserRepository;
_authManager = authManager;
_idGenerator = idGenerator;
}
#region 场次管理
///
/// 获取工作台场次列表
///
[HttpGet("workbench/sessions")]
public async Task> 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();
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((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();
}
///
/// 获取场次的出场记录列表
///
[HttpGet("workbench/sessions/{sessionId}/entries")]
public async Task> 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();
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.ReviewUserId,
r.ReviewTime,
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,
ReviewUserId = result.ReviewUserId,
ReviewTime = result.ReviewTime,
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 成绩投屏
///
/// 投出成绩到大屏
///
[HttpPost("workbench/screencast")]
public async Task 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 => new EventResultScreenCast { CastStatus = 20 },
appendColumnsByDataFilter: true)
.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((r, e) => r.SessionEntryId == e.Id)
.InnerJoin((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()
.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 => new EventResultScreenCast { CastStatus = 20 },
appendColumnsByDataFilter: true)
.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("无效的投屏类型");
}
}
///
/// 撤回投屏内容
///
[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);
}
///
/// 获取当前投屏内容
///
[HttpGet("workbench/screencast/current")]
public async Task 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 成绩修正审批
///
/// 获取成绩修正申请列表
///
[HttpGet("workbench/corrections")]
public async Task> 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();
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();
}
///
/// 获取成绩修正申请详情
///
[HttpGet("workbench/corrections/{correctionId}")]
public async Task 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,
ReviewUserId = result.ReviewUserId,
ReviewTime = result.ReviewTime
} : 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
};
}
///
/// 审批成绩修正申请
///
[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 成绩管理
///
/// 确认成绩
///
[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.ReviewUserId = currentUserId;
result.ReviewTime = now;
result.UpdatedUserId = currentUserId;
result.UpdatedTime = now;
await _resultRepository.UpdateAsync(result);
}
///
/// 批量确认成绩
///
[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((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.ReviewUserId = currentUserId;
result.ReviewTime = now;
result.UpdatedUserId = currentUserId;
result.UpdatedTime = now;
}
await _resultRepository.UpdateRangeAsync(results);
}
///
/// 创建重赛
///
[HttpPost("workbench/entries/rematch")]
public async Task 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
};
}
///
/// 作废成绩
///
[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 数据导出
///
/// 导出成绩册(与成绩管理页面筛选条件一致,默认仅导出已确认、未DQ成绩)
///
[DisplayName("导出成绩册")]
[HttpGet("workbench/results/export")]
public async Task ExportResults([FromQuery] ResultExportQueryInput input)
{
var query = _resultRepository.AsQueryable()
.InnerJoin((r, e) => r.SessionEntryId == e.Id)
.LeftJoin((r, e, s) => e.SessionId == s.Id)
.Where((r, e, s) => !r.DeleteMark && !e.DeleteMark && r.EventId == input.EventId);
if (input.ResultStatus.HasValue)
{
var resultStatus = input.ResultStatus.Value;
query = query.Where((r, e, s) => r.ResultStatus == resultStatus);
}
if (!input.IncludeDq)
query = query.Where((r, e, s) => !r.IsDisqualified);
if (!string.IsNullOrWhiteSpace(input.GroupName))
{
var groupName = input.GroupName.Trim();
query = query.Where((r, e, s) => e.GroupName.Contains(groupName) || r.GroupName.Contains(groupName));
}
if (input.EventProjectId.HasValue)
{
var eventProjectId = input.EventProjectId.Value;
query = query.Where((r, e, s) => s.EventProjectId == eventProjectId);
}
var flat = await query
.OrderBy((r, e, s) => r.Rank)
.Select((r, e, s) => new
{
r.Id,
r.Rank,
EntryNo = e.EntryNo,
Participant = e.ParticipantName,
Team = e.TeamName,
GroupName = e.GroupName,
r.ProjectId,
r.DifficultyScore,
r.PerformanceScore,
r.DeductionScore,
r.TotalScore,
r.Score,
r.IsDisqualified,
r.ResultStatus,
r.ResultVersion,
r.ReviewTime,
EventProjectId = (long?)s.EventProjectId
})
.ToListAsync();
if (!flat.Any())
throw Oops.Bah("暂无数据可导出");
var eventProjectIds = flat.Where(x => x.EventProjectId != null).Select(x => x.EventProjectId!.Value).Distinct().ToList();
var eventProjects = await _eventProjectRepository.AsQueryable()
.Where(x => eventProjectIds.Contains(x.Id))
.Select(x => new { x.Id, x.ProjectId })
.ToListAsync();
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projectNames = (await _projectRepository.AsQueryable()
.Where(x => projectIds.Contains(x.Id))
.Select(x => new { x.Id, x.Name })
.ToListAsync())
.ToDictionary(x => x.Id, x => x.Name);
var eventProjectProject = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId);
var rows = flat.Select(x =>
{
long? projectId = x.EventProjectId != null && eventProjectProject.TryGetValue(x.EventProjectId.Value, out var pid)
? pid
: x.ProjectId > 0 ? (long?)x.ProjectId : null;
var projectName = projectId != null && projectNames.TryGetValue(projectId.Value, out var pn) ? pn : string.Empty;
return new object?[]
{
x.IsDisqualified ? "DQ" : (x.Rank > 0 ? x.Rank.ToString() : string.Empty),
x.EntryNo,
string.IsNullOrWhiteSpace(x.Team) ? x.Participant : x.Team,
projectName,
x.GroupName,
Num(x.DifficultyScore),
Num(x.PerformanceScore),
Num(x.DeductionScore),
x.TotalScore.HasValue ? Num(x.TotalScore.Value) : x.Score,
x.IsDisqualified ? "取消资格" : GetResultStatusName(x.ResultStatus),
x.ResultVersion,
FmtTime(x.ReviewTime)
};
}).ToList();
var headers = new[] { "名次", "出场编号", "姓名/队名", "项目", "组别", "难度分", "表现分", "扣分", "总分", "状态", "成绩版本", "确认时间" };
var bytes = await new WorkbenchListExcelExporter("成绩册", $"成绩册(导出时间:{DateTime.Now:yyyy-MM-dd HH:mm})", headers, rows,
new double[] { 8, 16, 16, 16, 12, 10, 10, 10, 10, 10, 10, 20 }).ExportAsync();
return ExcelFile(bytes, $"成绩册_{input.EventId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
}
///
/// 导出评分单明细(按场次/项目,逐出场逐裁判列出评分项与提交信息)
///
[DisplayName("导出评分单明细")]
[HttpGet("workbench/score-sheets/export")]
public async Task ExportScoreSheets([FromQuery] ScoreSheetExportQueryInput input)
{
if (!input.SessionId.HasValue && !input.EventProjectId.HasValue)
throw Oops.Bah("场次ID与赛事项目ID至少传一个");
var query = _sheetRepository.AsQueryable()
.Where(s => !s.DeleteMark && s.EventId == input.EventId && s.IsCurrent);
if (input.SessionId.HasValue)
{
var sessionId = input.SessionId.Value;
query = query.Where(s => s.SessionId == sessionId);
}
if (input.EventProjectId.HasValue)
{
var eventProjectId = input.EventProjectId.Value;
var projectSessionIds = await _sessionRepository.AsQueryable()
.Where(s => !s.DeleteMark && s.EventProjectId == eventProjectId)
.Select(s => s.Id)
.ToListAsync();
query = query.Where(s => projectSessionIds.Contains(s.SessionId));
}
var sheets = await query
.OrderBy(s => s.SessionId)
.OrderBy(s => s.SessionEntryId)
.OrderBy(s => s.JudgeRole)
.ToListAsync();
if (!sheets.Any())
throw Oops.Bah("暂无数据可导出");
var entryIds = sheets.Select(s => s.SessionEntryId).Distinct().ToList();
var sessionIds = sheets.Select(s => s.SessionId).Distinct().ToList();
var refereeIds = sheets.Select(s => s.RefereeId).Distinct().ToList();
var sheetIds = sheets.Select(s => s.Id).ToList();
var entries = (await _entryRepository.AsQueryable()
.Where(e => entryIds.Contains(e.Id))
.Select(e => new { e.Id, e.EntryNo, e.ParticipantName, e.TeamName })
.ToListAsync())
.ToDictionary(e => e.Id);
var sessions = (await _sessionRepository.AsQueryable()
.Where(s => sessionIds.Contains(s.Id))
.Select(s => new { s.Id, s.SessionNo })
.ToListAsync())
.ToDictionary(s => s.Id);
var referees = (await _refereeRepository.AsQueryable()
.Where(r => refereeIds.Contains(r.Id))
.Select(r => new { r.Id, r.Name })
.ToListAsync())
.ToDictionary(r => r.Id);
var details = (await _scoreDetailRepository.AsQueryable()
.Where(d => !d.DeleteMark && sheetIds.Contains(d.ScoreSheetId))
.OrderBy(d => d.Sort)
.ToListAsync())
.GroupBy(d => d.ScoreSheetId)
.ToDictionary(g => g.Key, g => g.ToList());
var rows = new List