Sfoglia il codice sorgente

干部月度考核

wangxx 1 mese fa
parent
commit
bf9f344102

+ 110 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/personnel/PersonnelMonthlyAssessmentController.java

@@ -0,0 +1,110 @@
1
+package com.sundot.airport.web.controller.personnel;
2
+
3
+import com.sundot.airport.common.annotation.Log;
4
+import com.sundot.airport.common.core.controller.BaseController;
5
+import com.sundot.airport.common.core.domain.AjaxResult;
6
+import com.sundot.airport.common.enums.BusinessType;
7
+import com.sundot.airport.common.utils.poi.ExcelUtil;
8
+import com.sundot.airport.common.core.page.TableDataInfo;
9
+import com.sundot.airport.personnel.domain.PersonnelMonthlyAssessment;
10
+import com.sundot.airport.personnel.service.IPersonnelMonthlyAssessmentService;
11
+import org.springframework.beans.factory.annotation.Autowired;
12
+import org.springframework.security.access.prepost.PreAuthorize;
13
+import org.springframework.web.bind.annotation.*;
14
+
15
+import javax.servlet.http.HttpServletResponse;
16
+import java.util.List;
17
+
18
+/**
19
+ * 人员月度考核Controller
20
+ *
21
+ * @author ruoyi
22
+ * @date 2026-05-07
23
+ */
24
+@RestController
25
+@RequestMapping("/personnel/cadre-assessment")
26
+public class PersonnelMonthlyAssessmentController extends BaseController {
27
+    @Autowired
28
+    private IPersonnelMonthlyAssessmentService personnelMonthlyAssessmentService;
29
+
30
+    /**
31
+     * 查询人员月度考核列表
32
+     */
33
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:list')")
34
+    @GetMapping("/list")
35
+    public TableDataInfo list(PersonnelMonthlyAssessment personnelMonthlyAssessment) {
36
+        startPage();
37
+        List<PersonnelMonthlyAssessment> list = personnelMonthlyAssessmentService.selectPersonnelMonthlyAssessmentList(personnelMonthlyAssessment);
38
+        return getDataTable(list);
39
+    }
40
+
41
+    /**
42
+     * 导出人员月度考核列表
43
+     */
44
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:export')")
45
+    @Log(title = "人员月度考核", businessType = BusinessType.EXPORT)
46
+    @PostMapping("/export")
47
+    public void export(HttpServletResponse response, PersonnelMonthlyAssessment personnelMonthlyAssessment) {
48
+        List<PersonnelMonthlyAssessment> list = personnelMonthlyAssessmentService.selectPersonnelMonthlyAssessmentList(personnelMonthlyAssessment);
49
+        ExcelUtil<PersonnelMonthlyAssessment> util = new ExcelUtil<PersonnelMonthlyAssessment>(PersonnelMonthlyAssessment.class);
50
+        util.exportExcel(response, list, "人员月度考核数据");
51
+    }
52
+
53
+    /**
54
+     * 获取人员月度考核详细信息
55
+     */
56
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:query')")
57
+    @GetMapping(value = "/{id}")
58
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
59
+        return success(personnelMonthlyAssessmentService.selectPersonnelMonthlyAssessmentById(id));
60
+    }
61
+
62
+    /**
63
+     * 新增人员月度考核
64
+     */
65
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:add')")
66
+    @Log(title = "人员月度考核", businessType = BusinessType.INSERT)
67
+    @PostMapping
68
+    public AjaxResult add(@RequestBody PersonnelMonthlyAssessment personnelMonthlyAssessment) {
69
+        personnelMonthlyAssessment.setCreateBy(getUsername());
70
+        return toAjax(personnelMonthlyAssessmentService.insertPersonnelMonthlyAssessment(personnelMonthlyAssessment));
71
+    }
72
+
73
+    /**
74
+     * 修改人员月度考核
75
+     */
76
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:edit')")
77
+    @Log(title = "人员月度考核", businessType = BusinessType.UPDATE)
78
+    @PutMapping
79
+    public AjaxResult edit(@RequestBody PersonnelMonthlyAssessment personnelMonthlyAssessment) {
80
+        personnelMonthlyAssessment.setUpdateBy(getUsername());
81
+        return toAjax(personnelMonthlyAssessmentService.updatePersonnelMonthlyAssessment(personnelMonthlyAssessment));
82
+    }
83
+
84
+    /**
85
+     * 删除人员月度考核
86
+     */
87
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:remove')")
88
+    @Log(title = "人员月度考核", businessType = BusinessType.DELETE)
89
+    @DeleteMapping("/{ids}")
90
+    public AjaxResult remove(@PathVariable Long[] ids) {
91
+        return toAjax(personnelMonthlyAssessmentService.deletePersonnelMonthlyAssessmentByIds(ids));
92
+    }
93
+
94
+    /**
95
+     * 生成本月考核数据
96
+     * @param year 年份
97
+     * @param month 月份
98
+     */
99
+    @PreAuthorize("@ss.hasPermi('personnel:assessment:generate')")
100
+    @Log(title = "生成本月考核", businessType = BusinessType.INSERT)
101
+    @PostMapping("/generate")
102
+    public AjaxResult generate(@RequestParam Integer year, @RequestParam Integer month) {
103
+        try {
104
+            int count = personnelMonthlyAssessmentService.generateMonthlyAssessment(year, month);
105
+            return success(String.format("成功生成/更新%d条考核记录!", count));
106
+        } catch (Exception e) {
107
+            return error("生成考核数据失败:" + e.getMessage());
108
+        }
109
+    }
110
+}

+ 151 - 0
airport-personnel/src/main/java/com/sundot/airport/personnel/domain/PersonnelMonthlyAssessment.java

@@ -0,0 +1,151 @@
1
+package com.sundot.airport.personnel.domain;
2
+
3
+import com.baomidou.mybatisplus.annotation.IdType;
4
+import com.baomidou.mybatisplus.annotation.TableId;
5
+import com.sundot.airport.common.annotation.Excel;
6
+import com.sundot.airport.common.core.domain.BaseEntity;
7
+import lombok.Getter;
8
+import lombok.Setter;
9
+
10
+import java.io.Serializable;
11
+import java.util.Date;
12
+
13
+/**
14
+ * 人员月度考核对象 personnel_monthly_assessment
15
+ *
16
+ * @author ruoyi
17
+ * @date 2026-05-07
18
+ */
19
+@Getter
20
+@Setter
21
+public class PersonnelMonthlyAssessment extends BaseEntity implements Serializable {
22
+    private static final long serialVersionUID = 1L;
23
+
24
+    /**
25
+     * 主键ID
26
+     */
27
+    @TableId(value = "id", type = IdType.AUTO)
28
+    private Long id;
29
+
30
+    /**
31
+     * 用户ID
32
+     */
33
+    @Excel(name = "用户ID")
34
+    private Long userId;
35
+
36
+    /**
37
+     * 姓名
38
+     */
39
+    @Excel(name = "姓名")
40
+    private String name;
41
+
42
+    /**
43
+     * 岗位
44
+     */
45
+    @Excel(name = "岗位")
46
+    private String post;
47
+
48
+    /**
49
+     * 区域
50
+     */
51
+    @Excel(name = "区域")
52
+    private String area;
53
+
54
+    /**
55
+     * 部门ID
56
+     */
57
+    @Excel(name = "部门ID")
58
+    private Long deptId;
59
+
60
+    /**
61
+     * 部门
62
+     */
63
+    @Excel(name = "部门")
64
+    private String deptName;
65
+
66
+    /**
67
+     * 红线扣分
68
+     */
69
+    @Excel(name = "红线扣分")
70
+    private Integer redLineDeduction;
71
+
72
+    /**
73
+     * 违规排名扣分
74
+     */
75
+    @Excel(name = "违规排名扣分")
76
+    private Integer violationRankingDeduction;
77
+
78
+    /**
79
+     * 技能排名扣分
80
+     */
81
+    @Excel(name = "技能排名扣分")
82
+    private Integer skillRankingDeduction;
83
+
84
+    /**
85
+     * 技能排名扣分豁免情况
86
+     */
87
+    @Excel(name = "技能排名扣分豁免情况")
88
+    private String skillExemptionStatus;
89
+
90
+    /**
91
+     * 总分
92
+     */
93
+    @Excel(name = "总分")
94
+    private Integer totalScore;
95
+
96
+    /**
97
+     * 考核结果
98
+     */
99
+    @Excel(name = "考核结果")
100
+    private String assessmentResult;
101
+
102
+    /**
103
+     * 考核结果备注
104
+     */
105
+    @Excel(name = "考核结果备注")
106
+    private String assessmentRemark;
107
+
108
+    /**
109
+     * 考核年份
110
+     */
111
+    @Excel(name = "考核年份")
112
+    private Integer year;
113
+
114
+    /**
115
+     * 考核月份(1-12)
116
+     */
117
+    @Excel(name = "考核月份")
118
+    private Integer month;
119
+
120
+    /**
121
+     * 应用方式
122
+     */
123
+    @Excel(name = "应用方式")
124
+    private String applicationMethod;
125
+
126
+    /**
127
+     * 应用方式备注
128
+     */
129
+    @Excel(name = "应用方式备注")
130
+    private String applicationRemark;
131
+
132
+    /**
133
+     * 删除标志(0代表存在 2代表删除)
134
+     */
135
+    private String delFlag;
136
+
137
+    /**
138
+     * 是否参加考核
139
+     */
140
+    private String takeAssessment;
141
+
142
+    /**
143
+     * 免考核开始时间
144
+     */
145
+    private Date exemptTakeAssessmentStartTime;
146
+
147
+    /**
148
+     * 免考核结束时间
149
+     */
150
+    private Date exemptTakeAssessmentEndTime;
151
+}

