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:
+3
-11
@@ -14,8 +14,6 @@ import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||
import org.springblade.modules.martial.service.IMartialContactService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@@ -43,16 +41,10 @@ public class MartialContactController extends BladeController {
|
||||
@Operation(summary = "保存", description = "新增或修改联系人")
|
||||
public R<Boolean> submit(@RequestBody MartialContact contact) {
|
||||
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) {
|
||||
contact.setCreateUser(userId);
|
||||
contact.setCreateTime(new Date());
|
||||
}
|
||||
contact.setUpdateUser(userId);
|
||||
contact.setUpdateTime(new Date());
|
||||
|
||||
return R.data(contactService.saveOrUpdate(contact));
|
||||
return R.data(contactService.saveContact(contact, userId));
|
||||
}
|
||||
|
||||
@PostMapping("/remove")
|
||||
|
||||
+193
-1
@@ -13,21 +13,26 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||
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.MartialTeam;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
||||
import org.springblade.modules.martial.pojo.dto.RegistrationSubmitDTO;
|
||||
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.IMartialCompetitionService;
|
||||
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||
import org.springblade.modules.martial.service.IMartialRegistrationOrderService;
|
||||
import org.springblade.modules.martial.service.IMartialTeamService;
|
||||
import org.springblade.modules.martial.mapper.MartialTeamMemberMapper;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@@ -40,6 +45,7 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialTeamService teamService;
|
||||
private final IMartialCompetitionService competitionService;
|
||||
private final IMartialProjectService projectService;
|
||||
private final MartialTeamMemberMapper teamMemberMapper;
|
||||
|
||||
@GetMapping("/detail")
|
||||
@@ -60,6 +66,192 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
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")
|
||||
@Operation(summary = "提交报名", description = "提交报名订单并关联选手或集体")
|
||||
@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);
|
||||
|
||||
/**
|
||||
* Save contact with default uniqueness handling
|
||||
*/
|
||||
boolean saveContact(MartialContact contact, Long userId);
|
||||
|
||||
}
|
||||
|
||||
+32
@@ -1,6 +1,7 @@
|
||||
package org.springblade.modules.martial.service.impl;
|
||||
|
||||
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.extension.plugins.pagination.Page;
|
||||
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.service.IMartialContactService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Contact Service Implementation
|
||||
@@ -35,4 +39,32 @@ public class MartialContactServiceImpl extends ServiceImpl<MartialContactMapper,
|
||||
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