4 Revize 80621230dd ... 37234630a3

Autor SHA1 Zpráva Datum
  simonlll 37234630a3 feat: 员工画像接口增强(基本信息、获奖、配分明细、质控) před 1 měsícem
  simonlll 9af9b6e991 fix: 员工画像查询改用实体参数替代 QueryWrapper před 1 měsícem
  simonlll f7a21d71bc feat: 新增员工画像详情页菜单 SQL (menu_id=2523) před 1 měsícem
  simonlll a1b3d19713 feat: 实现员工画像后端接口 před 1 měsícem

+ 31 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/score/EmployeePortraitController.java

@@ -0,0 +1,31 @@
1
+package com.sundot.airport.web.controller.score;
2
+
3
+import com.sundot.airport.common.core.controller.BaseController;
4
+import com.sundot.airport.common.core.domain.AjaxResult;
5
+import com.sundot.airport.ledger.service.IEmployeePortraitService;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.web.bind.annotation.GetMapping;
8
+import org.springframework.web.bind.annotation.RequestMapping;
9
+import org.springframework.web.bind.annotation.RequestParam;
10
+import org.springframework.web.bind.annotation.RestController;
11
+
12
+@RestController
13
+@RequestMapping("/score/portrait")
14
+public class EmployeePortraitController extends BaseController {
15
+
16
+    @Autowired
17
+    private IEmployeePortraitService portraitService;
18
+
19
+    @GetMapping("/searchUsers")
20
+    public AjaxResult searchUsers(@RequestParam(required = false, defaultValue = "") String keyword) {
21
+        return AjaxResult.success(portraitService.searchUsers(keyword));
22
+    }
23
+
24
+    @GetMapping("/employee")
25
+    public AjaxResult getEmployee(
26
+            @RequestParam String personName,
27
+            @RequestParam(required = false) String beginTime,
28
+            @RequestParam(required = false) String endTime) {
29
+        return AjaxResult.success(portraitService.getPortrait(personName, beginTime, endTime));
30
+    }
31
+}

+ 238 - 0
airport-ledger/src/main/java/com/sundot/airport/ledger/domain/vo/EmployeePortraitVO.java

