Compare commits
7 Commits
b94ac501de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| df7efac819 | |||
| 559dea702a | |||
| c40ca5b35b | |||
| 742272026b | |||
| 496537ceef | |||
| e0d3572e34 | |||
| a262ca9279 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,6 +15,7 @@ import org.springblade.core.tool.utils.Func;
|
|||||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialAthleteMapper;
|
||||||
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
||||||
import org.springblade.modules.system.pojo.entity.User;
|
import org.springblade.modules.system.pojo.entity.User;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -43,11 +44,9 @@ public class MartialCompetitionController extends BladeController {
|
|||||||
public R<MartialCompetition> detail(@RequestParam Long id) {
|
public R<MartialCompetition> detail(@RequestParam Long id) {
|
||||||
MartialCompetition detail = competitionService.getById(id);
|
MartialCompetition detail = competitionService.getById(id);
|
||||||
if (detail != null) {
|
if (detail != null) {
|
||||||
Long cnt = martialAthleteService.count(Wrappers.<MartialAthlete>query().lambda()
|
// Count distinct participants by id_card
|
||||||
.eq(MartialAthlete::getCompetitionId, detail.getId())
|
Long cnt = ((MartialAthleteMapper) martialAthleteService.getBaseMapper()).countDistinctParticipants(detail.getId());
|
||||||
.eq(MartialAthlete::getIsDeleted, 0)
|
detail.setTotalParticipants(cnt != null ? cnt.intValue() : 0);
|
||||||
);
|
|
||||||
detail.setTotalParticipants(cnt.intValue());
|
|
||||||
}
|
}
|
||||||
return R.data(detail);
|
return R.data(detail);
|
||||||
}
|
}
|
||||||
@@ -61,11 +60,9 @@ public class MartialCompetitionController extends BladeController {
|
|||||||
IPage<MartialCompetition> pages = competitionService.page(Condition.getPage(query), Condition.getQueryWrapper(competition));
|
IPage<MartialCompetition> pages = competitionService.page(Condition.getPage(query), Condition.getQueryWrapper(competition));
|
||||||
List<MartialCompetition> pagelist = pages.getRecords();
|
List<MartialCompetition> pagelist = pages.getRecords();
|
||||||
for (MartialCompetition martialCompetition : pagelist) {
|
for (MartialCompetition martialCompetition : pagelist) {
|
||||||
Long cnt = martialAthleteService.count(Wrappers.<MartialAthlete>query().lambda()
|
// Count distinct participants by id_card
|
||||||
.eq(MartialAthlete::getCompetitionId, martialCompetition.getId())
|
Long cnt = ((MartialAthleteMapper) martialAthleteService.getBaseMapper()).countDistinctParticipants(martialCompetition.getId());
|
||||||
.eq(MartialAthlete::getIsDeleted, 0)
|
martialCompetition.setTotalParticipants(cnt != null ? cnt.intValue() : 0);
|
||||||
);
|
|
||||||
martialCompetition.setTotalParticipants(cnt.intValue());
|
|
||||||
}
|
}
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import org.springblade.modules.martial.pojo.entity.MartialContact;
|
|||||||
import org.springblade.modules.martial.service.IMartialContactService;
|
import org.springblade.modules.martial.service.IMartialContactService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -43,16 +41,10 @@ public class MartialContactController extends BladeController {
|
|||||||
@Operation(summary = "保存", description = "新增或修改联系人")
|
@Operation(summary = "保存", description = "新增或修改联系人")
|
||||||
public R<Boolean> submit(@RequestBody MartialContact contact) {
|
public R<Boolean> submit(@RequestBody MartialContact contact) {
|
||||||
Long userId = AuthUtil.getUserId();
|
Long userId = AuthUtil.getUserId();
|
||||||
log.info("Contact submit - id: {}, name: {}, userId: {}", contact.getId(), contact.getName(), userId);
|
log.info("Contact submit - id: {}, name: {}, userId: {}, isDefault: {}",
|
||||||
|
contact.getId(), contact.getName(), userId, contact.getIsDefault());
|
||||||
|
|
||||||
if (contact.getId() == null) {
|
return R.data(contactService.saveContact(contact, userId));
|
||||||
contact.setCreateUser(userId);
|
|
||||||
contact.setCreateTime(new Date());
|
|
||||||
}
|
|
||||||
contact.setUpdateUser(userId);
|
|
||||||
contact.setUpdateTime(new Date());
|
|
||||||
|
|
||||||
return R.data(contactService.saveOrUpdate(contact));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/remove")
|
@PostMapping("/remove")
|
||||||
|
|||||||
@@ -10,26 +10,19 @@ import org.springblade.core.tool.utils.DateUtil;
|
|||||||
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
||||||
import org.springblade.modules.martial.excel.ResultExportExcel;
|
import org.springblade.modules.martial.excel.ResultExportExcel;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
import org.springblade.modules.martial.service.IMartialResultService;
|
import org.springblade.modules.martial.service.IMartialResultService;
|
||||||
import org.springblade.modules.martial.service.IMartialScheduleService;
|
import org.springblade.modules.martial.service.IMartialScheduleService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出打印 控制器
|
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/martial/export")
|
@RequestMapping("/martial/export")
|
||||||
@@ -40,67 +33,47 @@ public class MartialExportController {
|
|||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialScheduleService scheduleService;
|
private final IMartialScheduleService scheduleService;
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.1: 导出成绩单
|
|
||||||
*/
|
|
||||||
@GetMapping("/results")
|
@GetMapping("/results")
|
||||||
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
||||||
public void exportResults(
|
public void exportResults(@RequestParam Long competitionId, @RequestParam(required = false) Long projectId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
@RequestParam(required = false) Long projectId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
||||||
String fileName = "成绩单_" + DateUtil.today();
|
String fileName = "成绩单_" + DateUtil.today();
|
||||||
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
||||||
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.2: 导出运动员名单
|
|
||||||
*/
|
|
||||||
@GetMapping("/athletes")
|
@GetMapping("/athletes")
|
||||||
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
||||||
public void exportAthletes(
|
public void exportAthletes(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
||||||
String fileName = "运动员名单_" + DateUtil.today();
|
String fileName = "运动员名单_" + DateUtil.today();
|
||||||
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.3: 导出赛程表
|
|
||||||
*/
|
|
||||||
@GetMapping("/schedule")
|
@GetMapping("/schedule")
|
||||||
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
||||||
public void exportSchedule(
|
public void exportSchedule(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
||||||
String fileName = "赛程表_" + DateUtil.today();
|
String fileName = "赛程表_" + DateUtil.today();
|
||||||
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@GetMapping("/schedule2")
|
||||||
* Task 3.4: 生成单个证书(HTML格式)
|
@Operation(summary = "导出赛程表-模板2", description = "按场地导出比赛时间表格式的赛程安排")
|
||||||
*/
|
public void exportScheduleTemplate2(@RequestParam Long competitionId, @RequestParam(required = false) Long venueId,
|
||||||
@GetMapping("/certificate/{resultId}")
|
@RequestParam(required = false) String venueName, @RequestParam(required = false) String timeSlot, HttpServletResponse response) {
|
||||||
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面,可打印为PDF")
|
List<ScheduleExportExcel2> list = scheduleService.exportScheduleTemplate2(competitionId, venueId);
|
||||||
public void generateCertificate(
|
String fileName = "比赛时间_" + (venueName != null ? venueName : "全部场地") + "_" + DateUtil.today();
|
||||||
@PathVariable Long resultId,
|
String sheetName = (venueName != null ? venueName : "全部场地") + (timeSlot != null ? "_" + timeSlot : "");
|
||||||
HttpServletResponse response
|
ExcelUtil.export(response, fileName, sheetName, list, ScheduleExportExcel2.class);
|
||||||
) throws IOException {
|
}
|
||||||
// 1. 获取证书数据
|
|
||||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
|
||||||
|
|
||||||
// 2. 读取HTML模板
|
@GetMapping("/certificate/{resultId}")
|
||||||
|
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面")
|
||||||
|
public void generateCertificate(@PathVariable Long resultId, HttpServletResponse response) throws IOException {
|
||||||
|
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||||
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
||||||
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// 3. 替换模板变量
|
|
||||||
String html = template
|
String html = template
|
||||||
.replace("${playerName}", certificate.getPlayerName())
|
.replace("${playerName}", certificate.getPlayerName())
|
||||||
.replace("${competitionName}", certificate.getCompetitionName())
|
.replace("${competitionName}", certificate.getCompetitionName())
|
||||||
@@ -109,15 +82,10 @@ public class MartialExportController {
|
|||||||
.replace("${medalClass}", certificate.getMedalClass())
|
.replace("${medalClass}", certificate.getMedalClass())
|
||||||
.replace("${organization}", certificate.getOrganization())
|
.replace("${organization}", certificate.getOrganization())
|
||||||
.replace("${issueDate}", certificate.getIssueDate());
|
.replace("${issueDate}", certificate.getIssueDate());
|
||||||
|
|
||||||
// 4. 返回HTML
|
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
response.setContentType("text/html;charset=UTF-8");
|
||||||
response.getWriter().write(html);
|
response.getWriter().write(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.4: 批量生成证书数据
|
|
||||||
*/
|
|
||||||
@GetMapping("/certificates/batch")
|
@GetMapping("/certificates/batch")
|
||||||
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
||||||
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
||||||
@@ -125,14 +93,10 @@ public class MartialExportController {
|
|||||||
return R.data(certificates);
|
return R.data(certificates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.4: 获取单个证书数据(JSON格式)
|
|
||||||
*/
|
|
||||||
@GetMapping("/certificate/data/{resultId}")
|
@GetMapping("/certificate/data/{resultId}")
|
||||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式),供前端渲染")
|
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式)")
|
||||||
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
||||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||||
return R.data(certificate);
|
return R.data(certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
|
|||||||
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.LineupGroupVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.LineupParticipantVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.springblade.modules.martial.service.*;
|
import org.springblade.modules.martial.service.*;
|
||||||
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||||
@@ -27,6 +30,8 @@ import org.springblade.modules.martial.pojo.entity.MtVenue;
|
|||||||
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||||
import org.springblade.core.redis.cache.BladeRedis;
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialScheduleStatusMapper;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -34,6 +39,8 @@ import java.math.RoundingMode;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -60,6 +67,8 @@ public class MartialMiniController extends BladeController {
|
|||||||
private final IMartialScoreService scoreService;
|
private final IMartialScoreService scoreService;
|
||||||
private final BladeRedis bladeRedis;
|
private final BladeRedis bladeRedis;
|
||||||
private final IMartialResultService resultService;
|
private final IMartialResultService resultService;
|
||||||
|
private final MartialScheduleStatusMapper scheduleStatusMapper;
|
||||||
|
private final MartialScheduleGroupMapper scheduleGroupMapper;
|
||||||
|
|
||||||
// Redis缓存key前缀
|
// Redis缓存key前缀
|
||||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||||
@@ -817,4 +826,145 @@ public class MartialMiniController extends BladeController {
|
|||||||
return R.data(list);
|
return R.data(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 出场顺序相关 API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编排状态
|
||||||
|
*/
|
||||||
|
@GetMapping("/schedule/status")
|
||||||
|
@Operation(summary = "获取编排状态", description = "检查赛事编排是否完成")
|
||||||
|
public R<Map<String, Object>> getScheduleStatus(@RequestParam Long competitionId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialScheduleStatus> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialScheduleStatus::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialScheduleStatus::getIsDeleted, 0);
|
||||||
|
wrapper.last("LIMIT 1");
|
||||||
|
MartialScheduleStatus status = scheduleStatusMapper.selectOne(wrapper);
|
||||||
|
|
||||||
|
if (status == null) {
|
||||||
|
result.put("isCompleted", false);
|
||||||
|
result.put("scheduleStatus", 0);
|
||||||
|
result.put("statusText", "未编排");
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCompleted = status.getScheduleStatus() != null && status.getScheduleStatus() == 2;
|
||||||
|
result.put("isCompleted", isCompleted);
|
||||||
|
result.put("scheduleStatus", status.getScheduleStatus());
|
||||||
|
result.put("statusText", getScheduleStatusText(status.getScheduleStatus()));
|
||||||
|
result.put("totalGroups", status.getTotalGroups());
|
||||||
|
result.put("totalParticipants", status.getTotalParticipants());
|
||||||
|
result.put("lockedTime", status.getLockedTime());
|
||||||
|
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getScheduleStatusText(Integer status) {
|
||||||
|
if (status == null) return "未编排";
|
||||||
|
switch (status) {
|
||||||
|
case 0: return "未编排";
|
||||||
|
case 1: return "编排中";
|
||||||
|
case 2: return "已锁定";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取出场顺序
|
||||||
|
*/
|
||||||
|
@GetMapping("/schedule/lineup")
|
||||||
|
@Operation(summary = "获取出场顺序", description = "获取已编排的出场顺序列表")
|
||||||
|
public R<Map<String, Object>> getLineup(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam(required = false) Long projectId
|
||||||
|
) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
// 使用现有mapper查询编排详情
|
||||||
|
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||||
|
|
||||||
|
if (details == null || details.isEmpty()) {
|
||||||
|
result.put("groups", new ArrayList<>());
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按项目过滤
|
||||||
|
if (projectId != null) {
|
||||||
|
// 需要通过groupName或其他字段判断项目,这里先获取项目名
|
||||||
|
MartialProject project = projectService.getById(projectId);
|
||||||
|
if (project != null) {
|
||||||
|
String projectName = project.getProjectName();
|
||||||
|
details = details.stream()
|
||||||
|
.filter(d -> d.getGroupName() != null && d.getGroupName().contains(projectName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为LineupGroupVO格式
|
||||||
|
Map<Long, LineupGroupVO> groupMap = new HashMap<>();
|
||||||
|
for (ScheduleGroupDetailVO detail : details) {
|
||||||
|
Long groupId = detail.getGroupId();
|
||||||
|
LineupGroupVO group = groupMap.get(groupId);
|
||||||
|
if (group == null) {
|
||||||
|
group = new LineupGroupVO();
|
||||||
|
group.setGroupId(groupId);
|
||||||
|
group.setGroupName(detail.getGroupName());
|
||||||
|
group.setCategory(detail.getCategory());
|
||||||
|
group.setVenueName(detail.getVenueName());
|
||||||
|
group.setTimeSlot(detail.getTimeSlot());
|
||||||
|
group.setTableNo(generateTableNo(detail));
|
||||||
|
group.setParticipants(new ArrayList<>());
|
||||||
|
groupMap.put(groupId, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加参赛者
|
||||||
|
if (detail.getParticipantId() != null) {
|
||||||
|
LineupParticipantVO participant = new LineupParticipantVO();
|
||||||
|
participant.setId(detail.getParticipantId());
|
||||||
|
participant.setOrder(detail.getPerformanceOrder() != null ? detail.getPerformanceOrder() : group.getParticipants().size() + 1);
|
||||||
|
participant.setPlayerName(detail.getPlayerName());
|
||||||
|
participant.setOrganization(detail.getOrganization());
|
||||||
|
participant.setStatus(detail.getScheduleStatus() != null ? detail.getScheduleStatus() : "waiting");
|
||||||
|
group.getParticipants().add(participant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("groups", new ArrayList<>(groupMap.values()));
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成表号: 场地(1位) + 时段(1位) + 序号(2位)
|
||||||
|
*/
|
||||||
|
private String generateTableNo(ScheduleGroupDetailVO detail) {
|
||||||
|
// 场地编号(简单取第一个数字或默认1)
|
||||||
|
int venueNo = 1;
|
||||||
|
if (detail.getVenueName() != null) {
|
||||||
|
String venueName = detail.getVenueName();
|
||||||
|
for (char c : venueName.toCharArray()) {
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
venueNo = Character.getNumericValue(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时段:上午=1, 下午=2
|
||||||
|
int period = 1;
|
||||||
|
if (detail.getTimeSlot() != null) {
|
||||||
|
try {
|
||||||
|
int hour = Integer.parseInt(detail.getTimeSlot().split(":")[0]);
|
||||||
|
period = hour < 12 ? 1 : 2;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序号:使用displayOrder或默认1
|
||||||
|
int orderNo = detail.getDisplayOrder() != null ? detail.getDisplayOrder() : 1;
|
||||||
|
|
||||||
|
return String.format("%d%d%02d", venueNo, period, orderNo);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,21 @@ import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
|||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.springblade.modules.martial.service.*;
|
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.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -45,9 +54,17 @@ public class MartialMiniController extends BladeController {
|
|||||||
private final IMartialJudgeService judgeService;
|
private final IMartialJudgeService judgeService;
|
||||||
private final IMartialCompetitionService competitionService;
|
private final IMartialCompetitionService competitionService;
|
||||||
private final IMartialVenueService venueService;
|
private final IMartialVenueService venueService;
|
||||||
|
private final IMtVenueService mtVenueService;
|
||||||
private final IMartialProjectService projectService;
|
private final IMartialProjectService projectService;
|
||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialScoreService scoreService;
|
private final IMartialScoreService scoreService;
|
||||||
|
private final BladeRedis bladeRedis;
|
||||||
|
private final IMartialResultService resultService;
|
||||||
|
|
||||||
|
// Redis缓存key前缀
|
||||||
|
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||||
|
// 登录缓存过期时间(7天)
|
||||||
|
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录验证
|
* 登录验证
|
||||||
@@ -91,26 +108,55 @@ public class MartialMiniController extends BladeController {
|
|||||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||||
judgeInviteService.updateById(invite);
|
judgeInviteService.updateById(invite);
|
||||||
|
|
||||||
MartialVenue venue = null;
|
// 从 martial_venue 表获取场地信息
|
||||||
|
MartialVenue martialVenue = null;
|
||||||
if (invite.getVenueId() != null) {
|
if (invite.getVenueId() != null) {
|
||||||
venue = venueService.getById(invite.getVenueId());
|
martialVenue = venueService.getById(invite.getVenueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
MiniLoginVO vo = new MiniLoginVO();
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
vo.setToken(token);
|
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) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
vo.setMatchId(competition.getId());
|
vo.setMatchId(competition.getId());
|
||||||
vo.setMatchName(competition.getCompetitionName());
|
vo.setMatchName(competition.getCompetitionName());
|
||||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||||
competition.getCompetitionStartTime().toString() : "");
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
vo.setJudgeId(judge.getId());
|
vo.setJudgeId(judge.getId());
|
||||||
vo.setJudgeName(judge.getName());
|
vo.setJudgeName(judge.getName());
|
||||||
vo.setVenueId(venue != null ? venue.getId() : null);
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
vo.setVenueName(venue != null ? venue.getVenueName() : null);
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
vo.setProjects(projects);
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
return R.data(vo);
|
return R.data(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +198,136 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean success = scoreService.save(score);
|
boolean success = scoreService.save(score);
|
||||||
|
|
||||||
|
// 评分保存成功后,计算并更新选手总分
|
||||||
|
if (success) {
|
||||||
|
Long athleteId = parseLong(dto.getAthleteId());
|
||||||
|
Long projectId = parseLong(dto.getProjectId());
|
||||||
|
Long venueId = parseLong(dto.getVenueId());
|
||||||
|
if (athleteId != null && projectId != null) {
|
||||||
|
updateAthleteTotalScore(athleteId, projectId, venueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算并更新选手总分
|
||||||
|
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
* 只有所有裁判都评分完成后才更新总分
|
||||||
|
*/
|
||||||
|
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
||||||
|
try {
|
||||||
|
// 1. 查询该场地的裁判员数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 2. 获取主裁判ID列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
||||||
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
|
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
|
List<MartialScore> scores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
|
// 4. 判断是否所有裁判都已评分
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了裁判数量,检查是否评分完成
|
||||||
|
if (requiredJudgeCount > 0 && scores.size() < requiredJudgeCount) {
|
||||||
|
// 未完成评分,清空总分
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null && athlete.getTotalScore() != null) {
|
||||||
|
athlete.setTotalScore(null);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 计算总分(去掉最高最低分取平均)
|
||||||
|
BigDecimal totalScore = calculateTotalScore(scores);
|
||||||
|
|
||||||
|
// 5. 更新选手总分
|
||||||
|
if (totalScore != null) {
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null) {
|
||||||
|
athlete.setTotalScore(totalScore);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 记录错误但不影响评分提交
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
||||||
|
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
||||||
|
*/
|
||||||
|
private int getRequiredJudgeCount(Long venueId) {
|
||||||
|
if (venueId == null || venueId <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
||||||
|
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||||
|
// Use distinct judge_id to count unique judges
|
||||||
|
return (int) judges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算总分
|
||||||
|
* 算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
*/
|
||||||
|
private BigDecimal calculateTotalScore(List<MartialScore> scores) {
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有分数并排序
|
||||||
|
List<BigDecimal> scoreValues = scores.stream()
|
||||||
|
.map(MartialScore::getScore)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
int count = scoreValues.size();
|
||||||
|
if (count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 3) {
|
||||||
|
// 裁判数量<3,直接取平均分
|
||||||
|
BigDecimal sum = scoreValues.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(count), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉最高分和最低分(已排序,去掉第一个和最后一个)
|
||||||
|
List<BigDecimal> middleScores = scoreValues.subList(1, count - 1);
|
||||||
|
|
||||||
|
// 计算平均分
|
||||||
|
BigDecimal sum = middleScores.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(middleScores.size()), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全地将String转换为Long
|
* 安全地将String转换为Long
|
||||||
@@ -171,8 +345,8 @@ public class MartialMiniController extends BladeController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(支持分页)
|
* 获取选手列表(支持分页)
|
||||||
* - 普通裁判:获取所有选手,标记是否已评分
|
* - 裁判员:获取所有选手,标记是否已评分
|
||||||
* - 裁判长:获取已有评分的选手列表
|
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/score/athletes")
|
@GetMapping("/score/athletes")
|
||||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||||
@@ -181,6 +355,7 @@ public class MartialMiniController extends BladeController {
|
|||||||
@RequestParam Integer refereeType,
|
@RequestParam Integer refereeType,
|
||||||
@RequestParam(required = false) Long projectId,
|
@RequestParam(required = false) Long projectId,
|
||||||
@RequestParam(required = false) Long venueId,
|
@RequestParam(required = false) Long venueId,
|
||||||
|
@RequestParam(required = false) Long competitionId,
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "10") Integer size
|
@RequestParam(defaultValue = "10") Integer size
|
||||||
) {
|
) {
|
||||||
@@ -188,6 +363,11 @@ public class MartialMiniController extends BladeController {
|
|||||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
|
||||||
|
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||||
|
if (competitionId != null) {
|
||||||
|
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||||
|
}
|
||||||
|
|
||||||
if (projectId != null) {
|
if (projectId != null) {
|
||||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||||
}
|
}
|
||||||
@@ -196,35 +376,48 @@ public class MartialMiniController extends BladeController {
|
|||||||
|
|
||||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||||
|
|
||||||
// 2. 获取所有评分记录
|
// 2. 获取该场地所有主裁判的judge_id列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
if (projectId != null) {
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
// 添加场地过滤
|
||||||
|
if (venueId != null && venueId > 0) {
|
||||||
|
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
||||||
|
}
|
||||||
|
// 排除主裁判的评分
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
// 按选手ID分组统计评分
|
// 按选手ID分组统计评分
|
||||||
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
||||||
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||||
|
|
||||||
// 3. 根据裁判类型处理选手列表
|
// 4. 获取该场地的应评裁判数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 5. 根据裁判类型处理选手列表
|
||||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||||
|
|
||||||
if (refereeType == 1) {
|
if (refereeType == 1) {
|
||||||
// 裁判长:返回已有评分的选手
|
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||||
filteredList = athletes.stream()
|
filteredList = athletes.stream()
|
||||||
.filter(athlete -> {
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
List<MartialScore> scores = scoresByAthlete.get(athlete.getId());
|
|
||||||
return scores != null && !scores.isEmpty();
|
|
||||||
})
|
|
||||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
|
||||||
.collect(java.util.stream.Collectors.toList());
|
.collect(java.util.stream.Collectors.toList());
|
||||||
} else {
|
} else {
|
||||||
// 普通裁判:返回所有选手,标记是否已评分
|
// 裁判员:返回所有选手,标记是否已评分
|
||||||
filteredList = athletes.stream()
|
filteredList = athletes.stream()
|
||||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
.collect(java.util.stream.Collectors.toList());
|
.collect(java.util.stream.Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 手动分页
|
// 6. 手动分页
|
||||||
int total = filteredList.size();
|
int total = filteredList.size();
|
||||||
int fromIndex = (current - 1) * size;
|
int fromIndex = (current - 1) * size;
|
||||||
int toIndex = Math.min(fromIndex + size, total);
|
int toIndex = Math.min(fromIndex + size, total);
|
||||||
@@ -236,13 +429,31 @@ public class MartialMiniController extends BladeController {
|
|||||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 构建分页结果
|
// 7. 构建分页结果
|
||||||
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||||
page.setRecords(pageRecords);
|
page.setRecords(pageRecords);
|
||||||
|
|
||||||
return R.data(page);
|
return R.data(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取场地所有主裁判的judge_id列表
|
||||||
|
*/
|
||||||
|
private List<Long> getChiefJudgeIds(Long venueId) {
|
||||||
|
if (venueId == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge");
|
||||||
|
List<MartialJudgeInvite> chiefJudges = judgeInviteService.list(judgeQuery);
|
||||||
|
return chiefJudges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情
|
* 获取评分详情
|
||||||
*/
|
*/
|
||||||
@@ -254,10 +465,10 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
*/
|
*/
|
||||||
@PutMapping("/score/modify")
|
@PutMapping("/score/modify")
|
||||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||||
@@ -268,26 +479,107 @@ public class MartialMiniController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
@Operation(summary = "退出登录", description = "清除登录状态")
|
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||||
public R logout() {
|
public R logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
// 从Redis删除登录缓存
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.del(cacheKey);
|
||||||
|
}
|
||||||
return R.success("退出成功");
|
return R.success("退出成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token验证
|
* Token验证(从Redis恢复登录状态)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/verify")
|
@GetMapping("/verify")
|
||||||
@Operation(summary = "Token验证", description = "验证当前token是否有效")
|
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
||||||
public R verify() {
|
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
return R.success("Token有效");
|
if (token == null || token.isEmpty()) {
|
||||||
|
return R.fail("Token不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis获取登录信息
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
MiniLoginVO loginInfo = bladeRedis.get(cacheKey);
|
||||||
|
|
||||||
|
if (loginInfo != null) {
|
||||||
|
// 刷新缓存过期时间
|
||||||
|
bladeRedis.setEx(cacheKey, loginInfo, LOGIN_CACHE_EXPIRE);
|
||||||
|
return R.data(loginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis中没有,尝试从数据库恢复
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getAccessToken, token);
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||||
|
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("Token无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.getTokenExpireTime() != null && invite.getTokenExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return R.fail("Token已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重建登录信息
|
||||||
|
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||||
|
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||||
|
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
||||||
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
|
vo.setToken(token);
|
||||||
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
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 ?
|
||||||
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
|
vo.setJudgeId(judge != null ? judge.getId() : null);
|
||||||
|
vo.setJudgeName(judge != null ? judge.getName() : null);
|
||||||
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 重新缓存到Redis
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
|
return R.data(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换选手实体为VO
|
* 转换选手实体为VO
|
||||||
|
* 新增:只有评分完成时才显示总分
|
||||||
*/
|
*/
|
||||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||||
MartialAthlete athlete,
|
MartialAthlete athlete,
|
||||||
List<MartialScore> scores,
|
List<MartialScore> scores,
|
||||||
Long currentJudgeId) {
|
Long currentJudgeId,
|
||||||
|
int requiredJudgeCount) {
|
||||||
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
||||||
vo.setAthleteId(athlete.getId());
|
vo.setAthleteId(athlete.getId());
|
||||||
vo.setName(athlete.getPlayerName());
|
vo.setName(athlete.getPlayerName());
|
||||||
@@ -296,7 +588,9 @@ public class MartialMiniController extends BladeController {
|
|||||||
vo.setTeam(athlete.getTeamName());
|
vo.setTeam(athlete.getTeamName());
|
||||||
vo.setOrderNum(athlete.getOrderNum());
|
vo.setOrderNum(athlete.getOrderNum());
|
||||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||||
vo.setTotalScore(athlete.getTotalScore());
|
|
||||||
|
// 设置应评分裁判数量
|
||||||
|
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||||
|
|
||||||
// 设置项目名称
|
// 设置项目名称
|
||||||
if (athlete.getProjectId() != null) {
|
if (athlete.getProjectId() != null) {
|
||||||
@@ -307,8 +601,10 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置评分状态
|
// 设置评分状态
|
||||||
|
int scoredCount = 0;
|
||||||
if (scores != null && !scores.isEmpty()) {
|
if (scores != null && !scores.isEmpty()) {
|
||||||
vo.setScoredJudgeCount(scores.size());
|
scoredCount = scores.size();
|
||||||
|
vo.setScoredJudgeCount(scoredCount);
|
||||||
|
|
||||||
// 查找当前裁判的评分
|
// 查找当前裁判的评分
|
||||||
MartialScore myScore = scores.stream()
|
MartialScore myScore = scores.stream()
|
||||||
@@ -326,6 +622,23 @@ public class MartialMiniController extends BladeController {
|
|||||||
vo.setScored(false);
|
vo.setScored(false);
|
||||||
vo.setScoredJudgeCount(0);
|
vo.setScoredJudgeCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断评分是否完成(所有裁判都已评分)
|
||||||
|
boolean scoringComplete = false;
|
||||||
|
if (requiredJudgeCount > 0) {
|
||||||
|
scoringComplete = scoredCount >= requiredJudgeCount;
|
||||||
|
} else {
|
||||||
|
// 如果没有配置裁判数量,只要有评分就算完成
|
||||||
|
scoringComplete = scoredCount > 0;
|
||||||
|
}
|
||||||
|
vo.setScoringComplete(scoringComplete);
|
||||||
|
|
||||||
|
// 只有评分完成时才显示总分
|
||||||
|
if (scoringComplete) {
|
||||||
|
vo.setTotalScore(athlete.getTotalScore());
|
||||||
|
} else {
|
||||||
|
vo.setTotalScore(null);
|
||||||
|
}
|
||||||
|
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -378,4 +691,130 @@ public class MartialMiniController extends BladeController {
|
|||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取比赛的所有项目
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据场地获取项目列表
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getVenueId, venueId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========== 三级裁判评分流程 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<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/pending")
|
||||||
|
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||||
|
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有场地列表(总裁用)
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/venues")
|
||||||
|
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||||
|
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||||
|
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||||
|
wrapper.orderByAsc(MartialVenue::getVenueName);
|
||||||
|
List<MartialVenue> venues = venueService.list(wrapper);
|
||||||
|
return R.data(venues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/confirmed")
|
||||||
|
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -78,9 +79,32 @@ public class MartialProjectController extends BladeController {
|
|||||||
@PostMapping("/submit")
|
@PostMapping("/submit")
|
||||||
@Operation(summary = "新增或修改", description = "传入实体")
|
@Operation(summary = "新增或修改", description = "传入实体")
|
||||||
public R submit(@RequestBody MartialProject project) {
|
public R submit(@RequestBody MartialProject project) {
|
||||||
|
// Auto-generate project code for new projects
|
||||||
|
if (project.getId() == null && StringUtil.isBlank(project.getProjectCode())) {
|
||||||
|
String projectCode = generateProjectCode(project.getCompetitionId());
|
||||||
|
project.setProjectCode(projectCode);
|
||||||
|
}
|
||||||
return R.status(projectService.saveOrUpdate(project));
|
return R.status(projectService.saveOrUpdate(project));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate project code: competition prefix + sequence number
|
||||||
|
* Format: C{competitionId}-P{sequence}, e.g., C1-P001
|
||||||
|
*/
|
||||||
|
private String generateProjectCode(Long competitionId) {
|
||||||
|
if (competitionId == null) {
|
||||||
|
return "P" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count existing projects for this competition
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||||
|
long count = projectService.count(wrapper);
|
||||||
|
|
||||||
|
// Generate code: C{competitionId}-P{sequence}
|
||||||
|
return String.format("C%d-P%03d", competitionId, count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 删除
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,21 +13,26 @@ import org.springblade.core.tool.api.R;
|
|||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialRegistrationOrder;
|
import org.springblade.modules.martial.pojo.entity.MartialRegistrationOrder;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
||||||
import org.springblade.modules.martial.pojo.dto.RegistrationSubmitDTO;
|
import org.springblade.modules.martial.pojo.dto.RegistrationSubmitDTO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MartialRegistrationOrderVO;
|
import org.springblade.modules.martial.pojo.vo.MartialRegistrationOrderVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.OrganizationStatsVO;
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
||||||
|
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||||
import org.springblade.modules.martial.service.IMartialRegistrationOrderService;
|
import org.springblade.modules.martial.service.IMartialRegistrationOrderService;
|
||||||
import org.springblade.modules.martial.service.IMartialTeamService;
|
import org.springblade.modules.martial.service.IMartialTeamService;
|
||||||
import org.springblade.modules.martial.mapper.MartialTeamMemberMapper;
|
import org.springblade.modules.martial.mapper.MartialTeamMemberMapper;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@@ -40,6 +45,7 @@ public class MartialRegistrationOrderController extends BladeController {
|
|||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialTeamService teamService;
|
private final IMartialTeamService teamService;
|
||||||
private final IMartialCompetitionService competitionService;
|
private final IMartialCompetitionService competitionService;
|
||||||
|
private final IMartialProjectService projectService;
|
||||||
private final MartialTeamMemberMapper teamMemberMapper;
|
private final MartialTeamMemberMapper teamMemberMapper;
|
||||||
|
|
||||||
@GetMapping("/detail")
|
@GetMapping("/detail")
|
||||||
@@ -60,6 +66,192 @@ public class MartialRegistrationOrderController extends BladeController {
|
|||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/organization-stats")
|
||||||
|
@Operation(summary = "单位统计", description = "按单位统计运动员、项目、金额")
|
||||||
|
public R<List<OrganizationStatsVO>> getOrganizationStats(@RequestParam Long competitionId) {
|
||||||
|
log.info("获取单位统计: competitionId={}", competitionId);
|
||||||
|
|
||||||
|
// 1. Get all athletes for this competition
|
||||||
|
LambdaQueryWrapper<MartialAthlete> athleteWrapper = new LambdaQueryWrapper<>();
|
||||||
|
athleteWrapper.eq(MartialAthlete::getCompetitionId, competitionId)
|
||||||
|
.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
List<MartialAthlete> athletes = athleteService.list(athleteWrapper);
|
||||||
|
|
||||||
|
if (athletes.isEmpty()) {
|
||||||
|
return R.data(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get all projects for this competition
|
||||||
|
Set<Long> projectIds = athletes.stream()
|
||||||
|
.map(MartialAthlete::getProjectId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
final Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||||
|
if (!projectIds.isEmpty()) {
|
||||||
|
List<MartialProject> projects = projectService.listByIds(projectIds);
|
||||||
|
projectMap.putAll(projects.stream().collect(Collectors.toMap(MartialProject::getId, p -> p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get team members for team projects
|
||||||
|
Set<Long> teamIds = athletes.stream()
|
||||||
|
.filter(a -> {
|
||||||
|
MartialProject project = projectMap.get(a.getProjectId());
|
||||||
|
return project != null && project.getType() != null && project.getType() == 2;
|
||||||
|
})
|
||||||
|
.map(a -> {
|
||||||
|
// Try to get team ID from team table by team name
|
||||||
|
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||||
|
teamWrapper.eq(MartialTeam::getTeamName, a.getTeamName())
|
||||||
|
.eq(MartialTeam::getIsDeleted, 0)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||||
|
return team != null ? team.getId() : null;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// Get team members
|
||||||
|
Map<Long, List<MartialTeamMember>> teamMembersMap = new HashMap<>();
|
||||||
|
if (!teamIds.isEmpty()) {
|
||||||
|
LambdaQueryWrapper<MartialTeamMember> memberWrapper = new LambdaQueryWrapper<>();
|
||||||
|
memberWrapper.in(MartialTeamMember::getTeamId, teamIds)
|
||||||
|
.eq(MartialTeamMember::getIsDeleted, 0);
|
||||||
|
List<MartialTeamMember> members = teamMemberMapper.selectList(memberWrapper);
|
||||||
|
teamMembersMap = members.stream().collect(Collectors.groupingBy(MartialTeamMember::getTeamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Group by organization and calculate stats
|
||||||
|
Map<String, OrganizationStatsVO> orgStatsMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (MartialAthlete athlete : athletes) {
|
||||||
|
String org = athlete.getOrganization();
|
||||||
|
if (org == null || org.isEmpty()) {
|
||||||
|
org = "未知单位";
|
||||||
|
}
|
||||||
|
|
||||||
|
OrganizationStatsVO stats = orgStatsMap.computeIfAbsent(org, k -> {
|
||||||
|
OrganizationStatsVO vo = new OrganizationStatsVO();
|
||||||
|
vo.setOrganization(k);
|
||||||
|
vo.setAthleteCount(0);
|
||||||
|
vo.setProjectCount(0);
|
||||||
|
vo.setSingleProjectCount(0);
|
||||||
|
vo.setTeamProjectCount(0);
|
||||||
|
vo.setMaleCount(0);
|
||||||
|
vo.setFemaleCount(0);
|
||||||
|
vo.setTotalAmount(BigDecimal.ZERO);
|
||||||
|
vo.setProjectAmounts(new ArrayList<>());
|
||||||
|
return vo;
|
||||||
|
});
|
||||||
|
|
||||||
|
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||||
|
if (project == null) continue;
|
||||||
|
|
||||||
|
// Check if project already counted for this org
|
||||||
|
boolean projectExists = stats.getProjectAmounts().stream()
|
||||||
|
.anyMatch(pa -> pa.getProjectId().equals(athlete.getProjectId()));
|
||||||
|
|
||||||
|
if (!projectExists) {
|
||||||
|
// Add project amount item
|
||||||
|
OrganizationStatsVO.ProjectAmountItem item = new OrganizationStatsVO.ProjectAmountItem();
|
||||||
|
item.setProjectId(project.getId());
|
||||||
|
item.setProjectName(project.getProjectName());
|
||||||
|
item.setProjectType(project.getType());
|
||||||
|
item.setCount(1);
|
||||||
|
item.setPrice(project.getPrice() != null ? project.getPrice() : BigDecimal.ZERO);
|
||||||
|
item.setAmount(item.getPrice());
|
||||||
|
stats.getProjectAmounts().add(item);
|
||||||
|
|
||||||
|
stats.setProjectCount(stats.getProjectCount() + 1);
|
||||||
|
if (project.getType() != null && project.getType() == 2) {
|
||||||
|
stats.setTeamProjectCount(stats.getTeamProjectCount() + 1);
|
||||||
|
} else {
|
||||||
|
stats.setSingleProjectCount(stats.getSingleProjectCount() + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Update count for existing project
|
||||||
|
stats.getProjectAmounts().stream()
|
||||||
|
.filter(pa -> pa.getProjectId().equals(athlete.getProjectId()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(pa -> {
|
||||||
|
pa.setCount(pa.getCount() + 1);
|
||||||
|
pa.setAmount(pa.getPrice().multiply(BigDecimal.valueOf(pa.getCount())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Calculate unique athletes and gender counts per organization
|
||||||
|
for (Map.Entry<String, OrganizationStatsVO> entry : orgStatsMap.entrySet()) {
|
||||||
|
String org = entry.getKey();
|
||||||
|
OrganizationStatsVO stats = entry.getValue();
|
||||||
|
|
||||||
|
// Get all athletes for this org
|
||||||
|
Set<String> uniqueIdCards = new HashSet<>();
|
||||||
|
int maleCount = 0;
|
||||||
|
int femaleCount = 0;
|
||||||
|
|
||||||
|
for (MartialAthlete athlete : athletes) {
|
||||||
|
String athleteOrg = athlete.getOrganization();
|
||||||
|
if (athleteOrg == null || athleteOrg.isEmpty()) athleteOrg = "未知单位";
|
||||||
|
if (!athleteOrg.equals(org)) continue;
|
||||||
|
|
||||||
|
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||||
|
if (project == null) continue;
|
||||||
|
|
||||||
|
// For individual projects, count the athlete
|
||||||
|
if (project.getType() == null || project.getType() == 1) {
|
||||||
|
String idCard = athlete.getIdCard();
|
||||||
|
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||||
|
uniqueIdCards.add(idCard);
|
||||||
|
if (athlete.getGender() != null && athlete.getGender() == 1) {
|
||||||
|
maleCount++;
|
||||||
|
} else if (athlete.getGender() != null && athlete.getGender() == 2) {
|
||||||
|
femaleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For team projects, count team members
|
||||||
|
String teamName = athlete.getTeamName();
|
||||||
|
if (teamName != null) {
|
||||||
|
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||||
|
teamWrapper.eq(MartialTeam::getTeamName, teamName)
|
||||||
|
.eq(MartialTeam::getIsDeleted, 0)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||||
|
if (team != null && teamMembersMap.containsKey(team.getId())) {
|
||||||
|
for (MartialTeamMember member : teamMembersMap.get(team.getId())) {
|
||||||
|
MartialAthlete memberAthlete = athleteService.getById(member.getAthleteId());
|
||||||
|
if (memberAthlete != null) {
|
||||||
|
String idCard = memberAthlete.getIdCard();
|
||||||
|
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||||
|
uniqueIdCards.add(idCard);
|
||||||
|
if (memberAthlete.getGender() != null && memberAthlete.getGender() == 1) {
|
||||||
|
maleCount++;
|
||||||
|
} else if (memberAthlete.getGender() != null && memberAthlete.getGender() == 2) {
|
||||||
|
femaleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.setAthleteCount(uniqueIdCards.size());
|
||||||
|
stats.setMaleCount(maleCount);
|
||||||
|
stats.setFemaleCount(femaleCount);
|
||||||
|
|
||||||
|
// Calculate total amount
|
||||||
|
BigDecimal totalAmount = stats.getProjectAmounts().stream()
|
||||||
|
.map(OrganizationStatsVO.ProjectAmountItem::getAmount)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
stats.setTotalAmount(totalAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.data(new ArrayList<>(orgStatsMap.values()));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/submit")
|
@PostMapping("/submit")
|
||||||
@Operation(summary = "提交报名", description = "提交报名订单并关联选手或集体")
|
@Operation(summary = "提交报名", description = "提交报名订单并关联选手或集体")
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -193,6 +385,8 @@ public class MartialRegistrationOrderController extends BladeController {
|
|||||||
newRecord.setGender(existingAthlete.getGender());
|
newRecord.setGender(existingAthlete.getGender());
|
||||||
newRecord.setIdCard(existingAthlete.getIdCard());
|
newRecord.setIdCard(existingAthlete.getIdCard());
|
||||||
newRecord.setIdCardType(existingAthlete.getIdCardType());
|
newRecord.setIdCardType(existingAthlete.getIdCardType());
|
||||||
|
newRecord.setBirthDate(existingAthlete.getBirthDate());
|
||||||
|
newRecord.setAge(existingAthlete.getAge());
|
||||||
newRecord.setContactPhone(existingAthlete.getContactPhone());
|
newRecord.setContactPhone(existingAthlete.getContactPhone());
|
||||||
newRecord.setOrganization(existingAthlete.getOrganization());
|
newRecord.setOrganization(existingAthlete.getOrganization());
|
||||||
newRecord.setTeamName(existingAthlete.getTeamName());
|
newRecord.setTeamName(existingAthlete.getTeamName());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.springblade.core.mp.support.Query;
|
|||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||||
import org.springblade.modules.martial.service.IMartialScoreService;
|
import org.springblade.modules.martial.service.IMartialScoreService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -43,8 +44,8 @@ public class MartialScoreController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "分页列表", description = "分页查询")
|
@Operation(summary = "分页列表", description = "分页查询")
|
||||||
public R<IPage<MartialScore>> list(MartialScore score, Query query) {
|
public R<IPage<MartialScoreVO>> list(MartialScore score, Query query) {
|
||||||
IPage<MartialScore> pages = scoreService.page(Condition.getPage(query), Condition.getQueryWrapper(score));
|
IPage<MartialScoreVO> pages = scoreService.selectScoreVOPage(Condition.getPage(query), score);
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.springblade.modules.martial.excel;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
|
||||||
|
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule export Excel - Template 2 (Competition Schedule Format)
|
||||||
|
* Format: Sequence, Project, Participants, Groups, Time, Table Number
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ColumnWidth(12)
|
||||||
|
@HeadRowHeight(25)
|
||||||
|
@ContentRowHeight(20)
|
||||||
|
public class ScheduleExportExcel2 implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ExcelProperty("序号")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer sequenceNo;
|
||||||
|
|
||||||
|
@ExcelProperty("项目")
|
||||||
|
@ColumnWidth(25)
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
@ExcelProperty("人数")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer participantCount;
|
||||||
|
|
||||||
|
@ExcelProperty("组数")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer groupCount;
|
||||||
|
|
||||||
|
@ExcelProperty("时间")
|
||||||
|
@ColumnWidth(10)
|
||||||
|
private Integer durationMinutes;
|
||||||
|
|
||||||
|
@ExcelProperty("表号")
|
||||||
|
@ColumnWidth(10)
|
||||||
|
private String tableNo;
|
||||||
|
}
|
||||||
@@ -22,4 +22,10 @@ public interface MartialAthleteMapper extends BaseMapper<MartialAthlete> {
|
|||||||
*/
|
*/
|
||||||
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, @Param("athlete") MartialAthlete athlete);
|
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, @Param("athlete") MartialAthlete athlete);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count distinct participants by id_card for a competition
|
||||||
|
*/
|
||||||
|
@org.apache.ibatis.annotations.Select("SELECT COUNT(DISTINCT id_card) FROM martial_athlete WHERE competition_id = #{competitionId} AND is_deleted = 0")
|
||||||
|
Long countDistinctParticipants(@Param("competitionId") Long competitionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package org.springblade.modules.martial.mapper;
|
package org.springblade.modules.martial.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Score Mapper 接口
|
* Score Mapper 接口
|
||||||
@@ -10,4 +13,6 @@ import org.springblade.modules.martial.pojo.entity.MartialScore;
|
|||||||
*/
|
*/
|
||||||
public interface MartialScoreMapper extends BaseMapper<MartialScore> {
|
public interface MartialScoreMapper extends BaseMapper<MartialScore> {
|
||||||
|
|
||||||
|
IPage<MartialScoreVO> selectScoreVOPage(IPage<MartialScoreVO> page, @Param("score") MartialScore score);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,43 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="org.springblade.modules.martial.mapper.MartialScoreMapper">
|
<mapper namespace="org.springblade.modules.martial.mapper.MartialScoreMapper">
|
||||||
|
|
||||||
|
<select id="selectScoreVOPage" resultType="org.springblade.modules.martial.pojo.vo.MartialScoreVO">
|
||||||
|
SELECT
|
||||||
|
s.*,
|
||||||
|
a.player_name as playerName,
|
||||||
|
a.team_name as teamName,
|
||||||
|
a.id_card as idCard,
|
||||||
|
a.player_no as playerNo,
|
||||||
|
p.project_name as projectName,
|
||||||
|
v.venue_name as venueName,
|
||||||
|
r.chief_judge_score as chiefJudgeScore,
|
||||||
|
r.score_status as scoreStatus,
|
||||||
|
(SELECT GROUP_CONCAT(d.item_name SEPARATOR ', ')
|
||||||
|
FROM martial_deduction_item d
|
||||||
|
WHERE FIND_IN_SET(d.id, REPLACE(REPLACE(s.deduction_items, '[', ''), ']', ''))
|
||||||
|
) as deductionItemsText
|
||||||
|
FROM martial_score s
|
||||||
|
LEFT JOIN martial_athlete a ON s.athlete_id = a.id AND a.is_deleted = 0
|
||||||
|
LEFT JOIN martial_project p ON s.project_id = p.id AND p.is_deleted = 0
|
||||||
|
LEFT JOIN martial_venue v ON s.venue_id = v.id AND v.is_deleted = 0
|
||||||
|
LEFT JOIN martial_result r ON s.athlete_id = r.athlete_id AND s.project_id = r.project_id AND r.is_deleted = 0
|
||||||
|
WHERE s.is_deleted = 0
|
||||||
|
<if test="score.competitionId != null">
|
||||||
|
AND s.competition_id = #{score.competitionId}
|
||||||
|
</if>
|
||||||
|
<if test="score.athleteId != null">
|
||||||
|
AND s.athlete_id = #{score.athleteId}
|
||||||
|
</if>
|
||||||
|
<if test="score.projectId != null">
|
||||||
|
AND s.project_id = #{score.projectId}
|
||||||
|
</if>
|
||||||
|
<if test="score.judgeId != null">
|
||||||
|
AND s.judge_id = #{score.judgeId}
|
||||||
|
</if>
|
||||||
|
<if test="score.venueId != null">
|
||||||
|
AND s.venue_id = #{score.venueId}
|
||||||
|
</if>
|
||||||
|
ORDER BY s.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.springblade.modules.martial.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出场顺序分组VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LineupGroupVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long groupId;
|
||||||
|
private String groupName;
|
||||||
|
private String projectName;
|
||||||
|
private String category;
|
||||||
|
private String venueName;
|
||||||
|
private String timeSlot;
|
||||||
|
private String tableNo;
|
||||||
|
private List<LineupParticipantVO> participants;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.springblade.modules.martial.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出场顺序参赛者VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LineupParticipantVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Integer order;
|
||||||
|
private String playerName;
|
||||||
|
private String organization;
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@@ -47,4 +47,35 @@ public class MartialScoreVO extends MartialScore {
|
|||||||
@Schema(description = "状态文本")
|
@Schema(description = "状态文本")
|
||||||
private String statusText;
|
private String statusText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主裁判确认分数
|
||||||
|
*/
|
||||||
|
@Schema(description = "主裁判确认分数")
|
||||||
|
private java.math.BigDecimal chiefJudgeScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评分状态 (0-待评分, 1-裁判已评分, 2-主裁判已确认)
|
||||||
|
*/
|
||||||
|
@Schema(description = "评分状态")
|
||||||
|
private Integer scoreStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 队伍名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "队伍名称")
|
||||||
|
private String teamName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证")
|
||||||
|
private String idCard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选手编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "选手编号")
|
||||||
|
private String playerNo;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.springblade.modules.martial.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization Statistics VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "单位统计视图对象")
|
||||||
|
public class OrganizationStatsVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "单位名称")
|
||||||
|
private String organization;
|
||||||
|
|
||||||
|
@Schema(description = "运动员人数(去重)")
|
||||||
|
private Integer athleteCount;
|
||||||
|
|
||||||
|
@Schema(description = "项目数量")
|
||||||
|
private Integer projectCount;
|
||||||
|
|
||||||
|
@Schema(description = "单人项目数")
|
||||||
|
private Integer singleProjectCount;
|
||||||
|
|
||||||
|
@Schema(description = "集体项目数")
|
||||||
|
private Integer teamProjectCount;
|
||||||
|
|
||||||
|
@Schema(description = "男运动员数")
|
||||||
|
private Integer maleCount;
|
||||||
|
|
||||||
|
@Schema(description = "女运动员数")
|
||||||
|
private Integer femaleCount;
|
||||||
|
|
||||||
|
@Schema(description = "总金额")
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
|
||||||
|
@Schema(description = "项目金额明细")
|
||||||
|
private List<ProjectAmountItem> projectAmounts;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "项目金额明细")
|
||||||
|
public static class ProjectAmountItem implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "项目ID")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
@Schema(description = "项目名称")
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
@Schema(description = "项目类型(1=单人,2=集体)")
|
||||||
|
private Integer projectType;
|
||||||
|
|
||||||
|
@Schema(description = "报名人数/集体数")
|
||||||
|
private Integer count;
|
||||||
|
|
||||||
|
@Schema(description = "单价")
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@Schema(description = "小计金额")
|
||||||
|
private BigDecimal amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,4 +19,9 @@ public interface IMartialContactService extends IService<MartialContact> {
|
|||||||
*/
|
*/
|
||||||
MartialContact getContactDetail(Long id);
|
MartialContact getContactDetail(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save contact with default uniqueness handling
|
||||||
|
*/
|
||||||
|
boolean saveContact(MartialContact contact, Long userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.springblade.modules.martial.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
||||||
@@ -10,74 +11,27 @@ import org.springblade.modules.martial.pojo.entity.MartialSchedule;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule 服务类
|
* Schedule Service
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
*/
|
||||||
public interface IMartialScheduleService extends IService<MartialSchedule> {
|
public interface IMartialScheduleService extends IService<MartialSchedule> {
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.3: 导出赛程表
|
|
||||||
*/
|
|
||||||
List<ScheduleExportExcel> exportSchedule(Long competitionId);
|
List<ScheduleExportExcel> exportSchedule(Long competitionId);
|
||||||
|
|
||||||
/**
|
List<ScheduleExportExcel2> exportScheduleTemplate2(Long competitionId, Long venueId);
|
||||||
* 获取赛程编排结果
|
|
||||||
* @param competitionId 赛事ID
|
|
||||||
* @return 赛程编排结果
|
|
||||||
*/
|
|
||||||
ScheduleResultDTO getScheduleResult(Long competitionId);
|
ScheduleResultDTO getScheduleResult(Long competitionId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存编排草稿
|
|
||||||
* @param dto 编排草稿数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean saveDraftSchedule(SaveScheduleDraftDTO dto);
|
boolean saveDraftSchedule(SaveScheduleDraftDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 完成编排并锁定
|
|
||||||
* @param competitionId 赛事ID
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean saveAndLockSchedule(Long competitionId);
|
boolean saveAndLockSchedule(Long competitionId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动赛程分组到指定场地和时间段
|
|
||||||
* @param dto 移动请求数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取调度数据
|
|
||||||
* @param competitionId 赛事ID
|
|
||||||
* @param venueId 场地ID
|
|
||||||
* @param timeSlotIndex 时间段索引
|
|
||||||
* @return 调度数据
|
|
||||||
*/
|
|
||||||
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
||||||
|
|
||||||
/**
|
|
||||||
* 调整出场顺序
|
|
||||||
* @param dto 调整请求数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean adjustOrder(org.springblade.modules.martial.pojo.dto.AdjustOrderDTO dto);
|
boolean adjustOrder(org.springblade.modules.martial.pojo.dto.AdjustOrderDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量保存调度
|
|
||||||
* @param dto 保存调度数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean saveDispatch(org.springblade.modules.martial.pojo.dto.SaveDispatchDTO dto);
|
boolean saveDispatch(org.springblade.modules.martial.pojo.dto.SaveDispatchDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新参赛者签到状态
|
|
||||||
* @param participantId 参赛者ID
|
|
||||||
* @param status 状态:未签到/已签到/异常
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean updateParticipantCheckInStatus(Long participantId, String status);
|
boolean updateParticipantCheckInStatus(Long participantId, String status);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.springblade.modules.martial.service;
|
package org.springblade.modules.martial.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -15,6 +17,11 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface IMartialScoreService extends IService<MartialScore> {
|
public interface IMartialScoreService extends IService<MartialScore> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询评分记录(包含关联字段)
|
||||||
|
*/
|
||||||
|
IPage<MartialScoreVO> selectScoreVOPage(IPage<MartialScoreVO> page, MartialScore score);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task 2.2: 分数范围验证
|
* Task 2.2: 分数范围验证
|
||||||
*/
|
*/
|
||||||
@@ -37,17 +44,11 @@ public interface IMartialScoreService extends IService<MartialScore> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 小程序接口:获取评分详情
|
* 小程序接口:获取评分详情
|
||||||
*
|
|
||||||
* @param athleteId 选手ID
|
|
||||||
* @return 评分详情(选手信息+所有评委评分+修改记录)
|
|
||||||
*/
|
*/
|
||||||
MiniScoreDetailVO getScoreDetailForMini(Long athleteId);
|
MiniScoreDetailVO getScoreDetailForMini(Long athleteId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小程序接口:修改评分(主裁判)
|
* 小程序接口:修改评分(主裁判)
|
||||||
*
|
|
||||||
* @param dto 修改信息
|
|
||||||
* @return 修改成功/失败
|
|
||||||
*/
|
*/
|
||||||
boolean modifyScoreByAdmin(MiniScoreModifyDTO dto);
|
boolean modifyScoreByAdmin(MiniScoreModifyDTO dto);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.springblade.modules.martial.service.impl;
|
package org.springblade.modules.martial.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
@@ -10,6 +11,9 @@ import org.springblade.modules.martial.mapper.MartialContactMapper;
|
|||||||
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||||
import org.springblade.modules.martial.service.IMartialContactService;
|
import org.springblade.modules.martial.service.IMartialContactService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contact Service Implementation
|
* Contact Service Implementation
|
||||||
@@ -35,4 +39,32 @@ public class MartialContactServiceImpl extends ServiceImpl<MartialContactMapper,
|
|||||||
return this.getById(id);
|
return this.getById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean saveContact(MartialContact contact, Long userId) {
|
||||||
|
// If setting as default, clear other defaults first
|
||||||
|
if (Boolean.TRUE.equals(contact.getIsDefault())) {
|
||||||
|
LambdaUpdateWrapper<MartialContact> updateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
updateWrapper.eq(MartialContact::getCreateUser, userId)
|
||||||
|
.eq(MartialContact::getIsDeleted, 0)
|
||||||
|
.set(MartialContact::getIsDefault, false);
|
||||||
|
// Exclude current contact if it's an update
|
||||||
|
if (contact.getId() != null) {
|
||||||
|
updateWrapper.ne(MartialContact::getId, contact.getId());
|
||||||
|
}
|
||||||
|
this.update(updateWrapper);
|
||||||
|
log.info("Cleared default status for user {}'s other contacts", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set audit fields
|
||||||
|
if (contact.getId() == null) {
|
||||||
|
contact.setCreateUser(userId);
|
||||||
|
contact.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
contact.setUpdateUser(userId);
|
||||||
|
contact.setUpdateTime(new Date());
|
||||||
|
|
||||||
|
return this.saveOrUpdate(contact);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,10 +458,14 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动拆分大组:如果人数过多,拆分成多个小组
|
// 自动拆分大组:如果人数过多,拆分成多个小组
|
||||||
// 改进策略:根据人数动态拆分,确保能充分利用所有时间槽
|
// 优先使用项目配置的单位容纳人数(maxParticipants)
|
||||||
// 目标:让分组数量接近可用时间槽数量,实现均匀分配
|
|
||||||
int maxPeoplePerGroup;
|
int maxPeoplePerGroup;
|
||||||
if (members.size() <= scheduleConfig.getMaxPeoplePerGroup()) {
|
Integer projectMax = project.getMaxParticipants();
|
||||||
|
if (projectMax != null && projectMax > 0) {
|
||||||
|
// 使用项目配置的单位容纳人数
|
||||||
|
maxPeoplePerGroup = projectMax;
|
||||||
|
log.debug("项目 '{}' 使用自定义容纳人数: {}", projectName, projectMax);
|
||||||
|
} else if (members.size() <= scheduleConfig.getMaxPeoplePerGroup()) {
|
||||||
// 35人以内不拆分
|
// 35人以内不拆分
|
||||||
maxPeoplePerGroup = members.size();
|
maxPeoplePerGroup = members.size();
|
||||||
} else {
|
} else {
|
||||||
@@ -585,8 +589,22 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
|
|
||||||
log.info("总共初始化了 {} 个场地×时间段组合", slots.size());
|
log.info("总共初始化了 {} 个场地×时间段组合", slots.size());
|
||||||
|
|
||||||
// 按预计时长降序排序(先安排时间长的)
|
// 排序策略: 集体项目(projectType=2)优先,同类型按预计时长降序
|
||||||
groups.sort((a, b) -> b.getEstimatedDuration() - a.getEstimatedDuration());
|
groups.sort((a, b) -> {
|
||||||
|
// 1. 集体项目优先于单人项目
|
||||||
|
int aType = a.getProjectType() != null ? a.getProjectType() : 1;
|
||||||
|
int bType = b.getProjectType() != null ? b.getProjectType() : 1;
|
||||||
|
if (aType != bType) {
|
||||||
|
return bType - aType; // 2(集体) > 1(单人)
|
||||||
|
}
|
||||||
|
// 2. 同类型按预计时长降序(先安排时间长的)
|
||||||
|
int aDuration = a.getEstimatedDuration() != null ? a.getEstimatedDuration() : 0;
|
||||||
|
int bDuration = b.getEstimatedDuration() != null ? b.getEstimatedDuration() : 0;
|
||||||
|
return bDuration - aDuration;
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("排序后分组顺序(集体优先): {}",
|
||||||
|
groups.stream().map(g -> g.getGroupName() + "(type=" + g.getProjectType() + ")").collect(java.util.stream.Collectors.joining(", ")));
|
||||||
|
|
||||||
// 使用轮询算法进行均匀分配
|
// 使用轮询算法进行均匀分配
|
||||||
int assignedCount = 0;
|
int assignedCount = 0;
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package org.springblade.modules.martial.service.impl;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.mapper.MartialScheduleDetailMapper;
|
import org.springblade.modules.martial.mapper.MartialScheduleDetailMapper;
|
||||||
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
||||||
import org.springblade.modules.martial.mapper.MartialScheduleParticipantMapper;
|
import org.springblade.modules.martial.mapper.MartialScheduleParticipantMapper;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialAthleteMapper;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
import org.springblade.modules.martial.pojo.dto.CompetitionGroupDTO;
|
import org.springblade.modules.martial.pojo.dto.CompetitionGroupDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.ParticipantDTO;
|
import org.springblade.modules.martial.pojo.dto.ParticipantDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
||||||
@@ -53,6 +56,8 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MartialScheduleParticipantMapper scheduleParticipantMapper;
|
private MartialScheduleParticipantMapper scheduleParticipantMapper;
|
||||||
|
@Autowired
|
||||||
|
private MartialAthleteMapper athleteMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task 3.3: 导出赛程表
|
* Task 3.3: 导出赛程表
|
||||||
@@ -132,6 +137,40 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
|
|
||||||
return exportList;
|
return exportList;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public List<ScheduleExportExcel2> exportScheduleTemplate2(Long competitionId, Long venueId) {
|
||||||
|
List<ScheduleExportExcel2> exportList = new ArrayList<>();
|
||||||
|
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||||
|
if (details.isEmpty()) {
|
||||||
|
return exportList;
|
||||||
|
}
|
||||||
|
if (venueId != null) {
|
||||||
|
details = details.stream().filter(d -> venueId.equals(d.getVenueId())).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
Map<Long, List<ScheduleGroupDetailVO>> groupMap = details.stream()
|
||||||
|
.collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId));
|
||||||
|
List<Long> sortedGroupIds = details.stream()
|
||||||
|
.collect(Collectors.toMap(ScheduleGroupDetailVO::getGroupId, d -> d.getDisplayOrder() != null ? d.getDisplayOrder() : 999, (a, b) -> a))
|
||||||
|
.entrySet().stream().sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).collect(Collectors.toList());
|
||||||
|
int sequenceNo = 1;
|
||||||
|
int tableNoBase = 1101;
|
||||||
|
for (Long groupId : sortedGroupIds) {
|
||||||
|
List<ScheduleGroupDetailVO> groupDetails = groupMap.get(groupId);
|
||||||
|
if (groupDetails == null || groupDetails.isEmpty()) continue;
|
||||||
|
ScheduleGroupDetailVO firstDetail = groupDetails.get(0);
|
||||||
|
long participantCount = groupDetails.stream().filter(d -> d.getParticipantId() != null).count();
|
||||||
|
int durationMinutes = (int) (participantCount * 4);
|
||||||
|
ScheduleExportExcel2 excel = new ScheduleExportExcel2();
|
||||||
|
excel.setSequenceNo(sequenceNo++);
|
||||||
|
excel.setProjectName(firstDetail.getGroupName());
|
||||||
|
excel.setParticipantCount((int) participantCount);
|
||||||
|
excel.setGroupCount(1);
|
||||||
|
excel.setDurationMinutes(durationMinutes);
|
||||||
|
excel.setTableNo(String.valueOf(tableNoBase++));
|
||||||
|
exportList.add(excel);
|
||||||
|
}
|
||||||
|
return exportList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取赛程编排结果(优化版本:使用单次JOIN查询)
|
* 获取赛程编排结果(优化版本:使用单次JOIN查询)
|
||||||
@@ -309,7 +348,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
groupDTO.setType("单人");
|
groupDTO.setType("单人");
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
groupDTO.setType("双人");
|
groupDTO.setType("集体");
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
groupDTO.setType("集体");
|
groupDTO.setType("集体");
|
||||||
@@ -456,6 +495,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean saveAndLockSchedule(Long competitionId) {
|
public boolean saveAndLockSchedule(Long competitionId) {
|
||||||
|
log.info("=== saveAndLockSchedule 开始 === competitionId: {}", competitionId);
|
||||||
// 1. 查询所有分组
|
// 1. 查询所有分组
|
||||||
List<MartialScheduleGroup> groups = scheduleGroupMapper.selectList(
|
List<MartialScheduleGroup> groups = scheduleGroupMapper.selectList(
|
||||||
new QueryWrapper<MartialScheduleGroup>()
|
new QueryWrapper<MartialScheduleGroup>()
|
||||||
@@ -478,9 +518,32 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
.eq("is_deleted", 0)
|
.eq("is_deleted", 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 按分组和出场顺序分配选手编号
|
||||||
|
Map<Long, Integer> groupCounters = new HashMap<>();
|
||||||
|
participants.sort((a, b) -> {
|
||||||
|
int groupCompare = a.getScheduleGroupId().compareTo(b.getScheduleGroupId());
|
||||||
|
if (groupCompare != 0) return groupCompare;
|
||||||
|
return Integer.compare(a.getPerformanceOrder() != null ? a.getPerformanceOrder() : 0,
|
||||||
|
b.getPerformanceOrder() != null ? b.getPerformanceOrder() : 0);
|
||||||
|
});
|
||||||
|
|
||||||
for (MartialScheduleParticipant participant : participants) {
|
for (MartialScheduleParticipant participant : participants) {
|
||||||
participant.setScheduleStatus("completed");
|
participant.setScheduleStatus("completed");
|
||||||
scheduleParticipantMapper.updateById(participant);
|
scheduleParticipantMapper.updateById(participant);
|
||||||
|
|
||||||
|
// 分配选手编号
|
||||||
|
if (participant.getParticipantId() != null) {
|
||||||
|
Long groupId = participant.getScheduleGroupId();
|
||||||
|
int counter = groupCounters.getOrDefault(groupId, 0) + 1;
|
||||||
|
groupCounters.put(groupId, counter);
|
||||||
|
|
||||||
|
// 更新选手编号 (格式: 分组序号-出场序号)
|
||||||
|
MartialAthlete athlete = athleteMapper.selectById(participant.getParticipantId());
|
||||||
|
if (athlete != null && (athlete.getPlayerNo() == null || athlete.getPlayerNo().isEmpty())) {
|
||||||
|
athlete.setPlayerNo(String.format("%03d", counter));
|
||||||
|
athleteMapper.updateById(athlete);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
|||||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialJudge;
|
import org.springblade.modules.martial.pojo.entity.MartialJudge;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import org.springblade.modules.martial.mapper.MartialScoreMapper;
|
import org.springblade.modules.martial.mapper.MartialScoreMapper;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
@@ -448,4 +450,8 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
|
|||||||
return athleteUpdated && recordSaved && resultSaved;
|
return athleteUpdated && recordSaved && resultSaved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<MartialScoreVO> selectScoreVOPage(IPage<MartialScoreVO> page, MartialScore score) {
|
||||||
|
return baseMapper.selectScoreVOPage(page, score);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,14 @@ blade:
|
|||||||
- /blade-auth/captcha/send
|
- /blade-auth/captcha/send
|
||||||
- /blade-auth/oauth/token
|
- /blade-auth/oauth/token
|
||||||
- /mini/login
|
- /mini/login
|
||||||
- /**
|
- /mini/**
|
||||||
|
- /martial/competition/list
|
||||||
|
- /martial/competition/detail
|
||||||
|
- /martial/project/list
|
||||||
|
- /martial/project/detail
|
||||||
|
- /martial/venue/list
|
||||||
|
- /martial/venue/detail
|
||||||
|
- /blade-martial/**
|
||||||
#授权认证配置
|
#授权认证配置
|
||||||
auth:
|
auth:
|
||||||
- method: ALL
|
- method: ALL
|
||||||
|
|||||||
Reference in New Issue
Block a user