diff --git a/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java b/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java index 6d3a357..0f67a7e 100644 --- a/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java +++ b/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java @@ -21,8 +21,11 @@ import org.springblade.modules.martial.pojo.vo.MiniLoginVO; import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO; import com.alibaba.fastjson.JSON; import org.springblade.modules.martial.service.*; +import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO; +import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO; import org.springblade.modules.martial.pojo.entity.MtVenue; import org.springblade.modules.martial.pojo.entity.MartialVenue; +import org.springblade.modules.martial.pojo.entity.MartialResult; import org.springblade.core.redis.cache.BladeRedis; import org.springframework.web.bind.annotation.*; @@ -56,6 +59,7 @@ public class MartialMiniController extends BladeController { private final IMartialAthleteService athleteService; private final IMartialScoreService scoreService; private final BladeRedis bladeRedis; + private final IMartialResultService resultService; // Redis缓存key前缀 private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:"; @@ -121,7 +125,15 @@ public class MartialMiniController extends BladeController { MiniLoginVO vo = new MiniLoginVO(); vo.setToken(token); - vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub"); + String role = invite.getRole(); + Integer refereeType = invite.getRefereeType(); + if ("general_judge".equals(role) || (refereeType != null && refereeType == 3)) { + vo.setUserRole("general"); + } else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 2)) { + vo.setUserRole("admin"); + } else { + vo.setUserRole("pub"); + } vo.setMatchId(competition.getId()); vo.setMatchName(competition.getCompetitionName()); vo.setMatchTime(competition.getCompetitionStartTime() != null ? @@ -517,7 +529,15 @@ public class MartialMiniController extends BladeController { MiniLoginVO vo = new MiniLoginVO(); vo.setToken(token); - vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub"); + String role = invite.getRole(); + Integer refereeType = invite.getRefereeType(); + if ("general_judge".equals(role) || (refereeType != null && refereeType == 3)) { + vo.setUserRole("general"); + } else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 2)) { + vo.setUserRole("admin"); + } else { + vo.setUserRole("pub"); + } vo.setMatchId(competition != null ? competition.getId() : null); vo.setMatchName(competition != null ? competition.getCompetitionName() : null); vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ? @@ -679,4 +699,71 @@ public class MartialMiniController extends BladeController { return projects; } -} \ No newline at end of file + + // ========== 三级裁判评分流程 API ========== + + /** + * 主裁判确认/修改分数 + */ + @PostMapping("/chief/confirm") + @Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数") + public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) { + Long resultId = parseLong(dto.getResultId()); + Long chiefJudgeId = parseLong(dto.getChiefJudgeId()); + if (resultId == null || chiefJudgeId == null) { + return R.fail("参数错误"); + } + boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote()); + return success ? R.success("确认成功") : R.fail("确认失败"); + } + + /** + * 总裁确认/修改分数 + */ + @PostMapping("/general/confirm") + @Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数") + public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) { + Long resultId = parseLong(dto.getResultId()); + Long generalJudgeId = parseLong(dto.getGeneralJudgeId()); + if (resultId == null || generalJudgeId == null) { + return R.fail("参数错误"); + } + boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote()); + return success ? R.success("确认成功") : R.fail("确认失败"); + } + + /** + * 获取待主裁判确认的成绩列表 + */ + @GetMapping("/chief/pending") + @Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表") + public R> getPendingChiefConfirmList(@RequestParam Long venueId) { + List list = resultService.getPendingChiefConfirmList(venueId); + return R.data(list); + } + + /** + * 获取待总裁确认的成绩列表 + */ + @GetMapping("/general/pending") + @Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)") + public R> getPendingGeneralConfirmList(@RequestParam Long competitionId) { + List list = resultService.getPendingGeneralConfirmList(competitionId); + return R.data(list); + } + + /** + * 获取所有场地列表(总裁用) + */ + @GetMapping("/general/venues") + @Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表") + public R> getAllVenues(@RequestParam Long competitionId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(MartialVenue::getCompetitionId, competitionId); + wrapper.eq(MartialVenue::getIsDeleted, 0); + wrapper.orderByAsc(MartialVenue::getVenueName); + List venues = venueService.list(wrapper); + return R.data(venues); + } + +} diff --git a/src/main/java/org/springblade/modules/martial/pojo/dto/ChiefJudgeConfirmDTO.java b/src/main/java/org/springblade/modules/martial/pojo/dto/ChiefJudgeConfirmDTO.java new file mode 100644 index 0000000..6997416 --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/pojo/dto/ChiefJudgeConfirmDTO.java @@ -0,0 +1,26 @@ +package org.springblade.modules.martial.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 主裁判确认评分DTO + */ +@Data +@Schema(description = "主裁判确认评分DTO") +public class ChiefJudgeConfirmDTO { + + @Schema(description = "成绩ID") + private String resultId; + + @Schema(description = "主裁判ID") + private String chiefJudgeId; + + @Schema(description = "确认/修改后的分数(null表示直接确认原分数)") + private BigDecimal score; + + @Schema(description = "备注") + private String note; +} diff --git a/src/main/java/org/springblade/modules/martial/pojo/dto/GeneralJudgeConfirmDTO.java b/src/main/java/org/springblade/modules/martial/pojo/dto/GeneralJudgeConfirmDTO.java new file mode 100644 index 0000000..591c78a --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/pojo/dto/GeneralJudgeConfirmDTO.java @@ -0,0 +1,26 @@ +package org.springblade.modules.martial.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 总裁确认评分DTO + */ +@Data +@Schema(description = "总裁确认评分DTO") +public class GeneralJudgeConfirmDTO { + + @Schema(description = "成绩ID") + private String resultId; + + @Schema(description = "总裁ID") + private String generalJudgeId; + + @Schema(description = "确认/修改后的分数(null表示直接确认原分数)") + private BigDecimal score; + + @Schema(description = "备注") + private String note; +} diff --git a/src/main/java/org/springblade/modules/martial/pojo/entity/MartialJudgeInvite.java b/src/main/java/org/springblade/modules/martial/pojo/entity/MartialJudgeInvite.java index 0ac03ac..0025d3b 100644 --- a/src/main/java/org/springblade/modules/martial/pojo/entity/MartialJudgeInvite.java +++ b/src/main/java/org/springblade/modules/martial/pojo/entity/MartialJudgeInvite.java @@ -37,6 +37,14 @@ public class MartialJudgeInvite extends TenantEntity { private static final long serialVersionUID = 1L; + // ========== 角色常量 ========== + /** 裁判员 */ + public static final String ROLE_JUDGE = "judge"; + /** 主裁判 */ + public static final String ROLE_CHIEF_JUDGE = "chief_judge"; + /** 总裁(裁判长) */ + public static final String ROLE_GENERAL_JUDGE = "general_judge"; + /** * 赛事ID */ @@ -56,13 +64,13 @@ public class MartialJudgeInvite extends TenantEntity { private String inviteCode; /** - * 角色(judge-裁判员,chief_judge-主裁判) + * 角色(judge-裁判员, chief_judge-主裁判, general_judge-总裁) */ @Schema(description = "角色") private String role; /** - * 分配场地ID + * 分配场地ID (总裁时为null,表示负责所有场地) */ @Schema(description = "分配场地ID") private Long venueId; @@ -169,4 +177,25 @@ public class MartialJudgeInvite extends TenantEntity { @Schema(description = "裁判类型") private Integer refereeType; + /** + * 判断是否为裁判员 + */ + public boolean isJudge() { + return ROLE_JUDGE.equals(this.role); + } + + /** + * 判断是否为主裁判 + */ + public boolean isChiefJudge() { + return ROLE_CHIEF_JUDGE.equals(this.role); + } + + /** + * 判断是否为总裁 + */ + public boolean isGeneralJudge() { + return ROLE_GENERAL_JUDGE.equals(this.role); + } + } diff --git a/src/main/java/org/springblade/modules/martial/pojo/entity/MartialResult.java b/src/main/java/org/springblade/modules/martial/pojo/entity/MartialResult.java index 8ca5ff6..43177b8 100644 --- a/src/main/java/org/springblade/modules/martial/pojo/entity/MartialResult.java +++ b/src/main/java/org/springblade/modules/martial/pojo/entity/MartialResult.java @@ -38,6 +38,14 @@ public class MartialResult extends TenantEntity { private static final long serialVersionUID = 1L; + // ========== 评分状态常量 ========== + /** 评分状态:裁判员评分中 */ + public static final int SCORE_STATUS_JUDGING = 0; + /** 评分状态:主裁判已确认 */ + public static final int SCORE_STATUS_CHIEF_CONFIRMED = 1; + /** 评分状态:总裁已确认 */ + public static final int SCORE_STATUS_GENERAL_CONFIRMED = 2; + /** * 赛事ID */ @@ -158,4 +166,62 @@ public class MartialResult extends TenantEntity { @Schema(description = "发布时间") private LocalDateTime publishTime; + // ========== 主裁判确认相关字段 ========== + + /** + * 主裁判确认/修改后的分数 + */ + @Schema(description = "主裁判确认/修改后的分数") + private BigDecimal chiefJudgeScore; + + /** + * 主裁判ID + */ + @Schema(description = "主裁判ID") + private Long chiefJudgeId; + + /** + * 主裁判确认时间 + */ + @Schema(description = "主裁判确认时间") + private LocalDateTime chiefJudgeTime; + + /** + * 主裁判备注 + */ + @Schema(description = "主裁判备注") + private String chiefJudgeNote; + + // ========== 总裁确认相关字段 ========== + + /** + * 总裁确认/修改后的分数 + */ + @Schema(description = "总裁确认/修改后的分数") + private BigDecimal generalJudgeScore; + + /** + * 总裁ID + */ + @Schema(description = "总裁ID") + private Long generalJudgeId; + + /** + * 总裁确认时间 + */ + @Schema(description = "总裁确认时间") + private LocalDateTime generalJudgeTime; + + /** + * 总裁备注 + */ + @Schema(description = "总裁备注") + private String generalJudgeNote; + + /** + * 评分状态: 0-裁判员评分中, 1-主裁判已确认, 2-总裁已确认 + */ + @Schema(description = "评分状态: 0-裁判员评分中, 1-主裁判已确认, 2-总裁已确认") + private Integer scoreStatus; + } diff --git a/src/main/java/org/springblade/modules/martial/service/IMartialResultService.java b/src/main/java/org/springblade/modules/martial/service/IMartialResultService.java index 537b9d3..939d3d8 100644 --- a/src/main/java/org/springblade/modules/martial/service/IMartialResultService.java +++ b/src/main/java/org/springblade/modules/martial/service/IMartialResultService.java @@ -70,4 +70,27 @@ public interface IMartialResultService extends IService { */ List batchGenerateCertificates(Long projectId); + + // ========== 三级裁判评分流程方法 ========== + + /** + * 主裁判确认/修改分数 + */ + boolean confirmByChiefJudge(Long resultId, Long chiefJudgeId, BigDecimal score, String note); + + /** + * 总裁确认/修改分数 + */ + boolean confirmByGeneralJudge(Long resultId, Long generalJudgeId, BigDecimal score, String note); + + /** + * 获取待主裁判确认的成绩列表 + */ + List getPendingChiefConfirmList(Long venueId); + + /** + * 获取待总裁确认的成绩列表 + */ + List getPendingGeneralConfirmList(Long competitionId); + } diff --git a/src/main/java/org/springblade/modules/martial/service/impl/MartialResultServiceImpl.java b/src/main/java/org/springblade/modules/martial/service/impl/MartialResultServiceImpl.java index 7284e5e..15c16f3 100644 --- a/src/main/java/org/springblade/modules/martial/service/impl/MartialResultServiceImpl.java +++ b/src/main/java/org/springblade/modules/martial/service/impl/MartialResultServiceImpl.java @@ -559,4 +559,158 @@ public class MartialResultServiceImpl extends ServiceImpl= MartialResult.SCORE_STATUS_CHIEF_CONFIRMED) { + throw new ServiceException("该成绩已被主裁判确认,无法重复操作"); + } + + // 确定最终分数 + BigDecimal finalScore = score != null ? score : result.getTotalScore(); + + // 验证分数范围(如果有修改) + if (score != null && result.getTotalScore() != null) { + BigDecimal minAllowed = result.getTotalScore().subtract(new BigDecimal("0.050")); + BigDecimal maxAllowed = result.getTotalScore().add(new BigDecimal("0.050")); + if (score.compareTo(minAllowed) < 0 || score.compareTo(maxAllowed) > 0) { + throw new ServiceException("修改分数只能在原始总分±0.050范围内"); + } + } + + // 更新主裁判确认信息 + result.setChiefJudgeScore(finalScore); + result.setChiefJudgeId(chiefJudgeId); + result.setChiefJudgeTime(LocalDateTime.now()); + result.setChiefJudgeNote(note); + result.setScoreStatus(MartialResult.SCORE_STATUS_CHIEF_CONFIRMED); + + // 更新最终得分 + result.setFinalScore(finalScore); + + boolean success = this.updateById(result); + + log.info("主裁判确认成绩 - 成绩ID:{}, 主裁判ID:{}, 原分数:{}, 确认分数:{}, 备注:{}", + resultId, chiefJudgeId, result.getTotalScore(), finalScore, note); + + return success; + } + + /** + * 总裁确认/修改分数 + * + * @param resultId 成绩ID + * @param generalJudgeId 总裁ID + * @param score 确认/修改后的分数(null表示直接确认原分数) + * @param note 备注 + * @return 是否成功 + */ + @Transactional(rollbackFor = Exception.class) + public boolean confirmByGeneralJudge(Long resultId, Long generalJudgeId, BigDecimal score, String note) { + MartialResult result = this.getById(resultId); + if (result == null) { + throw new ServiceException("成绩记录不存在"); + } + + // 检查状态:必须主裁判已确认 + if (result.getScoreStatus() == null || result.getScoreStatus() < MartialResult.SCORE_STATUS_CHIEF_CONFIRMED) { + throw new ServiceException("主裁判尚未确认,总裁无法操作"); + } + + if (result.getScoreStatus() >= MartialResult.SCORE_STATUS_GENERAL_CONFIRMED) { + throw new ServiceException("该成绩已被总裁确认,无法重复操作"); + } + + // 确定最终分数(基于主裁判确认的分数) + BigDecimal baseScore = result.getChiefJudgeScore() != null ? result.getChiefJudgeScore() : result.getTotalScore(); + BigDecimal finalScore = score != null ? score : baseScore; + + // 验证分数范围(如果有修改) + if (score != null && baseScore != null) { + BigDecimal minAllowed = baseScore.subtract(new BigDecimal("0.050")); + BigDecimal maxAllowed = baseScore.add(new BigDecimal("0.050")); + if (score.compareTo(minAllowed) < 0 || score.compareTo(maxAllowed) > 0) { + throw new ServiceException("修改分数只能在主裁判确认分数±0.050范围内"); + } + } + + // 更新总裁确认信息 + result.setGeneralJudgeScore(finalScore); + result.setGeneralJudgeId(generalJudgeId); + result.setGeneralJudgeTime(LocalDateTime.now()); + result.setGeneralJudgeNote(note); + result.setScoreStatus(MartialResult.SCORE_STATUS_GENERAL_CONFIRMED); + + // 更新最终得分 + result.setFinalScore(finalScore); + + boolean success = this.updateById(result); + + log.info("总裁确认成绩 - 成绩ID:{}, 总裁ID:{}, 主裁判分数:{}, 确认分数:{}, 备注:{}", + resultId, generalJudgeId, baseScore, finalScore, note); + + return success; + } + + /** + * 获取待主裁判确认的成绩列表 + * + * @param venueId 场地ID + * @return 成绩列表 + */ + public List getPendingChiefConfirmList(Long venueId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("venue_id", venueId); + wrapper.eq("is_deleted", 0); + wrapper.and(w -> w.isNull("score_status").or().eq("score_status", MartialResult.SCORE_STATUS_JUDGING)); + wrapper.orderByAsc("create_time"); + return this.list(wrapper); + } + + /** + * 获取待总裁确认的成绩列表 + * + * @param competitionId 比赛ID(总裁可以看所有场地) + * @return 成绩列表 + */ + @Override + public List getPendingGeneralConfirmList(Long competitionId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("competition_id", competitionId); + wrapper.eq("is_deleted", 0); + wrapper.eq("score_status", MartialResult.SCORE_STATUS_CHIEF_CONFIRMED); + wrapper.orderByAsc("create_time"); + List results = this.list(wrapper); + + // 填充选手信息 + for (MartialResult result : results) { + if (result.getAthleteId() != null) { + MartialAthlete athlete = athleteService.getById(result.getAthleteId()); + if (athlete != null) { + result.setPlayerName(athlete.getPlayerName()); + result.setTeamName(athlete.getTeamName()); + } + } + } + + return results; + } + } diff --git a/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java b/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java index d3bc99d..87eb6d6 100644 --- a/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java +++ b/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java @@ -15,6 +15,8 @@ import org.springblade.modules.martial.service.IMartialAthleteService; import org.springblade.modules.martial.service.IMartialJudgeProjectService; import org.springblade.modules.martial.service.IMartialJudgeService; import org.springblade.modules.martial.service.IMartialScoreService; +import org.springblade.modules.martial.service.IMartialResultService; +import org.springblade.modules.martial.pojo.entity.MartialResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -43,6 +45,9 @@ public class MartialScoreServiceImpl extends ServiceImpl resultQuery = new LambdaQueryWrapper<>(); + resultQuery.eq(MartialResult::getAthleteId, athlete.getId()) + .eq(MartialResult::getProjectId, athlete.getProjectId()) + .eq(MartialResult::getIsDeleted, 0); + MartialResult result = resultService.getOne(resultQuery); + + if (result == null) { + // 创建新的成绩记录 + result = new MartialResult(); + result.setCompetitionId(athlete.getCompetitionId()); + result.setAthleteId(athlete.getId()); + result.setProjectId(athlete.getProjectId()); + result.setVenueId(dto.getVenueId()); + result.setOriginalScore(originalCalculatedScore); + result.setFinalScore(dto.getModifiedScore()); + } + + // 更新主裁判确认信息 + result.setChiefJudgeScore(dto.getModifiedScore()); + result.setChiefJudgeId(dto.getModifierId()); + result.setChiefJudgeTime(LocalDateTime.now()); + result.setChiefJudgeNote(dto.getNote()); + result.setScoreStatus(MartialResult.SCORE_STATUS_CHIEF_CONFIRMED); + result.setFinalScore(dto.getModifiedScore()); + + boolean resultSaved = resultService.saveOrUpdate(result); + log.info("主裁判确认成绩 - 选手ID:{}, 成绩ID:{}, 分数:{}, score_status:{}", + athlete.getId(), result.getId(), dto.getModifiedScore(), result.getScoreStatus()); + + return athleteUpdated && recordSaved && resultSaved; } }