@@ -0,0 +1,238 @@
1
+package com.sundot.airport.ledger.domain.vo;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+
5
+import java.math.BigDecimal;
6
+import java.util.Date;
7
+import java.util.List;
8
+
9
+/**
10
+ * 员工画像聚合 VO
11
+ */
12
+public class EmployeePortraitVO {
13
+
14
+    // ── 基本信息 ──────────────────────────────────────────────────────────────
15
+
16
+    private Long userId;
17
+    private String personName;
18
+    private String avatar;
19
+    private String sex;
20
+    private String sexText;
21
+    private String deptName;
22
+    private String parentDeptName;
23
+    private String deptPath;
24
+    private String qualificationLevelText;
25
+    private String schooling;
26
+    private String politicalStatusText;
27
+    private String characterCharacteristics;
28
+    private String workingStyle;
29
+    private String phonenumber;
30
+    private String birthday;       // 出生日期,从身份证计算
31
+    private String major;          // 专业(新增字段)
32
+    private String roleNames;      // 职务(来自 sys_role)
33
+    private String postNames;      // 岗位(来自 sys_post)
34
+
35
+    @JsonFormat(pattern = "yyyy-MM-dd")
36
+    private Date startWorkingDate;
37
+
38
+    @JsonFormat(pattern = "yyyy-MM-dd")
39
+    private Date securityCheckStartDate;
40
+
41
+    private Integer workYears;
42
+    private Integer securityCheckYears;
43
+    private String securityInspectionPosition;
44
+
45
+    // ── 综合得分 ──────────────────────────────────────────────────────────────
46
+
47
+    private BigDecimal totalScore;
48
+
49
+    // ── 六维度雷达 ────────────────────────────────────────────────────────────
50
+
51
+    private List<DimScoreItem> dimensions;
52
+
53
+    // ── 最新考试成绩 ──────────────────────────────────────────────────────────
54
+
55
+    private BigDecimal theoryScore;
56
+    private BigDecimal imageScore;
57
+    private String examCategory;
58
+    private String examPeriod;
59
+
60
+    @JsonFormat(pattern = "yyyy-MM-dd")
61
+    private Date examDate;
62
+
63
+    // ── 获奖/加分记录 ─────────────────────────────────────────────────────────
64
+
65
+    private List<AwardRecord> awards;
66
+
67
+    // ── 质控情况 ──────────────────────────────────────────────────────────────
68
+
69
+    private Integer qualityControlCount;   // 查获违规品次数
70
+
71
+    // ── 配分明细(加分+扣分全量) ──────────────────────────────────────────────
72
+
73
+    private List<ScoreDetail> scoreDetails;
74
+
75
+    // ── getter / setter ───────────────────────────────────────────────────────
76
+
77
+    public Long getUserId() { return userId; }
78
+    public void setUserId(Long userId) { this.userId = userId; }
79
+
80
+    public String getPersonName() { return personName; }
81
+    public void setPersonName(String personName) { this.personName = personName; }
82
+
83
+    public String getAvatar() { return avatar; }
84
+    public void setAvatar(String avatar) { this.avatar = avatar; }
85
+
86
+    public String getSex() { return sex; }
87
+    public void setSex(String sex) { this.sex = sex; }
88
+
89
+    public String getSexText() { return sexText; }
90
+    public void setSexText(String sexText) { this.sexText = sexText; }
91
+
92
+    public String getDeptName() { return deptName; }
93
+    public void setDeptName(String deptName) { this.deptName = deptName; }
94
+
95
+    public String getParentDeptName() { return parentDeptName; }
96
+    public void setParentDeptName(String parentDeptName) { this.parentDeptName = parentDeptName; }
97
+
98
+    public String getDeptPath() { return deptPath; }
99
+    public void setDeptPath(String deptPath) { this.deptPath = deptPath; }
100
+
101
+    public String getQualificationLevelText() { return qualificationLevelText; }
102
+    public void setQualificationLevelText(String qualificationLevelText) { this.qualificationLevelText = qualificationLevelText; }
103
+
104
+    public String getSchooling() { return schooling; }
105
+    public void setSchooling(String schooling) { this.schooling = schooling; }
106
+
107
+    public String getPoliticalStatusText() { return politicalStatusText; }
108
+    public void setPoliticalStatusText(String politicalStatusText) { this.politicalStatusText = politicalStatusText; }
109
+
110
+    public String getCharacterCharacteristics() { return characterCharacteristics; }
111
+    public void setCharacterCharacteristics(String characterCharacteristics) { this.characterCharacteristics = characterCharacteristics; }
112
+
113
+    public String getWorkingStyle() { return workingStyle; }
114
+    public void setWorkingStyle(String workingStyle) { this.workingStyle = workingStyle; }
115
+
116
+    public String getPhonenumber() { return phonenumber; }
117
+    public void setPhonenumber(String phonenumber) { this.phonenumber = phonenumber; }
118
+
119
+    public String getBirthday() { return birthday; }
120
+    public void setBirthday(String birthday) { this.birthday = birthday; }
121
+
122
+    public String getMajor() { return major; }
123
+    public void setMajor(String major) { this.major = major; }
124
+
125
+    public String getRoleNames() { return roleNames; }
126
+    public void setRoleNames(String roleNames) { this.roleNames = roleNames; }
127
+
128
+    public String getPostNames() { return postNames; }
129
+    public void setPostNames(String postNames) { this.postNames = postNames; }
130
+
131
+    public Date getStartWorkingDate() { return startWorkingDate; }
132
+    public void setStartWorkingDate(Date startWorkingDate) { this.startWorkingDate = startWorkingDate; }
133
+
134
+    public Date getSecurityCheckStartDate() { return securityCheckStartDate; }
135
+    public void setSecurityCheckStartDate(Date securityCheckStartDate) { this.securityCheckStartDate = securityCheckStartDate; }
136
+
137
+    public Integer getWorkYears() { return workYears; }
138
+    public void setWorkYears(Integer workYears) { this.workYears = workYears; }
139
+
140
+    public Integer getSecurityCheckYears() { return securityCheckYears; }
141
+    public void setSecurityCheckYears(Integer securityCheckYears) { this.securityCheckYears = securityCheckYears; }
142
+
143
+    public String getSecurityInspectionPosition() { return securityInspectionPosition; }
144
+    public void setSecurityInspectionPosition(String securityInspectionPosition) { this.securityInspectionPosition = securityInspectionPosition; }
145
+
146
+    public BigDecimal getTotalScore() { return totalScore; }
147
+    public void setTotalScore(BigDecimal totalScore) { this.totalScore = totalScore; }
148
+
149
+    public List<DimScoreItem> getDimensions() { return dimensions; }
150
+    public void setDimensions(List<DimScoreItem> dimensions) { this.dimensions = dimensions; }
151
+
152
+    public BigDecimal getTheoryScore() { return theoryScore; }
153
+    public void setTheoryScore(BigDecimal theoryScore) { this.theoryScore = theoryScore; }
154
+
155
+    public BigDecimal getImageScore() { return imageScore; }
156
+    public void setImageScore(BigDecimal imageScore) { this.imageScore = imageScore; }
157
+
158
+    public String getExamCategory() { return examCategory; }
159
+    public void setExamCategory(String examCategory) { this.examCategory = examCategory; }
160
+
161
+    public String getExamPeriod() { return examPeriod; }
162
+    public void setExamPeriod(String examPeriod) { this.examPeriod = examPeriod; }
163
+
164
+    public Date getExamDate() { return examDate; }
165
+    public void setExamDate(Date examDate) { this.examDate = examDate; }
166
+
167
+    public List<AwardRecord> getAwards() { return awards; }
168
+    public void setAwards(List<AwardRecord> awards) { this.awards = awards; }
169
+
170
+    // ── 内部类 ────────────────────────────────────────────────────────────────
171
+
172
+    public static class DimScoreItem {
173
+        private String name;
174
+        private BigDecimal score;
175
+        private BigDecimal weight;
176
+        private BigDecimal baseScore;
177
+        private BigDecimal eventScore;
178
+
179
+        public String getName() { return name; }
180
+        public void setName(String name) { this.name = name; }
181
+
182
+        public BigDecimal getScore() { return score; }
183
+        public void setScore(BigDecimal score) { this.score = score; }
184
+
185
+        public BigDecimal getWeight() { return weight; }
186
+        public void setWeight(BigDecimal weight) { this.weight = weight; }
187
+
188
+        public BigDecimal getBaseScore() { return baseScore; }
189
+        public void setBaseScore(BigDecimal baseScore) { this.baseScore = baseScore; }
190
+
191
+        public BigDecimal getEventScore() { return eventScore; }
192
+        public void setEventScore(BigDecimal eventScore) { this.eventScore = eventScore; }
193
+    }
194
+
195
+    public static class AwardRecord {
196
+        private String level2Name;   // 二级指标
197
+        private String level3Name;   // 三级指标
198
+        private String level4Name;   // 四级指标
199
+        private BigDecimal score;    // 获奖情况(分值)
200
+
201
+        public String getLevel2Name() { return level2Name; }
202
+        public void setLevel2Name(String level2Name) { this.level2Name = level2Name; }
203
+
204
+        public String getLevel3Name() { return level3Name; }
205
+        public void setLevel3Name(String level3Name) { this.level3Name = level3Name; }
206
+
207
+        public String getLevel4Name() { return level4Name; }
208
+        public void setLevel4Name(String level4Name) { this.level4Name = level4Name; }
209
+
210
+        public BigDecimal getScore() { return score; }
211
+        public void setScore(BigDecimal score) { this.score = score; }
212
+    }
213
+
214
+    public Integer getQualityControlCount() { return qualityControlCount; }
215
+    public void setQualityControlCount(Integer qualityControlCount) { this.qualityControlCount = qualityControlCount; }
216
+
217
+    public List<ScoreDetail> getScoreDetails() { return scoreDetails; }
218
+    public void setScoreDetails(List<ScoreDetail> scoreDetails) { this.scoreDetails = scoreDetails; }
219
+
220
+    public static class ScoreDetail {
221
+        private String dimensionName;
222
+        private String level2Name;
223
+        private String level3Name;
224
+        private BigDecimal totalScore;
225
+
226
+        public String getDimensionName() { return dimensionName; }
227
+        public void setDimensionName(String dimensionName) { this.dimensionName = dimensionName; }
228
+
229
+        public String getLevel2Name() { return level2Name; }
230
+        public void setLevel2Name(String level2Name) { this.level2Name = level2Name; }
231
+
232
+        public String getLevel3Name() { return level3Name; }
233
+        public void setLevel3Name(String level3Name) { this.level3Name = level3Name; }
234
+
235
+        public BigDecimal getTotalScore() { return totalScore; }
236
+        public void setTotalScore(BigDecimal totalScore) { this.totalScore = totalScore; }
237
+    }
238
+}

