|
|
@@ -34,12 +34,13 @@ import com.sundot.airport.blocked.dto.BlockedTimePeriodLuggageStatsDTO;
|
|
34
|
34
|
import com.sundot.airport.blocked.dto.BlockedTimePeriodStatsDTO;
|
|
35
|
35
|
import com.sundot.airport.blocked.mapper.BlockedDashboardMapper;
|
|
36
|
36
|
import com.sundot.airport.blocked.service.IBlockedDashboardService;
|
|
|
37
|
+import com.sundot.airport.common.core.domain.entity.SysDept;
|
|
|
38
|
+import com.sundot.airport.system.mapper.SysDeptMapper;
|
|
37
|
39
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
38
|
40
|
import org.springframework.stereotype.Service;
|
|
39
|
41
|
|
|
40
|
42
|
import java.math.BigDecimal;
|
|
41
|
|
-import java.util.Collections;
|
|
42
|
|
-import java.util.List;
|
|
|
43
|
+import java.util.*;
|
|
43
|
44
|
import java.util.stream.Collectors;
|
|
44
|
45
|
|
|
45
|
46
|
/**
|
|
|
@@ -53,6 +54,9 @@ public class BlockedDashboardServiceImpl implements IBlockedDashboardService {
|
|
53
|
54
|
|
|
54
|
55
|
@Autowired
|
|
55
|
56
|
private BlockedDashboardMapper blockedDashboardMapper;
|
|
|
57
|
+
|
|
|
58
|
+ @Autowired
|
|
|
59
|
+ private SysDeptMapper sysDeptMapper;
|
|
56
|
60
|
|
|
57
|
61
|
/**
|
|
58
|
62
|
* 查询查堵总数
|
|
|
@@ -476,14 +480,213 @@ public class BlockedDashboardServiceImpl implements IBlockedDashboardService {
|
|
476
|
480
|
|
|
477
|
481
|
/**
|
|
478
|
482
|
* 查询大队开机人员证书分布(用于柱状图)
|
|
|
483
|
+ * 通过ancestors字段找到每个用户所属的最上级大队,然后按大队分组统计
|
|
479
|
484
|
*
|
|
480
|
485
|
* @param queryDTO 查询参数
|
|
481
|
486
|
* @return 大队证书分布统计列表
|
|
482
|
487
|
*/
|
|
483
|
488
|
@Override
|
|
484
|
489
|
public List<BlockedCertificateLevelDTO> getBrigadeCertificateDistribution(BlockedDashboardQueryDTO queryDTO) {
|
|
485
|
|
- List<BlockedCertificateLevelDTO> resultList = blockedDashboardMapper.selectBrigadeCertificateDistribution(queryDTO);
|
|
486
|
|
- return resultList != null ? resultList : Collections.emptyList();
|
|
|
490
|
+ // 1. 从数据库获取原始数据
|
|
|
491
|
+ List<Map<String, Object>> rawData = blockedDashboardMapper.selectBrigadeCertificateDistribution(queryDTO);
|
|
|
492
|
+
|
|
|
493
|
+ if (rawData == null || rawData.isEmpty()) {
|
|
|
494
|
+ return Collections.emptyList();
|
|
|
495
|
+ }
|
|
|
496
|
+
|
|
|
497
|
+ // 2. 收集所有需要查询的部门ID(包括ancestors中的所有ID和当前dept_id)
|
|
|
498
|
+ Set<Long> allDeptIds = new HashSet<>();
|
|
|
499
|
+ for (Map<String, Object> row : rawData) {
|
|
|
500
|
+ String ancestors = (String) row.get("ancestors");
|
|
|
501
|
+ Long deptId = ((Number) row.get("dept_id")).longValue();
|
|
|
502
|
+
|
|
|
503
|
+ if (ancestors != null && !ancestors.isEmpty()) {
|
|
|
504
|
+ String[] ancestorIds = ancestors.split(",");
|
|
|
505
|
+ for (String idStr : ancestorIds) {
|
|
|
506
|
+ try {
|
|
|
507
|
+ Long id = Long.parseLong(idStr.trim());
|
|
|
508
|
+ if (id > 0) {
|
|
|
509
|
+ allDeptIds.add(id);
|
|
|
510
|
+ }
|
|
|
511
|
+ } catch (NumberFormatException e) {
|
|
|
512
|
+ // 忽略
|
|
|
513
|
+ }
|
|
|
514
|
+ }
|
|
|
515
|
+ }
|
|
|
516
|
+ if (deptId != null) {
|
|
|
517
|
+ allDeptIds.add(deptId);
|
|
|
518
|
+ }
|
|
|
519
|
+ }
|
|
|
520
|
+
|
|
|
521
|
+ // 3. 批量查询所有部门信息,构建大队ID映射缓存
|
|
|
522
|
+ Map<Long, Long> deptToBrigadeMap = buildDeptToBrigadeMap(allDeptIds);
|
|
|
523
|
+
|
|
|
524
|
+ // 4. 在Java层按大队分组统计
|
|
|
525
|
+ Map<Long, BlockedCertificateLevelDTO> brigadeMap = new LinkedHashMap<>();
|
|
|
526
|
+
|
|
|
527
|
+ for (Map<String, Object> row : rawData) {
|
|
|
528
|
+ String qualificationLevel = (String) row.get("qualification_level");
|
|
|
529
|
+ Long deptId = ((Number) row.get("dept_id")).longValue();
|
|
|
530
|
+
|
|
|
531
|
+ // 5. 从缓存中获取大队ID
|
|
|
532
|
+ Long brigadeId = deptToBrigadeMap.get(deptId);
|
|
|
533
|
+
|
|
|
534
|
+ if (brigadeId == null) {
|
|
|
535
|
+ continue; // 跳过无法找到大队的用户
|
|
|
536
|
+ }
|
|
|
537
|
+
|
|
|
538
|
+ // 6. 获取或创建大队统计对象
|
|
|
539
|
+ BlockedCertificateLevelDTO stats = brigadeMap.computeIfAbsent(brigadeId, id -> {
|
|
|
540
|
+ BlockedCertificateLevelDTO dto = new BlockedCertificateLevelDTO();
|
|
|
541
|
+ dto.setBrigadeId(id);
|
|
|
542
|
+ dto.setSeniorCount(0);
|
|
|
543
|
+ dto.setMiddleCount(0);
|
|
|
544
|
+ dto.setJuniorCount(0);
|
|
|
545
|
+ dto.setTotalCount(0);
|
|
|
546
|
+ return dto;
|
|
|
547
|
+ });
|
|
|
548
|
+
|
|
|
549
|
+ // 7. 根据证书级别统计
|
|
|
550
|
+ if ("LEVEL_ONE".equals(qualificationLevel) ||
|
|
|
551
|
+ "LEVEL_TWO".equals(qualificationLevel) ||
|
|
|
552
|
+ "LEVEL_THREE".equals(qualificationLevel)) {
|
|
|
553
|
+ stats.setSeniorCount(stats.getSeniorCount() + 1);
|
|
|
554
|
+ } else if ("LEVEL_FOUR".equals(qualificationLevel)) {
|
|
|
555
|
+ stats.setMiddleCount(stats.getMiddleCount() + 1);
|
|
|
556
|
+ } else if ("LEVEL_FIVE".equals(qualificationLevel)) {
|
|
|
557
|
+ stats.setJuniorCount(stats.getJuniorCount() + 1);
|
|
|
558
|
+ }
|
|
|
559
|
+
|
|
|
560
|
+ stats.setTotalCount(stats.getTotalCount() + 1);
|
|
|
561
|
+ }
|
|
|
562
|
+
|
|
|
563
|
+ // 8. 转换为列表并填充大队名称
|
|
|
564
|
+ List<BlockedCertificateLevelDTO> resultList = new ArrayList<>(brigadeMap.values());
|
|
|
565
|
+ fillBrigadeNames(resultList);
|
|
|
566
|
+
|
|
|
567
|
+ // 9. 按总人数降序排序
|
|
|
568
|
+ resultList.sort((a, b) -> b.getTotalCount().compareTo(a.getTotalCount()));
|
|
|
569
|
+
|
|
|
570
|
+ return resultList;
|
|
|
571
|
+ }
|
|
|
572
|
+
|
|
|
573
|
+ /**
|
|
|
574
|
+ * 构建部门到大队的映射关系
|
|
|
575
|
+ * 遍历所有部门的ancestors,找到每个部门所属的最上级大队
|
|
|
576
|
+ *
|
|
|
577
|
+ * @param allDeptIds 所有需要查询的部门ID集合
|
|
|
578
|
+ * @return 部门ID -> 大队ID 的映射
|
|
|
579
|
+ */
|
|
|
580
|
+ private Map<Long, Long> buildDeptToBrigadeMap(Set<Long> allDeptIds) {
|
|
|
581
|
+ if (allDeptIds == null || allDeptIds.isEmpty()) {
|
|
|
582
|
+ return Collections.emptyMap();
|
|
|
583
|
+ }
|
|
|
584
|
+
|
|
|
585
|
+ // 1. 批量查询所有部门信息
|
|
|
586
|
+ List<SysDept> allDepts = sysDeptMapper.selectDeptByIdList(new ArrayList<>(allDeptIds));
|
|
|
587
|
+
|
|
|
588
|
+ if (allDepts == null || allDepts.isEmpty()) {
|
|
|
589
|
+ return Collections.emptyMap();
|
|
|
590
|
+ }
|
|
|
591
|
+
|
|
|
592
|
+ // 2. 构建部门ID到部门对象的映射
|
|
|
593
|
+ Map<Long, SysDept> deptMap = new HashMap<>();
|
|
|
594
|
+ for (SysDept dept : allDepts) {
|
|
|
595
|
+ deptMap.put(dept.getDeptId(), dept);
|
|
|
596
|
+ }
|
|
|
597
|
+
|
|
|
598
|
+ // 3. 找出所有大队类型的部门ID
|
|
|
599
|
+ Set<Long> brigadeIds = new HashSet<>();
|
|
|
600
|
+ for (SysDept dept : allDepts) {
|
|
|
601
|
+ if ("BRIGADE".equals(dept.getDeptType())) {
|
|
|
602
|
+ brigadeIds.add(dept.getDeptId());
|
|
|
603
|
+ }
|
|
|
604
|
+ }
|
|
|
605
|
+
|
|
|
606
|
+ // 4. 为每个部门找到其所属的最上级大队
|
|
|
607
|
+ Map<Long, Long> deptToBrigadeMap = new HashMap<>();
|
|
|
608
|
+ for (SysDept dept : allDepts) {
|
|
|
609
|
+ Long brigadeId = findBrigadeIdFromAncestors(dept.getAncestors(), dept.getDeptId(), brigadeIds);
|
|
|
610
|
+ if (brigadeId != null) {
|
|
|
611
|
+ deptToBrigadeMap.put(dept.getDeptId(), brigadeId);
|
|
|
612
|
+ }
|
|
|
613
|
+ }
|
|
|
614
|
+
|
|
|
615
|
+ return deptToBrigadeMap;
|
|
|
616
|
+ }
|
|
|
617
|
+
|
|
|
618
|
+ /**
|
|
|
619
|
+ * 从ancestors中找到最上级大队ID
|
|
|
620
|
+ *
|
|
|
621
|
+ * @param ancestors 祖级列表
|
|
|
622
|
+ * @param currentDeptId 当前部门ID
|
|
|
623
|
+ * @param brigadeIds 所有大队ID集合
|
|
|
624
|
+ * @return 最上级大队ID,如果找不到则返回null
|
|
|
625
|
+ */
|
|
|
626
|
+ private Long findBrigadeIdFromAncestors(String ancestors, Long currentDeptId, Set<Long> brigadeIds) {
|
|
|
627
|
+ if (ancestors == null || ancestors.isEmpty()) {
|
|
|
628
|
+ return null;
|
|
|
629
|
+ }
|
|
|
630
|
+
|
|
|
631
|
+ // 将ancestors分割成ID列表
|
|
|
632
|
+ String[] ancestorIds = ancestors.split(",");
|
|
|
633
|
+
|
|
|
634
|
+ // 遍历祖先节点,找到第一个大队类型的部门
|
|
|
635
|
+ for (String ancestorIdStr : ancestorIds) {
|
|
|
636
|
+ try {
|
|
|
637
|
+ Long ancestorId = Long.parseLong(ancestorIdStr.trim());
|
|
|
638
|
+ if (ancestorId > 0 && brigadeIds.contains(ancestorId)) {
|
|
|
639
|
+ return ancestorId;
|
|
|
640
|
+ }
|
|
|
641
|
+ } catch (NumberFormatException e) {
|
|
|
642
|
+ // 忽略解析错误
|
|
|
643
|
+ }
|
|
|
644
|
+ }
|
|
|
645
|
+
|
|
|
646
|
+ // 如果祖先节点中没有找到大队,检查当前部门是否为大队
|
|
|
647
|
+ if (currentDeptId != null && brigadeIds.contains(currentDeptId)) {
|
|
|
648
|
+ return currentDeptId;
|
|
|
649
|
+ }
|
|
|
650
|
+
|
|
|
651
|
+ return null;
|
|
|
652
|
+ }
|
|
|
653
|
+
|
|
|
654
|
+ /**
|
|
|
655
|
+ * 填充大队名称(批量查询)
|
|
|
656
|
+ *
|
|
|
657
|
+ * @param resultList 统计结果列表
|
|
|
658
|
+ */
|
|
|
659
|
+ private void fillBrigadeNames(List<BlockedCertificateLevelDTO> resultList) {
|
|
|
660
|
+ if (resultList == null || resultList.isEmpty()) {
|
|
|
661
|
+ return;
|
|
|
662
|
+ }
|
|
|
663
|
+
|
|
|
664
|
+ // 收集所有大队ID
|
|
|
665
|
+ List<Long> brigadeIds = resultList.stream()
|
|
|
666
|
+ .map(BlockedCertificateLevelDTO::getBrigadeId)
|
|
|
667
|
+ .filter(Objects::nonNull)
|
|
|
668
|
+ .distinct()
|
|
|
669
|
+ .collect(Collectors.toList());
|
|
|
670
|
+
|
|
|
671
|
+ if (brigadeIds.isEmpty()) {
|
|
|
672
|
+ return;
|
|
|
673
|
+ }
|
|
|
674
|
+
|
|
|
675
|
+ // 批量查询大队信息
|
|
|
676
|
+ List<SysDept> brigades = sysDeptMapper.selectDeptByIdList(brigadeIds);
|
|
|
677
|
+
|
|
|
678
|
+ // 构建大队ID到名称的映射
|
|
|
679
|
+ Map<Long, String> brigadeNameMap = new HashMap<>();
|
|
|
680
|
+ for (SysDept dept : brigades) {
|
|
|
681
|
+ brigadeNameMap.put(dept.getDeptId(), dept.getDeptName());
|
|
|
682
|
+ }
|
|
|
683
|
+
|
|
|
684
|
+ // 填充名称
|
|
|
685
|
+ for (BlockedCertificateLevelDTO dto : resultList) {
|
|
|
686
|
+ if (dto.getBrigadeId() != null) {
|
|
|
687
|
+ dto.setBrigadeName(brigadeNameMap.get(dto.getBrigadeId()));
|
|
|
688
|
+ }
|
|
|
689
|
+ }
|
|
487
|
690
|
}
|
|
488
|
691
|
|
|
489
|
692
|
/**
|
|
|
@@ -665,31 +868,55 @@ public class BlockedDashboardServiceImpl implements IBlockedDashboardService {
|
|
665
|
868
|
/**
|
|
666
|
869
|
* 查询证书级别人员基数(分大队统计)
|
|
667
|
870
|
* 数据源:用户表(sys_user) + 部门表(sys_dept)
|
|
668
|
|
- * 统计大队人员里面的人员证书级别构成
|
|
669
|
|
- *
|
|
|
871
|
+ * 统计指定大队及其下属所有部门的人员证书级别构成
|
|
|
872
|
+ *
|
|
670
|
873
|
* @param queryDTO 查询参数(大队ID必填)
|
|
671
|
874
|
* @return 证书级别人员基数统计列表
|
|
672
|
875
|
*/
|
|
673
|
876
|
@Override
|
|
674
|
877
|
public List<BlockedCertificateLevelDistributionDTO> getBrigadePersonnelCertificateBase(BlockedDashboardQueryDTO queryDTO) {
|
|
675
|
|
- List<BlockedCertificateLevelDistributionDTO> resultList = blockedDashboardMapper.selectBrigadePersonnelCertificateBase(queryDTO);
|
|
676
|
|
- if (resultList != null && !resultList.isEmpty()) {
|
|
677
|
|
- // 计算总数
|
|
678
|
|
- long totalCount = resultList.stream().mapToLong(item -> item.getCount() != null ? item.getCount() : 0).sum();
|
|
|
878
|
+ // 1. 从数据库获取原始数据
|
|
|
879
|
+ List<Map<String, Object>> rawData = blockedDashboardMapper.selectBrigadePersonnelCertificateBase(queryDTO);
|
|
|
880
|
+
|
|
|
881
|
+ if (rawData == null || rawData.isEmpty()) {
|
|
|
882
|
+ return Collections.emptyList();
|
|
|
883
|
+ }
|
|
|
884
|
+
|
|
|
885
|
+ // 2. 按证书级别分组统计
|
|
|
886
|
+ Map<String, Long> levelCountMap = new HashMap<>();
|
|
|
887
|
+ long totalCount = 0;
|
|
|
888
|
+
|
|
|
889
|
+ for (Map<String, Object> row : rawData) {
|
|
|
890
|
+ String qualificationLevel = (String) row.get("qualification_level");
|
|
|
891
|
+ if (qualificationLevel != null && !qualificationLevel.isEmpty()) {
|
|
|
892
|
+ levelCountMap.merge(qualificationLevel, 1L, Long::sum);
|
|
|
893
|
+ totalCount++;
|
|
|
894
|
+ }
|
|
|
895
|
+ }
|
|
|
896
|
+
|
|
|
897
|
+ // 3. 构建结果列表
|
|
|
898
|
+ List<BlockedCertificateLevelDistributionDTO> resultList = new ArrayList<>();
|
|
|
899
|
+ for (Map.Entry<String, Long> entry : levelCountMap.entrySet()) {
|
|
|
900
|
+ BlockedCertificateLevelDistributionDTO dto = new BlockedCertificateLevelDistributionDTO();
|
|
|
901
|
+ dto.setCertificateLevel(convertCertificateLevel(entry.getKey()));
|
|
|
902
|
+ dto.setCount(entry.getValue().intValue());
|
|
|
903
|
+
|
|
679
|
904
|
// 计算百分比
|
|
680
|
905
|
if (totalCount > 0) {
|
|
681
|
|
- resultList.forEach(item -> {
|
|
682
|
|
- long count = item.getCount() != null ? item.getCount() : 0;
|
|
683
|
|
- double percentage = Math.round(count * 10000.0 / totalCount) / 100.0;
|
|
684
|
|
- item.setPercentage(java.math.BigDecimal.valueOf(percentage));
|
|
685
|
|
- // 转换证书级别为中文
|
|
686
|
|
- if (item.getCertificateLevel() != null) {
|
|
687
|
|
- item.setCertificateLevel(convertCertificateLevel(item.getCertificateLevel()));
|
|
688
|
|
- }
|
|
689
|
|
- });
|
|
|
906
|
+ double percentage = Math.round(entry.getValue() * 10000.0 / totalCount) / 100.0;
|
|
|
907
|
+ dto.setPercentage(java.math.BigDecimal.valueOf(percentage));
|
|
690
|
908
|
}
|
|
|
909
|
+
|
|
|
910
|
+ resultList.add(dto);
|
|
691
|
911
|
}
|
|
692
|
|
- return resultList != null ? resultList : Collections.emptyList();
|
|
|
912
|
+
|
|
|
913
|
+ // 4. 按数量降序排序
|
|
|
914
|
+ resultList.sort((a, b) -> Long.compare(
|
|
|
915
|
+ b.getCount() != null ? b.getCount() : 0,
|
|
|
916
|
+ a.getCount() != null ? a.getCount() : 0
|
|
|
917
|
+ ));
|
|
|
918
|
+
|
|
|
919
|
+ return resultList;
|
|
693
|
920
|
}
|
|
694
|
921
|
|
|
695
|
922
|
/**
|