diff --git a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta index 7b4d10b..ab2b492 100644 Binary files a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta and b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta index e004016..8a44fce 100644 Binary files a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta index 8172249..916feb7 100644 Binary files a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage.json/xl.meta b/minio_data/.minio.sys/buckets/.usage.json/xl.meta index f5f0143..7ceca8f 100644 Binary files a/minio_data/.minio.sys/buckets/.usage.json/xl.meta and b/minio_data/.minio.sys/buckets/.usage.json/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta index dcf83ac..df9d00f 100644 Binary files a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta index b4cf950..47a2b17 100644 Binary files a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta index 6333f9d..cd7dc57 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta index cb44746..a48b7b1 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta differ diff --git a/src/main/java/org/springblade/modules/martial/controller/MartialContactController.java b/src/main/java/org/springblade/modules/martial/controller/MartialContactController.java index 051472d..717509c 100644 --- a/src/main/java/org/springblade/modules/martial/controller/MartialContactController.java +++ b/src/main/java/org/springblade/modules/martial/controller/MartialContactController.java @@ -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 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") diff --git a/src/main/java/org/springblade/modules/martial/controller/MartialRegistrationOrderController.java b/src/main/java/org/springblade/modules/martial/controller/MartialRegistrationOrderController.java index 772b2f6..4eb573d 100644 --- a/src/main/java/org/springblade/modules/martial/controller/MartialRegistrationOrderController.java +++ b/src/main/java/org/springblade/modules/martial/controller/MartialRegistrationOrderController.java @@ -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> getOrganizationStats(@RequestParam Long competitionId) { + log.info("获取单位统计: competitionId={}", competitionId); + + // 1. Get all athletes for this competition + LambdaQueryWrapper athleteWrapper = new LambdaQueryWrapper<>(); + athleteWrapper.eq(MartialAthlete::getCompetitionId, competitionId) + .eq(MartialAthlete::getIsDeleted, 0); + List athletes = athleteService.list(athleteWrapper); + + if (athletes.isEmpty()) { + return R.data(new ArrayList<>()); + } + + // 2. Get all projects for this competition + Set projectIds = athletes.stream() + .map(MartialAthlete::getProjectId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + final Map projectMap = new HashMap<>(); + if (!projectIds.isEmpty()) { + List projects = projectService.listByIds(projectIds); + projectMap.putAll(projects.stream().collect(Collectors.toMap(MartialProject::getId, p -> p))); + } + + // 3. Get team members for team projects + Set 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 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> teamMembersMap = new HashMap<>(); + if (!teamIds.isEmpty()) { + LambdaQueryWrapper memberWrapper = new LambdaQueryWrapper<>(); + memberWrapper.in(MartialTeamMember::getTeamId, teamIds) + .eq(MartialTeamMember::getIsDeleted, 0); + List members = teamMemberMapper.selectList(memberWrapper); + teamMembersMap = members.stream().collect(Collectors.groupingBy(MartialTeamMember::getTeamId)); + } + + // 4. Group by organization and calculate stats + Map 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 entry : orgStatsMap.entrySet()) { + String org = entry.getKey(); + OrganizationStatsVO stats = entry.getValue(); + + // Get all athletes for this org + Set 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 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) diff --git a/src/main/java/org/springblade/modules/martial/pojo/vo/OrganizationStatsVO.java b/src/main/java/org/springblade/modules/martial/pojo/vo/OrganizationStatsVO.java new file mode 100644 index 0000000..596aa78 --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/pojo/vo/OrganizationStatsVO.java @@ -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 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; + } +} diff --git a/src/main/java/org/springblade/modules/martial/service/IMartialContactService.java b/src/main/java/org/springblade/modules/martial/service/IMartialContactService.java index acbd713..cddb1a3 100644 --- a/src/main/java/org/springblade/modules/martial/service/IMartialContactService.java +++ b/src/main/java/org/springblade/modules/martial/service/IMartialContactService.java @@ -19,4 +19,9 @@ public interface IMartialContactService extends IService { */ MartialContact getContactDetail(Long id); + /** + * Save contact with default uniqueness handling + */ + boolean saveContact(MartialContact contact, Long userId); + } diff --git a/src/main/java/org/springblade/modules/martial/service/impl/MartialContactServiceImpl.java b/src/main/java/org/springblade/modules/martial/service/impl/MartialContactServiceImpl.java index e94a57a..8475dc2 100644 --- a/src/main/java/org/springblade/modules/martial/service/impl/MartialContactServiceImpl.java +++ b/src/main/java/org/springblade/modules/martial/service/impl/MartialContactServiceImpl.java @@ -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 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); + } + }