+ 5 - 0
airport-ledger/src/main/java/com/sundot/airport/ledger/mapper/LedgerSeizureStatsMapper.java

@@ -3,10 +3,15 @@ package com.sundot.airport.ledger.mapper;
3 3
 import java.util.List;
4 4
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 5
 import com.sundot.airport.ledger.domain.LedgerSeizureStats;
6
+import org.apache.ibatis.annotations.Param;
6 7
 
7 8
 /**
8 9
  * 查获违规品统计Mapper接口
9 10
  */
10 11
 public interface LedgerSeizureStatsMapper extends BaseMapper<LedgerSeizureStats> {
11 12
     List<LedgerSeizureStats> selectList(LedgerSeizureStats query);
13
+
14
+    int countByInspectorAndDateRange(@Param("inspectorName") String inspectorName,
15
+                                     @Param("beginTime")     String beginTime,
16
+                                     @Param("endTime")       String endTime);
12 17
 }

+ 26 - 0
airport-ledger/src/main/java/com/sundot/airport/ledger/service/IEmployeePortraitService.java

@@ -0,0 +1,26 @@
1
+package com.sundot.airport.ledger.service;
2
+
3
+import com.sundot.airport.ledger.domain.vo.EmployeePortraitVO;
4
+
5
+import java.util.List;
6
+import java.util.Map;
7
+
8
+/**
9
+ * 员工画像服务
10
+ */
11
+public interface IEmployeePortraitService {
12
+
13
+    /**
14
+     * 按姓名模糊搜索员工(用于前端下拉联想)
15
+     */
16
+    List<Map<String, Object>> searchUsers(String keyword);
17
+
18
+    /**
19
+     * 获取指定员工的完整画像数据
20
+     *
21
+     * @param personName 员工姓名(精确)
22
+     * @param beginTime  起始时间 yyyy-MM-dd,null 则不限
23
+     * @param endTime    结束时间 yyyy-MM-dd,null 则不限
24
+     */
25
+    EmployeePortraitVO getPortrait(String personName, String beginTime, String endTime);
26
+}

