Преглед изворни кода

Merge branch 'master' into develop

chenshudong пре 1 месец
родитељ
комит
e8ae1af737

+ 11 - 4
airport-admin/src/main/java/com/sundot/airport/web/controller/attendance/AttendancePostRecordController.java

@@ -1,5 +1,6 @@
1 1
 package com.sundot.airport.web.controller.attendance;
2 2
 
3
+import cn.hutool.core.bean.BeanUtil;
3 4
 import cn.hutool.core.collection.CollUtil;
4 5
 import cn.hutool.core.collection.CollectionUtil;
5 6
 import cn.hutool.core.date.DateUtil;
@@ -288,8 +289,6 @@ public class AttendancePostRecordController extends BaseController {
288 289
     public AjaxResult add(@RequestBody AttendancePostRecord attendancePostRecord) {
289 290
         // 处理部门信息
290 291
         extracted(attendancePostRecord);
291
-        UserInfo userInfo = userCache.getUserInfo(attendancePostRecord.getUserId());
292
-        attendancePostRecord.setUserName(userInfo.getNickName());
293 292
         attendancePostRecord.setRevision(1L);
294 293
         attendancePostRecord.setWorkDuration((attendancePostRecord.getCheckOutTime().getTime() - attendancePostRecord.getCheckInTime().getTime()) / 1000 / 60);
295 294
         attendancePostRecord.setAttendanceDate(DateUtil.date(attendancePostRecord.getCheckInTime()));
@@ -301,8 +300,16 @@ public class AttendancePostRecordController extends BaseController {
301 300
         BasePosition positionParam = new BasePosition();
302 301
         List<BasePosition> basePositions = basePositionService.selectBasePositionList(positionParam);
303 302
         setChannel(attendancePostRecord, basePositions);
304
-
305
-        return toAjax(attendancePostRecordService.insertAttendancePostRecord(attendancePostRecord));
303
+        attendancePostRecord.setLocked(false);
304
+        attendancePostRecord.getUserIdList().forEach(userId -> {
305
+            AttendancePostRecord item = new AttendancePostRecord();
306
+            BeanUtil.copyProperties(attendancePostRecord, item);
307
+            UserInfo userInfo = userCache.getUserInfo(userId);
308
+            item.setUserId(userId);
309
+            item.setUserName(userInfo.getNickName());
310
+            attendancePostRecordService.insertAttendancePostRecord(item);
311
+        });
312
+        return success("操作成功");
306 313
     }
307 314
 
308 315
     private void extracted(AttendancePostRecord attendancePostRecord) {

+ 8 - 2
airport-admin/src/main/java/com/sundot/airport/web/controller/system/SysHomeReportController.java

@@ -450,8 +450,14 @@ public class SysHomeReportController extends BaseController {
450 450
      */
451 451
     private List<SysHomeReportDetailDto> getAnswerList(List<SysHomePageDetailQueryParamDto> dtoList) {
452 452
         List<SysHomeReportDetailDto> result = new ArrayList<>();
453
-        for (SysHomePageDetailQueryParamDto item : dtoList) {
454
-            result.add(getAnswer(item));
453
+        // 批量调用前统一初始化部门缓存,避免每次 answerData 都重复查询数据库
454
+        accuracyStatisticsService.initDeptCache();
455
+        try {
456
+            for (SysHomePageDetailQueryParamDto item : dtoList) {
457
+                result.add(getAnswer(item));
458
+            }
459
+        } finally {
460
+            accuracyStatisticsService.clearDeptCache();
455 461
         }
456 462
         return result;
457 463
     }

+ 10 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/system/SysUserController.java

@@ -20,6 +20,7 @@ import com.sundot.airport.check.domain.CheckLargeScreenCorrectionQueryParamDto;
20 20
 import com.sundot.airport.common.core.domain.ResetUserPostDto;
21 21
 import com.sundot.airport.common.core.domain.ResetUserRoleDto;
22 22
 import com.sundot.airport.common.core.domain.SysLargeScreenWorkingPortraitDto;
23
+import com.sundot.airport.common.dto.SysUserConditionDto;
23 24
 import com.sundot.airport.common.enums.RoleTypeEnum;
24 25
 import com.sundot.airport.exam.domain.DailyTask;
25 26
 import com.sundot.airport.attendance.portrait.AttendanceModuleIndicatorResult;
@@ -150,6 +151,15 @@ public class SysUserController extends BaseController {
150 151
     }
151 152
 
152 153
     /**
154
+     * 查询用户列表
155
+     */
156
+    @PostMapping("/selectUserListByCondition")
157
+    public AjaxResult selectUserListByCondition(@RequestBody SysUserConditionDto dto) {
158
+        List<SysUser> result = userService.selectUserListByDeptIdAndRoleKeyListAndUserName(dto);
159
+        return success(result);
160
+    }
161
+
162
+    /**
153 163
      * 能力画像-协同配合
154 164
      */
155 165
     @Cacheable(

+ 5 - 1
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendancePostRecord.java

@@ -1,6 +1,7 @@
1 1
 package com.sundot.airport.attendance.domain;
2 2
 
3 3
 import java.util.Date;
4
+import java.util.List;
4 5
 
5 6
 import com.fasterxml.jackson.annotation.JsonFormat;
6 7
 import lombok.Data;
@@ -257,5 +258,8 @@ public class AttendancePostRecord extends BaseEntity {
257 258
     @JsonFormat(pattern = "yyyy-MM-dd")
258 259
     private Date endTime;
259 260
 
260
-
261
+    /**
262
+     * 用户ID列表
263
+     */
264
+    private List<Long> userIdList;
261 265
 }

+ 2 - 0
airport-attendance/src/main/resources/mapper/attendance/AttendancePostRecordMapper.xml

@@ -191,6 +191,7 @@
191 191
             <if test="statusDesc != null and statusDesc != ''">status_desc,</if>
192 192
             <if test="positionCode != null">position_code,</if>
193 193
             <if test="positionName != null">position_name,</if>
194
+            <if test="locked != null">locked,</if>
194 195
         </trim>
195 196
         <trim prefix="values (" suffix=")" suffixOverrides=",">
196 197
             <if test="tenantId != null">#{tenantId},</if>
@@ -229,6 +230,7 @@
229 230
             <if test="statusDesc != null and statusDesc != ''">#{statusDesc},</if>
230 231
             <if test="positionCode != null">#{positionCode},</if>
231 232
             <if test="positionName != null">#{positionName},</if>
233
+            <if test="locked != null">#{locked},</if>
232 234
         </trim>
233 235
     </insert>
234 236
 

+ 31 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/SysUserConditionDto.java

@@ -0,0 +1,31 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import lombok.Data;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * 查询用户列表参数
9
+ *
10
+ * @Author: wangchong
11
+ * @Date: 2025/7/15 16:12
12
+ **/
13
+@Data
14
+public class SysUserConditionDto {
15
+
16
+    /**
17
+     * 部门id
18
+     */
19
+    private Long deptId;
20
+
21
+    /**
22
+     * 角色编码列表
23
+     */
24
+    private List<String> roleKeyList;
25
+
26
+    /**
27
+     * 用户昵称
28
+     */
29
+    private String nickName;
30
+
31
+}

+ 10 - 0
airport-exam/src/main/java/com/sundot/airport/exam/service/IAccuracyStatisticsService.java

@@ -69,4 +69,14 @@ public interface IAccuracyStatisticsService {
69 69
      * @return 首页报表-抽问抽答正确率数据
70 70
      */
71 71
     com.sundot.airport.common.core.domain.SysHomeReportDetailDto answerData(BaseLargeScreenQueryParamDto dto);
72
+
73
+    /**
74
+     * 初始化部门缓存(批量调用 answerData 前调用,避免重复查询数据库)
75
+     */
76
+    void initDeptCache();
77
+
78
+    /**
79
+     * 清理部门缓存
80
+     */
81
+    void clearDeptCache();
72 82
 }

+ 9 - 6
airport-exam/src/main/java/com/sundot/airport/exam/service/impl/AccuracyStatisticsServiceImpl.java

@@ -159,7 +159,8 @@ public class AccuracyStatisticsServiceImpl implements IAccuracyStatisticsService
159 159
     /**
160 160
      * 初始化部门缓存
161 161
      */
162
-    private DeptCache initDeptCache() {
162
+    @Override
163
+    public void initDeptCache() {
163 164
         DeptCache cache = DEPT_CACHE.get();
164 165
         if (cache == null) {
165 166
             long start = System.currentTimeMillis();
@@ -168,13 +169,13 @@ public class AccuracyStatisticsServiceImpl implements IAccuracyStatisticsService
168 169
             DEPT_CACHE.set(cache);
169 170
             log.info("initDeptCache: loaded {} depts in {}ms", allDepts.size(), System.currentTimeMillis() - start);
170 171
         }
171
-        return cache;
172 172
     }
173 173
 
174 174
     /**
175 175
      * 清理缓存
176 176
      */
177
-    private void clearDeptCache() {
177
+    @Override
178
+    public void clearDeptCache() {
178 179
         DEPT_CACHE.remove();
179 180
     }
180 181
 
@@ -1253,7 +1254,8 @@ public class AccuracyStatisticsServiceImpl implements IAccuracyStatisticsService
1253 1254
             result.setName(sysUser.getNickName());
1254 1255
             result.setType(HomePageQueryEnum.USER.getCode());
1255 1256
         } else {
1256
-            SysDept sysDept = deptService.selectDeptById(dto.getDeptId());
1257
+            DeptCache cache = getDeptCache();
1258
+            SysDept sysDept = cache != null ? cache.deptMap.get(dto.getDeptId()) : deptService.selectDeptById(dto.getDeptId());
1257 1259
             if (ObjectUtil.isNull(sysDept)) {
1258 1260
                 throw new ServiceException("【" + dto.getDeptId() + "】部门不存在");
1259 1261
             }
@@ -1332,12 +1334,13 @@ public class AccuracyStatisticsServiceImpl implements IAccuracyStatisticsService
1332 1334
             }
1333 1335
         } else if (ObjectUtil.isNotNull(dto.getDeptId())) {
1334 1336
             // 部门维度:获取部门下所有用户的正确率
1335
-            SysDept sysDept = deptService.selectDeptById(dto.getDeptId());
1337
+            DeptCache cache = getDeptCache();
1338
+            SysDept sysDept = cache != null ? cache.deptMap.get(dto.getDeptId()) : deptService.selectDeptById(dto.getDeptId());
1336 1339
             if (sysDept == null) {
1337 1340
                 return result;
1338 1341
             }
1339 1342
 
1340
-            DeptHierarchy hierarchy = getDeptHierarchy(dto.getDeptId());
1343
+            DeptHierarchy hierarchy = getDeptHierarchyCached(dto.getDeptId());
1341 1344
             List<Long> teamIds = getTeamIdsUnderDept(dto.getDeptId(), hierarchy);
1342 1345
 
1343 1346
             if (teamIds.isEmpty()) {

+ 10 - 0
airport-system/src/main/java/com/sundot/airport/system/mapper/SysUserMapper.java

@@ -220,4 +220,14 @@ public interface SysUserMapper {
220 220
      * @return 首页-用户信息
221 221
      */
222 222
     public List<LargeScreenHomePageUserInfoSqlDto> homePageUserInfo();
223
+
224
+    /**
225
+     * 根据组织ID和角色编码列表和用户昵称查询用户列表
226
+     *
227
+     * @param deptId   组织ID
228
+     * @param list     角色编码列表
229
+     * @param nickName 用户昵称
230
+     * @return 用户信息集合
231
+     */
232
+    public List<SysUser> selectUserListByDeptIdAndRoleKeyListAndUserName(@Param("deptId") Long deptId, @Param("list") List<String> list, @Param("nickName") String nickName);
223 233
 }

+ 9 - 0
airport-system/src/main/java/com/sundot/airport/system/service/ISysUserService.java

@@ -7,6 +7,7 @@ import com.sundot.airport.common.core.domain.LargeScreenHomePageUserInfoSqlDto;
7 7
 import com.sundot.airport.common.core.domain.ResetUserPostDto;
8 8
 import com.sundot.airport.common.core.domain.ResetUserRoleDto;
9 9
 import com.sundot.airport.common.core.domain.entity.SysUser;
10
+import com.sundot.airport.common.dto.SysUserConditionDto;
10 11
 import com.sundot.airport.system.domain.SysLargeScreenCooperationDto;
11 12
 import com.sundot.airport.system.domain.SysLargeScreenCooperationQueryParamDto;
12 13
 
@@ -311,4 +312,12 @@ public interface ISysUserService {
311 312
      * @return 结果
312 313
      */
313 314
     public int resetUserPostByDept(ResetUserPostDto resetUserPostDto);
315
+
316
+    /**
317
+     * 查询用户列表
318
+     *
319
+     * @param dto 查询用户列表参数
320
+     * @return 用户信息集合
321
+     */
322
+    public List<SysUser> selectUserListByDeptIdAndRoleKeyListAndUserName(SysUserConditionDto dto);
314 323
 }

+ 73 - 5
airport-system/src/main/java/com/sundot/airport/system/service/approval/impl/ApprovalEngineServiceImpl.java

@@ -12,8 +12,10 @@ import java.util.regex.Matcher;
12 12
 import cn.hutool.core.collection.CollUtil;
13 13
 import cn.hutool.core.util.ObjectUtil;
14 14
 import com.sundot.airport.common.core.domain.entity.SysDept;
15
+import com.sundot.airport.common.core.domain.entity.SysRole;
15 16
 import com.sundot.airport.common.enums.RoleTypeEnum;
16 17
 import com.sundot.airport.system.service.ISysDeptService;
18
+import com.sundot.airport.system.service.ISysUserService;
17 19
 import org.slf4j.Logger;
18 20
 import org.slf4j.LoggerFactory;
19 21
 import com.sundot.airport.common.utils.DateUtils;
@@ -72,6 +74,8 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
72 74
 
73 75
     @Autowired
74 76
     private ISysDeptService sysDeptService;
77
+    @Autowired
78
+    private ISysUserService iSysUserService;
75 79
 
76 80
 
77 81
     /**
@@ -114,6 +118,7 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
114 118
         instanceMapper.insertApprovalInstance(instance);
115 119
 
116 120
         // 3. 获取第一个节点
121
+        boolean isAutoPass = false;// 是否自动审批通过
117 122
         ApprovalNodeDefinition firstNode = nodeDefinitionMapper.selectFirstNodeByWorkflowId(workflow.getId());
118 123
         if (firstNode != null) {
119 124
             instance.setCurrentNodeId(firstNode.getId());
@@ -124,7 +129,7 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
124 129
                 createTasksForNode(instance, firstNode, formData, null);
125 130
             } else {
126 131
                 // 开始节点,直接流转到下一个节点
127
-                moveToNextNode(instance, firstNode, submitterId, submitterName, "提交", formData, null);
132
+                isAutoPass = moveToNextNode(instance, firstNode, submitterId, submitterName, "提交", formData, null);
128 133
             }
129 134
         }
130 135
 
@@ -133,6 +138,14 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
133 138
 
134 139
         // 执行业务处理逻辑
135 140
         executeBusinessLogic(instance, "APPROVED");
141
+
142
+        // 自动审批通过
143
+        if (isAutoPass) {
144
+            firstNode.setSortOrder(firstNode.getSortOrder() + 1);
145
+            moveToNextNode(instance, firstNode, submitterId, submitterName, "自动审批通过", formData, null);
146
+            recordHistory(instance.getId(), null, null, null, "AUTO_APPROVE", submitterId, submitterName, "自动审批通过", formData);
147
+            executeBusinessLogic(instance, "APPROVED");
148
+        }
136 149
         return instance;
137 150
     }
138 151
 
@@ -735,8 +748,8 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
735 748
     /**
736 749
      * 流转到下一节点
737 750
      */
738
-    private void moveToNextNode(ApprovalInstance instance, ApprovalNodeDefinition currentNode,
739
-                                Long operatorId, String operatorName, String comment, Map<String, Object> formData, ApprovalTask currentTask) {
751
+    private boolean moveToNextNode(ApprovalInstance instance, ApprovalNodeDefinition currentNode,
752
+                                   Long operatorId, String operatorName, String comment, Map<String, Object> formData, ApprovalTask currentTask) {
740 753
         // 查找下一个节点
741 754
         ApprovalNodeDefinition nextNode = nodeDefinitionMapper.selectNextNodeByOrder(
742 755
                 instance.getWorkflowId(), currentNode.getSortOrder());
@@ -774,8 +787,17 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
774 787
                     if (ObjectUtil.isNotEmpty(currentTask) && ObjectUtil.isNotEmpty(currentTask.getAssigneeId())) {
775 788
                         submitterId = currentTask.getAssigneeId();
776 789
                     }
777
-                    // 违禁品审批流程:根据提交人角色分配给其主管
778
-                    createTasksForNodeWithKeZhang(instance, nextNode, formData, null, submitterId);
790
+                    SysUser sysUser = iSysUserService.selectUserById(submitterId);
791
+                    List<String> roleKeyList = sysUser.getRoles().stream().map(SysRole::getRoleKey).collect(Collectors.toList());
792
+                    boolean isAutoPass = roleKeyList.contains(RoleTypeEnum.kezhang.getCode());
793
+                    if (isAutoPass) {
794
+                        // 违禁品审批流程:根据提交人角色为主管则自动审批通过
795
+                        createTasksForNodeWithKeZhangAutoPass(instance, nextNode, formData, null, submitterId);
796
+                        return true;
797
+                    } else {
798
+                        // 违禁品审批流程:根据提交人角色分配给其主管
799
+                        createTasksForNodeWithKeZhang(instance, nextNode, formData, null, submitterId);
800
+                    }
779 801
                 } else if (isSeizureReportWorkflow(instance) && ("GROUP_LEADER".equals(nextNode.getApproverType()) || "SECTION_LEADER".equals(nextNode.getApproverType()))) {
780 802
                     Long submitterId = instance.getSubmitterId();
781 803
                     if ("SECTION_LEADER".equals(nextNode.getApproverType()) && ObjectUtil.isNotEmpty(currentTask) && ObjectUtil.isNotEmpty(currentTask.getAssigneeId())) {
@@ -805,6 +827,7 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
805 827
             // 没有下一节点,完成流程
806 828
             completeProcess(instance, operatorId, operatorName);
807 829
         }
830
+        return false;
808 831
     }
809 832
 
810 833
     /**
@@ -1844,4 +1867,49 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
1844 1867
             logger.error("创建经理1审批任务失败", e);
1845 1868
         }
1846 1869
     }
1870
+
1871
+    /**
1872
+     * 为违禁品审批节点创建任务(根据提交人角色为主管则自动审批通过)
1873
+     */
1874
+    private void createTasksForNodeWithKeZhangAutoPass(ApprovalInstance instance, ApprovalNodeDefinition node, Map<String, Object> formData, String remark, Long submitterId) {
1875
+        try {
1876
+            SysUser user = sysUserMapper.selectUserById(submitterId);
1877
+            if (user != null) {
1878
+                ApprovalTask task = new ApprovalTask();
1879
+                task.setTaskNo(taskMapper.generateTaskNo());
1880
+                task.setInstanceId(instance.getId());
1881
+                task.setNodeId(node.getId());
1882
+                task.setTaskName(node.getNodeName());
1883
+                task.setAssigneeId(user.getUserId());
1884
+                task.setAssigneeName(user.getNickName());
1885
+                task.setAssignTime(DateUtils.getNowDate());
1886
+                task.setStatus("APPROVED");
1887
+                task.setCompleteTime(DateUtils.getNowDate());
1888
+
1889
+                // 设置表单数据 - 如果formData为空,使用当前实例的formData
1890
+                try {
1891
+                    if (formData != null) {
1892
+                        task.setFormData(objectMapper.writeValueAsString(formData));
1893
+                    } else {
1894
+                        task.setFormData(instance.getFormData());
1895
+                    }
1896
+                } catch (Exception e) {
1897
+                    // 序列化失败时使用实例的formData
1898
+                    task.setFormData(instance.getFormData());
1899
+                }
1900
+
1901
+                // 设置超时时间
1902
+                if (node.getTimeoutHours() != null && node.getTimeoutHours() > 0) {
1903
+                    task.setTimeoutTime(DateUtils.addHours(DateUtils.getNowDate(), node.getTimeoutHours()));
1904
+                }
1905
+
1906
+                task.setCreateBy(SecurityUtils.getUsername());
1907
+                task.setCreateTime(DateUtils.getNowDate());
1908
+
1909
+                taskMapper.insertApprovalTask(task);
1910
+            }
1911
+        } catch (Exception e) {
1912
+            throw new RuntimeException("创建违禁品审批任务失败:" + e.getMessage(), e);
1913
+        }
1914
+    }
1847 1915
 }

+ 12 - 0
airport-system/src/main/java/com/sundot/airport/system/service/impl/SysUserServiceImpl.java

@@ -23,6 +23,7 @@ import com.sundot.airport.common.core.domain.ResetUserPostDto;
23 23
 import com.sundot.airport.common.core.domain.ResetUserRoleDto;
24 24
 import com.sundot.airport.common.core.domain.entity.SysDept;
25 25
 import com.sundot.airport.common.core.domain.entity.SysDictData;
26
+import com.sundot.airport.common.dto.SysUserConditionDto;
26 27
 import com.sundot.airport.common.dto.UserInfo;
27 28
 import com.sundot.airport.common.enums.DeptType;
28 29
 import com.sundot.airport.common.enums.RoleTypeEnum;
@@ -1174,4 +1175,15 @@ public class SysUserServiceImpl implements ISysUserService {
1174 1175
         }
1175 1176
         return 1;
1176 1177
     }
1178
+
1179
+    /**
1180
+     * 查询用户列表
1181
+     *
1182
+     * @param dto 查询用户列表参数
1183
+     * @return 用户信息集合
1184
+     */
1185
+    @Override
1186
+    public List<SysUser> selectUserListByDeptIdAndRoleKeyListAndUserName(SysUserConditionDto dto) {
1187
+        return userMapper.selectUserListByDeptIdAndRoleKeyListAndUserName(dto.getDeptId(), dto.getRoleKeyList(), dto.getNickName());
1188
+    }
1177 1189
 }

+ 16 - 0
airport-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -988,6 +988,22 @@
988 988
         </foreach>
989 989
     </select>
990 990
 
991
+    <select id="selectUserListByDeptIdAndRoleKeyListAndUserName" resultMap="SysUserResult">
992
+        <include refid="selectUserVo"/>
993
+        where (u.dept_id = #{deptId} OR u.dept_id IN (
994
+        SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors)))
995
+        and u.del_flag = '0'
996
+        <if test="list != null and list.size() > 0">
997
+            and r.role_key in
998
+            <foreach collection="list" item="item" open="(" separator="," close=")">
999
+                #{item}
1000
+            </foreach>
1001
+        </if>
1002
+        <if test="nickName != null and nickName != ''">
1003
+            and u.nick_name like concat('%', #{nickName}, '%')
1004
+        </if>
1005
+    </select>
1006
+
991 1007
     <select id="homePageUserInfo" resultType="com.sundot.airport.common.core.domain.LargeScreenHomePageUserInfoSqlDto">
992 1008
         select su.user_id      userId,
993 1009
                su.user_name    userName,