+ 127 - 0
airport-personnel/src/main/java/com/sundot/airport/personnel/mapper/PersonnelMonthlyAssessmentMapper.java

@@ -0,0 +1,127 @@
1
+package com.sundot.airport.personnel.mapper;
2
+
3
+import com.sundot.airport.personnel.domain.PersonnelMonthlyAssessment;
4
+import org.apache.ibatis.annotations.Param;
5
+
6
+import java.util.List;
7
+
8
+/**
9
+ * 人员月度考核Mapper接口
10
+ *
11
+ * @author ruoyi
12
+ * @date 2026-05-07
13
+ */
14
+public interface PersonnelMonthlyAssessmentMapper {
15
+    /**
16
+     * 查询人员月度考核
17
+     *
18
+     * @param id 人员月度考核主键
19
+     * @return 人员月度考核
20
+     */
21
+    PersonnelMonthlyAssessment selectPersonnelMonthlyAssessmentById(Long id);
22
+
23
+    /**
24
+     * 查询人员月度考核列表
25
+     *
26
+     * @param personnelMonthlyAssessment 人员月度考核
27
+     * @return 人员月度考核集合
28
+     */
29
+    List<PersonnelMonthlyAssessment> selectPersonnelMonthlyAssessmentList(PersonnelMonthlyAssessment personnelMonthlyAssessment);
30
+
31
+    /**
32
+     * 新增人员月度考核
33
+     *
34
+     * @param personnelMonthlyAssessment 人员月度考核
35
+     * @return 结果
36
+     */
37
+    int insertPersonnelMonthlyAssessment(PersonnelMonthlyAssessment personnelMonthlyAssessment);
38
+
39
+    /**
40
+     * 修改人员月度考核
41
+     *
42
+     * @param personnelMonthlyAssessment 人员月度考核
43
+     * @return 结果
44
+     */
45
+    int updatePersonnelMonthlyAssessment(PersonnelMonthlyAssessment personnelMonthlyAssessment);
46
+
47
+    /**
48
+     * 删除人员月度考核
49
+     *
50
+     * @param id 人员月度考核主键
51
+     * @return 结果
52
+     */
53
+    int deletePersonnelMonthlyAssessmentById(Long id);
54
+
55
+    /**
56
+     * 批量删除人员月度考核
57
+     *
58
+     * @param ids 需要删除的数据主键集合
59
+     * @return 结果
60
+     */
61
+    int deletePersonnelMonthlyAssessmentByIds(Long[] ids);
62
+
63
+    /**
64
+     * 根据用户ID、年份和月份查询考核记录
65
+     *
66
+     * @param userId 用户ID
67
+     * @param year   年份
68
+     * @param month  月份
69
+     * @return 人员月度考核
70
+     */
71
+    PersonnelMonthlyAssessment selectByUserIdAndYearMonth(@Param("userId") Long userId, @Param("year") Integer year, @Param("month") Integer month);
72
+
73
+    /**
74
+     * 批量插入考核记录
75
+     *
76
+     * @param list 考核记录列表
77
+     * @return 结果
78
+     */
79
+    int batchInsertPersonnelMonthlyAssessment(List<PersonnelMonthlyAssessment> list);
80
+
81
+    /**
82
+     * 查询需要考核的经理/副经理列表
83
+     *
84
+     * @return 经理列表
85
+     */
86
+    List<PersonnelMonthlyAssessment> selectManagersForAssessment();
87
+
88
+    /**
89
+     * 查询经理/副经理的下属用户ID列表
90
+     *
91
+     * @param deptIds 部门ID列表(包含当前部门及所有子部门)
92
+     * @param workArea 工作区域(副经理有值,经理为null或空)
93
+     * @param isManager 是否为经理(true-经理,false-副经理)
94
+     * @return 下属用户ID列表
95
+     */
96
+    List<Long> selectSubordinateUserIds(@Param("deptIds") List<Long> deptIds, @Param("workArea") String workArea, @Param("isManager") boolean isManager);
97
+
98
+    /**
99
+     * 批量统计红线行为次数
100
+     *
101
+     * @param userIds 用户ID列表
102
+     * @param year    年份
103
+     * @param month   月份
104
+     * @return 用户ID -> 红线次数的映射
105
+     */
106
+    List<java.util.HashMap<String, Object>> batchCountRedLineViolations(@Param("userIds") List<Long> userIds, @Param("year") Integer year, @Param("month") Integer month);
107
+
108
+    /**
109
+     * 批量查询团队SOC/品控扣分
110
+     *
111
+     * @param userIds 用户ID列表
112
+     * @param year    年份
113
+     * @param month   月份
114
+     * @return 用户ID -> 扣分的映射
115
+     */
116
+    List<java.util.HashMap<String, Object>> batchGetSocQcDeductions(@Param("userIds") List<Long> userIds, @Param("year") Integer year, @Param("month") Integer month);
117
+
118
+    /**
119
+     * 批量查询月考成绩(用于计算通过率)
120
+     *
121
+     * @param userIds 用户ID列表
122
+     * @param year    年份
123
+     * @param month   月份
124
+     * @return 用户ID -> (是否合格) 的映射
125
+     */
126
+    List<java.util.HashMap<String, Object>> batchGetExamScores(@Param("userIds") List<Long> userIds, @Param("year") Integer year, @Param("month") Integer month);
127
+}

+ 80 - 0
airport-personnel/src/main/java/com/sundot/airport/personnel/service/IPersonnelMonthlyAssessmentService.java