+ 370 - 0
airport-ledger/src/main/java/com/sundot/airport/ledger/service/impl/EmployeePortraitServiceImpl.java

@@ -0,0 +1,370 @@
1
+package com.sundot.airport.ledger.service.impl;
2
+
3
+import com.sundot.airport.common.core.domain.entity.SysDept;
4
+import com.sundot.airport.common.core.domain.entity.SysUser;
5
+import com.sundot.airport.ledger.domain.LedgerExamScore;
6
+import com.sundot.airport.ledger.domain.ScoreDimension;
7
+import com.sundot.airport.ledger.domain.ScoreEvent;
8
+import com.sundot.airport.ledger.domain.vo.EmployeePortraitVO;
9
+import com.sundot.airport.ledger.mapper.LedgerExamScoreMapper;
10
+import com.sundot.airport.ledger.mapper.LedgerSeizureStatsMapper;
11
+import com.sundot.airport.ledger.mapper.ScoreDimensionMapper;
12
+import com.sundot.airport.ledger.mapper.ScoreEventMapper;
13
+import com.sundot.airport.ledger.service.IEmployeePortraitService;
14
+import com.sundot.airport.system.domain.SysPost;
15
+import com.sundot.airport.common.core.domain.entity.SysRole;
16
+import com.sundot.airport.system.mapper.SysDeptMapper;
17
+import com.sundot.airport.system.mapper.SysPostMapper;
18
+import com.sundot.airport.system.mapper.SysRoleMapper;
19
+import com.sundot.airport.system.mapper.SysUserMapper;
20
+import org.springframework.beans.factory.annotation.Autowired;
21
+import org.springframework.stereotype.Service;
22
+
23
+import java.math.BigDecimal;
24
+import java.math.RoundingMode;
25
+import java.util.*;
26
+import java.util.stream.Collectors;
27
+
28
+@Service
29
+public class EmployeePortraitServiceImpl implements IEmployeePortraitService {
30
+
31
+    @Autowired
32
+    private SysUserMapper sysUserMapper;
33
+
34
+    @Autowired
35
+    private SysDeptMapper sysDeptMapper;
36
+
37
+    @Autowired
38
+    private SysRoleMapper sysRoleMapper;
39
+
40
+    @Autowired
41
+    private SysPostMapper sysPostMapper;
42
+
43
+    @Autowired
44
+    private ScoreEventMapper scoreEventMapper;
45
+
46
+    @Autowired
47
+    private ScoreDimensionMapper scoreDimensionMapper;
48
+
49
+    @Autowired
50
+    private LedgerExamScoreMapper ledgerExamScoreMapper;
51
+
52
+    @Autowired
53
+    private LedgerSeizureStatsMapper ledgerSeizureStatsMapper;
54
+
55
+    // ── 搜索员工 ─────────────────────────────────────────────────────────────
56
+
57
+    @Override
58
+    public List<Map<String, Object>> searchUsers(String keyword) {
59
+        SysUser query = new SysUser();
60
+        query.setNickName(keyword);
61
+        List<SysUser> users = sysUserMapper.selectUserList(query);
62
+        if (users == null) return Collections.emptyList();
63
+        return users.stream()
64
+                .filter(u -> "0".equals(u.getStatus()) && "0".equals(u.getDelFlag()))
65
+                .limit(20)
66
+                .map(u -> {
67
+                    Map<String, Object> m = new LinkedHashMap<>();
68
+                    m.put("userId", u.getUserId());
69
+                    m.put("nickName", u.getNickName());
70
+                    m.put("deptName", u.getDept() != null ? u.getDept().getDeptName() : "");
71
+                    return m;
72
+                })
73
+                .collect(Collectors.toList());
74
+    }
75
+
76
+    // ── 获取员工完整画像 ──────────────────────────────────────────────────────
77
+
78
+    @Override
79
+    public EmployeePortraitVO getPortrait(String personName, String beginTime, String endTime) {
80
+        EmployeePortraitVO vo = new EmployeePortraitVO();
81
+        vo.setPersonName(personName);
82
+
83
+        // 1. 用户基本信息
84
+        fillUserInfo(vo, personName);
85
+
86
+        // 2. 六维度评分(从 score_event 聚合)
87
+        fillScores(vo, personName, beginTime, endTime);
88
+
89
+        // 3. 最新考试成绩
90
+        fillExamScore(vo, personName);
91
+
92
+        // 4. 获奖记录
93
+        fillAwards(vo, personName);
94
+
95
+        // 5. 配分明细(全量加减分)
96
+        fillScoreDetails(vo, personName, beginTime, endTime);
97
+
98
+        // 6. 质控情况
99
+        int qcCount = ledgerSeizureStatsMapper.countByInspectorAndDateRange(personName, beginTime, endTime);
100
+        vo.setQualityControlCount(qcCount);
101
+
102
+        return vo;
103
+    }
104
+
105
+    // ── 用户基本信息 ─────────────────────────────────────────────────────────
106
+
107
+    private void fillUserInfo(EmployeePortraitVO vo, String personName) {
108
+        SysUser query = new SysUser();
109
+        query.setNickName(personName);
110
+        List<SysUser> users = sysUserMapper.selectUserList(query);
111
+        if (users == null || users.isEmpty()) return;
112
+
113
+        SysUser user = users.stream()
114
+                .filter(u -> personName.equals(u.getNickName()))
115
+                .findFirst()
116
+                .orElse(users.get(0));
117
+
118
+        vo.setUserId(user.getUserId());
119
+        vo.setAvatar(user.getAvatar());
120
+        vo.setPhonenumber(user.getPhonenumber());
121
+        vo.setSex(user.getSex());
122
+        vo.setSexText("0".equals(user.getSex()) ? "男" : "1".equals(user.getSex()) ? "女" : "");
123
+        vo.setSchooling(user.getSchooling());
124
+        vo.setQualificationLevelText(decodeQualLevel(user.getQualificationLevel()));
125
+        vo.setPoliticalStatusText(decodePolitical(user.getPoliticalStatus()));
126
+        vo.setCharacterCharacteristics(decodeChar(user.getCharacterCharacteristics()));
127
+        vo.setWorkingStyle(decodeWorkStyle(user.getWorkingStyle()));
128
+        vo.setStartWorkingDate(user.getStartWorkingDate());
129
+        vo.setSecurityCheckStartDate(user.getSecurityCheckStartDate());
130
+        vo.setSecurityInspectionPosition(user.getSecurityInspectionPosition());
131
+
132
+        // 司龄、开机年限
133
+        long nowMs = System.currentTimeMillis();
134
+        if (user.getStartWorkingDate() != null) {
135
+            vo.setWorkYears((int) ((nowMs - user.getStartWorkingDate().getTime()) / (365L * 24 * 3600 * 1000)));
136
+        }
137
+        if (user.getSecurityCheckStartDate() != null) {
138
+            vo.setSecurityCheckYears((int) ((nowMs - user.getSecurityCheckStartDate().getTime()) / (365L * 24 * 3600 * 1000)));
139
+        }
140
+
141
+        // 出生日期(从18位身份证第7~14位提取)
142
+        String card = user.getCardNumber();
143
+        if (card != null && card.length() >= 14) {
144
+            String bd = card.substring(6, 14);
145
+            vo.setBirthday(bd.substring(0, 4) + "-" + bd.substring(4, 6) + "-" + bd.substring(6, 8));
146
+        }
147
+
148
+        // 职务(sys_role)
149
+        List<SysRole> roles = sysRoleMapper.selectRolesByUserName(user.getUserName());
150
+        if (roles != null && !roles.isEmpty()) {
151
+            String rn = roles.stream()
152
+                    .map(SysRole::getRoleName)
153
+                    .filter(s -> s != null && !s.isEmpty())
154
+                    .collect(Collectors.joining("、"));
155
+            vo.setRoleNames(rn);
156
+        }
157
+
158
+        // 岗位(sys_post)
159
+        List<SysPost> posts = sysPostMapper.selectPostsByUserName(user.getUserName());
160
+        if (posts != null && !posts.isEmpty()) {
161
+            String pn = posts.stream()
162
+                    .map(SysPost::getPostName)
163
+                    .filter(s -> s != null && !s.isEmpty())
164
+                    .collect(Collectors.joining("、"));
165
+            vo.setPostNames(pn);
166
+        }
167
+
168
+        // 部门路径
169
+        if (user.getDept() != null) {
170
+            String deptName = user.getDept().getDeptName();
171
+            String parentDeptName = "";
172
+            Long parentId = user.getDept().getParentId();
173
+            if (parentId != null && parentId > 0) {
174
+                SysDept parent = sysDeptMapper.selectDeptById(parentId);
175
+                if (parent != null) parentDeptName = parent.getDeptName();
176
+            }
177
+            vo.setDeptName(deptName);
178
+            vo.setParentDeptName(parentDeptName);
179
+            vo.setDeptPath(parentDeptName.isEmpty() ? deptName : parentDeptName + "/" + deptName);
180
+        }
181
+    }
182
+
183
+    // ── 六维度评分 ────────────────────────────────────────────────────────────
184
+
185
+    private void fillScores(EmployeePortraitVO vo, String personName, String beginTime, String endTime) {
186
+        // 加载全部事件,按姓名 LIKE 过滤(支持逗号分隔多人)
187
+        ScoreEvent eventQuery = new ScoreEvent();
188
+        eventQuery.setPersonName(personName);
189
+        if (beginTime != null && !beginTime.isEmpty()) {
190
+            eventQuery.getParams().put("beginTime", beginTime);
191
+        }
192
+        if (endTime != null && !endTime.isEmpty()) {
193
+            eventQuery.getParams().put("endTime", endTime);
194
+        }
195
+        List<ScoreEvent> events = scoreEventMapper.selectList(eventQuery);
196
+
197
+        // 精确匹配(防止逗号多人字段的误匹配)
198
+        Map<Long, BigDecimal> dimMap = new HashMap<>();
199
+        for (ScoreEvent e : events) {
200
+            Long dimId = e.getDimensionId();
201
+            if (dimId == null) continue;
202
+            String raw = e.getPersonName();
203
+            if (raw == null) continue;
204
+            boolean matched = false;
205
+            for (String n : raw.split("[,,]")) {
206
+                if (personName.equals(n.trim())) { matched = true; break; }
207
+            }
208
+            if (!matched) continue;
209
+            BigDecimal val = e.getTotalScore() != null ? e.getTotalScore() : BigDecimal.ZERO;
210
+            dimMap.merge(dimId, val, BigDecimal::add);
211
+        }
212
+
213
+        // 加载维度定义
214
+        ScoreDimension dq = new ScoreDimension();
215
+        dq.setStatus("0");
216
+        List<ScoreDimension> dims = scoreDimensionMapper.selectList(dq);
217
+        dims.sort(Comparator.comparing(d -> d.getSortOrder() == null ? 999 : d.getSortOrder()));
218
+
219
+        List<EmployeePortraitVO.DimScoreItem> items = new ArrayList<>();
220
+        BigDecimal total = BigDecimal.ZERO;
221
+
222
+        for (ScoreDimension dim : dims) {
223
+            BigDecimal eventScore = dimMap.getOrDefault(dim.getId(), BigDecimal.ZERO);
224
+            BigDecimal base = dim.getBaseScore() != null ? dim.getBaseScore() : BigDecimal.valueOf(80);
225
+            BigDecimal dimScore = base.add(eventScore);
226
+            BigDecimal weight = dim.getWeight() != null ? dim.getWeight() : BigDecimal.ZERO;
227
+            BigDecimal contribution = dimScore.multiply(weight)
228
+                    .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
229
+            total = total.add(contribution);
230
+
231
+            EmployeePortraitVO.DimScoreItem item = new EmployeePortraitVO.DimScoreItem();
232
+            item.setName(dim.getName());
233
+            item.setScore(dimScore.setScale(1, RoundingMode.HALF_UP));
234
+            item.setWeight(weight);
235
+            item.setBaseScore(base);
236
+            item.setEventScore(eventScore.setScale(2, RoundingMode.HALF_UP));
237
+            items.add(item);
238
+        }
239
+
240
+        vo.setDimensions(items);
241
+        vo.setTotalScore(total.setScale(1, RoundingMode.HALF_UP));
242
+    }
243
+
244
+    // ── 考试成绩 ──────────────────────────────────────────────────────────────
245
+
246
+    private void fillExamScore(EmployeePortraitVO vo, String personName) {
247
+        LedgerExamScore q = new LedgerExamScore();
248
+        q.setPersonName(personName);
249
+        List<LedgerExamScore> list = ledgerExamScoreMapper.selectList(q);
250
+        if (list == null || list.isEmpty()) return;
251
+        list.sort((a, b) -> {
252
+            if (a.getExamDate() == null) return 1;
253
+            if (b.getExamDate() == null) return -1;
254
+            return b.getExamDate().compareTo(a.getExamDate());
255
+        });
256
+        LedgerExamScore exam = list.get(0);
257
+        vo.setTheoryScore(exam.getTheoryScore());
258
+        vo.setImageScore(exam.getImageScore());
259
+        vo.setExamDate(exam.getExamDate());
260
+        vo.setExamCategory(exam.getExamCategory());
261
+        vo.setExamPeriod(exam.getExamPeriod());
262
+    }
263
+
264
+    // ── 获奖记录 ──────────────────────────────────────────────────────────────
265
+
266
+    private void fillScoreDetails(EmployeePortraitVO vo, String personName, String beginTime, String endTime) {
267
+        ScoreEvent eq = new ScoreEvent();
268
+        eq.setPersonName(personName);
269
+        if (beginTime != null && !beginTime.isEmpty()) eq.getParams().put("beginTime", beginTime);
270
+        if (endTime   != null && !endTime.isEmpty())   eq.getParams().put("endTime",   endTime);
271
+        List<ScoreEvent> events = scoreEventMapper.selectList(eq);
272
+
273
+        List<EmployeePortraitVO.ScoreDetail> details = new ArrayList<>();
274
+        for (ScoreEvent e : events) {
275
+            String raw = e.getPersonName();
276
+            if (raw == null) continue;
277
+            boolean matched = false;
278
+            for (String n : raw.split("[,,]")) {
279
+                if (personName.equals(n.trim())) { matched = true; break; }
280
+            }
281
+            if (!matched) continue;
282
+
283
+            EmployeePortraitVO.ScoreDetail d = new EmployeePortraitVO.ScoreDetail();
284
+            d.setDimensionName(e.getDimensionName());
285
+            d.setLevel2Name(e.getLevel2Name());
286
+            d.setLevel3Name(e.getLevel3Name());
287
+            d.setTotalScore(e.getTotalScore());
288
+            details.add(d);
289
+        }
290
+        vo.setScoreDetails(details);
291
+    }
292
+
293
+    private void fillAwards(EmployeePortraitVO vo, String personName) {
294
+        // 群团协作能力 dimension_id = 5
295
+        ScoreEvent eq = new ScoreEvent();
296
+        eq.setPersonName(personName);
297
+        eq.setDimensionId(5L);
298
+        List<ScoreEvent> events = scoreEventMapper.selectList(eq);
299
+
300
+        List<EmployeePortraitVO.AwardRecord> awards = new ArrayList<>();
301
+        for (ScoreEvent e : events) {
302
+            String raw = e.getPersonName();
303
+            if (raw == null) continue;
304
+            boolean matched = false;
305
+            for (String n : raw.split("[,,]")) {
306
+                if (personName.equals(n.trim())) { matched = true; break; }
307
+            }
308
+            if (!matched) continue;
309
+
310
+            EmployeePortraitVO.AwardRecord ar = new EmployeePortraitVO.AwardRecord();
311
+            ar.setLevel3Name(e.getLevel3Name());
312
+            ar.setLevel2Name(e.getLevel2Name());
313
+            ar.setLevel4Name(e.getLevel4Name());
314
+            ar.setScore(e.getTotalScore());
315
+            awards.add(ar);
316
+        }
317
+        vo.setAwards(awards);
318
+    }
319
+
320
+    // ── 枚举解码工具 ─────────────────────────────────────────────────────────
321
+
322
+    private static String decodeQualLevel(String v) {
323
+        if (v == null) return "";
324
+        switch (v) {
325
+            case "LEVEL_ONE":   return "一级";
326
+            case "LEVEL_TWO":   return "二级";
327
+            case "LEVEL_THREE": return "三级";
328
+            case "LEVEL_FOUR":  return "四级";
329
+            case "LEVEL_FIVE":  return "五级";
330
+            default:            return v;
331
+        }
332
+    }
333
+
334
+    private static String decodePolitical(String v) {
335
+        if (v == null) return "";
336
+        switch (v) {
337
+            case "COMMUNIST_PARTY_MEMBER":             return "中共党员";
338
+            case "PROBATIONARY_COMMUNIST_PARTY_MEMBER":return "中共预备党员";
339
+            case "COMMUNIST_YOUTH_LEAGUE_MEMBER":      return "共青团员";
340
+            case "MASS_PUBLIC":                        return "群众";
341
+            default:                                   return v;
342
+        }
343
+    }
344
+
345
+    private static String decodeChar(String v) {
346
+        if (v == null) return "";
347
+        // 值格式如 "ISTJ",映射为 "ISTJ(检查员型)"
348
+        Map<String, String> m = new LinkedHashMap<>();
349
+        m.put("ISTJ", "ISTJ(检查员型)"); m.put("ISFJ", "ISFJ(守护者型)");
350
+        m.put("INFJ", "INFJ(咨询师型)"); m.put("INTJ", "INTJ(战略家型)");
351
+        m.put("ISTP", "ISTP(手艺人型)"); m.put("ISFP", "ISFP(艺术家型)");
352
+        m.put("INFP", "INFP(调停者型)"); m.put("INTP", "INTP(逻辑学家型)");
353
+        m.put("ESTP", "ESTP(企业家型)"); m.put("ESFP", "ESFP(表演者型)");
354
+        m.put("ENFP", "ENFP(活动家型)"); m.put("ENTP", "ENTP(辩论家型)");
355
+        m.put("ESTJ", "ESTJ(总经理型)"); m.put("ESFJ", "ESFJ(执政官型)");
356
+        m.put("ENFJ", "ENFJ(教育家型)"); m.put("ENTJ", "ENTJ(指挥官型)");
357
+        return m.getOrDefault(v, v);
358
+    }
359
+
360
+    private static String decodeWorkStyle(String v) {
361
+        if (v == null) return "";
362
+        switch (v) {
363
+            case "D": return "D型(支配型)";
364
+            case "I": return "I型(影响型)";
365
+            case "S": return "S型(稳健型)";
366
+            case "C": return "C型(尽责型)";
367
+            default:  return v;
368
+        }
369
+    }
370
+}

