diff --git a/YD_Event.Application/Event/Dtos/JudgeScoreDtos.cs b/YD_Event.Application/Event/Dtos/JudgeScoreDtos.cs
index 5f2bea0..4242490 100644
--- a/YD_Event.Application/Event/Dtos/JudgeScoreDtos.cs
+++ b/YD_Event.Application/Event/Dtos/JudgeScoreDtos.cs
@@ -75,6 +75,9 @@ public class JudgeBoardSheetScoreOutput
/// 是否由该裁判判定取消比赛资格
///
public bool IsDisqualified { get; set; }
+
+ /// 裁判记录画板地址。
+ public string? AnnotationUrl { get; set; }
}
///
diff --git a/YD_Event.Application/Event/JudgeScoreService.cs b/YD_Event.Application/Event/JudgeScoreService.cs
index 3643feb..75829d5 100644
--- a/YD_Event.Application/Event/JudgeScoreService.cs
+++ b/YD_Event.Application/Event/JudgeScoreService.cs
@@ -62,6 +62,7 @@ public class JudgeScoreService : IDynamicApiController, ITransient
private readonly ISqlSugarRepository _sheetRepository;
private readonly ISqlSugarRepository _correctionRepository;
private readonly ISqlSugarRepository _messageRepository;
+ private readonly ISqlSugarRepository _attachmentRepository;
private readonly ISqlSugarRepository _refereeRepository;
private readonly ISqlSugarRepository _resultRepository;
private readonly EventScoreCalculationService _calculationService;
@@ -75,6 +76,7 @@ public class JudgeScoreService : IDynamicApiController, ITransient
ISqlSugarRepository sheetRepository,
ISqlSugarRepository correctionRepository,
ISqlSugarRepository messageRepository,
+ ISqlSugarRepository attachmentRepository,
ISqlSugarRepository refereeRepository,
ISqlSugarRepository resultRepository,
EventScoreCalculationService calculationService, IIdGenerator idGenerator, AuthManager authManager)
@@ -82,6 +84,7 @@ public class JudgeScoreService : IDynamicApiController, ITransient
_sessionRepository = sessionRepository; _entryRepository = entryRepository; _venueRepository = venueRepository;
_ruleRepository = ruleRepository; _assignmentRepository = assignmentRepository; _sheetRepository = sheetRepository;
_correctionRepository = correctionRepository; _messageRepository = messageRepository;
+ _attachmentRepository = attachmentRepository;
_refereeRepository = refereeRepository; _resultRepository = resultRepository;
_calculationService = calculationService; _idGenerator = idGenerator; _authManager = authManager;
}
@@ -387,6 +390,13 @@ public class JudgeScoreService : IDynamicApiController, ITransient
var entryIds = outcomes.Select(x => (long?)x.SessionEntryId).ToList();
var results = await _resultRepository.GetListAsync(x => entryIds.Contains(x.SessionEntryId)
&& x.ResultStatus != EventScoreCalculationService.ResultStatusVoided);
+ var sheetIds = outcomes.SelectMany(x => x.Aggregate?.Symbols ?? new List())
+ .SelectMany(x => x.Judges).Select(x => x.ScoreSheetId).Distinct().ToList();
+ var annotationUrls = sheetIds.Count == 0
+ ? new Dictionary()
+ : (await _attachmentRepository.GetListAsync(x => sheetIds.Contains(x.ScoreSheetId)
+ && x.IsCurrent && x.AttachmentType == "score_annotation_png"))
+ .GroupBy(x => x.ScoreSheetId).ToDictionary(x => x.Key, x => x.OrderByDescending(y => y.CreatedTime).First().FileUrl);
// 一个出场(队伍)的多名运动员共享同一名次,取任意一条即可。
var resultByEntry = results.Where(x => x.SessionEntryId.HasValue)
.GroupBy(x => x.SessionEntryId!.Value).ToDictionary(x => x.Key, x => x.First());
@@ -428,7 +438,8 @@ public class JudgeScoreService : IDynamicApiController, ITransient
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
+ IsDisqualified = judge.IsDisqualified,
+ AnnotationUrl = annotationUrls.GetValueOrDefault(judge.ScoreSheetId)
}).ToList()
}).ToList();
}
diff --git a/YD_Event.Application/Event/Scoring/EventScoreCalculationService.cs b/YD_Event.Application/Event/Scoring/EventScoreCalculationService.cs
index 4f3b2cd..f93d5bb 100644
--- a/YD_Event.Application/Event/Scoring/EventScoreCalculationService.cs
+++ b/YD_Event.Application/Event/Scoring/EventScoreCalculationService.cs
@@ -173,8 +173,14 @@ public class EventScoreCalculationService : ITransient
// 已提交裁判分配即本场的应到裁判名单,只统计计分规则里参与计分的类型。
var assignments = await _assignmentRepository.GetListAsync(x => x.SessionId == sessionId && x.AssignmentStatus == 1);
var entryIds = entries.Select(x => x.Id).ToList();
- var sheets = await _sheetRepository.GetListAsync(x => entryIds.Contains(x.SessionEntryId)
- && x.IsCurrent && x.SheetStatus >= SheetStatusSubmitted);
+ var assignmentIds = assignments.Select(x => x.Id).ToList();
+ // 历史席位被替换后,其旧评分单仍需留档,但不能继续参与当前场次汇总。
+ // 否则同一角色会出现超过规则席位数的评分,并污染平均分与最终成绩。
+ var sheets = assignmentIds.Count == 0
+ ? new List()
+ : await _sheetRepository.GetListAsync(x => entryIds.Contains(x.SessionEntryId)
+ && assignmentIds.Contains(x.JudgeAssignmentId)
+ && x.IsCurrent && x.SheetStatus >= SheetStatusSubmitted);
var judgeNames = await LoadJudgeNamesAsync(assignments);
var sheetsByEntry = sheets.GroupBy(x => x.SessionEntryId).ToDictionary(x => x.Key, x => x.ToList());
diff --git a/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs b/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs
index f217f97..5dc82c9 100644
--- a/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs
+++ b/YD_Event.Application/Judge/Dtos/JudgeScoringDtos.cs
@@ -426,6 +426,9 @@ public class JudgeScoreSheetOutput
///
public long ScoreSheetId { get; set; }
+ /// 评分单版本;未产生评分单时为下一待提交版本。
+ public int SheetVersion { get; set; }
+
///
/// 评分单状态:0-草稿,10-已提交,20-已确认,30-已被修正,90-已作废
///
@@ -817,6 +820,9 @@ public class JudgeSymbolScoreDto
///
public class JudgeSymbolJudgeDto
{
+ /// 评分单ID。
+ public long ScoreSheetId { get; set; }
+
///
/// 裁判席位编码
///
@@ -831,6 +837,9 @@ public class JudgeSymbolJudgeDto
/// 该裁判的得分
///
public string Value { get; set; } = string.Empty;
+
+ /// 该评分单当前记录画板地址。
+ public string? AnnotationUrl { get; set; }
}
///
@@ -838,6 +847,9 @@ public class JudgeSymbolJudgeDto
///
public class JudgeCorrectionCandidateDto
{
+ /// 最近一次修正流程ID。
+ public long? CorrectionId { get; set; }
+
///
/// 评分单ID
///
@@ -878,6 +890,12 @@ public class JudgeCorrectionCandidateDto
///
public string CorrectionStatusName { get; set; } = string.Empty;
+ /// 修正申请原因。
+ public string? ApplyReason { get; set; }
+
+ /// 审批意见。
+ public string? ApproveRemark { get; set; }
+
///
/// 是否可以申请修正
///
@@ -940,6 +958,15 @@ public class JudgeMessageDto
///
public long? RelationId { get; set; }
+ /// 关联场次ID。
+ public long? SessionId { get; set; }
+
+ /// 关联出场记录ID。
+ public long? SessionEntryId { get; set; }
+
+ /// 关联评分单ID。
+ public long? ScoreSheetId { get; set; }
+
///
/// 是否已读
///
@@ -961,3 +988,23 @@ public class ReadJudgeMessageInput
///
public List MessageIds { get; set; } = new();
}
+
+/// 记录画板 PNG 上传输入。
+public class UploadJudgeScoreAnnotationInput
+{
+ public long ScoreSheetId { get; set; }
+ public string IdempotencyKey { get; set; } = null!;
+ public IFormFile File { get; set; } = null!;
+}
+
+/// 记录画板附件。
+public class JudgeScoreAttachmentDto
+{
+ public long Id { get; set; }
+ public long ScoreSheetId { get; set; }
+ public string AttachmentType { get; set; } = string.Empty;
+ public string FileName { get; set; } = string.Empty;
+ public string FileUrl { get; set; } = string.Empty;
+ public long FileSize { get; set; }
+ public DateTime UploadedTime { get; set; }
+}
diff --git a/YD_Event.Application/Judge/JudgeScoringController.cs b/YD_Event.Application/Judge/JudgeScoringController.cs
index a8bcbdc..2af9573 100644
--- a/YD_Event.Application/Judge/JudgeScoringController.cs
+++ b/YD_Event.Application/Judge/JudgeScoringController.cs
@@ -1,5 +1,7 @@
using YD_Event.Application.Auth;
+using YD_Event.Application.Common;
using YD_Event.Application.Scoring.Dtos;
+using System.Text.RegularExpressions;
namespace YD_Event.Application.Scoring;
@@ -15,6 +17,7 @@ namespace YD_Event.Application.Scoring;
/// 裁判提交的分值一律在服务端按计分规则重算,客户端上传的合计值不参与落库。
///
[ApiDescriptionSettings("打分系统")]
+[Route("api")]
[Authorize]
public class JudgeScoringController : IDynamicApiController
{
@@ -33,11 +36,13 @@ public class JudgeScoringController : IDynamicApiController
private readonly ISqlSugarRepository _detailRepository;
private readonly ISqlSugarRepository _correctionRepository;
private readonly ISqlSugarRepository _messageRepository;
+ private readonly ISqlSugarRepository _attachmentRepository;
private readonly ISqlSugarRepository _resultRepository;
private readonly ISqlSugarRepository _personRepository;
private readonly EventScoreCalculationService _calculationService;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
+ private readonly FileService _fileService;
public JudgeScoringController(
ISqlSugarRepository userRepository,
@@ -55,11 +60,13 @@ public class JudgeScoringController : IDynamicApiController
ISqlSugarRepository detailRepository,
ISqlSugarRepository correctionRepository,
ISqlSugarRepository messageRepository,
+ ISqlSugarRepository attachmentRepository,
ISqlSugarRepository resultRepository,
ISqlSugarRepository personRepository,
EventScoreCalculationService calculationService,
AuthManager authManager,
- IIdGenerator idGenerator)
+ IIdGenerator idGenerator,
+ FileService fileService)
{
_userRepository = userRepository;
_refereeRepository = refereeRepository;
@@ -76,11 +83,13 @@ public class JudgeScoringController : IDynamicApiController
_detailRepository = detailRepository;
_correctionRepository = correctionRepository;
_messageRepository = messageRepository;
+ _attachmentRepository = attachmentRepository;
_resultRepository = resultRepository;
_personRepository = personRepository;
_calculationService = calculationService;
_authManager = authManager;
_idGenerator = idGenerator;
+ _fileService = fileService;
}
///
@@ -253,6 +262,7 @@ public class JudgeScoringController : IDynamicApiController
SessionId = ctx.Session.Id,
JudgeTaskId = draft?.JudgeTaskId ?? submitted?.JudgeTaskId ?? 0,
ScoreSheetId = source?.Id ?? 0,
+ SheetVersion = source?.SheetVersion ?? nextVersion,
SheetStatus = source?.SheetStatus ?? SheetStatusDraft,
CanEdit = canEdit,
LockReason = canEdit ? null : "已提交评分,如需修改请先申请成绩修正",
@@ -346,6 +356,19 @@ public class JudgeScoringController : IDynamicApiController
var nextVersion = (submitted?.SheetVersion ?? 0) + 1;
await UpsertSheetAsync(draft, entry, ctx, assignment, roleType, task, calculation,
SheetStatusDraft, nextVersion, $"{assignment.Id}-{entry.Id}-v{nextVersion}-draft", null, null, null);
+ if (submitted != null)
+ {
+ var correction = await _correctionRepository.GetFirstAsync(x => x.OriginalScoreSheetId == submitted.Id
+ && x.CorrectionStatus == CorrectionStatusApproved);
+ if (correction != null)
+ {
+ correction.CorrectionTaskId = task.Id;
+ correction.CorrectionStatus = CorrectionStatusEditing;
+ correction.UpdatedUserId = _authManager.UserId;
+ correction.UpdatedTime = DateTime.Now;
+ await _correctionRepository.UpdateAsync(correction);
+ }
+ }
return ToPreviewDto(calculation, ctx.Config);
}
@@ -387,23 +410,24 @@ public class JudgeScoringController : IDynamicApiController
var draft = await _sheetRepository.GetFirstAsync(x => x.SessionEntryId == entry.Id
&& x.JudgeAssignmentId == assignment.Id && x.SheetStatus == SheetStatusDraft);
var nextVersion = (submitted?.SheetVersion ?? 0) + 1;
- var sheet = await UpsertSheetAsync(draft, entry, ctx, assignment, roleType, task, calculation,
- EventScoreCalculationService.SheetStatusSubmitted, nextVersion,
- idempotencyKey ?? $"{assignment.Id}-{entry.Id}-v{nextVersion}",
- input.DisqualifyRemark, submitted?.Id, correction?.ApplyReason);
-
var now = DateTime.Now;
if (submitted != null)
{
- // 修正后的评分单接替原评分单,原评分单留档但不再参与计分。
+ // 当前版本唯一索引要求先让原评分单退出当前态;UnitOfWork 会在后续失败时整体回滚。
submitted.IsCurrent = false;
submitted.SheetStatus = SheetStatusCorrected;
submitted.UpdatedUserId = _authManager.UserId;
submitted.UpdatedTime = now;
await _sheetRepository.UpdateAsync(submitted);
}
+ var sheet = await UpsertSheetAsync(draft, entry, ctx, assignment, roleType, task, calculation,
+ EventScoreCalculationService.SheetStatusSubmitted, nextVersion,
+ idempotencyKey ?? $"{assignment.Id}-{entry.Id}-v{nextVersion}",
+ input.DisqualifyRemark, submitted?.Id, correction?.ApplyReason);
+
if (correction != null)
{
+ correction.CorrectionTaskId = task.Id;
correction.NewScoreSheetId = sheet.Id;
correction.CorrectionStatus = CorrectionStatusCompleted;
correction.CompletedTime = now;
@@ -432,13 +456,24 @@ public class JudgeScoringController : IDynamicApiController
public async Task> GetResults(long sessionId)
{
var referee = await EnsureRefereeAsync();
- await GetMyAssignmentAsync(sessionId, referee.Id);
+ var assignment = await GetMyAssignmentAsync(sessionId, referee.Id);
+ var roleType = JudgeRoleSchemaService.NormalizeRoleType(
+ string.IsNullOrWhiteSpace(assignment.JudgeRoleType) ? assignment.JudgeRole : assignment.JudgeRoleType);
+ if (!string.Equals(roleType, JudgeRoleSchemaService.RoleChief, StringComparison.OrdinalIgnoreCase))
+ throw Oops.Bah("仅主裁判可查看场次最终成绩");
var ctx = await _calculationService.LoadContextAsync(sessionId);
var outcomes = await _calculationService.EvaluateSessionAsync(sessionId, ctx);
var entryIds = outcomes.Select(x => (long?)x.SessionEntryId).ToList();
var results = entryIds.Count == 0
? new List()
: await _resultRepository.GetListAsync(x => entryIds.Contains(x.SessionEntryId));
+ var scoreSheetIds = outcomes.SelectMany(x => x.Aggregate?.Symbols ?? new List())
+ .SelectMany(x => x.Judges).Select(x => x.ScoreSheetId).Distinct().ToList();
+ var annotationUrls = scoreSheetIds.Count == 0
+ ? new Dictionary()
+ : (await _attachmentRepository.GetListAsync(x => scoreSheetIds.Contains(x.ScoreSheetId)
+ && x.IsCurrent && x.AttachmentType == AnnotationAttachmentType))
+ .GroupBy(x => x.ScoreSheetId).ToDictionary(x => x.Key, x => x.OrderByDescending(y => y.CreatedTime).First().FileUrl);
return outcomes.Select(outcome =>
{
@@ -461,9 +496,11 @@ public class JudgeScoringController : IDynamicApiController
Value = ctx.Config.Format(symbol.Value),
Judges = symbol.Judges.Select(judge => new JudgeSymbolJudgeDto
{
+ ScoreSheetId = judge.ScoreSheetId,
JudgeRole = judge.JudgeRole,
JudgeName = judge.JudgeName ?? string.Empty,
- Value = ctx.Config.Format(judge.Value)
+ Value = ctx.Config.Format(judge.Value),
+ AnnotationUrl = annotationUrls.GetValueOrDefault(judge.ScoreSheetId)
}).ToList()
}).ToList(),
Factors = !ctx.Config.IsFactorTotal || outcome.Aggregate == null
@@ -497,18 +534,22 @@ public class JudgeScoringController : IDynamicApiController
if (sheets.Count == 0) return new List();
var sheetIds = sheets.Select(x => x.Id).ToList();
- var corrections = await _correctionRepository.GetListAsync(x => sheetIds.Contains(x.OriginalScoreSheetId));
+ var corrections = await _correctionRepository.GetListAsync(x => sheetIds.Contains(x.OriginalScoreSheetId)
+ || (x.NewScoreSheetId.HasValue && sheetIds.Contains(x.NewScoreSheetId.Value)));
var entryIds = sheets.Select(x => x.SessionEntryId).Distinct().ToList();
var entries = (await _entryRepository.GetListAsync(x => entryIds.Contains(x.Id))).ToDictionary(x => x.Id);
return sheets.Select(sheet =>
{
var entry = entries.GetValueOrDefault(sheet.SessionEntryId);
- var correction = corrections.Where(x => x.OriginalScoreSheetId == sheet.Id)
+ // 修正完成后当前评分单已经变成 NewScoreSheetId,仍需返回最近一次流程状态,
+ // 否则 App 会把刚完成的修正误显示成“可首次申请”。
+ var correction = corrections.Where(x => x.OriginalScoreSheetId == sheet.Id || x.NewScoreSheetId == sheet.Id)
.OrderByDescending(x => x.CreatedTime).FirstOrDefault();
var status = correction?.CorrectionStatus ?? -1;
return new JudgeCorrectionCandidateDto
{
+ CorrectionId = correction?.Id,
ScoreSheetId = sheet.Id,
SessionEntryId = sheet.SessionEntryId,
EntryNo = entry?.EntryNo ?? string.Empty,
@@ -518,6 +559,8 @@ public class JudgeScoringController : IDynamicApiController
SubmittedTime = sheet.SubmittedTime,
CorrectionStatus = status,
CorrectionStatusName = CorrectionStatusName(status),
+ ApplyReason = correction?.ApplyReason,
+ ApproveRemark = correction?.ApproveRemark,
// 已在流程中的评分单不能重复申请,被拒绝或已完成的可以再次申请。
CanApply = status is -1 or CorrectionStatusRejected or CorrectionStatusCompleted or CorrectionStatusCancelled
};
@@ -552,7 +595,7 @@ public class JudgeScoringController : IDynamicApiController
OriginalScoreSheetId = sheet.Id,
JudgeAssignmentId = sheet.JudgeAssignmentId,
JudgeRole = sheet.JudgeRole,
- ApplyUserId = _authManager.UserId,
+ ApplyRefereeId = referee.Id,
ApplyReason = input.ApplyReason.Trim(),
CorrectionStatus = ctx.Config.CorrectionNeedApproval ? CorrectionStatusApplied : CorrectionStatusApproved,
CreatedUserId = _authManager.UserId,
@@ -581,10 +624,21 @@ public class JudgeScoringController : IDynamicApiController
.WhereIF(onlyUnread == true, x => !x.IsRead)
.ToListAsync())
.OrderByDescending(x => x.CreatedTime).ToList();
+ var correctionIds = messages
+ .Where(x => string.Equals(x.RelationType, nameof(EventScoreCorrection), StringComparison.OrdinalIgnoreCase)
+ && x.RelationId.HasValue)
+ .Select(x => x.RelationId!.Value).Distinct().ToList();
+ var corrections = correctionIds.Count == 0
+ ? new Dictionary()
+ : (await _correctionRepository.GetListAsync(x => correctionIds.Contains(x.Id))).ToDictionary(x => x.Id);
return messages.Select(x => new JudgeMessageDto
{
Id = x.Id, EventId = x.EventId, MessageType = x.MessageType, Title = x.Title, Content = x.Content,
- RelationType = x.RelationType, RelationId = x.RelationId, IsRead = x.IsRead, CreatedTime = x.CreatedTime
+ RelationType = x.RelationType, RelationId = x.RelationId,
+ SessionId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionId : null,
+ SessionEntryId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionEntryId : null,
+ ScoreSheetId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.OriginalScoreSheetId : null,
+ IsRead = x.IsRead, CreatedTime = x.CreatedTime
}).ToList();
}
@@ -607,6 +661,82 @@ public class JudgeScoringController : IDynamicApiController
await _messageRepository.UpdateRangeAsync(messages);
}
+ /// 上传当前裁判评分单的记录画板 PNG。
+ [HttpPost("judge-scoring/annotations/upload")]
+ [DisplayName("上传裁判记录画板")]
+ [UnitOfWork]
+ public async Task UploadAnnotation([FromForm] UploadJudgeScoreAnnotationInput input)
+ {
+ var referee = await EnsureRefereeAsync();
+ var sheet = await _sheetRepository.GetFirstAsync(x => x.Id == input.ScoreSheetId && x.RefereeId == referee.Id);
+ if (sheet == null) throw Oops.Bah("评分单不存在或不属于当前裁判");
+ if (sheet.SheetStatus < EventScoreCalculationService.SheetStatusSubmitted)
+ throw Oops.Bah("请先提交成绩,再上传记录画板");
+ var key = input.IdempotencyKey?.Trim() ?? string.Empty;
+ if (key.Length is < 8 or > 64 || !Regex.IsMatch(key, "^[A-Za-z0-9_-]+$"))
+ throw Oops.Bah("画板上传幂等键无效");
+ var replay = await _attachmentRepository.GetFirstAsync(x => x.ScoreSheetId == sheet.Id && x.IdempotencyKey == key);
+ if (replay != null) return ToAttachmentDto(replay);
+
+ var file = input.File;
+ if (file == null || file.Length == 0) throw Oops.Bah("请选择画板 PNG 文件");
+ if (file.Length > MaxAnnotationBytes) throw Oops.Bah("画板图片不能超过5MB");
+ if (!string.Equals(Path.GetExtension(file.FileName), ".png", StringComparison.OrdinalIgnoreCase)
+ || !string.Equals(file.ContentType, "image/png", StringComparison.OrdinalIgnoreCase))
+ throw Oops.Bah("画板仅支持PNG格式");
+ await using var stream = file.OpenReadStream();
+ var signature = new byte[8];
+ if (await stream.ReadAsync(signature.AsMemory(0, signature.Length)) != signature.Length
+ || !signature.SequenceEqual(PngSignature))
+ throw Oops.Bah("上传文件不是有效的PNG图片");
+ stream.Position = 0;
+ var objectName = $"judge-score-annotations/{sheet.EventId}/{sheet.Id}/{key}.png";
+ var url = await _fileService.UploadFileStreamAsync(objectName, stream);
+
+ var now = DateTime.Now;
+ var current = await _attachmentRepository.GetListAsync(x => x.ScoreSheetId == sheet.Id
+ && x.AttachmentType == AnnotationAttachmentType && x.IsCurrent);
+ foreach (var old in current)
+ {
+ old.IsCurrent = false;
+ old.UpdatedUserId = _authManager.UserId;
+ old.UpdatedTime = now;
+ }
+ if (current.Count > 0) await _attachmentRepository.UpdateRangeAsync(current);
+ var attachment = new EventJudgeScoreAttachment
+ {
+ Id = _idGenerator.NextId(), EventId = sheet.EventId, SessionId = sheet.SessionId,
+ SessionEntryId = sheet.SessionEntryId, ScoreSheetId = sheet.Id,
+ JudgeAssignmentId = sheet.JudgeAssignmentId, RefereeId = referee.Id,
+ AttachmentType = AnnotationAttachmentType, FileName = Path.GetFileName(file.FileName),
+ FileUrl = url, FileSize = file.Length, ContentType = "image/png", IdempotencyKey = key,
+ IsCurrent = true, CreatedUserId = _authManager.UserId, CreatedTime = now
+ };
+ await _attachmentRepository.InsertAsync(attachment);
+ return ToAttachmentDto(attachment);
+ }
+
+ /// 查询评分单当前记录画板。
+ [HttpGet("judge-scoring/annotations/{scoreSheetId}")]
+ [DisplayName("查询裁判记录画板")]
+ public async Task GetAnnotation(long scoreSheetId)
+ {
+ var referee = await EnsureRefereeAsync();
+ var sheet = await _sheetRepository.GetFirstAsync(x => x.Id == scoreSheetId);
+ if (sheet == null) throw Oops.Bah("评分单不存在");
+ if (sheet.RefereeId != referee.Id)
+ {
+ var assignment = await GetMyAssignmentAsync(sheet.SessionId, referee.Id);
+ var roleType = JudgeRoleSchemaService.NormalizeRoleType(
+ string.IsNullOrWhiteSpace(assignment.JudgeRoleType) ? assignment.JudgeRole : assignment.JudgeRoleType);
+ if (!string.Equals(roleType, JudgeRoleSchemaService.RoleChief, StringComparison.OrdinalIgnoreCase))
+ throw Oops.Bah("无权查看该评分单画板");
+ }
+ var attachment = await _attachmentRepository.GetFirstAsync(x => x.ScoreSheetId == scoreSheetId
+ && x.AttachmentType == AnnotationAttachmentType && x.IsCurrent);
+ return attachment == null ? null : ToAttachmentDto(attachment);
+ }
+
///
/// 校验当前登录用户的裁判身份并取出裁判档案
///
@@ -668,6 +798,17 @@ public class JudgeScoringController : IDynamicApiController
|| x.CorrectionStatus == CorrectionStatusApproved
|| x.CorrectionStatus == CorrectionStatusEditing));
+ private static JudgeScoreAttachmentDto ToAttachmentDto(EventJudgeScoreAttachment attachment) => new()
+ {
+ Id = attachment.Id,
+ ScoreSheetId = attachment.ScoreSheetId,
+ AttachmentType = attachment.AttachmentType,
+ FileName = attachment.FileName,
+ FileUrl = attachment.FileUrl,
+ FileSize = attachment.FileSize,
+ UploadedTime = attachment.CreatedTime
+ };
+
///
/// 取(或按需创建)本裁判在该出场上的评分任务
///
@@ -675,7 +816,8 @@ public class JudgeScoringController : IDynamicApiController
EventJudgeAssignment assignment, string roleType, int taskType)
{
var task = await _taskRepository.GetFirstAsync(x => x.SessionEntryId == entry.Id
- && x.JudgeAssignmentId == assignment.Id && x.TaskType == taskType && x.TaskStatus != TaskStatusVoided);
+ && x.JudgeAssignmentId == assignment.Id && x.TaskType == taskType && x.TaskStatus != TaskStatusVoided
+ && (taskType != TaskTypeCorrection || x.TaskStatus != TaskStatusSubmitted));
var now = DateTime.Now;
if (task != null)
{
@@ -690,6 +832,9 @@ public class JudgeScoringController : IDynamicApiController
return task;
}
+ var taskHistory = await _taskRepository.GetListAsync(x => x.SessionEntryId == entry.Id
+ && x.JudgeAssignmentId == assignment.Id && !x.DeleteMark);
+ var previousTask = taskHistory.OrderByDescending(x => x.TaskVersion).FirstOrDefault();
task = new EventJudgeTask
{
Id = _idGenerator.NextId(),
@@ -718,6 +863,8 @@ public class JudgeScoringController : IDynamicApiController
scope = ctx.Scope
}),
TaskType = taskType,
+ TaskVersion = (previousTask?.TaskVersion ?? 0) + 1,
+ OriginalTaskId = taskType == TaskTypeCorrection ? previousTask?.Id : null,
TaskStatus = TaskStatusAccepted,
DispatchedTime = now,
AcceptedTime = now,
@@ -826,7 +973,9 @@ public class JudgeScoringController : IDynamicApiController
IsDuplicate = isDuplicate,
MyScore = ctx.Config.Format(sheet.TotalScore),
ResultCompleted = outcome?.IsCompleted ?? false,
- TotalScore = outcome == null || !outcome.IsCompleted ? null
+ TotalScore = !string.Equals(JudgeRoleSchemaService.NormalizeRoleType(sheet.JudgeRoleType),
+ JudgeRoleSchemaService.RoleChief, StringComparison.OrdinalIgnoreCase)
+ || outcome == null || !outcome.IsCompleted ? null
: outcome.IsDisqualified ? "取消资格" : ctx.Config.Format(outcome.TotalScore),
Progress = (outcome?.Roles ?? new List()).Select(ToProgressDto).ToList()
};
@@ -938,6 +1087,10 @@ public class JudgeScoringController : IDynamicApiController
///
private const int CorrectionStatusCancelled = 90;
+ private const string AnnotationAttachmentType = "score_annotation_png";
+ private const long MaxAnnotationBytes = 5L * 1024 * 1024;
+ private static readonly byte[] PngSignature = [137, 80, 78, 71, 13, 10, 26, 10];
+
///
/// 出场状态:退赛
///
diff --git a/YD_Event.Application/Workbench/WorkbenchController.cs b/YD_Event.Application/Workbench/WorkbenchController.cs
index cb2c791..195751a 100644
--- a/YD_Event.Application/Workbench/WorkbenchController.cs
+++ b/YD_Event.Application/Workbench/WorkbenchController.cs
@@ -601,7 +601,10 @@ public class WorkbenchController : IDynamicApiController
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 applyUserIds = corrections.Where(c => c.ApplyUserId.HasValue)
+ .Select(c => c.ApplyUserId!.Value).Distinct().ToList();
+ var applyRefereeIds = corrections.Where(c => c.ApplyRefereeId.HasValue)
+ .Select(c => c.ApplyRefereeId!.Value).Distinct().ToList();
var approveUserIds = corrections.Where(c => c.ApproveUserId.HasValue).Select(c => c.ApproveUserId!.Value).Distinct().ToList();
var sessions = (await _sessionRepository.AsQueryable()
@@ -619,11 +622,16 @@ public class WorkbenchController : IDynamicApiController
var eventInfo = await _eventRepository.GetFirstAsync(e => e.Id == input.EventId);
var allUserIds = applyUserIds.Concat(approveUserIds).Distinct().ToList();
- var users = (await _userRepository.AsQueryable()
+ var users = (await _sysUserRepository.AsQueryable()
.Where(u => allUserIds.Contains(u.Id))
.Select(u => new { u.Id, u.Name })
.ToListAsync())
.ToDictionary(u => u.Id, u => u.Name);
+ var applyingReferees = (await _refereeRepository.AsQueryable()
+ .Where(r => applyRefereeIds.Contains(r.Id))
+ .Select(r => new { r.Id, r.Name })
+ .ToListAsync())
+ .ToDictionary(r => r.Id, r => r.Name);
return corrections.Select(c =>
{
@@ -636,7 +644,9 @@ public class WorkbenchController : IDynamicApiController
EntryNo = entry?.EntryNo ?? "",
ParticipantName = entry?.ParticipantName ?? "",
JudgeRole = c.JudgeRole,
- ApplyUserName = users.GetValueOrDefault(c.ApplyUserId, ""),
+ ApplyUserName = c.ApplyRefereeId.HasValue
+ ? applyingReferees.GetValueOrDefault(c.ApplyRefereeId.Value, "")
+ : c.ApplyUserId.HasValue ? users.GetValueOrDefault(c.ApplyUserId.Value, "") : "",
ApplyReason = c.ApplyReason,
CreatedTime = c.CreatedTime,
CorrectionStatus = c.CorrectionStatus,
@@ -679,7 +689,12 @@ public class WorkbenchController : IDynamicApiController
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 applyUser = correction.ApplyUserId.HasValue
+ ? await _sysUserRepository.GetByIdAsync(correction.ApplyUserId.Value)
+ : null;
+ var applyReferee = correction.ApplyRefereeId.HasValue
+ ? await _refereeRepository.GetByIdAsync(correction.ApplyRefereeId.Value)
+ : null;
var approveUser = correction.ApproveUserId.HasValue
? await _userRepository.GetByIdAsync(correction.ApproveUserId.Value)
: null;
@@ -765,7 +780,7 @@ public class WorkbenchController : IDynamicApiController
SubmittedTime = newSheet.SubmittedTime,
HasPendingCorrection = false
} : null,
- ApplyUserName = applyUser?.Name ?? "",
+ ApplyUserName = applyReferee?.Name ?? applyUser?.Name ?? "",
ApplyReason = correction.ApplyReason,
CreatedTime = correction.CreatedTime,
CorrectionStatus = correction.CorrectionStatus,
diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml
index 0d4348a..82d3682 100644
--- a/YD_Event.Application/YD_Event.Application.xml
+++ b/YD_Event.Application/YD_Event.Application.xml
@@ -10614,6 +10614,9 @@
是否由该裁判判定取消比赛资格
+
+ 裁判记录画板地址。
+
后台记分台:单个计分符号的聚合得分
@@ -12326,6 +12329,9 @@
评分单ID(尚未保存过时为0)
+
+ 评分单版本;未产生评分单时为下一待提交版本。
+
评分单状态:0-草稿,10-已提交,20-已确认,30-已被修正,90-已作废
@@ -12706,6 +12712,9 @@
参与聚合的单个裁判得分
+
+ 评分单ID。
+
裁判席位编码
@@ -12721,11 +12730,17 @@
该裁判的得分
+
+ 该评分单当前记录画板地址。
+
可申请成绩修正的评分单
+
+ 最近一次修正流程ID。
+
评分单ID
@@ -12766,6 +12781,12 @@
修正申请状态名称
+
+ 修正申请原因。
+
+
+ 审批意见。
+
是否可以申请修正
@@ -12826,6 +12847,15 @@
关联业务ID
+
+ 关联场次ID。
+
+
+ 关联出场记录ID。
+
+
+ 关联评分单ID。
+
是否已读
@@ -12846,6 +12876,12 @@
消息ID,为空表示全部标记已读
+
+ 记录画板 PNG 上传输入。
+
+
+ 记录画板附件。
+
一个场次的计分上下文:场次、项目、计分规则与评分项。裁判端取评分表、后台算成绩都基于同一份上下文。
@@ -13330,6 +13366,12 @@
标记消息已读,不传消息ID表示全部标记已读
+
+ 上传当前裁判评分单的记录画板 PNG。
+
+
+ 查询评分单当前记录画板。
+
校验当前登录用户的裁判身份并取出裁判档案
@@ -15568,34 +15610,54 @@
- 序号(仅便于人工核对,导入时不参与落库)
+
+ 序号(仅便于人工核对,导入时不参与落库)
+
- 场地名称(必填)
+
+ 场地名称(必填)
+
- 项目名称(必填,需与赛事已配置项目一致,不存在将自动建档)
+
+ 项目名称(必填,需与赛事已配置项目一致,不存在将自动建档)
+
- 组别名称(可空,空则归入“公开组”)
+
+ 组别名称(可空,空则归入"公开组")
+
- 出场编号(可空,空则按同场次顺序自动生成)
+
+ 出场编号(可空,空则按同场次顺序自动生成)
+
- 姓名/队名(必填)
+
+ 姓名/队名(必填)
+
- 所属单位(可空,仅用于核对展示)
+
+ 所属单位(可空,仅用于核对展示)
+
- 联系电话(可空)
+
+ 联系电话(可空)
+
- 证件号(可空)
+
+ 证件号(可空)
+
- 备注(可空)
+
+ 备注(可空)
+
@@ -15603,25 +15665,39 @@
- 序号(仅便于人工核对)
+
+ 序号(仅便于人工核对)
+
- 裁判姓名(必填)
+
+ 裁判姓名(必填)
+
- 手机号(必填,用于匹配/创建裁判档案及打分账号)
+
+ 手机号(必填,用于匹配/创建裁判档案及打分账号)
+
- 裁判角色(可空,如:主裁/难度裁判/表现裁判)
+
+ 裁判角色(可空,如:主裁/难度裁判/表现裁判)
+
- 裁判等级(可空,如:国际级/国家级/一级/二级/三级)
+
+ 裁判等级(可空,如:国际级/国家级/一级/二级/三级)
+
- 负责场地(可空,对应出场秩序中的场地名称)
+
+ 负责场地(可空,对应出场秩序中的场地名称)
+
- 负责项目(可空)
+
+ 负责项目(可空)
+
@@ -15629,28 +15705,44 @@
- 导入批次ID(用于确认导入)
+
+ 导入批次ID(用于确认导入)
+
- 解析状态:0-解析中,10-待确认,20-存在错误
+
+ 解析状态:0-解析中,10-待确认,20-存在错误
+
- 原始数据行数(不含表头)
+
+ 原始数据行数(不含表头)
+
- 有效行数
+
+ 有效行数
+
- 错误数量(存在错误时不可确认)
+
+ 错误数量(存在错误时不可确认)
+
- 警告数量(不阻断确认)
+
+ 警告数量(不阻断确认)
+
- 解析预览统计
+
+ 解析预览统计
+
- 问题清单(错误/警告)
+
+ 问题清单(错误/警告)
+
@@ -15658,16 +15750,24 @@
- 场地数量
+
+ 场地数量
+
- 场次数量(项目×组别×场地)
+
+ 场次数量(项目×组别×场地)
+
- 出场记录数量
+
+ 出场记录数量
+
- 裁判数量
+
+ 裁判数量
+
@@ -15675,16 +15775,24 @@
- 所在行号(Excel 物理行号,含表头)
+
+ 所在行号(Excel 物理行号,含表头)
+
- 所在列名
+
+ 所在列名
+
- 问题描述
+
+ 问题描述
+
- 级别:error-错误(阻断确认),warning-警告(不阻断)
+
+ 级别:error-错误(阻断确认),warning-警告(不阻断)
+
@@ -15692,13 +15800,19 @@
- 导入批次ID(必填,由导入接口返回)
+
+ 导入批次ID(必填,由导入接口返回)
+
- 是否为裁判生成小程序打分账号
+
+ 是否为裁判生成小程序打分账号
+
- 打分账号初始密码(生成账号时必填,长度6-32位)
+
+ 打分账号初始密码(生成账号时必填,长度6-32位)
+
@@ -15706,19 +15820,29 @@
- 秩序册版本号
+
+ 秩序册版本号
+
- 生成场地数量
+
+ 生成场地数量
+
- 生成场次数量
+
+ 生成场次数量
+
- 生成出场记录数量
+
+ 生成出场记录数量
+
- 纳入裁判数量
+
+ 纳入裁判数量
+
@@ -15726,28 +15850,44 @@
- 批次ID
+
+ 批次ID
+
- 创建该导入批次的后台用户ID
+
+ 创建该导入批次的后台用户ID
+
- 解析结果是否包含阻断确认的错误
+
+ 解析结果是否包含阻断确认的错误
+
- 赛事ID(当前版本要求赛事已预先创建)
+
+ 赛事ID(当前版本要求赛事已预先创建)
+
- 导入模式:append-追加(默认),overwrite-覆盖该赛事既有编排
+
+ 导入模式:append-追加(默认),overwrite-覆盖该赛事既有编排
+
- 解析时间
+
+ 解析时间
+
- 出场明细(已通过基础校验)
+
+ 出场明细(已通过基础校验)
+
- 裁判明细
+
+ 裁判明细
+
@@ -15755,28 +15895,44 @@
- Excel 物理行号
+
+ Excel 物理行号
+
- 场地名称
+
+ 场地名称
+
- 项目名称
+
+ 项目名称
+
- 组别名称
+
+ 组别名称
+
- 原始出场编号(可空)
+
+ 原始出场编号(可空)
+
- 姓名/队名
+
+ 姓名/队名
+
- 单位
+
+ 单位
+
- 联系电话
+
+ 联系电话
+
@@ -15784,25 +15940,39 @@
- Excel 物理行号
+
+ Excel 物理行号
+
- 裁判姓名
+
+ 裁判姓名
+
- 手机号
+
+ 手机号
+
- 裁判角色文本
+
+ 裁判角色文本
+
- 裁判等级文本
+
+ 裁判等级文本
+
- 负责场地名称
+
+ 负责场地名称
+
- 负责项目名称
+
+ 负责项目名称
+
@@ -15813,31 +15983,24 @@
下载秩序册导入模板
+
+
上传并同步解析秩序册(不产生业务数据,结果缓存待确认)
+
+
+
+
确认导入:按解析结果生成场地、场次、出场及裁判名单(幂等,同一批次只生成一次)
-
-
- 项目(含赛事项目、组别)建档,返回 (项目名,组别名) -> 上下文 的映射
-
-
- 场地建档(追加模式按名称复用),返回场地名 -> 场地上下文 的映射
-
-
- 按 项目×组别×场地 生成场次,返回组合键 -> 场次 的映射
-
-
- 生成出场记录,返回数量
-
-
- 裁判档案 / 赛事裁判关系 / 可选打分账号,返回纳入的裁判数量
+
+
@@ -19237,19 +19400,29 @@
- 赛事ID(必填)
+
+ 赛事ID(必填)
+
- 赛事项目ID(可选)
+
+ 赛事项目ID(可选)
+
- 组别名称(可选,模糊匹配)
+
+ 组别名称(可选,模糊匹配)
+
- 成绩状态(可选,默认20-已确认;0-计算中,10-待复核,20-已确认,90-已作废)
+
+ 成绩状态(可选,默认20-已确认;0-计算中,10-待复核,20-已确认,90-已作废)
+
- 是否包含被取消比赛资格(DQ)的记录,默认不包含
+
+ 是否包含被取消比赛资格(DQ)的记录,默认不包含
+
@@ -19257,13 +19430,19 @@
- 赛事ID(必填)
+
+ 赛事ID(必填)
+
- 场次ID(与赛事项目ID至少传一个)
+
+ 场次ID(与赛事项目ID至少传一个)
+
- 赛事项目ID(与场次ID至少传一个)
+
+ 赛事项目ID(与场次ID至少传一个)
+
@@ -19271,19 +19450,29 @@
- 赛事ID(必填,按请求参数中的赛事ID匹配)
+
+ 赛事ID(必填,按请求参数中的赛事ID匹配)
+
- 场次ID(可选)
+
+ 场次ID(可选)
+
- 开始时间(可选)
+
+ 开始时间(可选)
+
- 结束时间(可选)
+
+ 结束时间(可选)
+
- 操作人ID(可选)
+
+ 操作人ID(可选)
+
diff --git a/YD_Event.Core/Entities/EventJudgeScoreAttachment.cs b/YD_Event.Core/Entities/EventJudgeScoreAttachment.cs
new file mode 100644
index 0000000..96d2aad
--- /dev/null
+++ b/YD_Event.Core/Entities/EventJudgeScoreAttachment.cs
@@ -0,0 +1,39 @@
+namespace YD_Event.Core.Entities;
+
+///
+/// 裁判评分单附件。每次上传保留一个版本,IsCurrent 指向评分单当前画板。
+///
+[SugarTable("EventJudgeScoreAttachment")]
+public class EventJudgeScoreAttachment : Entity, ISoftDelete, ICreatedUserId, ICreatedTime, IUpdatedUserId, IUpdatedTime
+{
+ public long EventId { get; set; }
+ public long SessionId { get; set; }
+ public long SessionEntryId { get; set; }
+ public long ScoreSheetId { get; set; }
+ public long JudgeAssignmentId { get; set; }
+ public long RefereeId { get; set; }
+
+ [SugarColumn(Length = 32)]
+ public string AttachmentType { get; set; } = "score_annotation_png";
+
+ [SugarColumn(Length = 128)]
+ public string FileName { get; set; } = null!;
+
+ [SugarColumn(Length = 1000)]
+ public string FileUrl { get; set; } = null!;
+
+ public long FileSize { get; set; }
+
+ [SugarColumn(Length = 64)]
+ public string ContentType { get; set; } = "image/png";
+
+ [SugarColumn(Length = 64)]
+ public string IdempotencyKey { get; set; } = null!;
+
+ public bool IsCurrent { get; set; } = true;
+ public bool DeleteMark { get; set; }
+ public long CreatedUserId { get; set; }
+ public DateTime CreatedTime { get; set; }
+ [SugarColumn(IsNullable = true)] public long? UpdatedUserId { get; set; }
+ [SugarColumn(IsNullable = true)] public DateTime? UpdatedTime { get; set; }
+}
diff --git a/YD_Event.Core/Entities/EventScoreCorrection.cs b/YD_Event.Core/Entities/EventScoreCorrection.cs
index 8b6b9de..60a3fec 100644
--- a/YD_Event.Core/Entities/EventScoreCorrection.cs
+++ b/YD_Event.Core/Entities/EventScoreCorrection.cs
@@ -50,9 +50,16 @@ public class EventScoreCorrection : Entity, ISoftDelete, ICreatedUserId, I
public long? NewScoreSheetId { get; set; }
///
- /// 申请人ID
+ /// 后台申请人ID(SysUser)。裁判端申请时为空。
///
- public long ApplyUserId { get; set; }
+ [SugarColumn(IsNullable = true)]
+ public long? ApplyUserId { get; set; }
+
+ ///
+ /// 裁判端申请人ID(Referee)。后台发起时为空。
+ ///
+ [SugarColumn(IsNullable = true)]
+ public long? ApplyRefereeId { get; set; }
///
/// 申请修正原因
diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml
index 97e2925..6f04b17 100644
--- a/YD_Event.Core/YD_Event.Core.xml
+++ b/YD_Event.Core/YD_Event.Core.xml
@@ -1873,6 +1873,11 @@
更新时间
+
+
+ 裁判评分单附件。每次上传保留一个版本,IsCurrent 指向评分单当前画板。
+
+
裁判评分明细表
@@ -3580,7 +3585,12 @@
- 申请人ID
+ 后台申请人ID(SysUser)。裁判端申请时为空。
+
+
+
+
+ 裁判端申请人ID(Referee)。后台发起时为空。
@@ -8638,6 +8648,11 @@
拦截器
+
+
+ 有有效登录用户时以当前用户为准;无用户上下文时保留后台任务显式传入的操作者。
+
+
初始化基础数据
diff --git a/二期需求/20260903_add_score_attachments.sql b/二期需求/20260903_add_score_attachments.sql
new file mode 100644
index 0000000..7e0ef71
--- /dev/null
+++ b/二期需求/20260903_add_score_attachments.sql
@@ -0,0 +1,29 @@
+SET XACT_ABORT ON;
+BEGIN TRANSACTION;
+
+IF OBJECT_ID(N'dbo.[EventJudgeScoreAttachment]','U') IS NULL
+BEGIN
+ CREATE TABLE dbo.[EventJudgeScoreAttachment] (
+ [Id] BIGINT NOT NULL CONSTRAINT [PK_EventJudgeScoreAttachment] PRIMARY KEY,
+ [EventId] BIGINT NOT NULL, [SessionId] BIGINT NOT NULL,
+ [SessionEntryId] BIGINT NOT NULL, [ScoreSheetId] BIGINT NOT NULL,
+ [JudgeAssignmentId] BIGINT NOT NULL, [RefereeId] BIGINT NOT NULL,
+ [AttachmentType] NVARCHAR(32) NOT NULL, [FileName] NVARCHAR(128) NOT NULL,
+ [FileUrl] NVARCHAR(1000) NOT NULL, [FileSize] BIGINT NOT NULL,
+ [ContentType] NVARCHAR(64) NOT NULL, [IdempotencyKey] NVARCHAR(64) NOT NULL,
+ [IsCurrent] BIT NOT NULL CONSTRAINT [DF_EventJudgeScoreAttachment_IsCurrent] DEFAULT (1),
+ [DeleteMark] BIT NOT NULL CONSTRAINT [DF_EventJudgeScoreAttachment_DeleteMark] DEFAULT (0),
+ [CreatedUserId] BIGINT NOT NULL, [CreatedTime] DATETIME2(3) NOT NULL,
+ [UpdatedUserId] BIGINT NULL, [UpdatedTime] DATETIME2(3) NULL
+ );
+END;
+
+IF OBJECT_ID(N'dbo.[FK_EventJudgeScoreAttachment_Sheet]','F') IS NULL
+ ALTER TABLE dbo.[EventJudgeScoreAttachment] WITH CHECK
+ ADD CONSTRAINT [FK_EventJudgeScoreAttachment_Sheet] FOREIGN KEY([ScoreSheetId]) REFERENCES dbo.[EventJudgeScoreSheet]([Id]);
+IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreAttachment]') AND name=N'UX_EventJudgeScoreAttachment_Idempotency')
+ CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeScoreAttachment_Idempotency] ON dbo.[EventJudgeScoreAttachment]([ScoreSheetId],[IdempotencyKey]);
+IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreAttachment]') AND name=N'UX_EventJudgeScoreAttachment_Current')
+ CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeScoreAttachment_Current] ON dbo.[EventJudgeScoreAttachment]([ScoreSheetId],[AttachmentType]) WHERE [IsCurrent]=1 AND [DeleteMark]=0;
+
+COMMIT TRANSACTION;
diff --git a/二期需求/20260904_support_referee_score_corrections.sql b/二期需求/20260904_support_referee_score_corrections.sql
new file mode 100644
index 0000000..622f1c2
--- /dev/null
+++ b/二期需求/20260904_support_referee_score_corrections.sql
@@ -0,0 +1,28 @@
+SET XACT_ABORT ON;
+BEGIN TRANSACTION;
+
+IF COL_LENGTH(N'dbo.EventScoreCorrection', N'ApplyRefereeId') IS NULL
+ EXEC(N'ALTER TABLE dbo.EventScoreCorrection ADD ApplyRefereeId BIGINT NULL;');
+
+IF EXISTS (
+ SELECT 1
+ FROM sys.columns
+ WHERE object_id = OBJECT_ID(N'dbo.EventScoreCorrection')
+ AND name = N'ApplyUserId'
+ AND is_nullable = 0
+)
+ ALTER TABLE dbo.EventScoreCorrection ALTER COLUMN ApplyUserId BIGINT NULL;
+
+IF OBJECT_ID(N'dbo.FK_EventScoreCorrection_ApplyReferee', N'F') IS NULL
+ EXEC(N'ALTER TABLE dbo.EventScoreCorrection WITH CHECK
+ ADD CONSTRAINT FK_EventScoreCorrection_ApplyReferee
+ FOREIGN KEY (ApplyRefereeId) REFERENCES dbo.Referee(Id);');
+
+IF OBJECT_ID(N'dbo.CK_EventScoreCorrection_Applicant', N'C') IS NULL
+ EXEC(N'ALTER TABLE dbo.EventScoreCorrection WITH CHECK
+ ADD CONSTRAINT CK_EventScoreCorrection_Applicant CHECK (
+ (ApplyUserId IS NOT NULL AND ApplyRefereeId IS NULL)
+ OR (ApplyUserId IS NULL AND ApplyRefereeId IS NOT NULL)
+ );');
+
+COMMIT TRANSACTION;
diff --git a/二期需求/20260907_fix_score_correction_open_index.sql b/二期需求/20260907_fix_score_correction_open_index.sql
new file mode 100644
index 0000000..2a93e1e
--- /dev/null
+++ b/二期需求/20260907_fix_score_correction_open_index.sql
@@ -0,0 +1,25 @@
+SET ANSI_NULLS ON;
+SET ANSI_PADDING ON;
+SET ANSI_WARNINGS ON;
+SET ARITHABORT ON;
+SET CONCAT_NULL_YIELDS_NULL ON;
+SET QUOTED_IDENTIFIER ON;
+SET NUMERIC_ROUNDABORT OFF;
+SET XACT_ABORT ON;
+BEGIN TRANSACTION;
+
+IF EXISTS (
+ SELECT 1 FROM sys.indexes
+ WHERE object_id = OBJECT_ID(N'dbo.EventScoreCorrection')
+ AND name = N'UX_EventScoreCorrection_OriginalSheet_Open'
+)
+ DROP INDEX UX_EventScoreCorrection_OriginalSheet_Open ON dbo.EventScoreCorrection;
+
+CREATE UNIQUE NONCLUSTERED INDEX UX_EventScoreCorrection_OriginalSheet_Open
+ON dbo.EventScoreCorrection(OriginalScoreSheetId)
+WHERE DeleteMark = 0
+ AND CorrectionStatus >= 0
+ AND CorrectionStatus <= 30
+ AND CorrectionStatus <> 20;
+
+COMMIT TRANSACTION;
diff --git a/二期需求/dbo.sql b/二期需求/dbo.sql
index 7939861..2f0bfcf 100644
--- a/二期需求/dbo.sql
+++ b/二期需求/dbo.sql
@@ -34,6 +34,14 @@ BEGIN TRY
EXEC @LockResult=sys.sp_getapplock @Resource=N'YD_Event_Phase2_Production_Deployment_v3',@LockMode='Exclusive',@LockOwner='Transaction',@LockTimeout=10000;
IF @LockResult<0 THROW 51002,N'无法取得二期部署排他锁,可能有另一个部署正在执行。',1;
+ /* EventScoreCorrection:同时支持后台账号与裁判账号发起修正。 */
+ IF OBJECT_ID(N'dbo.EventScoreCorrection','U') IS NOT NULL AND COL_LENGTH(N'dbo.EventScoreCorrection', N'ApplyRefereeId') IS NULL
+ ALTER TABLE dbo.EventScoreCorrection ADD ApplyRefereeId BIGINT NULL;
+ IF OBJECT_ID(N'dbo.EventScoreCorrection','U') IS NOT NULL AND EXISTS (
+ SELECT 1 FROM sys.columns WHERE object_id=OBJECT_ID(N'dbo.EventScoreCorrection') AND name=N'ApplyUserId' AND is_nullable=0
+ )
+ ALTER TABLE dbo.EventScoreCorrection ALTER COLUMN ApplyUserId BIGINT NULL;
+
/* ====================================================================================
[1/9] 新增表 · 创建二期业务表(共 19 张;IF OBJECT_ID 判空,可重复执行)
说明:新表外键只引用一期已有表主键(Id),不依赖 [2/9] 新增列,因此置于“改表结构”之前。
@@ -441,6 +449,32 @@ BEGIN TRY
);
END;
+ /* EventJudgeScoreAttachment:评分单记录画板,按上传版本留档。 */
+ IF OBJECT_ID(N'dbo.[EventJudgeScoreAttachment]','U') IS NULL
+ BEGIN
+ CREATE TABLE dbo.[EventJudgeScoreAttachment] (
+ [Id] BIGINT NOT NULL CONSTRAINT [PK_EventJudgeScoreAttachment] PRIMARY KEY,
+ [EventId] BIGINT NOT NULL,
+ [SessionId] BIGINT NOT NULL,
+ [SessionEntryId] BIGINT NOT NULL,
+ [ScoreSheetId] BIGINT NOT NULL,
+ [JudgeAssignmentId] BIGINT NOT NULL,
+ [RefereeId] BIGINT NOT NULL,
+ [AttachmentType] NVARCHAR(32) NOT NULL,
+ [FileName] NVARCHAR(128) NOT NULL,
+ [FileUrl] NVARCHAR(1000) NOT NULL,
+ [FileSize] BIGINT NOT NULL,
+ [ContentType] NVARCHAR(64) NOT NULL,
+ [IdempotencyKey] NVARCHAR(64) NOT NULL,
+ [IsCurrent] BIT NOT NULL CONSTRAINT [DF_EventJudgeScoreAttachment_IsCurrent] DEFAULT (1),
+ [DeleteMark] BIT NOT NULL CONSTRAINT [DF_EventJudgeScoreAttachment_DeleteMark] DEFAULT (0),
+ [CreatedUserId] BIGINT NOT NULL,
+ [CreatedTime] DATETIME2(3) NOT NULL,
+ [UpdatedUserId] BIGINT NULL,
+ [UpdatedTime] DATETIME2(3) NULL
+ );
+ END;
+
/* EventJudgeScoreDetail:评分单逐项录入和计算明细。 */
IF OBJECT_ID(N'dbo.[EventJudgeScoreDetail]','U') IS NULL
BEGIN
@@ -514,7 +548,8 @@ BEGIN TRY
[JudgeRole] NVARCHAR(32) NOT NULL, -- 裁判席位编码
[CorrectionTaskId] BIGINT NULL, -- 修正任务Id
[NewScoreSheetId] BIGINT NULL, -- 修正完成后产生的新评分单Id
- [ApplyUserId] BIGINT NOT NULL, -- 申请人Id
+ [ApplyUserId] BIGINT NULL, -- 后台申请人Id(SysUser),裁判端申请时为空
+ [ApplyRefereeId] BIGINT NULL, -- 裁判端申请人Id(Referee),后台发起时为空
[ApplyReason] NVARCHAR(512) NOT NULL, -- 申请修正原因
[ApproveUserId] BIGINT NULL, -- 审批人Id
[ApproveRemark] NVARCHAR(512) NULL, -- 审批意见
@@ -637,6 +672,7 @@ BEGIN TRY
IF OBJECT_ID('CK_EventSession_Time','C') IS NULL ALTER TABLE dbo.EventSession WITH CHECK ADD CONSTRAINT CK_EventSession_Time CHECK(EndTime IS NULL OR StartTime IS NULL OR EndTime>=StartTime);
IF OBJECT_ID('CK_ScoreRule_Version','C') IS NULL ALTER TABLE dbo.ScoreRule WITH CHECK ADD CONSTRAINT CK_ScoreRule_Version CHECK(RuleVersion>=1);
IF OBJECT_ID('CK_EventJudgeScoreSheet_Version','C') IS NULL ALTER TABLE dbo.EventJudgeScoreSheet WITH CHECK ADD CONSTRAINT CK_EventJudgeScoreSheet_Version CHECK(RuleVersion>=1 AND SheetVersion>=1);
+ IF OBJECT_ID('CK_EventScoreCorrection_Applicant','C') IS NULL ALTER TABLE dbo.EventScoreCorrection WITH CHECK ADD CONSTRAINT CK_EventScoreCorrection_Applicant CHECK((ApplyUserId IS NOT NULL AND ApplyRefereeId IS NULL) OR (ApplyUserId IS NULL AND ApplyRefereeId IS NOT NULL));
/* 3.2 外键:二期表引用一期表主键、以及二期表之间的引用关系 */
IF OBJECT_ID(N'dbo.[FK_RefereeApplication_Event]','F') IS NULL ALTER TABLE dbo.[RefereeApplication] WITH CHECK ADD CONSTRAINT [FK_RefereeApplication_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
IF OBJECT_ID(N'dbo.[FK_RefereeApplication_AppUser]','F') IS NULL ALTER TABLE dbo.[RefereeApplication] WITH CHECK ADD CONSTRAINT [FK_RefereeApplication_AppUser] FOREIGN KEY([RefereeUserId]) REFERENCES dbo.[AppUser]([Id]);
@@ -658,12 +694,14 @@ BEGIN TRY
IF OBJECT_ID(N'dbo.[FK_EventJudgeScoreSheet_Task]','F') IS NULL ALTER TABLE dbo.[EventJudgeScoreSheet] WITH CHECK ADD CONSTRAINT [FK_EventJudgeScoreSheet_Task] FOREIGN KEY([JudgeTaskId]) REFERENCES dbo.[EventJudgeTask]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventJudgeScoreSheet_Entry]','F') IS NULL ALTER TABLE dbo.[EventJudgeScoreSheet] WITH CHECK ADD CONSTRAINT [FK_EventJudgeScoreSheet_Entry] FOREIGN KEY([SessionEntryId]) REFERENCES dbo.[EventSessionEntry]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventJudgeScoreSheet_Rule]','F') IS NULL ALTER TABLE dbo.[EventJudgeScoreSheet] WITH CHECK ADD CONSTRAINT [FK_EventJudgeScoreSheet_Rule] FOREIGN KEY([ScoreRuleId]) REFERENCES dbo.[ScoreRule]([Id]);
+ IF OBJECT_ID(N'dbo.[FK_EventJudgeScoreAttachment_Sheet]','F') IS NULL ALTER TABLE dbo.[EventJudgeScoreAttachment] WITH CHECK ADD CONSTRAINT [FK_EventJudgeScoreAttachment_Sheet] FOREIGN KEY([ScoreSheetId]) REFERENCES dbo.[EventJudgeScoreSheet]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventJudgeScoreDetail_Sheet]','F') IS NULL ALTER TABLE dbo.[EventJudgeScoreDetail] WITH CHECK ADD CONSTRAINT [FK_EventJudgeScoreDetail_Sheet] FOREIGN KEY([ScoreSheetId]) REFERENCES dbo.[EventJudgeScoreSheet]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventResultVersion_Result]','F') IS NULL ALTER TABLE dbo.[EventResultVersion] WITH CHECK ADD CONSTRAINT [FK_EventResultVersion_Result] FOREIGN KEY([EventResultId]) REFERENCES dbo.[EventResult]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventResultVersion_Entry]','F') IS NULL ALTER TABLE dbo.[EventResultVersion] WITH CHECK ADD CONSTRAINT [FK_EventResultVersion_Entry] FOREIGN KEY([SessionEntryId]) REFERENCES dbo.[EventSessionEntry]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventResultVersion_Rule]','F') IS NULL ALTER TABLE dbo.[EventResultVersion] WITH CHECK ADD CONSTRAINT [FK_EventResultVersion_Rule] FOREIGN KEY([ScoreRuleId]) REFERENCES dbo.[ScoreRule]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventScoreCorrection_Entry]','F') IS NULL ALTER TABLE dbo.[EventScoreCorrection] WITH CHECK ADD CONSTRAINT [FK_EventScoreCorrection_Entry] FOREIGN KEY([SessionEntryId]) REFERENCES dbo.[EventSessionEntry]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventScoreCorrection_OriginalSheet]','F') IS NULL ALTER TABLE dbo.[EventScoreCorrection] WITH CHECK ADD CONSTRAINT [FK_EventScoreCorrection_OriginalSheet] FOREIGN KEY([OriginalScoreSheetId]) REFERENCES dbo.[EventJudgeScoreSheet]([Id]);
+ IF OBJECT_ID(N'dbo.[FK_EventScoreCorrection_ApplyReferee]','F') IS NULL ALTER TABLE dbo.[EventScoreCorrection] WITH CHECK ADD CONSTRAINT [FK_EventScoreCorrection_ApplyReferee] FOREIGN KEY([ApplyRefereeId]) REFERENCES dbo.[Referee]([Id]);
IF OBJECT_ID(N'dbo.[FK_EventResultScreenCast_Event]','F') IS NULL ALTER TABLE dbo.[EventResultScreenCast] WITH CHECK ADD CONSTRAINT [FK_EventResultScreenCast_Event] FOREIGN KEY([EventId]) REFERENCES dbo.[EventInfo]([Id]);
/* 3.3 索引:UX_ 唯一索引 / IX_ 非聚集索引;WHERE DeleteMark=0 的过滤索引仅约束有效数据 */
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[Referee]') AND name=N'UX_Referee_Mobile_Active') CREATE UNIQUE NONCLUSTERED INDEX [UX_Referee_Mobile_Active] ON dbo.[Referee]([Mobile]) WHERE [DeleteMark]=0;
@@ -680,10 +718,13 @@ BEGIN TRY
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeAssignment]') AND name=N'UX_EventJudgeAssignment_Session_Role_Active') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeAssignment_Session_Role_Active] ON dbo.[EventJudgeAssignment]([SessionId],[JudgeRole]) WHERE [DeleteMark]=0 AND [Status]=0;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeTask]') AND name=N'IX_EventJudgeTask_User_Status') CREATE NONCLUSTERED INDEX [IX_EventJudgeTask_User_Status] ON dbo.[EventJudgeTask]([JudgeUserId],[TaskStatus],[ExpiresTime]) WHERE [DeleteMark]=0;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeTask]') AND name=N'UX_EventJudgeTask_Business_Version') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeTask_Business_Version] ON dbo.[EventJudgeTask]([SessionEntryId],[JudgeAssignmentId],[TaskVersion]) WHERE [DeleteMark]=0;
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventScoreCorrection]') AND name=N'UX_EventScoreCorrection_OriginalSheet_Open') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventScoreCorrection_OriginalSheet_Open] ON dbo.[EventScoreCorrection]([OriginalScoreSheetId]) WHERE [DeleteMark]=0 AND [CorrectionStatus]>=0 AND [CorrectionStatus]<=30 AND [CorrectionStatus]<>20;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[ScoreRule]') AND name=N'UX_ScoreRule_Code_Active') CREATE UNIQUE NONCLUSTERED INDEX [UX_ScoreRule_Code_Active] ON dbo.[ScoreRule]([RuleCode]) WHERE [DeleteMark]=0;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[ScoreRuleItem]') AND name=N'UX_ScoreRuleItem_Rule_Role_Code_Active') CREATE UNIQUE NONCLUSTERED INDEX [UX_ScoreRuleItem_Rule_Role_Code_Active] ON dbo.[ScoreRuleItem]([ScoreRuleId],[JudgeRoleType],[ItemCode]) WHERE [DeleteMark]=0;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreSheet]') AND name=N'UX_EventJudgeScoreSheet_Idempotency') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeScoreSheet_Idempotency] ON dbo.[EventJudgeScoreSheet]([IdempotencyKey]);
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreSheet]') AND name=N'UX_EventJudgeScoreSheet_Current') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeScoreSheet_Current] ON dbo.[EventJudgeScoreSheet]([SessionEntryId],[JudgeAssignmentId]) WHERE [IsCurrent]=1 AND [DeleteMark]=0;
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreAttachment]') AND name=N'UX_EventJudgeScoreAttachment_Idempotency') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeScoreAttachment_Idempotency] ON dbo.[EventJudgeScoreAttachment]([ScoreSheetId],[IdempotencyKey]);
+ IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreAttachment]') AND name=N'UX_EventJudgeScoreAttachment_Current') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventJudgeScoreAttachment_Current] ON dbo.[EventJudgeScoreAttachment]([ScoreSheetId],[AttachmentType]) WHERE [IsCurrent]=1 AND [DeleteMark]=0;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeScoreDetail]') AND name=N'IX_EventJudgeScoreDetail_Sheet') CREATE NONCLUSTERED INDEX [IX_EventJudgeScoreDetail_Sheet] ON dbo.[EventJudgeScoreDetail]([ScoreSheetId],[Sort]);
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventResultVersion]') AND name=N'UX_EventResultVersion_Result_Version') CREATE UNIQUE NONCLUSTERED INDEX [UX_EventResultVersion_Result_Version] ON dbo.[EventResultVersion]([EventResultId],[ResultVersion]) WHERE [DeleteMark]=0;
IF NOT EXISTS(SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'dbo.[EventJudgeMessage]') AND name=N'IX_EventJudgeMessage_Receiver_Unread') CREATE NONCLUSTERED INDEX [IX_EventJudgeMessage_Receiver_Unread] ON dbo.[EventJudgeMessage]([ReceiverUserId],[IsRead],[CreatedTime]) WHERE [DeleteMark]=0;
@@ -1047,7 +1088,7 @@ BEGIN SET NOCOUNT ON; SELECT * FROM dbo.vw_Phase2_SessionScoreProgress WHERE Ses
EXEC(N'CREATE OR ALTER PROCEDURE dbo.usp_Phase2_ValidateDeployment AS
BEGIN SET NOCOUNT ON;
SELECT N''Table'' AS ObjectType,t.name AS ObjectName FROM sys.tables t WHERE t.name IN
-(''RefereeApplication'',''CheckinTask'',''CheckinRecord'',''Referee'',''EventReferee'',''EventJudgeAssignment'',''EventJudgeTask'',''EventVenue'',''EventSession'',''EventSessionEntry'',''EventSessionEntryRematch'',''EventJudgeMessage'',''ScoreRule'',''ScoreRuleItem'',''EventJudgeScoreSheet'',''EventJudgeScoreDetail'',''EventResultVersion'',''EventScoreCorrection'',''EventResultScreenCast'')
+(''RefereeApplication'',''CheckinTask'',''CheckinRecord'',''Referee'',''EventReferee'',''EventJudgeAssignment'',''EventJudgeTask'',''EventVenue'',''EventSession'',''EventSessionEntry'',''EventSessionEntryRematch'',''EventJudgeMessage'',''ScoreRule'',''ScoreRuleItem'',''EventJudgeScoreSheet'',''EventJudgeScoreAttachment'',''EventJudgeScoreDetail'',''EventResultVersion'',''EventScoreCorrection'',''EventResultScreenCast'')
UNION ALL SELECT N''View'',name FROM sys.views WHERE name LIKE ''vw_Phase2[_]%''
UNION ALL SELECT N''Routine'',name FROM sys.objects WHERE type IN(''P'',''FN'') AND (name LIKE ''usp_Phase2[_]%'' OR name LIKE ''fn_Phase2[_]%'');
SELECT r.RuleCode,COUNT(i.Id) AS ItemCount FROM dbo.ScoreRule r LEFT JOIN dbo.ScoreRuleItem i ON i.ScoreRuleId=r.Id AND i.DeleteMark=0 WHERE r.RuleCode IN(''ROPE_PRESCRIBED'',''ROPE_FREESTYLE_WHEEL'',''ROPE_FREESTYLE_WIRE'',''ROPE_SHOWCASE'') AND r.DeleteMark=0 GROUP BY r.RuleCode; END');
@@ -1085,7 +1126,7 @@ SELECT r.RuleCode,COUNT(i.Id) AS ItemCount FROM dbo.ScoreRule r LEFT JOIN dbo.Sc
DECLARE business_tables CURSOR LOCAL FAST_FORWARD FOR SELECT [Name] FROM (VALUES
(N'RefereeApplication'),(N'CheckinTask'),(N'CheckinRecord'),(N'Referee'),(N'EventReferee'),(N'EventJudgeAssignment'),
(N'EventJudgeTask'),(N'EventVenue'),(N'EventSession'),(N'EventSessionEntry'),(N'EventSessionEntryRematch'),
- (N'EventJudgeMessage'),(N'ScoreRule'),(N'ScoreRuleItem'),(N'EventJudgeScoreSheet'),(N'EventJudgeScoreDetail'),
+ (N'EventJudgeMessage'),(N'ScoreRule'),(N'ScoreRuleItem'),(N'EventJudgeScoreSheet'),(N'EventJudgeScoreAttachment'),(N'EventJudgeScoreDetail'),
(N'EventResultVersion'),(N'EventScoreCorrection'),(N'EventResultScreenCast'))x([Name]);
OPEN business_tables; FETCH NEXT FROM business_tables INTO @BusinessTable;
WHILE @@FETCH_STATUS=0
@@ -1104,7 +1145,7 @@ SELECT r.RuleCode,COUNT(i.Id) AS ItemCount FROM dbo.ScoreRule r LEFT JOIN dbo.Sc
PRINT N'[7/9] 执行后置校验';
DECLARE @Validation TABLE([Category] NVARCHAR(32),[Item] NVARCHAR(128),[Passed] BIT,[Actual] NVARCHAR(1000));
DECLARE @ExpectedTables TABLE(Name SYSNAME);
- INSERT @ExpectedTables VALUES(N'RefereeApplication'),(N'CheckinTask'),(N'CheckinRecord'),(N'Referee'),(N'EventReferee'),(N'EventJudgeAssignment'),(N'EventJudgeTask'),(N'EventVenue'),(N'EventSession'),(N'EventSessionEntry'),(N'EventSessionEntryRematch'),(N'EventJudgeMessage'),(N'ScoreRule'),(N'ScoreRuleItem'),(N'EventJudgeScoreSheet'),(N'EventJudgeScoreDetail'),(N'EventResultVersion'),(N'EventScoreCorrection'),(N'EventResultScreenCast');
+ INSERT @ExpectedTables VALUES(N'RefereeApplication'),(N'CheckinTask'),(N'CheckinRecord'),(N'Referee'),(N'EventReferee'),(N'EventJudgeAssignment'),(N'EventJudgeTask'),(N'EventVenue'),(N'EventSession'),(N'EventSessionEntry'),(N'EventSessionEntryRematch'),(N'EventJudgeMessage'),(N'ScoreRule'),(N'ScoreRuleItem'),(N'EventJudgeScoreSheet'),(N'EventJudgeScoreAttachment'),(N'EventJudgeScoreDetail'),(N'EventResultVersion'),(N'EventScoreCorrection'),(N'EventResultScreenCast');
INSERT @Validation SELECT N'TABLE',Name,CASE WHEN OBJECT_ID(N'dbo.'+QUOTENAME(Name),'U') IS NOT NULL THEN 1 ELSE 0 END,N'dbo.'+Name FROM @ExpectedTables;
DECLARE @ExpectedColumns TABLE(TableName SYSNAME,ColumnName SYSNAME);