@@ -0,0 +1,80 @@
1
+package com.sundot.airport.personnel.service;
2
+
3
+import com.sundot.airport.personnel.domain.PersonnelMonthlyAssessment;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * 人员月度考核Service接口
9
+ *
10
+ * @author ruoyi
11
+ * @date 2026-05-07
12
+ */
13
+public interface IPersonnelMonthlyAssessmentService {
14
+    /**
15
+     * 查询人员月度考核
16
+     *
17
+     * @param id 人员月度考核主键
18
+     * @return 人员月度考核
19
+     */
20
+    PersonnelMonthlyAssessment selectPersonnelMonthlyAssessmentById(Long id);
21
+
22
+    /**
23
+     * 查询人员月度考核列表
24
+     *
25
+     * @param personnelMonthlyAssessment 人员月度考核
26
+     * @return 人员月度考核集合
27
+     */
28
+    List<PersonnelMonthlyAssessment> selectPersonnelMonthlyAssessmentList(PersonnelMonthlyAssessment personnelMonthlyAssessment);
29
+
30
+    /**
31
+     * 新增人员月度考核
32
+     *
33
+     * @param personnelMonthlyAssessment 人员月度考核
34
+     * @return 结果
35
+     */
36
+    int insertPersonnelMonthlyAssessment(PersonnelMonthlyAssessment personnelMonthlyAssessment);
37
+
38
+    /**
39
+     * 修改人员月度考核
40
+     *
41
+     * @param personnelMonthlyAssessment 人员月度考核
42
+     * @return 结果
43
+     */
44
+    int updatePersonnelMonthlyAssessment(PersonnelMonthlyAssessment personnelMonthlyAssessment);
45
+
46
+    /**
47
+     * 批量删除人员月度考核
48
+     *
49
+     * @param ids 需要删除的人员月度考核主键集合
50
+     * @return 结果
51
+     */
52
+    int deletePersonnelMonthlyAssessmentByIds(Long[] ids);
53
+
54
+    /**
55
+     * 删除人员月度考核信息
56
+     *
57
+     * @param id 人员月度考核主键
58
+     * @return 结果
59
+     */
60
+    int deletePersonnelMonthlyAssessmentById(Long id);
61
+
62
+    /**
63
+     * 根据用户ID、年份和月份查询考核记录
64
+     *
65
+     * @param userId 用户ID
66
+     * @param year   年份
67
+     * @param month  月份
68
+     * @return 人员月度考核
69
+     */
70
+    PersonnelMonthlyAssessment selectByUserIdAndYearMonth(Long userId, Integer year, Integer month);
71
+
72
+    /**
73
+     * 生成本月考核数据
74
+     *
75
+     * @param year  年份
76
+     * @param month 月份
77
+     * @return 生成的考核记录数量
78
+     */
79
+    int generateMonthlyAssessment(Integer year, Integer month);
80
+}

+ 697 - 0
airport-personnel/src/main/java/com/sundot/airport/personnel/service/impl/PersonnelMonthlyAssessmentServiceImpl.java

