feat: 评委替换接口与作品评分列表按分配返回(含未评分行与 canReplaceJudge)
Made-with: Cursor
This commit is contained in:
parent
4d0b13524c
commit
4ba2af18f6
@ -20,7 +20,7 @@
|
|||||||
| 评审状态筛选无效 | 第一层评审状态下拉选了不生效(前端计算但未传后端) |
|
| 评审状态筛选无效 | 第一层评审状态下拉选了不生效(前端计算但未传后端) |
|
||||||
| 缺少全局统计 | 没有全平台评审总进度概览 |
|
| 缺少全局统计 | 没有全平台评审总进度概览 |
|
||||||
| 无跨活动维度 | 不能跨活动查看所有作品的评审状态 |
|
| 无跨活动维度 | 不能跨活动查看所有作品的评审状态 |
|
||||||
| TODO 未实现 | 未提交作品列表、评委替换 API 均为空实现 |
|
| ~~TODO~~ 部分已补 | 未提交作品列表仍为 TODO;**评委替换**已由 `POST /api/contests/reviews/replace-judge` 实现(分配 ID + 新评委用户 ID,未评分前可换) |
|
||||||
| 样式不一致 | 主色 `#1890ff` |
|
| 样式不一致 | 主色 `#1890ff` |
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -133,10 +133,11 @@
|
|||||||
|
|
||||||
## 4. 后端改动
|
## 4. 后端改动
|
||||||
|
|
||||||
无新增接口。复用已有能力:
|
- **评委替换**:`POST /api/contests/reviews/replace-judge`(权限 `review:assign`),请求体 `{ "assignmentId": long, "newJudgeId": long }`;该分配下若已有有效评分则不允许替换。
|
||||||
|
- 其余复用已有能力:
|
||||||
- 统计:复用 `GET /api/contests/works/stats`(已有 submitted/reviewing/reviewed)
|
- 统计:复用 `GET /api/contests/works/stats`(已有 submitted/reviewing/reviewed)
|
||||||
- 列表:复用 `GET /api/contests/works`(已返回 reviewedCount/totalJudgesCount/averageScore/assignments)
|
- 列表:复用 `GET /api/contests/works`(已返回 reviewedCount/totalJudgesCount/averageScore/assignments)
|
||||||
- 评分详情:复用 `GET /api/contests/reviews/works/:workId/scores`(已有)
|
- 评分详情:复用 `GET /api/contests/reviews/work/{workId}/scores`(已有)
|
||||||
|
|
||||||
### 4.1 评审进度前端过滤
|
### 4.1 评审进度前端过滤
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import com.lesingle.modules.biz.review.dto.AssignWorkDto;
|
|||||||
import com.lesingle.modules.biz.review.dto.BatchAssignDto;
|
import com.lesingle.modules.biz.review.dto.BatchAssignDto;
|
||||||
import com.lesingle.modules.biz.review.dto.CreateScoreDto;
|
import com.lesingle.modules.biz.review.dto.CreateScoreDto;
|
||||||
import com.lesingle.modules.biz.review.dto.MarkContestWorkViolationDto;
|
import com.lesingle.modules.biz.review.dto.MarkContestWorkViolationDto;
|
||||||
|
import com.lesingle.modules.biz.review.dto.ReplaceJudgeDto;
|
||||||
import com.lesingle.modules.biz.review.service.IContestReviewService;
|
import com.lesingle.modules.biz.review.service.IContestReviewService;
|
||||||
import com.lesingle.security.annotation.RequirePermission;
|
import com.lesingle.security.annotation.RequirePermission;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -36,6 +37,15 @@ public class ContestReviewController {
|
|||||||
return Result.success(contestReviewService.assignWork(contestId, dto.getWorkId(), dto.getJudgeIds(), creatorId));
|
return Result.success(contestReviewService.assignWork(contestId, dto.getWorkId(), dto.getJudgeIds(), creatorId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/replace-judge")
|
||||||
|
@RequirePermission("review:assign")
|
||||||
|
@Operation(summary = "替换评委", description = "将某条作品-评委分配改为另一名评委;该分配下若已有有效评分则不允许替换")
|
||||||
|
public Result<Map<String, Object>> replaceJudge(@Valid @RequestBody ReplaceJudgeDto dto) {
|
||||||
|
Long operatorId = SecurityUtil.getCurrentUserId();
|
||||||
|
return Result.success(contestReviewService.replaceJudge(
|
||||||
|
dto.getAssignmentId(), dto.getNewJudgeId(), operatorId));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/batch-assign")
|
@PostMapping("/batch-assign")
|
||||||
@RequirePermission("review:assign")
|
@RequirePermission("review:assign")
|
||||||
@Operation(summary = "批量分配作品给评委")
|
@Operation(summary = "批量分配作品给评委")
|
||||||
@ -129,7 +139,7 @@ public class ContestReviewController {
|
|||||||
|
|
||||||
@GetMapping("/work/{workId}/scores")
|
@GetMapping("/work/{workId}/scores")
|
||||||
@RequirePermission("review:read")
|
@RequirePermission("review:read")
|
||||||
@Operation(summary = "查询作品评分记录")
|
@Operation(summary = "查询作品评委与评分", description = "按作品评委分配逐行返回;未评分行仍可替换评委(canReplaceJudge=true);已评分为 false")
|
||||||
public Result<List<Map<String, Object>>> getWorkScores(@PathVariable Long workId) {
|
public Result<List<Map<String, Object>>> getWorkScores(@PathVariable Long workId) {
|
||||||
return Result.success(contestReviewService.getWorkScores(workId));
|
return Result.success(contestReviewService.getWorkScores(workId));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.lesingle.modules.biz.review.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "替换作品评委分配(仅未提交评分时可替换)")
|
||||||
|
public class ReplaceJudgeDto {
|
||||||
|
|
||||||
|
@NotNull(message = "分配记录ID不能为空")
|
||||||
|
@Schema(description = "作品评委分配表主键 t_biz_contest_work_judge_assignment.id")
|
||||||
|
private Long assignmentId;
|
||||||
|
|
||||||
|
@NotNull(message = "新评委用户ID不能为空")
|
||||||
|
@Schema(description = "新评委用户ID(须已加入本活动评委)")
|
||||||
|
private Long newJudgeId;
|
||||||
|
}
|
||||||
@ -36,4 +36,9 @@ public interface IContestReviewService {
|
|||||||
Map<String, Object> calculateFinalScore(Long workId);
|
Map<String, Object> calculateFinalScore(Long workId);
|
||||||
|
|
||||||
Map<String, Object> getJudgeContestDetail(Long judgeId, Long contestId);
|
Map<String, Object> getJudgeContestDetail(Long judgeId, Long contestId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将某条作品评委分配替换为另一名评委(该分配下尚无有效评分记录时才允许)。
|
||||||
|
*/
|
||||||
|
Map<String, Object> replaceJudge(Long assignmentId, Long newJudgeId, Long operatorId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,9 @@ import com.lesingle.modules.biz.review.mapper.ContestReviewRuleMapper;
|
|||||||
import com.lesingle.modules.biz.review.mapper.ContestWorkJudgeAssignmentMapper;
|
import com.lesingle.modules.biz.review.mapper.ContestWorkJudgeAssignmentMapper;
|
||||||
import com.lesingle.modules.biz.review.mapper.ContestWorkScoreMapper;
|
import com.lesingle.modules.biz.review.mapper.ContestWorkScoreMapper;
|
||||||
import com.lesingle.modules.biz.review.service.IContestReviewService;
|
import com.lesingle.modules.biz.review.service.IContestReviewService;
|
||||||
|
import com.lesingle.modules.sys.entity.SysTenant;
|
||||||
import com.lesingle.modules.sys.entity.SysUser;
|
import com.lesingle.modules.sys.entity.SysUser;
|
||||||
|
import com.lesingle.modules.sys.mapper.SysTenantMapper;
|
||||||
import com.lesingle.modules.sys.mapper.SysUserMapper;
|
import com.lesingle.modules.sys.mapper.SysUserMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -34,7 +36,18 @@ import org.springframework.util.StringUtils;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -50,6 +63,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
private final ContestRegistrationMapper registrationMapper;
|
private final ContestRegistrationMapper registrationMapper;
|
||||||
private final ContestReviewRuleMapper reviewRuleMapper;
|
private final ContestReviewRuleMapper reviewRuleMapper;
|
||||||
private final SysUserMapper sysUserMapper;
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final SysTenantMapper sysTenantMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅允许将「已在本活动评委表 t_biz_contest_judge 中显式配置」的评委新分配到作品。
|
* 仅允许将「已在本活动评委表 t_biz_contest_judge 中显式配置」的评委新分配到作品。
|
||||||
@ -692,27 +706,179 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> getWorkScores(Long workId) {
|
public List<Map<String, Object>> getWorkScores(Long workId) {
|
||||||
log.info("查询作品评分列表,作品ID:{}", workId);
|
log.info("查询作品评分列表(按评委分配),作品ID:{}", workId);
|
||||||
|
|
||||||
|
BizContestWork work = workMapper.selectById(workId);
|
||||||
|
if (work == null) {
|
||||||
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "作品不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<BizContestWorkJudgeAssignment> aw = new LambdaQueryWrapper<>();
|
||||||
|
aw.eq(BizContestWorkJudgeAssignment::getWorkId, workId);
|
||||||
|
aw.orderByAsc(BizContestWorkJudgeAssignment::getId);
|
||||||
|
List<BizContestWorkJudgeAssignment> assignments = assignmentMapper.selectList(aw);
|
||||||
|
|
||||||
|
if (assignments.isEmpty()) {
|
||||||
|
return listWorkScoresFallbackByScoresOnly(workId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Long> judgeIds = assignments.stream()
|
||||||
|
.map(BizContestWorkJudgeAssignment::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Map<Long, SysUser> judgeUserById = loadUsersByIds(judgeIds);
|
||||||
|
Map<Long, String> tenantNameById = loadTenantNames(judgeUserById.values());
|
||||||
|
|
||||||
|
List<Map<String, Object>> rows = new ArrayList<>();
|
||||||
|
for (BizContestWorkJudgeAssignment a : assignments) {
|
||||||
|
Long assignmentId = a.getId();
|
||||||
|
Long judgeId = a.getJudgeId();
|
||||||
|
|
||||||
|
BizContestWorkScore s = findValidScoreForAssignment(workId, assignmentId, judgeId);
|
||||||
|
|
||||||
|
boolean scored = s != null;
|
||||||
|
boolean canReplaceJudge = !scored;
|
||||||
|
|
||||||
|
SysUser judgeUser = judgeId != null ? judgeUserById.get(judgeId) : null;
|
||||||
|
String judgeName = scored && StringUtils.hasText(s.getJudgeName())
|
||||||
|
? s.getJudgeName()
|
||||||
|
: null;
|
||||||
|
if (!StringUtils.hasText(judgeName) && judgeUser != null) {
|
||||||
|
judgeName = StringUtils.hasText(judgeUser.getNickname())
|
||||||
|
? judgeUser.getNickname()
|
||||||
|
: judgeUser.getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
map.put("assignmentId", assignmentId);
|
||||||
|
map.put("judgeId", judgeId);
|
||||||
|
map.put("judgeName", judgeName);
|
||||||
|
map.put("canReplaceJudge", canReplaceJudge);
|
||||||
|
map.put("scored", scored);
|
||||||
|
|
||||||
|
if (judgeUser != null) {
|
||||||
|
map.put("judge", buildJudgeDetailMap(judgeUser, tenantNameById));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s != null) {
|
||||||
|
map.put("id", s.getId());
|
||||||
|
map.put("scoreId", s.getId());
|
||||||
|
map.put("tenantId", s.getTenantId());
|
||||||
|
map.put("contestId", s.getContestId());
|
||||||
|
map.put("workId", s.getWorkId());
|
||||||
|
map.put("dimensionScores", s.getDimensionScores());
|
||||||
|
map.put("totalScore", s.getTotalScore());
|
||||||
|
map.put("comments", s.getComments());
|
||||||
|
map.put("scoreTime", s.getScoreTime());
|
||||||
|
} else {
|
||||||
|
map.put("id", assignmentId);
|
||||||
|
map.put("scoreId", null);
|
||||||
|
map.put("tenantId", judgeUser != null ? judgeUser.getTenantId() : null);
|
||||||
|
map.put("contestId", work.getContestId());
|
||||||
|
map.put("workId", workId);
|
||||||
|
map.put("dimensionScores", null);
|
||||||
|
map.put("totalScore", null);
|
||||||
|
map.put("comments", null);
|
||||||
|
map.put("scoreTime", null);
|
||||||
|
}
|
||||||
|
rows.add(map);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, SysUser> loadUsersByIds(Set<Long> judgeIds) {
|
||||||
|
Map<Long, SysUser> judgeUserById = new HashMap<>();
|
||||||
|
if (judgeIds == null || judgeIds.isEmpty()) {
|
||||||
|
return judgeUserById;
|
||||||
|
}
|
||||||
|
for (SysUser u : sysUserMapper.selectBatchIds(judgeIds)) {
|
||||||
|
if (u != null && u.getId() != null) {
|
||||||
|
judgeUserById.put(u.getId(), u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return judgeUserById;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, String> loadTenantNames(Collection<SysUser> users) {
|
||||||
|
Set<Long> tenantIds = users.stream()
|
||||||
|
.map(SysUser::getTenantId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Map<Long, String> tenantNameById = new HashMap<>();
|
||||||
|
for (Long tid : tenantIds) {
|
||||||
|
SysTenant t = sysTenantMapper.selectById(tid);
|
||||||
|
if (t != null && StringUtils.hasText(t.getName())) {
|
||||||
|
tenantNameById.put(tid, t.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tenantNameById;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildJudgeDetailMap(SysUser judgeUser, Map<Long, String> tenantNameById) {
|
||||||
|
Map<String, Object> judge = new LinkedHashMap<>();
|
||||||
|
judge.put("id", judgeUser.getId());
|
||||||
|
judge.put("username", judgeUser.getUsername());
|
||||||
|
judge.put("nickname", judgeUser.getNickname());
|
||||||
|
judge.put("phone", judgeUser.getPhone());
|
||||||
|
if (judgeUser.getTenantId() != null) {
|
||||||
|
String tn = tenantNameById.get(judgeUser.getTenantId());
|
||||||
|
if (StringUtils.hasText(tn)) {
|
||||||
|
Map<String, Object> tenant = new LinkedHashMap<>();
|
||||||
|
tenant.put("name", tn);
|
||||||
|
judge.put("tenant", tenant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return judge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先按分配 ID 关联评分;若无则按作品+评委兜底(历史数据 assignment_id 为空)。
|
||||||
|
*/
|
||||||
|
private BizContestWorkScore findValidScoreForAssignment(Long workId, Long assignmentId, Long judgeId) {
|
||||||
|
LambdaQueryWrapper<BizContestWorkScore> sw = new LambdaQueryWrapper<>();
|
||||||
|
sw.eq(BizContestWorkScore::getAssignmentId, assignmentId);
|
||||||
|
sw.eq(BizContestWorkScore::getValidState, 1);
|
||||||
|
List<BizContestWorkScore> byAssign = scoreMapper.selectList(sw);
|
||||||
|
if (!byAssign.isEmpty()) {
|
||||||
|
return pickLatestScore(byAssign);
|
||||||
|
}
|
||||||
|
if (judgeId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<BizContestWorkScore> legacy = new LambdaQueryWrapper<>();
|
||||||
|
legacy.eq(BizContestWorkScore::getWorkId, workId);
|
||||||
|
legacy.eq(BizContestWorkScore::getJudgeId, judgeId);
|
||||||
|
legacy.eq(BizContestWorkScore::getValidState, 1);
|
||||||
|
legacy.and(w -> w.isNull(BizContestWorkScore::getAssignmentId)
|
||||||
|
.or()
|
||||||
|
.eq(BizContestWorkScore::getAssignmentId, assignmentId));
|
||||||
|
List<BizContestWorkScore> list = scoreMapper.selectList(legacy);
|
||||||
|
return list.isEmpty() ? null : pickLatestScore(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BizContestWorkScore pickLatestScore(List<BizContestWorkScore> list) {
|
||||||
|
return list.stream()
|
||||||
|
.max(Comparator.comparing(BizContestWorkScore::getScoreTime, Comparator.nullsLast(LocalDateTime::compareTo))
|
||||||
|
.thenComparing(BizContestWorkScore::getId, Comparator.nullsLast(Long::compareTo)))
|
||||||
|
.orElse(list.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无分配记录时仍返回历史评分行(无 assignment 则不可替换评委)。
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> listWorkScoresFallbackByScoresOnly(Long workId) {
|
||||||
LambdaQueryWrapper<BizContestWorkScore> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWorkScore> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestWorkScore::getWorkId, workId);
|
wrapper.eq(BizContestWorkScore::getWorkId, workId);
|
||||||
wrapper.eq(BizContestWorkScore::getValidState, 1);
|
wrapper.eq(BizContestWorkScore::getValidState, 1);
|
||||||
wrapper.orderByAsc(BizContestWorkScore::getScoreTime);
|
wrapper.orderByAsc(BizContestWorkScore::getScoreTime);
|
||||||
|
|
||||||
List<BizContestWorkScore> scores = scoreMapper.selectList(wrapper);
|
List<BizContestWorkScore> scores = scoreMapper.selectList(wrapper);
|
||||||
|
|
||||||
Set<Long> judgeIds = scores.stream()
|
Set<Long> judgeIds = scores.stream()
|
||||||
.map(BizContestWorkScore::getJudgeId)
|
.map(BizContestWorkScore::getJudgeId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
Map<Long, SysUser> judgeUserById = new HashMap<>();
|
Map<Long, SysUser> judgeUserById = loadUsersByIds(judgeIds);
|
||||||
if (!judgeIds.isEmpty()) {
|
Map<Long, String> tenantNameById = loadTenantNames(judgeUserById.values());
|
||||||
for (SysUser u : sysUserMapper.selectBatchIds(judgeIds)) {
|
|
||||||
if (u != null && u.getId() != null) {
|
|
||||||
judgeUserById.put(u.getId(), u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scores.stream().map(s -> {
|
return scores.stream().map(s -> {
|
||||||
Map<String, Object> map = new LinkedHashMap<>();
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
@ -720,6 +886,8 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
map.put("scoreId", s.getId());
|
map.put("scoreId", s.getId());
|
||||||
map.put("assignmentId", s.getAssignmentId());
|
map.put("assignmentId", s.getAssignmentId());
|
||||||
map.put("judgeId", s.getJudgeId());
|
map.put("judgeId", s.getJudgeId());
|
||||||
|
map.put("canReplaceJudge", false);
|
||||||
|
map.put("scored", true);
|
||||||
SysUser judgeUser = s.getJudgeId() != null ? judgeUserById.get(s.getJudgeId()) : null;
|
SysUser judgeUser = s.getJudgeId() != null ? judgeUserById.get(s.getJudgeId()) : null;
|
||||||
String judgeName = s.getJudgeName();
|
String judgeName = s.getJudgeName();
|
||||||
if (!StringUtils.hasText(judgeName) && judgeUser != null) {
|
if (!StringUtils.hasText(judgeName) && judgeUser != null) {
|
||||||
@ -729,12 +897,11 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
}
|
}
|
||||||
map.put("judgeName", judgeName);
|
map.put("judgeName", judgeName);
|
||||||
if (judgeUser != null) {
|
if (judgeUser != null) {
|
||||||
Map<String, Object> judge = new LinkedHashMap<>();
|
map.put("judge", buildJudgeDetailMap(judgeUser, tenantNameById));
|
||||||
judge.put("id", judgeUser.getId());
|
|
||||||
judge.put("username", judgeUser.getUsername());
|
|
||||||
judge.put("nickname", judgeUser.getNickname());
|
|
||||||
map.put("judge", judge);
|
|
||||||
}
|
}
|
||||||
|
map.put("tenantId", s.getTenantId());
|
||||||
|
map.put("contestId", s.getContestId());
|
||||||
|
map.put("workId", s.getWorkId());
|
||||||
map.put("dimensionScores", s.getDimensionScores());
|
map.put("dimensionScores", s.getDimensionScores());
|
||||||
map.put("totalScore", s.getTotalScore());
|
map.put("totalScore", s.getTotalScore());
|
||||||
map.put("comments", s.getComments());
|
map.put("comments", s.getComments());
|
||||||
@ -900,4 +1067,59 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====== 替换评委 ======
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Map<String, Object> replaceJudge(Long assignmentId, Long newJudgeId, Long operatorId) {
|
||||||
|
log.info("替换评委分配,assignmentId:{},newJudgeId:{}", assignmentId, newJudgeId);
|
||||||
|
|
||||||
|
BizContestWorkJudgeAssignment assignment = assignmentMapper.selectById(assignmentId);
|
||||||
|
if (assignment == null) {
|
||||||
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "分配记录不存在");
|
||||||
|
}
|
||||||
|
Long contestId = assignment.getContestId();
|
||||||
|
Long workId = assignment.getWorkId();
|
||||||
|
Long oldJudgeId = assignment.getJudgeId();
|
||||||
|
|
||||||
|
if (newJudgeId != null && newJudgeId.equals(oldJudgeId)) {
|
||||||
|
Map<String, Object> noop = new LinkedHashMap<>();
|
||||||
|
noop.put("assignmentId", assignmentId);
|
||||||
|
noop.put("changed", false);
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<BizContestWorkScore> scoreWrapper = new LambdaQueryWrapper<>();
|
||||||
|
scoreWrapper.eq(BizContestWorkScore::getAssignmentId, assignmentId);
|
||||||
|
scoreWrapper.eq(BizContestWorkScore::getValidState, 1);
|
||||||
|
if (scoreMapper.selectCount(scoreWrapper) > 0) {
|
||||||
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "该评委已提交评分,无法替换");
|
||||||
|
}
|
||||||
|
|
||||||
|
assertJudgeAssignedToContest(contestId, newJudgeId);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<BizContestWorkJudgeAssignment> dupWrapper = new LambdaQueryWrapper<>();
|
||||||
|
dupWrapper.eq(BizContestWorkJudgeAssignment::getContestId, contestId);
|
||||||
|
dupWrapper.eq(BizContestWorkJudgeAssignment::getWorkId, workId);
|
||||||
|
dupWrapper.eq(BizContestWorkJudgeAssignment::getJudgeId, newJudgeId);
|
||||||
|
dupWrapper.ne(BizContestWorkJudgeAssignment::getId, assignmentId);
|
||||||
|
if (assignmentMapper.selectCount(dupWrapper) > 0) {
|
||||||
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "该评委已负责本作品,无需重复分配");
|
||||||
|
}
|
||||||
|
|
||||||
|
assignment.setJudgeId(newJudgeId);
|
||||||
|
assignment.setModifier(operatorId != null ? operatorId.intValue() : null);
|
||||||
|
assignment.setModifyTime(LocalDateTime.now());
|
||||||
|
assignmentMapper.updateById(assignment);
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("assignmentId", assignmentId);
|
||||||
|
result.put("workId", workId);
|
||||||
|
result.put("contestId", contestId);
|
||||||
|
result.put("oldJudgeId", oldJudgeId);
|
||||||
|
result.put("newJudgeId", newJudgeId);
|
||||||
|
result.put("changed", true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -504,17 +504,18 @@ export interface ContestWorkJudgeAssignment {
|
|||||||
export interface ContestWorkScore {
|
export interface ContestWorkScore {
|
||||||
/** 与 id 同源,评分列表扁平接口常用 scoreId */
|
/** 与 id 同源,评分列表扁平接口常用 scoreId */
|
||||||
scoreId?: number;
|
scoreId?: number;
|
||||||
|
/** 有评分记录时为评分表 id;仅有分配未评分时与 assignmentId 相同 */
|
||||||
id: number;
|
id: number;
|
||||||
tenantId: number;
|
tenantId?: number;
|
||||||
contestId: number;
|
contestId: number;
|
||||||
workId: number;
|
workId: number;
|
||||||
assignmentId: number;
|
assignmentId?: number;
|
||||||
judgeId: number;
|
judgeId: number;
|
||||||
judgeName: string;
|
judgeName: string;
|
||||||
dimensionScores: any;
|
dimensionScores: any;
|
||||||
totalScore: number;
|
totalScore: number | null;
|
||||||
comments?: string;
|
comments?: string;
|
||||||
scoreTime: string;
|
scoreTime: string | null;
|
||||||
creator?: number;
|
creator?: number;
|
||||||
modifier?: number;
|
modifier?: number;
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
@ -525,7 +526,12 @@ export interface ContestWorkScore {
|
|||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
|
phone?: string;
|
||||||
|
tenant?: { name?: string };
|
||||||
};
|
};
|
||||||
|
/** 后端按分配返回:未提交有效评分前为 true,已评分则为 false(仅可读的评审详情可隐藏「替换评委」) */
|
||||||
|
canReplaceJudge?: boolean;
|
||||||
|
scored?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AssignWorkForm {
|
export interface AssignWorkForm {
|
||||||
|
|||||||
@ -96,7 +96,7 @@
|
|||||||
<!-- 评审详情抽屉 -->
|
<!-- 评审详情抽屉 -->
|
||||||
<a-drawer v-model:open="scoreDrawerVisible" title="评审详情" placement="right" width="700">
|
<a-drawer v-model:open="scoreDrawerVisible" title="评审详情" placement="right" width="700">
|
||||||
<a-table :columns="scoreColumns" :data-source="scoreList" :loading="scoreLoading" :pagination="false"
|
<a-table :columns="scoreColumns" :data-source="scoreList" :loading="scoreLoading" :pagination="false"
|
||||||
row-key="scoreId" size="small">
|
:row-key="(r: any) => (r.assignmentId != null ? r.assignmentId : r.scoreId ?? r.id)" size="small">
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'judgeName'">
|
<template v-if="column.key === 'judgeName'">
|
||||||
{{
|
{{
|
||||||
@ -122,9 +122,15 @@
|
|||||||
{{ formatDate(record.scoreTime) }}
|
{{ formatDate(record.scoreTime) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'action'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<a-button type="link" size="small" @click="handleReplaceJudge(record)">
|
<a-button
|
||||||
|
v-if="record.canReplaceJudge === true"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="handleReplaceJudge(record)"
|
||||||
|
>
|
||||||
替换评委
|
替换评委
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<span v-else class="text-gray">—</span>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user