fix: 修复联系人默认唯一性问题 & 添加单位统计API
- 问题1: 设置默认联系人时自动取消其他默认联系人 - 问题3: 新增 /organization-stats API 按单位统计运动员、项目、金额 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
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.
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user