@@ -0,0 +1,697 @@
1
+package com.sundot.airport.personnel.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.common.exception.ServiceException;
6
+import com.sundot.airport.personnel.domain.PersonnelMonthlyAssessment;
7
+import com.sundot.airport.personnel.mapper.PersonnelMonthlyAssessmentMapper;
8
+import com.sundot.airport.personnel.service.IPersonnelMonthlyAssessmentService;
9
+import com.sundot.airport.system.mapper.SysDeptMapper;
10
+import com.sundot.airport.system.mapper.SysUserMapper;
11
+import org.springframework.beans.factory.annotation.Autowired;
12
+import org.springframework.stereotype.Service;
13
+import org.springframework.transaction.annotation.Transactional;
14
+
15
+import java.math.BigDecimal;
16
+import java.util.*;
17
+import java.util.stream.Collectors;
18
+
19
+/**
20
+ * 人员月度考核Service业务层处理
21
+ *
22
+ * @author ruoyi
23
+ * @date 2026-05-07
24
+ */
25
+@Service
26
+public class PersonnelMonthlyAssessmentServiceImpl implements IPersonnelMonthlyAssessmentService {
27
+    @Autowired
28
+    private PersonnelMonthlyAssessmentMapper personnelMonthlyAssessmentMapper;
29
+
30
+    @Autowired
31
+    private SysDeptMapper sysDeptMapper;
32
+
33
+    @Autowired
34
+    private SysUserMapper sysUserMapper;
35
+
36
+    /**
37
+     * 查询人员月度考核
38
+     *
39
+     * @param id 人员月度考核主键
40
+     * @return 人员月度考核
41
+     */
42
+    @Override
43
+    public PersonnelMonthlyAssessment selectPersonnelMonthlyAssessmentById(Long id) {
44
+        return personnelMonthlyAssessmentMapper.selectPersonnelMonthlyAssessmentById(id);
45
+    }
46
+
47
+    /**
48
+     * 查询人员月度考核列表
49
+     *
50
+     * @param personnelMonthlyAssessment 人员月度考核
51
+     * @return 人员月度考核
52
+     */
53
+    @Override
54
+    public List<PersonnelMonthlyAssessment> selectPersonnelMonthlyAssessmentList(PersonnelMonthlyAssessment personnelMonthlyAssessment) {
55
+        return personnelMonthlyAssessmentMapper.selectPersonnelMonthlyAssessmentList(personnelMonthlyAssessment);
56
+    }
57
+
58
+    /**
59
+     * 新增人员月度考核
60
+     *
61
+     * @param personnelMonthlyAssessment 人员月度考核
62
+     * @return 结果
63
+     */
64
+    @Override
65
+    public int insertPersonnelMonthlyAssessment(PersonnelMonthlyAssessment personnelMonthlyAssessment) {
66
+        // 校验同一用户同一年份月份是否已存在考核记录
67
+        PersonnelMonthlyAssessment existing = personnelMonthlyAssessmentMapper.selectByUserIdAndYearMonth(
68
+                personnelMonthlyAssessment.getUserId(),
69
+                personnelMonthlyAssessment.getYear(),
70
+                personnelMonthlyAssessment.getMonth()
71
+        );
72
+        if (existing != null) {
73
+            throw new ServiceException("该用户在此考核月份已存在考核记录,请勿重复添加!");
74
+        }
75
+
76
+        // 计算总分:100 - 红线扣分 - 违规排名扣分 - 技能排名扣分
77
+        calculateTotalScore(personnelMonthlyAssessment);
78
+
79
+        return personnelMonthlyAssessmentMapper.insertPersonnelMonthlyAssessment(personnelMonthlyAssessment);
80
+    }
81
+
82
+    /**
83
+     * 修改人员月度考核
84
+     *
85
+     * @param personnelMonthlyAssessment 人员月度考核
86
+     * @return 结果
87
+     */
88
+    @Override
89
+    public int updatePersonnelMonthlyAssessment(PersonnelMonthlyAssessment personnelMonthlyAssessment) {
90
+        // 计算总分:100 - 红线扣分 - 违规排名扣分 - 技能排名扣分
91
+        calculateTotalScore(personnelMonthlyAssessment);
92
+
93
+        return personnelMonthlyAssessmentMapper.updatePersonnelMonthlyAssessment(personnelMonthlyAssessment);
94
+    }
95
+
96
+    /**
97
+     * 批量删除人员月度考核
98
+     *
99
+     * @param ids 需要删除的人员月度考核主键
100
+     * @return 结果
101
+     */
102
+    @Override
103
+    public int deletePersonnelMonthlyAssessmentByIds(Long[] ids) {
104
+        return personnelMonthlyAssessmentMapper.deletePersonnelMonthlyAssessmentByIds(ids);
105
+    }
106
+
107
+    /**
108
+     * 删除人员月度考核信息
109
+     *
110
+     * @param id 人员月度考核主键
111
+     * @return 结果
112
+     */
113
+    @Override
114
+    public int deletePersonnelMonthlyAssessmentById(Long id) {
115
+        return personnelMonthlyAssessmentMapper.deletePersonnelMonthlyAssessmentById(id);
116
+    }
117
+
118
+    /**
119
+     * 根据用户ID、年份和月份查询考核记录
120
+     *
121
+     * @param userId 用户ID
122
+     * @param year   年份
123
+     * @param month  月份
124
+     * @return 人员月度考核
125
+     */
126
+    @Override
127
+    public PersonnelMonthlyAssessment selectByUserIdAndYearMonth(Long userId, Integer year, Integer month) {
128
+        return personnelMonthlyAssessmentMapper.selectByUserIdAndYearMonth(userId, year, month);
129
+    }
130
+
131
+    /**
132
+     * 计算总分
133
+     * 总分 = 100 - 红线扣分 - 违规排名扣分 - 技能排名扣分
134
+     *
135
+     * @param assessment 考核对象
136
+     */
137
+    private void calculateTotalScore(PersonnelMonthlyAssessment assessment) {
138
+        int redLineDeduction = assessment.getRedLineDeduction() == null ? 0 : assessment.getRedLineDeduction();
139
+        int violationRankingDeduction = assessment.getViolationRankingDeduction() == null ? 0 : assessment.getViolationRankingDeduction();
140
+        int skillRankingDeduction = assessment.getSkillRankingDeduction() == null ? 0 : assessment.getSkillRankingDeduction();
141
+
142
+        int totalScore = 100 - redLineDeduction - violationRankingDeduction - skillRankingDeduction;
143
+        assessment.setTotalScore(totalScore);
144
+    }
145
+
146
+    /**
147
+     * 过滤出需要生成本月考核的人员
148
+     * 
149
+     * @param managers 所有经理列表
150
+     * @param year 年份
151
+     * @param month 月份
152
+     * @return 需要生成考核的经理列表
153
+     */
154
+    private List<PersonnelMonthlyAssessment> filterManagersForMonth(List<PersonnelMonthlyAssessment> managers, Integer year, Integer month) {
155
+        List<PersonnelMonthlyAssessment> result = new ArrayList<>();
156
+        
157
+        // 计算本月的月初和月末日期
158
+        java.util.Calendar calendar = java.util.Calendar.getInstance();
159
+        calendar.set(year, month - 1, 1); // 月初(month-1因为Calendar的月份从0开始)
160
+        java.util.Date monthStart = calendar.getTime();
161
+        
162
+        calendar.set(year, month - 1, calendar.getActualMaximum(java.util.Calendar.DAY_OF_MONTH)); // 月末
163
+        java.util.Date monthEnd = calendar.getTime();
164
+        
165
+        for (PersonnelMonthlyAssessment manager : managers) {
166
+            boolean shouldGenerate = false;
167
+            
168
+            if ("是".equals(manager.getTakeAssessment())) {
169
+                // 条件1:参加考核
170
+                shouldGenerate = true;
171
+            } else if ("否".equals(manager.getTakeAssessment())) {
172
+                // 条件2:不参加考核,但需要检查免考核时间
173
+                if (manager.getExemptTakeAssessmentStartTime() != null && manager.getExemptTakeAssessmentEndTime() != null) {
174
+                    // 判断免考核时间是否包括整个月份
175
+                    boolean exemptCoversWholeMonth = 
176
+                        !manager.getExemptTakeAssessmentStartTime().after(monthStart) && 
177
+                        !manager.getExemptTakeAssessmentEndTime().before(monthEnd);
178
+                    
179
+                    // 如果免考核时间不包括整个月份,则需要生成考核
180
+                    if (!exemptCoversWholeMonth) {
181
+                        shouldGenerate = true;
182
+                    }
183
+                }
184
+            }
185
+            
186
+            if (shouldGenerate) {
187
+                result.add(manager);
188
+            }
189
+        }
190
+        
191
+        return result;
192
+    }
193
+
194
+    /**
195
+     * 获取经理/副经理的下属用户ID列表
196
+     * 
197
+     * @param managerUserId 经理/副经理用户ID
198
+     * @param workArea 工作区域(副经理有值,经理为null或空)
199
+     * @return 下属用户ID列表
200
+     */
201
+    private List<Long> getSubordinateUserIds(Long managerUserId, String workArea) {
202
+        // 1. 查询经理/副经理的用户信息
203
+        SysUser manager = sysUserMapper.selectUserById(managerUserId);
204
+        if (manager == null || manager.getDeptId() == null) {
205
+            return Collections.emptyList();
206
+        }
207
+
208
+        // 2. 获取该部门及所有子部门的ID列表
209
+        List<Long> deptIds = getAllSubDeptIds(manager.getDeptId());
210
+
211
+        // 3. 根据条件查询下属用户
212
+        boolean isManager = (workArea == null || workArea.isEmpty());
213
+        return personnelMonthlyAssessmentMapper.selectSubordinateUserIds(deptIds, workArea, isManager);
214
+    }
215
+
216
+    /**
217
+     * 获取部门及所有子部门的ID列表
218
+     * 
219
+     * @param deptId 部门ID
220
+     * @return 部门ID列表(包含当前部门及所有子部门)
221
+     */
222
+    private List<Long> getAllSubDeptIds(Long deptId) {
223
+        List<Long> result = new ArrayList<>();
224
+        result.add(deptId); // 添加当前部门
225
+
226
+        // 查询所有子部门
227
+        List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(deptId);
228
+        if (subDepts != null && !subDepts.isEmpty()) {
229
+            for (SysDept subDept : subDepts) {
230
+                if ("0".equals(subDept.getDelFlag())) { // 只包含未删除的部门
231
+                    result.add(subDept.getDeptId());
232
+                }
233
+            }
234
+        }
235
+
236
+        return result;
237
+    }
238
+
239
+    /**
240
+     * 生成本月考核数据
241
+     *
242
+     * @param year  年份
243
+     * @param month 月份
244
+     * @return 生成的记录数
245
+     */
246
+    @Override
247
+    @Transactional(rollbackFor = Exception.class)
248
+    public int generateMonthlyAssessment(Integer year, Integer month) {
249
+        // 1. 查询需要考核的经理/副经理列表
250
+        List<PersonnelMonthlyAssessment> managers = personnelMonthlyAssessmentMapper.selectManagersForAssessment();
251
+        if (managers == null || managers.isEmpty()) {
252
+            throw new ServiceException("未找到需要考核的经理人员!");
253
+        }
254
+
255
+        // 2. 过滤出需要生成本月考核的人员
256
+        List<PersonnelMonthlyAssessment> filteredManagers = filterManagersForMonth(managers, year, month);
257
+        if (filteredManagers.isEmpty()) {
258
+            throw new ServiceException("没有需要生成本月考核记录的人员!");
259
+        }
260
+
261
+        // 3. 检查是否已生成本月考核数据,收集需要更新的ID
262
+        Map<Long, Long> existingRecordMap = new HashMap<>(); // userId -> 已存在的记录ID
263
+        for (PersonnelMonthlyAssessment manager : filteredManagers) {
264
+            PersonnelMonthlyAssessment existing = personnelMonthlyAssessmentMapper.selectByUserIdAndYearMonth(
265
+                    manager.getUserId(), year, month);
266
+            if (existing != null) {
267
+                existingRecordMap.put(manager.getUserId(), existing.getId());
268
+            }
269
+        }
270
+
271
+        // 4. 为每个经理收集下属用户ID
272
+        Map<Long, List<Long>> managerSubordinatesMap = new HashMap<>();
273
+        List<Long> allSubordinateIds = new ArrayList<>();
274
+        for (PersonnelMonthlyAssessment manager : filteredManagers) {
275
+            // 获取下属用户ID列表
276
+            List<Long> subordinateIds = getSubordinateUserIds(manager.getUserId(), manager.getArea());
277
+            managerSubordinatesMap.put(manager.getUserId(), subordinateIds);
278
+            allSubordinateIds.addAll(subordinateIds);
279
+        }
280
+
281
+        // 5. 批量查询所有下属的红线行为次数
282
+        Map<Long, Integer> redLineCountMap = batchQueryRedLineCounts(allSubordinateIds, year, month);
283
+
284
+        // 6. 批量查询所有下属的SOC/品控扣分
285
+        Map<Long, BigDecimal> socQcDeductionMap = batchQuerySocQcDeductions(allSubordinateIds, year, month);
286
+
287
+        // 7. 批量查询所有下属的月考成绩
288
+        Map<Long, List<Map<String, Object>>> examScoresMap = batchQueryExamScores(allSubordinateIds, year, month);
289
+
290
+        // 8. 计算每个经理的各项扣分
291
+        Map<Long, Integer> redLineDeductionMap = new HashMap<>();
292
+        Map<Long, BigDecimal> teamDeductionMap = new HashMap<>();
293
+        Map<Long, BigDecimal> teamPassRateMap = new HashMap<>();
294
+
295
+        for (PersonnelMonthlyAssessment manager : filteredManagers) {
296
+            Long managerId = manager.getUserId();
297
+            List<Long> subordinateIds = managerSubordinatesMap.get(managerId);
298
+
299
+            // 计算红线扣分
300
+            int totalRedLineCount = 0;
301
+            for (Long subId : subordinateIds) {
302
+                totalRedLineCount += redLineCountMap.getOrDefault(subId, 0);
303
+            }
304
+            redLineDeductionMap.put(managerId, calculateRedLineDeduction(totalRedLineCount));
305
+
306
+            // 判断是否为经理(workArea为空或null表示经理,有值表示副经理)
307
+            boolean isManager = (manager.getArea() == null || manager.getArea().isEmpty());
308
+            
309
+            if (isManager) {
310
+                // 经理的扣分 = 所有下属副经理扣分*50% 的总和
311
+                BigDecimal managerDeduction = calculateManagerDeduction(
312
+                    manager, filteredManagers, managerSubordinatesMap, socQcDeductionMap
313
+                );
314
+                teamDeductionMap.put(managerId, managerDeduction);
315
+            } else {
316
+                // 副经理的扣分是其下属所有用户的扣分总和
317
+                BigDecimal teamDeduction = BigDecimal.ZERO;
318
+                for (Long subId : subordinateIds) {
319
+                    teamDeduction = teamDeduction.add(socQcDeductionMap.getOrDefault(subId, BigDecimal.ZERO));
320
+                }
321
+                teamDeductionMap.put(managerId, teamDeduction);
322
+            }
323
+
324
+            // 计算团队通过率
325
+            BigDecimal passRate = calculateTeamPassRate(subordinateIds, examScoresMap);
326
+            teamPassRateMap.put(managerId, passRate);
327
+        }
328
+
329
+        // 8. 计算违规排名扣分(全局排序)
330
+        Map<Long, Integer> violationDeductionMap = calculateViolationRankingDeduction(teamDeductionMap);
331
+
332
+        // 9. 计算技能排名扣分(全局排序)
333
+        Map<Long, Integer> skillDeductionMap = calculateSkillRankingDeduction(teamPassRateMap);
334
+
335
+        // 10. 查询上月通过率用于豁免判断
336
+        Map<Long, BigDecimal> lastMonthPassRateMap = batchQueryLastMonthPassRate(filteredManagers, year, month);
337
+
338
+        // 11. 构建考核记录列表
339
+        List<PersonnelMonthlyAssessment> insertList = new ArrayList<>();
340
+        List<PersonnelMonthlyAssessment> updateList = new ArrayList<>();
341
+        
342
+        for (PersonnelMonthlyAssessment manager : filteredManagers) {
343
+            PersonnelMonthlyAssessment assessment = new PersonnelMonthlyAssessment();
344
+            assessment.setUserId(manager.getUserId());
345
+            assessment.setName(manager.getName());
346
+            assessment.setPost(manager.getPost());
347
+            assessment.setArea(manager.getArea());
348
+            assessment.setDeptId(manager.getDeptId());
349
+            assessment.setDeptName(manager.getDeptName());
350
+            assessment.setYear(year);
351
+            assessment.setMonth(month);
352
+
353
+            // 设置各项扣分
354
+            assessment.setRedLineDeduction(redLineDeductionMap.getOrDefault(manager.getUserId(), 0));
355
+            assessment.setViolationRankingDeduction(violationDeductionMap.getOrDefault(manager.getUserId(), 0));
356
+            assessment.setSkillRankingDeduction(skillDeductionMap.getOrDefault(manager.getUserId(), 0));
357
+
358
+            // 检查豁免情况
359
+            BigDecimal currentPassRate = teamPassRateMap.get(manager.getUserId());
360
+            BigDecimal lastPassRate = lastMonthPassRateMap.get(manager.getUserId());
361
+            if (currentPassRate != null && lastPassRate != null 
362
+                && currentPassRate.compareTo(BigDecimal.ZERO) > 0 
363
+                && lastPassRate.compareTo(BigDecimal.ZERO) > 0
364
+                && currentPassRate.compareTo(lastPassRate) > 0) {
365
+                assessment.setSkillExemptionStatus("通过率较上月提升,豁免技能排名扣分");
366
+                assessment.setSkillRankingDeduction(0);
367
+            } else {
368
+                assessment.setSkillExemptionStatus("无豁免");
369
+            }
370
+
371
+            // 计算总分
372
+            calculateTotalScore(assessment);
373
+
374
+            // 计算考核结果和备注
375
+            calculateAssessmentResult(assessment);
376
+
377
+            // 判断是新增还是更新
378
+            Long existingId = existingRecordMap.get(manager.getUserId());
379
+            if (existingId != null) {
380
+                // 已存在,设置为更新
381
+                assessment.setId(existingId);
382
+                updateList.add(assessment);
383
+            } else {
384
+                // 不存在,设置为新增
385
+                insertList.add(assessment);
386
+            }
387
+        }
388
+
389
+        // 12. 批量插入和更新
390
+        int insertCount = 0;
391
+        int updateCount = 0;
392
+        
393
+        if (!insertList.isEmpty()) {
394
+            insertCount = personnelMonthlyAssessmentMapper.batchInsertPersonnelMonthlyAssessment(insertList);
395
+        }
396
+        
397
+        if (!updateList.isEmpty()) {
398
+            for (PersonnelMonthlyAssessment assessment : updateList) {
399
+                updateCount += personnelMonthlyAssessmentMapper.updatePersonnelMonthlyAssessment(assessment);
400
+            }
401
+        }
402
+
403
+        return insertCount + updateCount;
404
+    }
405
+
406
+    /**
407
+     * 计算红线扣分
408
+     * 1次扣5分,2次扣10分,3次及以上扣20分
409
+     *
410
+     * @param redLineCount 红线行为次数
411
+     * @return 扣分分值
412
+     */
413
+    private int calculateRedLineDeduction(int redLineCount) {
414
+        if (redLineCount <= 0) {
415
+            return 0;
416
+        } else if (redLineCount == 1) {
417
+            return 5;
418
+        } else if (redLineCount == 2) {
419
+            return 10;
420
+        } else {
421
+            return 20;
422
+        }
423
+    }
424
+
425
+    /**
426
+     * 批量查询红线行为次数
427
+     */
428
+    private Map<Long, Integer> batchQueryRedLineCounts(List<Long> userIds, Integer year, Integer month) {
429
+        Map<Long, Integer> resultMap = new HashMap<>();
430
+        if (userIds == null || userIds.isEmpty()) {
431
+            return resultMap;
432
+        }
433
+
434
+        List<HashMap<String, Object>> results = personnelMonthlyAssessmentMapper.batchCountRedLineViolations(userIds, year, month);
435
+        for (HashMap<String, Object> row : results) {
436
+            Long userId = ((Number) row.get("userId")).longValue();
437
+            Integer count = ((Number) row.get("redLineCount")).intValue();
438
+            resultMap.put(userId, count);
439
+        }
440
+        return resultMap;
441
+    }
442
+
443
+    /**
444
+     * 批量查询SOC/品控扣分
445
+     */
446
+    private Map<Long, BigDecimal> batchQuerySocQcDeductions(List<Long> userIds, Integer year, Integer month) {
447
+        Map<Long, BigDecimal> resultMap = new HashMap<>();
448
+        if (userIds == null || userIds.isEmpty()) {
449
+            return resultMap;
450
+        }
451
+
452
+        List<HashMap<String, Object>> results = personnelMonthlyAssessmentMapper.batchGetSocQcDeductions(userIds, year, month);
453
+        for (HashMap<String, Object> row : results) {
454
+            Long userId = ((Number) row.get("userId")).longValue();
455
+            BigDecimal deduction = new BigDecimal(row.get("totalDeduction").toString());
456
+            resultMap.put(userId, deduction);
457
+        }
458
+        return resultMap;
459
+    }
460
+
461
+    /**
462
+     * 批量查询月考成绩
463
+     */
464
+    private Map<Long, List<Map<String, Object>>> batchQueryExamScores(List<Long> userIds, Integer year, Integer month) {
465
+        Map<Long, List<Map<String, Object>>> resultMap = new HashMap<>();
466
+        if (userIds == null || userIds.isEmpty()) {
467
+            return resultMap;
468
+        }
469
+
470
+        List<HashMap<String, Object>> results = personnelMonthlyAssessmentMapper.batchGetExamScores(userIds, year, month);
471
+        for (HashMap<String, Object> row : results) {
472
+            Long userId = ((Number) row.get("userId")).longValue();
473
+            resultMap.computeIfAbsent(userId, k -> new ArrayList<>()).add(row);
474
+        }
475
+        return resultMap;
476
+    }
477
+
478
+    /**
479
+     * 计算团队通过率
480
+     */
481
+    private BigDecimal calculateTeamPassRate(List<Long> subordinateIds, Map<Long, List<Map<String, Object>>> examScoresMap) {
482
+        if (subordinateIds == null || subordinateIds.isEmpty()) {
483
+            return BigDecimal.ZERO;
484
+        }
485
+
486
+        int totalCount = 0;
487
+        int passCount = 0;
488
+
489
+        for (Long userId : subordinateIds) {
490
+            List<Map<String, Object>> scores = examScoresMap.get(userId);
491
+            if (scores != null) {
492
+                for (Map<String, Object> score : scores) {
493
+                    totalCount++;
494
+                    String post = (String) score.get("post");
495
+                    BigDecimal scoreValue = new BigDecimal(score.get("score").toString());
496
+
497
+                    // X光机操作员90分合格,四级安检员60分合格
498
+                    if (("X光机操作员".equals(post) && scoreValue.compareTo(new BigDecimal("90")) >= 0)
499
+                        || ("四级安检员".equals(post) && scoreValue.compareTo(new BigDecimal("60")) >= 0)) {
500
+                        passCount++;
501
+                    }
502
+                }
503
+            }
504
+        }
505
+
506
+        if (totalCount == 0) {
507
+            return BigDecimal.ZERO;
508
+        }
509
+
510
+        return new BigDecimal(passCount).multiply(new BigDecimal("100")).divide(new BigDecimal(totalCount), 2, BigDecimal.ROUND_HALF_UP);
511
+    }
512
+
513
+    /**
514
+     * 批量查询上月通过率
515
+     */
516
+    private Map<Long, BigDecimal> batchQueryLastMonthPassRate(List<PersonnelMonthlyAssessment> managers, Integer year, Integer month) {
517
+        Map<Long, BigDecimal> resultMap = new HashMap<>();
518
+
519
+        // 计算上月的年月
520
+        int lastYear = year;
521
+        int lastMonth = month - 1;
522
+        if (lastMonth == 0) {
523
+            lastMonth = 12;
524
+            lastYear = year - 1;
525
+        }
526
+
527
+        // 为每个经理查询上月通过率
528
+        for (PersonnelMonthlyAssessment manager : managers) {
529
+            List<Long> subordinateIds = getSubordinateUserIds(manager.getUserId(), manager.getArea());
530
+            Map<Long, List<Map<String, Object>>> examScoresMap = batchQueryExamScores(subordinateIds, lastYear, lastMonth);
531
+            BigDecimal passRate = calculateTeamPassRate(subordinateIds, examScoresMap);
532
+            resultMap.put(manager.getUserId(), passRate);
533
+        }
534
+
535
+        return resultMap;
536
+    }
537
+
538
+    /**
539
+     * 计算违规排名扣分
540
+     * 末位扣3分,倒数第二扣2分,倒数第三扣1分
541
+     *
542
+     * @param teamDeductionMap 经理ID -> 团队扣分总额的映射
543
+     * @return 经理ID -> 扣分的映射
544
+     */
545
+    private Map<Long, Integer> calculateViolationRankingDeduction(Map<Long, BigDecimal> teamDeductionMap) {
546
+        Map<Long, Integer> deductionMap = new HashMap<>();
547
+
548
+        if (teamDeductionMap == null || teamDeductionMap.isEmpty()) {
549
+            return deductionMap;
550
+        }
551
+
552
+        // 按扣分总额从高到低排序
553
+        List<Map.Entry<Long, BigDecimal>> sortedList = new ArrayList<>(teamDeductionMap.entrySet());
554
+        sortedList.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue()));
555
+
556
+        int size = sortedList.size();
557
+        if (size >= 3) {
558
+            // 末位(扣分最少)扣3分
559
+            deductionMap.put(sortedList.get(size - 1).getKey(), 3);
560
+            // 倒数第二扣2分
561
+            deductionMap.put(sortedList.get(size - 2).getKey(), 2);
562
+            // 倒数第三扣1分
563
+            deductionMap.put(sortedList.get(size - 3).getKey(), 1);
564
+        } else if (size == 2) {
565
+            deductionMap.put(sortedList.get(1).getKey(), 3);
566
+            deductionMap.put(sortedList.get(0).getKey(), 2);
567
+        } else if (size == 1) {
568
+            deductionMap.put(sortedList.get(0).getKey(), 3);
569
+        }
570
+
571
+        return deductionMap;
572
+    }
573
+
574
+    /**
575
+     * 计算技能排名扣分
576
+     * 末位扣3分,倒数第二扣2分,倒数第三扣1分
577
+     *
578
+     * @param teamPassRateMap 经理ID -> 团队通过率的映射
579
+     * @return 经理ID -> 扣分的映射
580
+     */
581
+    private Map<Long, Integer> calculateSkillRankingDeduction(Map<Long, BigDecimal> teamPassRateMap) {
582
+        Map<Long, Integer> deductionMap = new HashMap<>();
583
+
584
+        if (teamPassRateMap == null || teamPassRateMap.isEmpty()) {
585
+            return deductionMap;
586
+        }
587
+
588
+        // 按通过率从低到高排序
589
+        List<Map.Entry<Long, BigDecimal>> sortedList = new ArrayList<>(teamPassRateMap.entrySet());
590
+        sortedList.sort((e1, e2) -> e1.getValue().compareTo(e2.getValue()));
591
+
592
+        int size = sortedList.size();
593
+        if (size >= 3) {
594
+            // 末位(通过率最低)扣3分
595
+            deductionMap.put(sortedList.get(0).getKey(), 3);
596
+            // 倒数第二扣2分
597
+            deductionMap.put(sortedList.get(1).getKey(), 2);
598
+            // 倒数第三扣1分
599
+            deductionMap.put(sortedList.get(2).getKey(), 1);
600
+        } else if (size == 2) {
601
+            deductionMap.put(sortedList.get(0).getKey(), 3);
602
+            deductionMap.put(sortedList.get(1).getKey(), 2);
603
+        } else if (size == 1) {
604
+            deductionMap.put(sortedList.get(0).getKey(), 3);
605
+        }
606
+
607
+        return deductionMap;
608
+    }
609
+
610
+    /**
611
+     * 计算考核结果和备注
612
+     * 预警:得分低于90分
613
+     * 待改进:得分低于85分
614
+     * 不称职:得分低于80分
615
+     *
616
+     * @param assessment 考核对象
617
+     */
618
+    private void calculateAssessmentResult(PersonnelMonthlyAssessment assessment) {
619
+        int totalScore = assessment.getTotalScore() == null ? 100 : assessment.getTotalScore();
620
+
621
+        if (totalScore < 80) {
622
+            assessment.setAssessmentResult("不称职");
623
+            assessment.setAssessmentRemark("得分低于80分,认定不称职,建议调整岗位或降职处理");
624
+            // 应用方式:降职降薪
625
+            assessment.setApplicationMethod("降职降薪");
626
+            assessment.setApplicationRemark("考核结果为不称职的,则进行降职降薪处理。");
627
+        } else if (totalScore < 85) {
628
+            assessment.setAssessmentResult("待改进");
629
+            assessment.setAssessmentRemark("得分低于85分,认定待改进,同时启动干部任职画像民主测评");
630
+            // 应用方式:民主测评
631
+            assessment.setApplicationMethod("民主测评");
632
+            assessment.setApplicationRemark("考核结果为待改进,启动干部任职画像民主测评,围绕专业能力、统筹协调能力、群众基础三个维度进行评价,据测评结果确定后续处置意见:");
633
+        } else if (totalScore < 90) {
634
+            assessment.setAssessmentResult("预警");
635
+            assessment.setAssessmentRemark("得分低于90分,启动预警机制,由分管部门班子进行专项督导,推动管理提升");
636
+            // 预警暂无特定应用方式
637
+            assessment.setApplicationMethod(null);
638
+            assessment.setApplicationRemark(null);
639
+        } else {
640
+            assessment.setAssessmentResult(null);
641
+            assessment.setAssessmentRemark(null);
642
+            assessment.setApplicationMethod(null);
643
+            assessment.setApplicationRemark(null);
644
+        }
645
+    }
646
+
647
+    /**
648
+     * 计算经理的品控扣分
649
+     * 规则:经理的扣分 = 所有下属副经理扣分*50% 的总和
650
+     *
651
+     * @param manager 经理对象
652
+     * @param allManagers 所有经理/副经理列表
653
+     * @param managerSubordinatesMap 经理ID -> 下属用户ID列表的映射
654
+     * @param socQcDeductionMap 用户ID -> SOC/品控扣分的映射
655
+     * @return 经理的品控扣分
656
+     */
657
+    private BigDecimal calculateManagerDeduction(
658
+            PersonnelMonthlyAssessment manager,
659
+            List<PersonnelMonthlyAssessment> allManagers,
660
+            Map<Long, List<Long>> managerSubordinatesMap,
661
+            Map<Long, BigDecimal> socQcDeductionMap) {
662
+        
663
+        // 1. 找出该经理下属的所有副经理(同一部门且有workArea)
664
+        List<PersonnelMonthlyAssessment> viceManagersUnderManager = allManagers.stream()
665
+            .filter(m -> m.getArea() != null && !m.getArea().isEmpty()) // 是副经理
666
+            .filter(m -> {
667
+                // 判断副经理是否属于该经理的部门
668
+                SysUser viceManager = sysUserMapper.selectUserById(m.getUserId());
669
+                return viceManager != null && viceManager.getDeptId() != null 
670
+                    && viceManager.getDeptId().equals(manager.getDeptId());
671
+            })
672
+            .collect(Collectors.toList());
673
+
674
+        // 2. 计算每个副经理的扣分*50%,然后累加
675
+        BigDecimal managerTotalDeduction = BigDecimal.ZERO;
676
+        for (PersonnelMonthlyAssessment viceManager : viceManagersUnderManager) {
677
+            // 获取该副经理的下属用户ID列表
678
+            List<Long> viceManagerSubIds = managerSubordinatesMap.get(viceManager.getUserId());
679
+            
680
+            // 累加该副经理下属的所有扣分
681
+            BigDecimal viceManagerTotalDeduction = BigDecimal.ZERO;
682
+            for (Long subId : viceManagerSubIds) {
683
+                viceManagerTotalDeduction = viceManagerTotalDeduction.add(
684
+                    socQcDeductionMap.getOrDefault(subId, BigDecimal.ZERO)
685
+                );
686
+            }
687
+            
688
+            // 该副经理的扣分 * 50%
689
+            viceManagerTotalDeduction = viceManagerTotalDeduction.multiply(new BigDecimal("0.5"));
690
+            
691
+            // 累加到经理的总扣分
692
+            managerTotalDeduction = managerTotalDeduction.add(viceManagerTotalDeduction);
693
+        }
694
+        
695
+        return managerTotalDeduction;
696
+    }
697
+}