+ 14 - 0
airport-ledger/src/main/resources/mapper/ledger/LedgerSeizureStatsMapper.xml

@@ -50,4 +50,18 @@
50 50
         ORDER BY id DESC
51 51
     </select>
52 52
 
53
+
54
+    <select id="countByInspectorAndDateRange" resultType="int">
55
+        SELECT count(*)
56
+        FROM ledger_seizure_stats
57
+        WHERE del_flag = '0'
58
+          AND inspector_name = #{inspectorName}
59
+          <if test="beginTime != null and beginTime != ''">
60
+              AND record_date &gt;= STR_TO_DATE(#{beginTime}, '%Y-%m-%d')
61
+          </if>
62
+          <if test="endTime != null and endTime != ''">
63
+              AND record_date &lt;= STR_TO_DATE(#{endTime}, '%Y-%m-%d')
64
+          </if>
65
+    </select>
66
+
53 67
 </mapper>

+ 12 - 0
sql/menu_radar.sql

@@ -22,3 +22,15 @@ INSERT INTO sys_role_menu(role_id, menu_id)
22 22
 SELECT 100, m.menu_id FROM sys_menu m
23 23
 WHERE m.menu_id IN (2500, 2501)
24 24
   AND NOT EXISTS (SELECT 1 FROM sys_role_menu WHERE role_id=100 AND menu_id=m.menu_id);
25
+
26
+-- 员工画像详情页(menu_id=2523,挂在配分管理2400下,隐藏侧边栏,从雷达大屏跳转)
27
+INSERT INTO `sys_menu`
28
+  (`menu_id`,`menu_name`,`parent_id`,`order_num`,`path`,`component`,
29
+   `query`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,
30
+   `perms`,`icon`,`create_by`,`create_time`,`update_by`,`update_time`,`remark`)
31
+VALUES
32
+  (2523,'员工画像详情',2400,5,'employeeProfile','portraitManagement/employeeProfile/index',
33
+   '','',1,1,'C','1','0',
34
+   '','#','admin',NOW(),'',NULL,'员工六维度画像详情页,从雷达大屏跳转');
35
+
36
+INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2523);