+ 265 - 0
airport-personnel/src/main/resources/mapper/personnel/PersonnelMonthlyAssessmentMapper.xml

@@ -0,0 +1,265 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper
3
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+<mapper namespace="com.sundot.airport.personnel.mapper.PersonnelMonthlyAssessmentMapper">
6
+
7
+    <resultMap type="PersonnelMonthlyAssessment" id="PersonnelMonthlyAssessmentResult">
8
+        <result property="id" column="id"/>
9
+        <result property="userId" column="user_id"/>
10
+        <result property="name" column="name"/>
11
+        <result property="post" column="post"/>
12
+        <result property="area" column="area"/>
13
+        <result property="deptId" column="dept_id"/>
14
+        <result property="deptName" column="dept_name"/>
15
+        <result property="redLineDeduction" column="red_line_deduction"/>
16
+        <result property="violationRankingDeduction" column="violation_ranking_deduction"/>
17
+        <result property="skillRankingDeduction" column="skill_ranking_deduction"/>
18
+        <result property="skillExemptionStatus" column="skill_exemption_status"/>
19
+        <result property="totalScore" column="total_score"/>
20
+        <result property="assessmentResult" column="assessment_result"/>
21
+        <result property="assessmentRemark" column="assessment_remark"/>
22
+        <result property="year" column="year"/>
23
+        <result property="month" column="month"/>
24
+        <result property="applicationMethod" column="application_method"/>
25
+        <result property="applicationRemark" column="application_remark"/>
26
+        <result property="createBy" column="create_by"/>
27
+        <result property="createTime" column="create_time"/>
28
+        <result property="updateBy" column="update_by"/>
29
+        <result property="updateTime" column="update_time"/>
30
+        <result property="remark" column="remark"/>
31
+        <result property="delFlag" column="del_flag"/>
32
+    </resultMap>
33
+
34
+    <sql id="selectPersonnelMonthlyAssessmentVo">
35
+        select id, user_id, name, post, area, dept_id, dept_name, red_line_deduction,
36
+               violation_ranking_deduction, skill_ranking_deduction, skill_exemption_status,
37
+               total_score, assessment_result, assessment_remark, year, month,
38
+               application_method, application_remark, create_by, create_time,
39
+               update_by, update_time, remark, del_flag
40
+        from personnel_monthly_assessment
41
+    </sql>
42
+
43
+    <select id="selectPersonnelMonthlyAssessmentList" parameterType="PersonnelMonthlyAssessment" resultMap="PersonnelMonthlyAssessmentResult">
44
+        <include refid="selectPersonnelMonthlyAssessmentVo"/>
45
+        <where>
46
+            del_flag = '0'
47
+            <if test="userId != null">and user_id = #{userId}</if>
48
+            <if test="name != null and name != ''">and name like concat('%', #{name}, '%')</if>
49
+            <if test="post != null and post != ''">and post = #{post}</if>
50
+            <if test="area != null and area != ''">and area = #{area}</if>
51
+            <if test="deptId != null">and dept_id = #{deptId}</if>
52
+            <if test="deptName != null and deptName != ''">and dept_name like concat('%', #{deptName}, '%')</if>
53
+            <if test="assessmentResult != null and assessmentResult != ''">and assessment_result = #{assessmentResult}</if>
54
+            <if test="year != null">and year = #{year}</if>
55
+            <if test="month != null">and month = #{month}</if>
56
+        </where>
57
+        order by year desc, month desc, create_time desc
58
+    </select>
59
+
60
+    <select id="selectPersonnelMonthlyAssessmentById" parameterType="Long" resultMap="PersonnelMonthlyAssessmentResult">
61
+        <include refid="selectPersonnelMonthlyAssessmentVo"/>
62
+        where id = #{id} and del_flag = '0'
63
+    </select>
64
+
65
+    <select id="selectByUserIdAndYearMonth" resultMap="PersonnelMonthlyAssessmentResult">
66
+        <include refid="selectPersonnelMonthlyAssessmentVo"/>
67
+        where user_id = #{userId} and year = #{year} and month = #{month} and del_flag = '0'
68
+    </select>
69
+
70
+    <insert id="insertPersonnelMonthlyAssessment" parameterType="PersonnelMonthlyAssessment" useGeneratedKeys="true" keyProperty="id">
71
+        insert into personnel_monthly_assessment
72
+        <trim prefix="(" suffix=")" suffixOverrides=",">
73
+            <if test="userId != null">user_id,</if>
74
+            <if test="name != null and name != ''">name,</if>
75
+            <if test="post != null and post != ''">post,</if>
76
+            <if test="area != null">area,</if>
77
+            <if test="deptId != null">dept_id,</if>
78
+            <if test="deptName != null and deptName != ''">dept_name,</if>
79
+            <if test="redLineDeduction != null">red_line_deduction,</if>
80
+            <if test="violationRankingDeduction != null">violation_ranking_deduction,</if>
81
+            <if test="skillRankingDeduction != null">skill_ranking_deduction,</if>
82
+            <if test="skillExemptionStatus != null">skill_exemption_status,</if>
83
+            <if test="totalScore != null">total_score,</if>
84
+            <if test="assessmentResult != null">assessment_result,</if>
85
+            <if test="assessmentRemark != null">assessment_remark,</if>
86
+            <if test="year != null">year,</if>
87
+            <if test="month != null">month,</if>
88
+            <if test="applicationMethod != null">application_method,</if>
89
+            <if test="applicationRemark != null">application_remark,</if>
90
+            <if test="createBy != null and createBy != ''">create_by,</if>
91
+            <if test="remark != null">remark,</if>
92
+            <if test="delFlag != null and delFlag != ''">del_flag,</if>
93
+            create_time
94
+        </trim>
95
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
96
+            <if test="userId != null">#{userId},</if>
97
+            <if test="name != null and name != ''">#{name},</if>
98
+            <if test="post != null and post != ''">#{post},</if>
99
+            <if test="area != null">#{area},</if>
100
+            <if test="deptId != null">#{deptId},</if>
101
+            <if test="deptName != null and deptName != ''">#{deptName},</if>
102
+            <if test="redLineDeduction != null">#{redLineDeduction},</if>
103
+            <if test="violationRankingDeduction != null">#{violationRankingDeduction},</if>
104
+            <if test="skillRankingDeduction != null">#{skillRankingDeduction},</if>
105
+            <if test="skillExemptionStatus != null">#{skillExemptionStatus},</if>
106
+            <if test="totalScore != null">#{totalScore},</if>
107
+            <if test="assessmentResult != null">#{assessmentResult},</if>
108
+            <if test="assessmentRemark != null">#{assessmentRemark},</if>
109
+            <if test="year != null">#{year},</if>
110
+            <if test="month != null">#{month},</if>
111
+            <if test="applicationMethod != null">#{applicationMethod},</if>
112
+            <if test="applicationRemark != null">#{applicationRemark},</if>
113
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
114
+            <if test="remark != null">#{remark},</if>
115
+            <if test="delFlag != null and delFlag != ''">#{delFlag},</if>
116
+            sysdate()
117
+        </trim>
118
+    </insert>
119
+
120
+    <update id="updatePersonnelMonthlyAssessment" parameterType="PersonnelMonthlyAssessment">
121
+        update personnel_monthly_assessment
122
+        <trim prefix="SET" suffixOverrides=",">
123
+            <if test="userId != null">user_id = #{userId},</if>
124
+            <if test="name != null and name != ''">name = #{name},</if>
125
+            <if test="post != null and post != ''">post = #{post},</if>
126
+            <if test="area != null">area = #{area},</if>
127
+            <if test="deptId != null">dept_id = #{deptId},</if>
128
+            <if test="deptName != null and deptName != ''">dept_name = #{deptName},</if>
129
+            <if test="redLineDeduction != null">red_line_deduction = #{redLineDeduction},</if>
130
+            <if test="violationRankingDeduction != null">violation_ranking_deduction = #{violationRankingDeduction},</if>
131
+            <if test="skillRankingDeduction != null">skill_ranking_deduction = #{skillRankingDeduction},</if>
132
+            <if test="skillExemptionStatus != null">skill_exemption_status = #{skillExemptionStatus},</if>
133
+            <if test="totalScore != null">total_score = #{totalScore},</if>
134
+            <if test="assessmentResult != null">assessment_result = #{assessmentResult},</if>
135
+            <if test="assessmentRemark != null">assessment_remark = #{assessmentRemark},</if>
136
+            <if test="year != null">year = #{year},</if>
137
+            <if test="month != null">month = #{month},</if>
138
+            <if test="applicationMethod != null">application_method = #{applicationMethod},</if>
139
+            <if test="applicationRemark != null">application_remark = #{applicationRemark},</if>
140
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
141
+            <if test="remark != null">remark = #{remark},</if>
142
+            update_time = sysdate()
143
+        </trim>
144
+        where id = #{id}
145
+    </update>
146
+
147
+    <delete id="deletePersonnelMonthlyAssessmentById" parameterType="Long">
148
+        update personnel_monthly_assessment set del_flag = '2' where id = #{id}
149
+    </delete>
150
+
151
+    <delete id="deletePersonnelMonthlyAssessmentByIds" parameterType="Long">
152
+        update personnel_monthly_assessment set del_flag = '2' where id in
153
+        <foreach item="id" collection="array" open="(" separator="," close=")">
154
+            #{id}
155
+        </foreach>
156
+    </delete>
157
+
158
+    <!-- 批量插入人员月度考核 -->
159
+    <insert id="batchInsertPersonnelMonthlyAssessment" parameterType="java.util.List">
160
+        insert into personnel_monthly_assessment
161
+        (user_id, name, post, area, dept_id, dept_name, red_line_deduction, violation_ranking_deduction,
162
+         skill_ranking_deduction, skill_exemption_status, total_score, assessment_result, assessment_remark,
163
+         year, month, application_method, application_remark, create_by, create_time)
164
+        values
165
+        <foreach collection="list" item="item" separator=",">
166
+            (#{item.userId}, #{item.name}, #{item.post}, #{item.area}, #{item.deptId}, #{item.deptName},
167
+             #{item.redLineDeduction}, #{item.violationRankingDeduction}, #{item.skillRankingDeduction},
168
+             #{item.skillExemptionStatus}, #{item.totalScore}, #{item.assessmentResult}, #{item.assessmentRemark},
169
+             #{item.year}, #{item.month}, #{item.applicationMethod}, #{item.applicationRemark},
170
+             #{item.createBy}, sysdate())
171
+        </foreach>
172
+    </insert>
173
+
174
+    <!-- 查询需要考核的经理/副经理列表 -->
175
+    <select id="selectManagersForAssessment" resultType="com.sundot.airport.personnel.domain.PersonnelMonthlyAssessment">
176
+        SELECT DISTINCT
177
+            u.user_id AS userId,
178
+            u.nick_name AS name,
179
+            u.post,
180
+            u.work_area AS area,
181
+            u.dept_id AS deptId,
182
+            d.dept_name AS deptName,
183
+            u.take_assessment AS takeAssessment,
184
+            u.exempt_take_assessment_start_time AS exemptTakeAssessmentStartTime,
185
+            u.exempt_take_assessment_end_time AS exemptTakeAssessmentEndTime
186
+        FROM sys_user u
187
+        LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
188
+        LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
189
+        LEFT JOIN sys_role r ON ur.role_id = r.role_id
190
+        WHERE u.del_flag = '0'
191
+          AND u.employment_type IN ('派遣工', '正式工')
192
+          AND r.role_key = 'manager'
193
+        ORDER BY u.dept_id, u.user_id
194
+    </select>
195
+
196
+    <!-- 查询经理/副经理的下属用户ID列表 -->
197
+    <select id="selectSubordinateUserIds" resultType="java.lang.Long">
198
+        SELECT u.user_id
199
+        FROM sys_user u
200
+        WHERE u.del_flag = '0'
201
+          AND u.dept_id IN
202
+        <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
203
+            #{deptId}
204
+        </foreach>
205
+        <if test="workArea != null and workArea != ''">
206
+          AND u.work_area = #{workArea}
207
+        </if>
208
+        <if test="!isManager">
209
+          AND u.user_id NOT IN (
210
+              SELECT DISTINCT ur.user_id
211
+              FROM sys_user_role ur
212
+              INNER JOIN sys_role r ON ur.role_id = r.role_id
213
+              WHERE r.role_key = 'jingli'
214
+          )
215
+        </if>
216
+    </select>
217
+
218
+    <!-- 批量统计红线行为次数 -->
219
+    <select id="batchCountRedLineViolations" resultType="java.util.HashMap">
220
+        SELECT
221
+            n.user_id AS userId,
222
+            COUNT(DISTINCT n.id) AS redLineCount
223
+        FROM personnel_non_cadre_monthly_assessment n
224
+        WHERE n.user_id IN
225
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
226
+            #{userId}
227
+        </foreach>
228
+          AND DATE_FORMAT(n.assessment_month, '%Y') = #{year}
229
+          AND DATE_FORMAT(n.assessment_month, '%m') = LPAD(#{month}, 2, '0')
230
+          AND n.red_line_index_trigger_count > 0
231
+        GROUP BY n.user_id
232
+    </select>
233
+
234
+    <!-- 批量查询团队SOC/品控扣分 -->
235
+    <select id="batchGetSocQcDeductions" resultType="java.util.HashMap">
236
+        SELECT
237
+            n.user_id AS userId,
238
+            COALESCE(SUM(n.soc_station_qc_involved_core_safety_deduction), 0) AS totalDeduction
239
+        FROM personnel_non_cadre_monthly_assessment n
240
+        WHERE n.user_id IN
241
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
242
+            #{userId}
243
+        </foreach>
244
+          AND DATE_FORMAT(n.assessment_month, '%Y') = #{year}
245
+          AND DATE_FORMAT(n.assessment_month, '%m') = LPAD(#{month}, 2, '0')
246
+        GROUP BY n.user_id
247
+    </select>
248
+
249
+    <!-- 批量查询月考成绩 -->
250
+    <select id="batchGetExamScores" resultType="java.util.HashMap">
251
+        SELECT
252
+            m.user_id AS userId,
253
+            m.comprehensive_score AS score,
254
+            m.job_position AS post
255
+        FROM exam_monthly_score m
256
+        WHERE m.user_id IN
257
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
258
+            #{userId}
259
+        </foreach>
260
+          AND m.year_month = CONCAT(#{year}, '-', LPAD(#{month}, 2, '0'))
261
+          AND m.del_flag = '0'
262
+    </select>
263
+
264
+
265
+</mapper>