ソースを参照

首页-考勤统计

chenshudong 2 ヶ月 前
コミット
33d2c38861
共有32 個のファイルを変更した2200 個の追加16 個の削除を含む
  1. 12 4
      airport-admin/src/main/java/com/sundot/airport/web/controller/attendance/AttendancePostRecordController.java
  2. 1 1
      airport-admin/src/main/java/com/sundot/airport/web/controller/attendance/AttendanceTeamUserRecordController.java
  3. 14 0
      airport-admin/src/main/java/com/sundot/airport/web/controller/attendance/api/AttendanceController.java
  4. 554 0
      airport-admin/src/main/java/com/sundot/airport/web/controller/home/AttendanceIndexStatsController.java
  5. 70 0
      airport-admin/src/main/java/com/sundot/airport/web/controller/system/SysHomePageController.java
  6. 42 1
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendancePostRecordMapper.java
  7. 12 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceRecordMapper.java
  8. 1 1
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceTeamUserRecordMapper.java
  9. 41 2
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendancePostRecordService.java
  10. 10 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceRecordService.java
  11. 1 1
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceTeamUserRecordService.java
  12. 39 2
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendancePostRecordServiceImpl.java
  13. 13 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceRecordServiceImpl.java
  14. 2 2
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceTeamUserRecordServiceImpl.java
  15. 60 0
      airport-attendance/src/main/resources/mapper/attendance/AttendancePostRecordMapper.xml
  16. 19 0
      airport-attendance/src/main/resources/mapper/attendance/AttendanceRecordMapper.xml
  17. 5 2
      airport-attendance/src/main/resources/mapper/attendance/AttendanceTeamUserRecordMapper.xml
  18. 28 0
      airport-common/src/main/java/com/sundot/airport/common/dto/AttendanceStatsResult.java
  19. 43 0
      airport-common/src/main/java/com/sundot/airport/common/dto/BrigadeLeaderStats.java
  20. 14 0
      airport-common/src/main/java/com/sundot/airport/common/dto/ChannelOpenStats.java
  21. 93 0
      airport-common/src/main/java/com/sundot/airport/common/dto/DutyScheduleGenerateReq.java
  22. 47 0
      airport-common/src/main/java/com/sundot/airport/common/dto/SectionLeaderStats.java
  23. 29 0
      airport-common/src/main/java/com/sundot/airport/common/dto/SecurityCheckStats.java
  24. 43 0
      airport-common/src/main/java/com/sundot/airport/common/dto/StationLeaderStats.java
  25. 37 0
      airport-common/src/main/java/com/sundot/airport/common/dto/TeamLeaderStats.java
  26. 139 0
      airport-item/src/main/java/com/sundot/airport/item/controller/SimpleDutyScheduleController.java
  27. 185 0
      airport-item/src/main/java/com/sundot/airport/item/domain/SimpleDutySchedule.java
  28. 85 0
      airport-item/src/main/java/com/sundot/airport/item/mapper/SimpleDutyScheduleMapper.java
  29. 95 0
      airport-item/src/main/java/com/sundot/airport/item/service/ISimpleDutyScheduleService.java
  30. 154 0
      airport-item/src/main/java/com/sundot/airport/item/service/impl/SimpleDutyScheduleServiceImpl.java
  31. 158 0
      airport-item/src/main/java/com/sundot/airport/item/utils/UnifiedDutyScheduler.java
  32. 154 0
      airport-item/src/main/resources/mapper/item/SimpleDutyScheduleMapper.xml

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

@@ -42,7 +42,6 @@ import org.springframework.web.bind.annotation.*;
42 42
 import org.springframework.web.multipart.MultipartFile;
43 43
 
44 44
 import javax.servlet.http.HttpServletResponse;
45
-import java.text.SimpleDateFormat;
46 45
 import java.time.LocalDateTime;
47 46
 import java.time.ZoneId;
48 47
 import java.util.*;
@@ -142,7 +141,7 @@ public class AttendancePostRecordController extends BaseController {
142 141
             queryDate = DateUtils.getNowDate();
143 142
         }
144 143
         // 先获取当前用户今天的记录,取出班次信息
145
-        List<AttendancePostRecord> userRecords = attendancePostRecordService.selectTodayRecordsByUserId(createBy, userId, queryDate);
144
+        List<AttendancePostRecord> userRecords = attendancePostRecordService.selectTodayRecordsByUserId(createBy, userId, queryDate, null);
146 145
 
147 146
         if (userRecords.isEmpty()) {
148 147
             return AjaxResult.success(new ArrayList<>());
@@ -1127,7 +1126,7 @@ public class AttendancePostRecordController extends BaseController {
1127 1126
             if (shiftDate == null) {
1128 1127
                 shiftDate = DateUtils.getNowDate();
1129 1128
             }
1130
-            List<AttendancePostRecord> userRecords = attendancePostRecordService.selectTodayRecordsByUserId(null, userId, shiftDate);
1129
+            List<AttendancePostRecord> userRecords = attendancePostRecordService.selectTodayRecordsByUserId(null, userId, shiftDate, null);
1131 1130
 
1132 1131
             // 检查是否有已锁定且在当前班次周期内的记录
1133 1132
 //            return userRecords.stream()
@@ -1162,5 +1161,14 @@ public class AttendancePostRecordController extends BaseController {
1162 1161
         }
1163 1162
     }
1164 1163
 
1165
-
1164
+    /**
1165
+     * 根据角色标识查询今日上岗用户列表
1166
+     */
1167
+    @PostMapping("/selectUserListByRoleKey")
1168
+    public AjaxResult selectUserListByRoleKey(@RequestBody List<String> roleTypeList) {
1169
+        // 计算当前时间对应的班次日期
1170
+        Date queryDate = DateUtils.getNowDate();
1171
+        List<SysUser> result = attendancePostRecordService.selectUserListByRoleKey(null, roleTypeList);
1172
+        return AjaxResult.success(result);
1173
+    }
1166 1174
 }

+ 1 - 1
airport-admin/src/main/java/com/sundot/airport/web/controller/attendance/AttendanceTeamUserRecordController.java

@@ -100,7 +100,7 @@ public class AttendanceTeamUserRecordController extends BaseController {
100 100
     @PreAuthorize("@ss.hasPermi('attendance:record:query')")
101 101
     @GetMapping(value = "/{userId}")
102 102
     public AjaxResult getInfo(@PathVariable("userId") Long userId, @PathVariable("attendanceDate") Date attendanceDate) {
103
-        return success(attendanceTeamUserRecordService.selectAttendanceTeamUserRecordByUserId(userId, attendanceDate));
103
+        return success(attendanceTeamUserRecordService.selectAttendanceTeamUserRecordByUserId(userId, attendanceDate, null));
104 104
     }
105 105
 
106 106
     /**

+ 14 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/attendance/api/AttendanceController.java

@@ -8,10 +8,12 @@ import com.sundot.airport.attendance.service.IAttendanceRecordService;
8 8
 import com.sundot.airport.common.annotation.Log;
9 9
 import com.sundot.airport.common.core.controller.BaseController;
10 10
 import com.sundot.airport.common.core.domain.AjaxResult;
11
+import com.sundot.airport.common.core.domain.entity.SysUser;
11 12
 import com.sundot.airport.common.dto.AttendanceRecordDTO;
12 13
 import com.sundot.airport.common.dto.AttendanceRecordReq;
13 14
 import com.sundot.airport.common.dto.UserInfo;
14 15
 import com.sundot.airport.common.enums.BusinessType;
16
+import com.sundot.airport.common.utils.DateUtils;
15 17
 import com.sundot.airport.system.service.ISysConfigService;
16 18
 import com.sundot.airport.web.core.cache.UserCache;
17 19
 import org.springframework.beans.factory.annotation.Autowired;
@@ -23,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
23 25
 import org.springframework.web.bind.annotation.RestController;
24 26
 
25 27
 import java.util.Date;
28
+import java.util.List;
26 29
 
27 30
 /**
28 31
  * 考勤管理 h5 api
@@ -70,4 +73,15 @@ public class AttendanceController extends BaseController {
70 73
         return AjaxResult.success("操作成功", attendanceRecordService.getRecordType());
71 74
     }
72 75
 
76
+    /**
77
+     * 根据角色标识查询今日打卡用户列表
78
+     */
79
+    @PostMapping("/selectUserListByRoleKey")
80
+    public AjaxResult selectUserListByRoleKey(@RequestBody List<String> roleTypeList) {
81
+        // 计算当前时间对应的班次日期
82
+        Date queryDate = DateUtils.getNowDate();
83
+        List<SysUser> result = attendanceRecordService.selectUserListByRoleKey(queryDate, roleTypeList);
84
+        return AjaxResult.success(result);
85
+    }
86
+
73 87
 }

+ 554 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/home/AttendanceIndexStatsController.java

@@ -0,0 +1,554 @@
1
+package com.sundot.airport.web.controller.home;
2
+
3
+import cn.hutool.core.collection.CollUtil;
4
+import cn.hutool.core.date.DateUtil;
5
+import cn.hutool.core.util.ObjectUtil;
6
+import cn.hutool.core.util.StrUtil;
7
+import com.sundot.airport.attendance.domain.AttendanceCheckRecord;
8
+import com.sundot.airport.attendance.domain.AttendancePostRecord;
9
+import com.sundot.airport.attendance.domain.AttendanceTeamUserRecord;
10
+import com.sundot.airport.attendance.enums.CheckInTypeEnum;
11
+import com.sundot.airport.attendance.service.IAttendanceCheckRecordService;
12
+import com.sundot.airport.attendance.service.IAttendancePostRecordService;
13
+import com.sundot.airport.attendance.service.IAttendanceTeamUserRecordService;
14
+import com.sundot.airport.check.service.ICheckLargeScreenService;
15
+import com.sundot.airport.common.core.controller.BaseController;
16
+import com.sundot.airport.common.core.domain.AjaxResult;
17
+import com.sundot.airport.common.core.domain.BaseLargeScreenQueryParamDto;
18
+import com.sundot.airport.common.core.domain.entity.SysDept;
19
+import com.sundot.airport.common.core.domain.entity.SysUser;
20
+import com.sundot.airport.common.core.domain.model.LoginUser;
21
+import com.sundot.airport.common.dto.AttendanceStatsResult;
22
+import com.sundot.airport.common.dto.BrigadeLeaderStats;
23
+import com.sundot.airport.common.dto.ChannelOpenStats;
24
+import com.sundot.airport.common.dto.SectionLeaderStats;
25
+import com.sundot.airport.common.dto.SecurityCheckStats;
26
+import com.sundot.airport.common.dto.StationLeaderStats;
27
+import com.sundot.airport.common.dto.TeamLeaderStats;
28
+import com.sundot.airport.common.enums.DeptType;
29
+import com.sundot.airport.common.enums.RoleTypeEnum;
30
+import com.sundot.airport.common.enums.SourceTypeEnum;
31
+import com.sundot.airport.common.exception.ServiceException;
32
+import com.sundot.airport.common.utils.SecurityUtils;
33
+import com.sundot.airport.common.utils.StringUtils;
34
+import com.sundot.airport.item.domain.ItemSeizureRecord;
35
+import com.sundot.airport.item.domain.ItemSeizureRecordDTO;
36
+import com.sundot.airport.item.domain.SimpleDutySchedule;
37
+import com.sundot.airport.item.mapper.SimpleDutyScheduleMapper;
38
+import com.sundot.airport.item.service.IItemSeizureRecordService;
39
+import com.sundot.airport.system.domain.vo.PositionInfoVO;
40
+import com.sundot.airport.system.service.IBasePositionService;
41
+import com.sundot.airport.system.service.ISysDeptService;
42
+import com.sundot.airport.system.service.ISysUserService;
43
+import org.springframework.beans.factory.annotation.Autowired;
44
+import org.springframework.web.bind.annotation.GetMapping;
45
+import org.springframework.web.bind.annotation.RequestHeader;
46
+import org.springframework.web.bind.annotation.RequestMapping;
47
+import org.springframework.web.bind.annotation.RestController;
48
+
49
+import java.util.ArrayList;
50
+import java.util.Arrays;
51
+import java.util.Calendar;
52
+import java.util.Collections;
53
+import java.util.Date;
54
+import java.util.List;
55
+import java.util.Objects;
56
+import java.util.stream.Collectors;
57
+
58
+/**
59
+ * 考勤统计控制器
60
+ * 实现不同角色的考勤统计数据查询
61
+ *
62
+ * @author wangxx
63
+ */
64
+@RestController
65
+@RequestMapping("/attendance/stats")
66
+public class AttendanceIndexStatsController extends BaseController {
67
+
68
+    @Autowired
69
+    private IAttendancePostRecordService attendancePostRecordService;
70
+
71
+    @Autowired
72
+    private IAttendanceTeamUserRecordService attendanceTeamUserRecordService;
73
+
74
+
75
+    @Autowired
76
+    private ISysUserService sysUserService;
77
+
78
+    @Autowired
79
+    private IItemSeizureRecordService itemSeizureRecordService;
80
+
81
+    @Autowired
82
+    private IAttendanceCheckRecordService attendanceCheckRecordService;
83
+
84
+    @Autowired
85
+    private IBasePositionService basePositionService;
86
+
87
+    @Autowired
88
+    private ICheckLargeScreenService checkLargeScreenService;
89
+
90
+    @Autowired
91
+    private SimpleDutyScheduleMapper simpleDutyScheduleMapper;
92
+
93
+    @Autowired
94
+    private ISysDeptService sysDeptService;
95
+
96
+    /**
97
+     * 获取考勤统计数据
98
+     * 根据当前登录用户的角色返回不同的考勤统计数据
99
+     */
100
+    @GetMapping("/getAttendanceStats")
101
+    public AjaxResult getAttendanceStats(@RequestHeader(value = "X-Request-Source", defaultValue = "mobile") String source) {
102
+        LoginUser loginUser = getLoginUser();
103
+        SysUser currentUser = loginUser.getUser();
104
+        Long userId = currentUser.getUserId();
105
+        Long deptId = currentUser.getDeptId();
106
+
107
+        // 获取用户角色列表
108
+        List<String> roleKeys = currentUser.getRoles().stream()
109
+                .map(role -> role.getRoleKey())
110
+                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
111
+
112
+        AttendanceStatsResult result = new AttendanceStatsResult();
113
+
114
+        // 根据角色返回不同的统计数据
115
+        if (StrUtil.equals(SourceTypeEnum.mobile.getCode(), source)) {
116
+            if (roleKeys.contains(RoleTypeEnum.SecurityCheck.getCode())) {
117
+                // 安检员角色:查出勤班组、出勤通道、出勤时间
118
+                result.setSecurityCheckStats(getSecurityCheckStats(userId, deptId));
119
+            } else if (roleKeys.contains(RoleTypeEnum.banzuzhang.getCode())) {
120
+                // 班组长角色:查出勤班组、出勤通道、出勤时间
121
+                result.setTeamLeaderStats(getTeamLeaderStats(userId, deptId));
122
+            } else if (roleKeys.contains(RoleTypeEnum.kezhang.getCode())) {
123
+                // 科长角色:查在岗班组、在岗人员、出勤时间、今日查获上报
124
+                result.setSectionLeaderStats(getSectionLeaderStats(userId, deptId));
125
+            } else if (roleKeys.contains(RoleTypeEnum.jingli.getCode()) || roleKeys.contains(RoleTypeEnum.xingzheng.getCode())) {
126
+                // 经理和大队行政角色:查在岗班组、在岗人员、通道开放、今日查获上报
127
+                result.setBrigadeLeaderStats(getBrigadeLeaderStats(userId, deptId));
128
+            } else if (roleKeys.contains(RoleTypeEnum.test.getCode()) || roleKeys.contains(RoleTypeEnum.zhijianke.getCode()) || roleKeys.contains(RoleTypeEnum.admin.getCode())) {
129
+                // 站长和质检科角色:查在岗班组、在岗人员、通道开放、今日查获上报
130
+                result.setStationLeaderStats(getStationLeaderStats(userId, deptId));
131
+            } else {
132
+                throw new ServiceException("当前用户角色无法获取考勤统计数据");
133
+            }
134
+        } else {
135
+            List<SysDept> sysDeptList = sysDeptService.selectAllDept(SecurityUtils.getLoginUser().getDeptId());
136
+            Collections.reverse(sysDeptList);
137
+            SysDept stationDept = sysDeptList.stream().filter(x -> StrUtil.equals(DeptType.STATION.getCode(), x.getDeptType())).findFirst().orElse(null);
138
+            if (ObjectUtil.isNull(stationDept)) {
139
+                throw new ServiceException("未查询到站级部门信息");
140
+            }
141
+            // 站长和质检科角色:查在岗班组、在岗人员、通道开放、今日查获上报
142
+            result.setStationLeaderStats(getStationLeaderStats(userId, stationDept.getDeptId()));
143
+        }
144
+
145
+        return AjaxResult.success(result);
146
+    }
147
+
148
+    /**
149
+     * 安检员考勤统计数据
150
+     * 出勤班组:查询哪个班组长在勤务板块将该安检员维护到自己班组,则显示班组长所在组织架构的班组名称
151
+     * 出勤通道:进入该界面的时候班组长将该安检员带上哪个通道(仅显示最后一个层级,例如A02通道)
152
+     * 出勤时间:安检员打卡时间,若未打卡需要提示未打卡
153
+     */
154
+    private SecurityCheckStats getSecurityCheckStats(Long userId, Long deptId) {
155
+        SecurityCheckStats stats = new SecurityCheckStats();
156
+
157
+        // 获取当前日期
158
+        Date today = DateUtil.beginOfDay(new Date());
159
+
160
+        // 获取该安检员今日的班组维护记录
161
+        AttendanceTeamUserRecord teamUserRecord = attendanceTeamUserRecordService.selectAttendanceTeamUserRecordByUserId(userId, null, "1");
162
+
163
+        if (teamUserRecord != null) {
164
+            stats.setAttendanceTeamName(teamUserRecord.getAttendanceTeamName());
165
+        } else {
166
+            stats.setAttendanceTeamName("未分配班组");
167
+        }
168
+
169
+        // 获取安检员的打卡记录(上岗时间)
170
+        List<AttendancePostRecord> checkInRecords = attendancePostRecordService.selectTodayRecordsByUserId(null, userId, null, "1");
171
+        if (CollUtil.isNotEmpty(checkInRecords)) {
172
+            // 获取最后一条上岗记录
173
+            AttendancePostRecord lastRecord = checkInRecords.get(checkInRecords.size() - 1);
174
+            // 获取通道信息(从通道名称中提取最后一个层级)
175
+            if (StrUtil.isNotBlank(lastRecord.getChannelName())) {
176
+                String channelName = extractChannelName(lastRecord.getChannelName());
177
+                stats.setAttendanceChannel(channelName);
178
+            } else {
179
+                stats.setAttendanceChannel("未分配通道");
180
+            }
181
+        } else {
182
+            stats.setAttendanceChannel("未分配通道");
183
+        }
184
+
185
+        // 获取安检员的打卡记录
186
+        AttendanceCheckRecord attendanceCheckRecord = new AttendanceCheckRecord();
187
+        attendanceCheckRecord.setUserId(userId);
188
+        List<AttendanceCheckRecord> attendanceCheckRecords = attendanceCheckRecordService.selectAttendanceCheckRecordList(attendanceCheckRecord);
189
+        if (CollUtil.isNotEmpty(attendanceCheckRecords)) {
190
+            AttendanceCheckRecord lastRecord = attendanceCheckRecords.get(attendanceCheckRecords.size() - 1);
191
+            if (StrUtil.equals(CheckInTypeEnum.CLOCK_IN.getValue(), lastRecord.getCheckInType())) {
192
+                stats.setAttendanceTime(lastRecord.getCheckInTime());
193
+                stats.setAttendanceTimeTips("已打卡");
194
+            } else {
195
+                stats.setAttendanceTime(null);
196
+                stats.setAttendanceTimeTips("未打卡");
197
+            }
198
+        } else {
199
+            stats.setAttendanceTime(null);
200
+            stats.setAttendanceTimeTips("未打卡");
201
+        }
202
+        return stats;
203
+    }
204
+
205
+    /**
206
+     * 班组长考勤统计数据
207
+     * 出勤班组:查询该班组长在勤务板块将该安检员维护到自己班组,则显示自己所在组织架构的班组名称
208
+     * 出勤通道:进入该界面的时候班组长将该安检员带上哪个通道(仅显示最后一个层级,例如A02通道)
209
+     * 出勤时间:班组长打卡时间,若未打卡需要提示未打卡
210
+     */
211
+    private TeamLeaderStats getTeamLeaderStats(Long userId, Long deptId) {
212
+        TeamLeaderStats stats = new TeamLeaderStats();
213
+        Date today = DateUtil.beginOfDay(new Date());
214
+
215
+        // 获取班组长所在班组信息
216
+        AttendanceTeamUserRecord teamUserRecord = attendanceTeamUserRecordService.selectAttendanceTeamUserRecordByUserId(userId, null, "1");
217
+
218
+        if (teamUserRecord != null) {
219
+            stats.setAttendanceTeamName(teamUserRecord.getAttendanceTeamName());
220
+        } else {
221
+            stats.setAttendanceTeamName("未分配班组");
222
+        }
223
+        // 获取通道信息(从通道名称中提取最后一个层级)
224
+        List<AttendancePostRecord> checkInRecords = attendancePostRecordService.selectTodayRecordsByUserId(null, userId, null, "1");
225
+        if (CollUtil.isNotEmpty(checkInRecords)) {
226
+            // 获取最后一条上岗记录
227
+            AttendancePostRecord lastRecord = checkInRecords.get(checkInRecords.size() - 1);
228
+            if (StrUtil.isNotBlank(lastRecord.getChannelName())) {
229
+                String channelName = extractChannelName(lastRecord.getChannelName());
230
+                stats.setAttendanceChannel(channelName);
231
+            } else {
232
+                stats.setAttendanceChannel("未分配通道");
233
+            }
234
+        } else {
235
+            stats.setAttendanceChannel("未分配通道");
236
+        }
237
+
238
+        // 获取班组长的打卡记录
239
+        AttendanceCheckRecord attendanceCheckRecord = new AttendanceCheckRecord();
240
+        attendanceCheckRecord.setUserId(userId);
241
+        List<AttendanceCheckRecord> attendanceCheckRecords = attendanceCheckRecordService.selectAttendanceCheckRecordList(attendanceCheckRecord);
242
+        if (CollUtil.isNotEmpty(attendanceCheckRecords)) {
243
+            AttendanceCheckRecord lastRecord = attendanceCheckRecords.get(attendanceCheckRecords.size() - 1);
244
+            if (StrUtil.equals(CheckInTypeEnum.CLOCK_IN.getValue(), lastRecord.getCheckInType())) {
245
+                stats.setAttendanceTime(lastRecord.getCheckInTime());
246
+                stats.setAttendanceTimeTips("已打卡");
247
+            } else {
248
+                stats.setAttendanceTime(null);
249
+                stats.setAttendanceTimeTips("未打卡");
250
+            }
251
+        } else {
252
+            stats.setAttendanceTime(null);
253
+            stats.setAttendanceTimeTips("未打卡");
254
+        }
255
+
256
+        return stats;
257
+    }
258
+
259
+    /**
260
+     * 科长考勤统计数据
261
+     * 在岗班组:进入界面时有多少班组长打了上岗卡/多少班组长维护人员区域
262
+     * 在岗人员:进入该界面的时候多少人被带上通道/多少人的信息被班组长进行了维护
263
+     * 出勤时间:科长打卡时间,若未打卡需要提示未打卡
264
+     * 今日查获上报:今日安检员和班组长进行上报的数量,不需要完成审核
265
+     */
266
+    private SectionLeaderStats getSectionLeaderStats(Long userId, Long deptId) {
267
+        SectionLeaderStats stats = new SectionLeaderStats();
268
+
269
+        // 获取当天开始和结束时间
270
+        Date[] dayRange = getDayStartAndEnd();
271
+        Date startOfDay = dayRange[0];
272
+        Date endOfDay = dayRange[1];
273
+
274
+        AttendanceCheckRecord attendanceCheckRecord = new AttendanceCheckRecord();
275
+        attendanceCheckRecord.setUserId(userId);
276
+        List<AttendanceCheckRecord> attendanceCheckRecords = attendanceCheckRecordService.selectAttendanceCheckRecordList(attendanceCheckRecord);
277
+        if (CollUtil.isNotEmpty(attendanceCheckRecords)) {
278
+            AttendanceCheckRecord lastRecord = attendanceCheckRecords.get(attendanceCheckRecords.size() - 1);
279
+            if (StrUtil.equals(CheckInTypeEnum.CLOCK_IN.getValue(), lastRecord.getCheckInType())) {
280
+                stats.setAttendanceTime(lastRecord.getCheckInTime());
281
+                stats.setAttendanceTimeTips("已打卡");
282
+            } else {
283
+                stats.setAttendanceTime(null);
284
+                stats.setAttendanceTimeTips("未打卡");
285
+            }
286
+        } else {
287
+            stats.setAttendanceTime(null);
288
+            stats.setAttendanceTimeTips("未打卡");
289
+        }
290
+        // 获取在岗班组数量(打了上岗卡的班组长数量/维护了人员区域的班组长数量)
291
+        String onDutyTeamCount = getOnDutyTeamCount(deptId, startOfDay, endOfDay);
292
+        stats.setOnDutyTeamCount(onDutyTeamCount);
293
+
294
+        // 获取在岗人员数量(被带上通道的人员数量或被班组长维护的人员数量)
295
+        String onDutyPersonnelCount = getOnDutyPersonnelCount(deptId, startOfDay, endOfDay);
296
+        stats.setOnDutyPersonnelCount(onDutyPersonnelCount);
297
+
298
+        // 获取今日查获上报数量(安检员和班组长上报的数量,不需要完成审核)
299
+        int todaySeizureReportCount = getTodaySeizureReportCount(deptId, startOfDay, endOfDay, "3");
300
+        stats.setTodaySeizureReportCount(todaySeizureReportCount);
301
+
302
+        // 获取今日巡检问题数
303
+        Integer todayCheckCorrectionCount = getTodayCheckCorrectionCount();
304
+        stats.setTodayCheckCorrectionCount(todayCheckCorrectionCount);
305
+
306
+        return stats;
307
+    }
308
+
309
+    /**
310
+     * 站长/质检科考勤统计数据
311
+     * 在岗班组:进入界面时有多少班组长打了上岗卡/多少班组长维护人员区域
312
+     * 在岗人员:进入该界面的时候多少人被带上通道/多少人的信息被班组长进行了维护
313
+     * 通道开放:开放通道/总通道
314
+     * 今日查获上报:今日安检员和班组长进行上报的数量,不需要完成审核
315
+     */
316
+    private StationLeaderStats getStationLeaderStats(Long userId, Long deptId) {
317
+        StationLeaderStats stats = new StationLeaderStats();
318
+
319
+        // 获取当天开始和结束时间
320
+        Date[] dayRange = getDayStartAndEnd();
321
+        Date startOfDay = dayRange[0];
322
+        Date endOfDay = dayRange[1];
323
+
324
+        // 获取在岗班组数量(打了上岗卡的班组长数量/维护了人员区域的班组长数量)
325
+        String onDutyTeamCount = getOnDutyTeamCount(deptId, startOfDay, endOfDay);
326
+        stats.setOnDutyTeamCount(onDutyTeamCount);
327
+
328
+        // 获取在岗人员数量(被带上通道的人员数量/被班组长维护的人员数量)
329
+        String onDutyPersonnelCount = getOnDutyPersonnelCount(deptId, startOfDay, endOfDay);
330
+        stats.setOnDutyPersonnelCount(onDutyPersonnelCount);
331
+
332
+        // 获取通道开放情况:开放通道/总通道
333
+        ChannelOpenStats channelStats = getChannelOpenStats(startOfDay, endOfDay);
334
+        stats.setChannelOpenStats(channelStats);
335
+
336
+        // 获取今日查获上报数量(安检员和班组长上报的数量,不需要完成审核)
337
+        int todaySeizureReportCount = getTodaySeizureReportCount(deptId, startOfDay, endOfDay, "1");
338
+        stats.setTodaySeizureReportCount(todaySeizureReportCount);
339
+
340
+        // 获取今日巡检问题数
341
+        Integer todayCheckCorrectionCount = getTodayCheckCorrectionCount();
342
+        stats.setTodayCheckCorrectionCount(todayCheckCorrectionCount);
343
+
344
+        SimpleDutySchedule simpleDutySchedule = simpleDutyScheduleMapper.selectSimpleDutyScheduleByDate(new Date());
345
+        if (Objects.nonNull(simpleDutySchedule)) {
346
+            stats.setDutyDeptName(simpleDutySchedule.getDepartmentName());
347
+        }
348
+
349
+        return stats;
350
+    }
351
+
352
+    /**
353
+     * 获取当天开始和结束时间
354
+     */
355
+    private Date[] getDayStartAndEnd() {
356
+        Calendar calendar = Calendar.getInstance();
357
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
358
+        calendar.set(Calendar.MINUTE, 0);
359
+        calendar.set(Calendar.SECOND, 0);
360
+        calendar.set(Calendar.MILLISECOND, 0);
361
+        Date startOfDay = calendar.getTime();
362
+
363
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
364
+        calendar.set(Calendar.MINUTE, 59);
365
+        calendar.set(Calendar.SECOND, 59);
366
+        calendar.set(Calendar.MILLISECOND, 999);
367
+        Date endOfDay = calendar.getTime();
368
+
369
+        return new Date[]{startOfDay, endOfDay};
370
+    }
371
+
372
+    /**
373
+     * 上岗卡的班组长数量/维护了人员区域的班组长数量
374
+     */
375
+    private String getOnDutyTeamCount(Long deptId, Date startOfDay, Date endOfDay) {
376
+        // 查询该部门下的班组长上岗记录(在指定时间范围内的记录)
377
+        // 先获取该部门下的所有班组长
378
+        List<SysUser> teamLeaderUsers = sysUserService.selectUserListByRoleKeyAndDeptId(Collections.singletonList(RoleTypeEnum.banzuzhang.getCode()), deptId);
379
+
380
+        if (CollUtil.isEmpty(teamLeaderUsers)) {
381
+            return "0/0";
382
+        }
383
+
384
+        int onDutyCount = 0;
385
+        // 统计在指定时间段内有上岗记录的班组长数量
386
+        for (SysUser teamLeader : teamLeaderUsers) {
387
+            List<AttendancePostRecord> attendancePostRecords = attendancePostRecordService.selectTeamOnList(new Date(), teamLeader.getDeptId());
388
+            if (CollUtil.isNotEmpty(attendancePostRecords)) {
389
+                onDutyCount++;
390
+            }
391
+        }
392
+
393
+        List<Long> teamList = teamLeaderUsers.stream().map(SysUser::getDeptId).distinct().collect(Collectors.toList());
394
+        // 统计该部门下维护了人员的班组数量
395
+        AttendanceTeamUserRecord queryRecord = new AttendanceTeamUserRecord();
396
+        queryRecord.setCheckInType("1");
397
+        List<AttendanceTeamUserRecord> teamUserRecords = attendanceTeamUserRecordService.selectAttendanceTeamUserRecordList(queryRecord, true);
398
+        List<Long> distinctTeamIds = teamUserRecords.stream().filter(team -> teamList.contains(team.getAttendanceTeamId())).map(AttendanceTeamUserRecord::getAttendanceTeamId).distinct().collect(Collectors.toList());
399
+
400
+
401
+        //打了上岗卡的班组长数量/维护了人员区域的班组长数量
402
+        return onDutyCount + "/" + distinctTeamIds.size();
403
+    }
404
+
405
+    /**
406
+     * 获取在岗人员数量(在岗人/被维护的人)
407
+     */
408
+    private String getOnDutyPersonnelCount(Long deptId, Date startOfDay, Date endOfDay) {
409
+        // 查询该部门下的人员上岗记录(在指定时间范围内的记录)
410
+        // 获取该部门下的所有安检员和班组长
411
+        List<SysUser> personnelUsers = sysUserService.selectUserListByRoleKeyAndDeptId(
412
+                Arrays.asList(RoleTypeEnum.banzuzhang.getCode(), RoleTypeEnum.SecurityCheck.getCode()),
413
+                deptId
414
+        );
415
+
416
+        if (CollUtil.isEmpty(personnelUsers)) {
417
+            return "0/0";
418
+        }
419
+
420
+        List<Long> personnelList = personnelUsers.stream().filter(item -> Objects.nonNull(item.getUserId())).map(item -> item.getUserId()).collect(Collectors.toList());
421
+
422
+
423
+        int onDutyCount = 0;
424
+        // 统计在指定时间段内有上岗记录的人员数量
425
+        for (Long personnel : personnelList) {
426
+            List<AttendancePostRecord> records = attendancePostRecordService.selectTodayRecordsByUserId(null, personnel, null, "1");
427
+            if (CollUtil.isNotEmpty(records)) {
428
+                onDutyCount++;
429
+            }
430
+        }
431
+
432
+        // 统计该部门下被维护到班组的人员数量
433
+        AttendanceTeamUserRecord queryRecord = new AttendanceTeamUserRecord();
434
+        queryRecord.setCheckInType("1");
435
+        List<AttendanceTeamUserRecord> teamUserRecords = attendanceTeamUserRecordService.selectAttendanceTeamUserRecordList(queryRecord, true);
436
+        List<Long> teamUserIds = teamUserRecords.stream().filter(team -> personnelList.contains(team.getUserId())).map(team -> team.getUserId()).collect(Collectors.toList());
437
+
438
+        // 返回两个统计中较大的值(被带上通道的人员数量或被班组长维护的人员数量)
439
+        return onDutyCount + "/" + teamUserIds.size();
440
+    }
441
+
442
+    /**
443
+     * 获取今日查获上报数量
444
+     */
445
+    private int getTodaySeizureReportCount(Long deptId, Date startOfDay, Date endOfDay, String type) {
446
+        // 查询今日的查获上报记录数量(安检员和班组长上报的数量,不需要完成审核)
447
+        ItemSeizureRecordDTO record = new ItemSeizureRecordDTO();
448
+        switch (type) {
449
+            case "1":
450
+                record.setInspectStationId(deptId);
451
+                break;
452
+            case "2":
453
+                record.setInspectBrigadeId(deptId);
454
+                break;
455
+            case "3":
456
+                record.setInspectDepartmentId(deptId);
457
+                break;
458
+            default:
459
+                break;
460
+        }
461
+        record.setBeginTime(startOfDay);
462
+        record.setEndTime(endOfDay);
463
+        // 不限制process_status,即包含所有状态的记录(不需要完成审核)
464
+        List<ItemSeizureRecord> seizureRecords = itemSeizureRecordService.selectItemSeizureRecordList(record);
465
+        return seizureRecords.size();
466
+    }
467
+
468
+    /**
469
+     * 获取通道开放统计
470
+     */
471
+    private ChannelOpenStats getChannelOpenStats(Date startOfDay, Date endOfDay) {
472
+        ChannelOpenStats stats = new ChannelOpenStats();
473
+
474
+        // 查询所有通道
475
+        List<PositionInfoVO> positionInfoVOS = basePositionService.selectChannelsUnderArea(new ArrayList<>());
476
+        // 获取所有通道的开放状态
477
+        AttendancePostRecord queryPostRecord = new AttendancePostRecord();
478
+        queryPostRecord.setCheckOutTimeType(1);
479
+        List<AttendancePostRecord> postRecords = attendancePostRecordService.selectAttendancePostRecordList(queryPostRecord);
480
+        List<String> collect = postRecords.stream().filter(item -> StringUtils.isNotEmpty(item.getChannelName())).map(item -> extractChannelName(item.getChannelName())).distinct().collect(Collectors.toList());
481
+
482
+        stats.setOpenChannels(collect.size());
483
+        stats.setTotalChannels(positionInfoVOS.size());
484
+        return stats;
485
+    }
486
+
487
+    /**
488
+     * 从通道名称中提取最后一个层级(例如从"安检A区/A02通道"中提取"A02通道")
489
+     */
490
+    private String extractChannelName(String fullChannelName) {
491
+        if (StrUtil.isBlank(fullChannelName)) {
492
+            return "";
493
+        }
494
+
495
+        // 按斜杠分割,取最后一部分
496
+        String[] parts = fullChannelName.split("/");
497
+        if (parts.length > 0) {
498
+            return parts[parts.length - 1].trim();
499
+        }
500
+
501
+        return fullChannelName.trim();
502
+    }
503
+
504
+    /**
505
+     * 获取今日巡检问题数
506
+     */
507
+    private Integer getTodayCheckCorrectionCount() {
508
+        BaseLargeScreenQueryParamDto queryParam = new BaseLargeScreenQueryParamDto();
509
+        return checkLargeScreenService.checkCorrectionCount(queryParam);
510
+    }
511
+
512
+    /**
513
+     * 经理和大队行政考勤统计数据
514
+     * 在岗班组:进入界面时有多少班组长打了上岗卡/多少班组长维护人员区域
515
+     * 在岗人员:进入该界面的时候多少人被带上通道/多少人的信息被班组长进行了维护
516
+     * 通道开放:开放通道/总通道
517
+     * 今日查获上报:今日安检员和班组长进行上报的数量,不需要完成审核
518
+     */
519
+    private BrigadeLeaderStats getBrigadeLeaderStats(Long userId, Long deptId) {
520
+        BrigadeLeaderStats stats = new BrigadeLeaderStats();
521
+
522
+        // 获取当天开始和结束时间
523
+        Date[] dayRange = getDayStartAndEnd();
524
+        Date startOfDay = dayRange[0];
525
+        Date endOfDay = dayRange[1];
526
+
527
+        // 获取在岗班组数量(打了上岗卡的班组长数量/维护了人员区域的班组长数量)
528
+        String onDutyTeamCount = getOnDutyTeamCount(deptId, startOfDay, endOfDay);
529
+        stats.setOnDutyTeamCount(onDutyTeamCount);
530
+
531
+        // 获取在岗人员数量(被带上通道的人员数量/被班组长维护的人员数量)
532
+        String onDutyPersonnelCount = getOnDutyPersonnelCount(deptId, startOfDay, endOfDay);
533
+        stats.setOnDutyPersonnelCount(onDutyPersonnelCount);
534
+
535
+        // 获取通道开放情况:开放通道/总通道
536
+        ChannelOpenStats channelStats = getChannelOpenStats(startOfDay, endOfDay);
537
+        stats.setChannelOpenStats(channelStats);
538
+
539
+        // 获取今日查获上报数量(安检员和班组长上报的数量,不需要完成审核)
540
+        int todaySeizureReportCount = getTodaySeizureReportCount(deptId, startOfDay, endOfDay, "2");
541
+        stats.setTodaySeizureReportCount(todaySeizureReportCount);
542
+
543
+        // 获取今日巡检问题数
544
+        Integer todayCheckCorrectionCount = getTodayCheckCorrectionCount();
545
+        stats.setTodayCheckCorrectionCount(todayCheckCorrectionCount);
546
+
547
+        SimpleDutySchedule simpleDutySchedule = simpleDutyScheduleMapper.selectSimpleDutyScheduleByDate(new Date());
548
+        if (Objects.nonNull(simpleDutySchedule)) {
549
+            stats.setDutyDeptName(simpleDutySchedule.getDepartmentName());
550
+        }
551
+
552
+        return stats;
553
+    }
554
+}

+ 70 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/system/SysHomePageController.java

@@ -1,5 +1,6 @@
1 1
 package com.sundot.airport.web.controller.system;
2 2
 
3
+import cn.hutool.core.collection.CollUtil;
3 4
 import cn.hutool.core.util.ObjectUtil;
4 5
 import cn.hutool.core.util.StrUtil;
5 6
 import com.sundot.airport.attendance.service.IAttendancePostRecordService;
@@ -17,6 +18,7 @@ import com.sundot.airport.common.enums.DataPermissionType;
17 18
 import com.sundot.airport.common.enums.DeptType;
18 19
 import com.sundot.airport.common.enums.DeptTypeEnum;
19 20
 import com.sundot.airport.common.enums.HomePageQueryEnum;
21
+import com.sundot.airport.common.enums.RoleTypeEnum;
20 22
 import com.sundot.airport.common.enums.SourceTypeEnum;
21 23
 import com.sundot.airport.common.exception.ServiceException;
22 24
 import com.sundot.airport.common.utils.DateUtils;
@@ -37,6 +39,7 @@ import org.springframework.web.bind.annotation.RestController;
37 39
 import java.math.BigDecimal;
38 40
 import java.math.RoundingMode;
39 41
 import java.util.ArrayList;
42
+import java.util.Arrays;
40 43
 import java.util.Calendar;
41 44
 import java.util.Collections;
42 45
 import java.util.List;
@@ -282,6 +285,8 @@ public class SysHomePageController extends BaseController {
282 285
             resultItem.setCheckPassRateGraph(resultItem.getCheckPassRate());
283 286
             resultItem.setSeizureCount(getSeizureCount(item));
284 287
             resultItem.setSeizureCountGraph(getSeizureCountGraph(item));
288
+            resultItem.setWorkingHours(getWorkingHours(item));
289
+            resultItem.setWorkingHoursGraph(getWorkingHoursGraph(item));
285 290
             // 抽问抽答正确率
286 291
             resultItem.setAnswersAccuracy(getAnswersAccuracy(item));
287 292
             resultItem.setAnswersAccuracyGraph(resultItem.getAnswersAccuracy());
@@ -473,4 +478,69 @@ public class SysHomePageController extends BaseController {
473 478
         return station != null ? station.getDeptId() : 0;
474 479
     }
475 480
 
481
+    private BigDecimal getWorkingHours(SysHomePageDetailQueryParamDto item) {
482
+        if (item.getEndDate() != null) {
483
+            item.setEndDate(DateUtils.addSeconds(DateUtils.truncate(item.getEndDate(), Calendar.DAY_OF_MONTH), 86399));
484
+        }
485
+        if (StrUtil.equals(HomePageQueryEnum.USER.getCode(), item.getType())) {
486
+            BigDecimal bigDecimal = attendancePostRecordService.selectPersonalTotalWorkDurationInTimeRange(item.getId(), item.getStartDate(), item.getEndDate());
487
+            return bigDecimal != null ? bigDecimal.divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO;
488
+        } else {
489
+            return calculateDeptBasedValue(item, this::calculateAverageWorkDurationForDeptType);
490
+        }
491
+    }
492
+
493
+    private BigDecimal getWorkingHoursGraph(SysHomePageDetailQueryParamDto item) {
494
+        if (item.getEndDate() != null) {
495
+            item.setEndDate(DateUtils.addSeconds(DateUtils.truncate(item.getEndDate(), Calendar.DAY_OF_MONTH), 86399));
496
+        }
497
+        if (StrUtil.equals(HomePageQueryEnum.USER.getCode(), item.getType())) {
498
+            BigDecimal totalWorkDuration = attendancePostRecordService.selectPersonalTotalWorkDurationInTimeRange(item.getId(), item.getStartDate(), item.getEndDate());
499
+            Long stationId = getStationIdByUserId(item.getId());
500
+            BigDecimal maxUserWorkDuration = attendancePostRecordService.selectTopUserWorkDurationInTimeRange(stationId, item.getStartDate(), item.getEndDate());
501
+            return (maxUserWorkDuration != null && maxUserWorkDuration.compareTo(BigDecimal.ZERO) > 0) ?
502
+                    totalWorkDuration.divide(maxUserWorkDuration, 4, RoundingMode.HALF_UP) : BigDecimal.ZERO;
503
+        } else {
504
+            return calculateDeptBasedValue(item, this::calculateWorkDurationForDeptType);
505
+        }
506
+    }
507
+
508
+    /**
509
+     * 平均工作时长计算
510
+     */
511
+    private BigDecimal calculateAverageWorkDurationForDeptType(String deptType, SysHomePageDetailQueryParamDto item) {
512
+        List<SysUser> sysUserList = sysUserService.selectUserListByRoleKeyAndDeptId(Arrays.asList(RoleTypeEnum.banzuzhang.getCode(), RoleTypeEnum.SecurityCheck.getCode()), item.getId());
513
+        if (CollUtil.isEmpty(sysUserList)) {
514
+            return BigDecimal.ZERO;
515
+        }
516
+        List<Long> list = sysUserList.stream().map(SysUser::getUserId).distinct().collect(Collectors.toList());
517
+        int size = list.size();
518
+        BigDecimal totalWorkDuration = attendancePostRecordService.selectTotalWorkDurationInTimeRange(list, item.getStartDate(), item.getEndDate());
519
+        return totalWorkDuration.divide(new BigDecimal(60), 2, RoundingMode.HALF_UP).divide(new BigDecimal(size), 2, RoundingMode.HALF_UP);
520
+    }
521
+
522
+    /**
523
+     * 工作时长图表计算
524
+     */
525
+    private BigDecimal calculateWorkDurationForDeptType(String deptType, SysHomePageDetailQueryParamDto item) {
526
+        List<SysDept> deptList = deptService.selectAllDept(item.getId());
527
+        if (CollUtil.isEmpty(deptList)) {
528
+            return BigDecimal.ZERO;
529
+        }
530
+        Collections.reverse(deptList);
531
+        SysDept station = deptList.stream().filter(x -> StrUtil.equals(DeptType.STATION.getCode(), x.getDeptType())).findFirst().orElse(null);
532
+        BigDecimal maxUserWorkDuration = attendancePostRecordService.selectTopUserWorkDurationInTimeRange(station.getDeptId(), item.getStartDate(), item.getEndDate());
533
+        if (maxUserWorkDuration == null || maxUserWorkDuration.compareTo(BigDecimal.ZERO) == 0) {
534
+            return BigDecimal.ZERO;
535
+        }
536
+        List<SysUser> sysUserList = sysUserService.selectUserListByRoleKeyAndDeptId(Arrays.asList(RoleTypeEnum.banzuzhang.getCode(), RoleTypeEnum.SecurityCheck.getCode()), item.getId());
537
+        if (CollUtil.isEmpty(sysUserList)) {
538
+            return BigDecimal.ZERO;
539
+        }
540
+        List<Long> list = sysUserList.stream().map(SysUser::getUserId).distinct().collect(Collectors.toList());
541
+        int size = list.size();
542
+        BigDecimal totalWorkDuration = attendancePostRecordService.selectTotalWorkDurationInTimeRange(list, item.getStartDate(), item.getEndDate());
543
+        return totalWorkDuration.divide(new BigDecimal(size), 2, RoundingMode.HALF_UP).divide(maxUserWorkDuration, 4, RoundingMode.HALF_UP);
544
+    }
545
+
476 546
 }

+ 42 - 1
airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendancePostRecordMapper.java

@@ -1,8 +1,10 @@
1 1
 package com.sundot.airport.attendance.mapper;
2 2
 
3 3
 import com.sundot.airport.attendance.domain.AttendancePostRecord;
4
+import com.sundot.airport.common.core.domain.entity.SysUser;
4 5
 import org.apache.ibatis.annotations.Param;
5 6
 
7
+import java.math.BigDecimal;
6 8
 import java.util.Date;
7 9
 import java.util.List;
8 10
 
@@ -69,7 +71,7 @@ public interface AttendancePostRecordMapper {
69 71
      * @param today    当天日期
70 72
      * @return 上岗记录集合
71 73
      */
72
-    public List<AttendancePostRecord> selectTodayRecordsByUserId(@Param("createBy") Long createBy, @Param("userId") Long userId, @Param("today") Date today);
74
+    public List<AttendancePostRecord> selectTodayRecordsByUserId(@Param("createBy") Long createBy, @Param("userId") Long userId, @Param("today") Date today, @Param("locked") String locked);
73 75
 
74 76
     /**
75 77
      * 查询指定班次代码当天的上岗记录
@@ -146,4 +148,43 @@ public interface AttendancePostRecordMapper {
146 148
      * @return 考勤日期
147 149
      */
148 150
     public Date selectMaxDateByUserId(@Param("createBy") Long createBy, @Param("userId") Long userId);
151
+
152
+    /**
153
+     * 根据角色标识查询今日上岗用户列表
154
+     *
155
+     * @param date 考勤日期
156
+     * @param list 角色标识
157
+     * @return 用户列表
158
+     */
159
+    List<SysUser> selectUserListByRoleKey(@Param("date") Date date, @Param("locked") String locked, @Param("list") List<String> list);
160
+
161
+    /**
162
+     * 查询指定个人在指定时间段内的总工作时长
163
+     *
164
+     * @param userId    用户ID
165
+     * @param startTime 开始时间
166
+     * @param endTime   结束时间
167
+     * @return 个人总工作时长(分钟)
168
+     */
169
+    BigDecimal selectPersonalTotalWorkDurationInTimeRange(@Param("userId") Long userId, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
170
+
171
+    /**
172
+     * 查询站级别指定时间段内工作时长总和最高的时长
173
+     *
174
+     * @param attendanceStationId 考勤站点ID
175
+     * @param startTime           开始时间
176
+     * @param endTime             结束时间
177
+     * @return 包含用户ID和总工作时长的结果Map,key为userId和totalWorkDuration
178
+     */
179
+    BigDecimal selectTopUserWorkDurationInTimeRange(@Param("attendanceStationId") Long attendanceStationId, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
180
+
181
+    /**
182
+     * 查询指定用户ID列表在指定时间段内的总工作时长
183
+     *
184
+     * @param list      用户ID列表
185
+     * @param startTime 开始时间
186
+     * @param endTime   结束时间
187
+     * @return 总工作时长(分钟)
188
+     */
189
+    BigDecimal selectTotalWorkDurationInTimeRange(@Param("list") List<Long> list, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
149 190
 }

+ 12 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceRecordMapper.java

@@ -1,7 +1,10 @@
1 1
 package com.sundot.airport.attendance.mapper;
2 2
 
3 3
 import com.sundot.airport.attendance.domain.AttendanceRecord;
4
+import com.sundot.airport.common.core.domain.entity.SysUser;
5
+import org.apache.ibatis.annotations.Param;
4 6
 
7
+import java.util.Date;
5 8
 import java.util.List;
6 9
 
7 10
 /**
@@ -58,4 +61,13 @@ public interface AttendanceRecordMapper {
58 61
      * @return 结果
59 62
      */
60 63
     public int deleteAttendanceRecordByIds(Long[] ids);
64
+
65
+    /**
66
+     * 根据角色标识查询今日打卡用户列表
67
+     *
68
+     * @param date 考勤日期
69
+     * @param list 角色标识
70
+     * @return 用户列表
71
+     */
72
+    List<SysUser> selectUserListByRoleKey(@Param("date") Date date, @Param("locked") String locked, @Param("list") List<String> list);
61 73
 }

+ 1 - 1
airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceTeamUserRecordMapper.java

@@ -19,7 +19,7 @@ public interface AttendanceTeamUserRecordMapper {
19 19
      * @param userId 考勤班组成员主键
20 20
      * @return 考勤班组成员
21 21
      */
22
-    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(@Param("userId") Long userId, @Param("attendanceDate") Date attendanceDate);
22
+    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(@Param("userId") Long userId, @Param("attendanceDate") Date attendanceDate, @Param("checkInType") String checkInType);
23 23
 
24 24
     /**
25 25
      * 查询考勤班组成员列表

+ 41 - 2
airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendancePostRecordService.java

@@ -1,8 +1,9 @@
1 1
 package com.sundot.airport.attendance.service;
2 2
 
3 3
 import com.sundot.airport.attendance.domain.AttendancePostRecord;
4
-import com.sundot.airport.attendance.dto.AttendanceRecordCountDTO;
4
+import com.sundot.airport.common.core.domain.entity.SysUser;
5 5
 
6
+import java.math.BigDecimal;
6 7
 import java.util.Date;
7 8
 import java.util.List;
8 9
 import java.util.Map;
@@ -70,7 +71,7 @@ public interface IAttendancePostRecordService {
70 71
      * @param today    当天日期
71 72
      * @return 上岗记录集合
72 73
      */
73
-    public List<AttendancePostRecord> selectTodayRecordsByUserId(Long createBy, Long userId, Date today);
74
+    public List<AttendancePostRecord> selectTodayRecordsByUserId(Long createBy, Long userId, Date today, String locked);
74 75
 
75 76
     /**
76 77
      * 查询指定班次代码当天的上岗记录
@@ -156,4 +157,42 @@ public interface IAttendancePostRecordService {
156 157
      * @return 考勤日期
157 158
      */
158 159
     public Date selectMaxDateByUserId(Long createBy, Long userId);
160
+
161
+    /**
162
+     * 查询指定个人在指定时间段内的总工作时长
163
+     *
164
+     * @param userId    用户ID
165
+     * @param startTime 开始时间
166
+     * @param endTime   结束时间
167
+     * @return 个人总工作时长(分钟)
168
+     */
169
+    BigDecimal selectPersonalTotalWorkDurationInTimeRange(Long userId, Date startTime, Date endTime);
170
+
171
+    /**
172
+     * 查询站级别指定时间段内工作时长总和最高的时长
173
+     *
174
+     * @param startTime 开始时间
175
+     * @param endTime   结束时间
176
+     * @return 在岗记录列表
177
+     */
178
+    BigDecimal selectTopUserWorkDurationInTimeRange(Long userId, Date startTime, Date endTime);
179
+
180
+    /**
181
+     * 根据角色标识查询今日上岗用户列表
182
+     *
183
+     * @param date 考勤日期
184
+     * @param list 角色标识
185
+     * @return 用户列表
186
+     */
187
+    public List<SysUser> selectUserListByRoleKey(Date date, List<String> list);
188
+
189
+    /**
190
+     * 查询指定用户ID列表在指定时间段内的总工作时长
191
+     *
192
+     * @param list      用户ID列表
193
+     * @param startTime 开始时间
194
+     * @param endTime   结束时间
195
+     * @return 总工作时长(分钟)
196
+     */
197
+    BigDecimal selectTotalWorkDurationInTimeRange(List<Long> list, Date startTime, Date endTime);
159 198
 }

+ 10 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceRecordService.java

@@ -2,6 +2,7 @@ package com.sundot.airport.attendance.service;
2 2
 
3 3
 import com.sundot.airport.attendance.domain.AttendanceCheckRecord;
4 4
 import com.sundot.airport.attendance.domain.AttendanceRecord;
5
+import com.sundot.airport.common.core.domain.entity.SysUser;
5 6
 import com.sundot.airport.common.dto.AttendanceRecordDTO;
6 7
 import com.sundot.airport.common.dto.AttendanceRecordRep;
7 8
 import com.sundot.airport.common.dto.AttendanceRecordReq;
@@ -97,4 +98,13 @@ public interface IAttendanceRecordService {
97 98
      * 查询可打卡类型:1=签到,2=签退
98 99
      */
99 100
     String getRecordType();
101
+
102
+    /**
103
+     * 根据角色标识查询今日打卡用户列表
104
+     *
105
+     * @param date 考勤日期
106
+     * @param list 角色标识
107
+     * @return 用户列表
108
+     */
109
+    public List<SysUser> selectUserListByRoleKey(Date date, List<String> list);
100 110
 }

+ 1 - 1
airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceTeamUserRecordService.java

@@ -18,7 +18,7 @@ public interface IAttendanceTeamUserRecordService {
18 18
      * @param userId 考勤班组成员主键
19 19
      * @return 考勤班组成员
20 20
      */
21
-    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(Long userId, Date attendanceDate);
21
+    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(Long userId, Date attendanceDate, String checkInType);
22 22
 
23 23
     /**
24 24
      * 查询考勤班组成员列表

+ 39 - 2
airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendancePostRecordServiceImpl.java

@@ -4,12 +4,14 @@ import cn.hutool.core.collection.CollectionUtil;
4 4
 import com.sundot.airport.attendance.domain.AttendancePostRecord;
5 5
 import com.sundot.airport.attendance.mapper.AttendancePostRecordMapper;
6 6
 import com.sundot.airport.attendance.service.IAttendancePostRecordService;
7
+import com.sundot.airport.common.core.domain.entity.SysUser;
7 8
 import com.sundot.airport.common.utils.DateUtils;
8 9
 import com.sundot.airport.common.utils.StringUtils;
9 10
 import com.sundot.airport.system.service.ISysConfigService;
10 11
 import org.springframework.beans.factory.annotation.Autowired;
11 12
 import org.springframework.stereotype.Service;
12 13
 
14
+import java.math.BigDecimal;
13 15
 import java.util.*;
14 16
 import java.util.stream.Collectors;
15 17
 
@@ -128,8 +130,8 @@ public class AttendancePostRecordServiceImpl implements IAttendancePostRecordSer
128 130
      * @return 上岗记录集合
129 131
      */
130 132
     @Override
131
-    public List<AttendancePostRecord> selectTodayRecordsByUserId(Long createBy, Long userId, Date today) {
132
-        return attendancePostRecordMapper.selectTodayRecordsByUserId(createBy, userId, today);
133
+    public List<AttendancePostRecord> selectTodayRecordsByUserId(Long createBy, Long userId, Date today, String locked) {
134
+        return attendancePostRecordMapper.selectTodayRecordsByUserId(createBy, userId, today, locked);
133 135
     }
134 136
 
135 137
     /**
@@ -306,4 +308,39 @@ public class AttendancePostRecordServiceImpl implements IAttendancePostRecordSer
306 308
     public Date selectMaxDateByUserId(Long createBy, Long userId) {
307 309
         return attendancePostRecordMapper.selectMaxDateByUserId(createBy, userId);
308 310
     }
311
+
312
+    @Override
313
+    public BigDecimal selectPersonalTotalWorkDurationInTimeRange(Long userId, Date startTime, Date endTime) {
314
+        return attendancePostRecordMapper.selectPersonalTotalWorkDurationInTimeRange(userId, startTime, endTime);
315
+    }
316
+
317
+    @Override
318
+    public BigDecimal selectTopUserWorkDurationInTimeRange(Long attendanceStationId, Date startTime, Date endTime) {
319
+        return attendancePostRecordMapper.selectTopUserWorkDurationInTimeRange(attendanceStationId, startTime, endTime);
320
+    }
321
+
322
+    /**
323
+     * 根据角色标识查询今日上岗用户列表
324
+     *
325
+     * @param date 考勤日期
326
+     * @param list 角色标识
327
+     * @return 用户列表
328
+     */
329
+    @Override
330
+    public List<SysUser> selectUserListByRoleKey(Date date, List<String> list) {
331
+        return attendancePostRecordMapper.selectUserListByRoleKey(date, "1", list);
332
+    }
333
+
334
+    /**
335
+     * 查询指定用户ID列表在指定时间段内的总工作时长
336
+     *
337
+     * @param list      用户ID列表
338
+     * @param startTime 开始时间
339
+     * @param endTime   结束时间
340
+     * @return 总工作时长(分钟)
341
+     */
342
+    @Override
343
+    public BigDecimal selectTotalWorkDurationInTimeRange(List<Long> list, Date startTime, Date endTime) {
344
+        return attendancePostRecordMapper.selectTotalWorkDurationInTimeRange(list, startTime, endTime);
345
+    }
309 346
 }

+ 13 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceRecordServiceImpl.java

@@ -15,6 +15,7 @@ import com.sundot.airport.attendance.service.IAttendancePostRecordService;
15 15
 import com.sundot.airport.attendance.service.IAttendanceRecordService;
16 16
 import com.sundot.airport.attendance.service.IAttendanceTeamUserRecordService;
17 17
 import com.sundot.airport.common.core.domain.entity.SysRole;
18
+import com.sundot.airport.common.core.domain.entity.SysUser;
18 19
 import com.sundot.airport.common.dto.AttendanceRecordDTO;
19 20
 import com.sundot.airport.common.dto.AttendanceRecordRep;
20 21
 import com.sundot.airport.common.dto.AttendanceRecordReq;
@@ -618,4 +619,16 @@ public class AttendanceRecordServiceImpl implements IAttendanceRecordService {
618 619
         }
619 620
         return "2";
620 621
     }
622
+
623
+    /**
624
+     * 根据角色标识查询今日打卡用户列表
625
+     *
626
+     * @param date 考勤日期
627
+     * @param list 角色标识
628
+     * @return 用户列表
629
+     */
630
+    @Override
631
+    public List<SysUser> selectUserListByRoleKey(Date date, List<String> list) {
632
+        return attendanceRecordMapper.selectUserListByRoleKey(date, "1", list);
633
+    }
621 634
 }

+ 2 - 2
airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceTeamUserRecordServiceImpl.java

@@ -47,8 +47,8 @@ public class AttendanceTeamUserRecordServiceImpl implements IAttendanceTeamUserR
47 47
      * @return 考勤班组成员
48 48
      */
49 49
     @Override
50
-    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(Long userId, Date attendanceDate) {
51
-        return attendanceTeamUserRecordMapper.selectAttendanceTeamUserRecordByUserId(userId, attendanceDate);
50
+    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(Long userId, Date attendanceDate, String checkInType) {
51
+        return attendanceTeamUserRecordMapper.selectAttendanceTeamUserRecordByUserId(userId, attendanceDate, checkInType);
52 52
     }
53 53
 
54 54
     @Override

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

@@ -307,6 +307,9 @@
307 307
             <if test="today != null">
308 308
                 AND DATE(attendance_date) = DATE(#{today})
309 309
             </if>
310
+            <if test="locked != null">
311
+                and locked=#{locked}
312
+            </if>
310 313
         </where>
311 314
         ORDER BY check_in_time
312 315
     </select>
@@ -426,4 +429,61 @@
426 429
             </if>
427 430
         </where>
428 431
     </select>
432
+
433
+    <select id="selectPersonalTotalWorkDurationInTimeRange" resultType="java.math.BigDecimal">
434
+        SELECT COALESCE(SUM(work_duration), 0) as totalWorkDuration
435
+        FROM attendance_post_record
436
+        WHERE user_id = #{userId}
437
+          AND check_in_time &lt;= #{endTime}
438
+          AND check_out_time >= #{startTime}
439
+          AND work_duration IS NOT NULL
440
+          AND work_duration > 0
441
+    </select>
442
+
443
+    <select id="selectTopUserWorkDurationInTimeRange" resultType="java.math.BigDecimal">
444
+        select max(totalWorkDuration) as maxTotalWorkDuration
445
+        from (SELECT user_id as userId, SUM(work_duration) as totalWorkDuration
446
+              FROM attendance_post_record
447
+              WHERE attendance_station_id = #{attendanceStationId}
448
+                AND check_in_time &lt;= #{endTime}
449
+                AND check_out_time >= #{startTime}
450
+                AND work_duration IS NOT NULL
451
+                AND work_duration > 0
452
+              GROUP BY user_id) as user_durations
453
+    </select>
454
+
455
+    <select id="selectUserListByRoleKey" resultType="sysUser">
456
+        select distinct su.user_id userId,
457
+        su.user_name userName,
458
+        su.nick_name nickName
459
+        from attendance_post_record apr
460
+        inner join sys_user su on su.user_id = apr.user_id
461
+        inner join sys_user_role sur on sur.user_id = su.user_id
462
+        inner join sys_role sr on sr.role_id = sur.role_id
463
+        where 1 = 1
464
+        <if test="date != null">
465
+            and date(apr.attendance_date) = date(#{date})
466
+        </if>
467
+        <if test="locked != null">
468
+            and apr.locked = #{locked}
469
+        </if>
470
+        and sr.role_key in
471
+        <foreach collection="list" item="item" open="(" separator="," close=")">
472
+            #{item}
473
+        </foreach>
474
+    </select>
475
+
476
+    <select id="selectTotalWorkDurationInTimeRange" resultType="java.math.BigDecimal">
477
+        select coalesce(sum(work_duration), 0) as totalWorkDuration
478
+        from attendance_post_record
479
+        where user_id in
480
+        <foreach collection="list" item="item" open="(" separator="," close=")">
481
+            #{item}
482
+        </foreach>
483
+        and check_in_time &lt;= #{endTime}
484
+        and check_out_time >= #{startTime}
485
+        and work_duration is not null
486
+        and work_duration > 0
487
+    </select>
488
+
429 489
 </mapper>

+ 19 - 0
airport-attendance/src/main/resources/mapper/attendance/AttendanceRecordMapper.xml

@@ -204,4 +204,23 @@
204 204
             #{id}
205 205
         </foreach>
206 206
     </delete>
207
+
208
+    <select id="selectUserListByRoleKey" resultType="sysUser">
209
+        select distinct su.user_id userId,
210
+        su.user_name userName,
211
+        su.nick_name nickName
212
+        from attendance_record ar
213
+        inner join sys_user su on su.user_id = ar.user_id
214
+        inner join sys_user_role sur on sur.user_id = su.user_id
215
+        inner join sys_role sr on sr.role_id = sur.role_id
216
+        where 1 = 1
217
+        <if test="date != null">
218
+            and date(ar.attendance_date) = date(#{date})
219
+        </if>
220
+        and sr.role_key in
221
+        <foreach collection="list" item="item" open="(" separator="," close=")">
222
+            #{item}
223
+        </foreach>
224
+    </select>
225
+
207 226
 </mapper>

+ 5 - 2
airport-attendance/src/main/resources/mapper/attendance/AttendanceTeamUserRecordMapper.xml

@@ -68,8 +68,11 @@
68 68
 
69 69
     <select id="selectAttendanceTeamUserRecordByUserId" resultMap="AttendanceTeamUserRecordResult">
70 70
         <include refid="selectAttendanceTeamUserRecordVo"/>
71
-        where user_id = #{userId}
72
-        and DATE(attendance_date) = DATE(#{attendanceDate})
71
+        where 1 = 1
72
+        <if test="userId != null ">and user_id = #{userId}</if>
73
+        <if test="attendanceDate != null ">and DATE(attendance_date) = DATE(#{attendanceDate})</if>
74
+        <if test="checkInType != null ">and check_in_type = #{checkInType}</if>
75
+        limit 1
73 76
     </select>
74 77
 
75 78
     <insert id="insertAttendanceTeamUserRecord" parameterType="AttendanceTeamUserRecord">

+ 28 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/AttendanceStatsResult.java

@@ -0,0 +1,28 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import io.swagger.annotations.ApiModelProperty;
4
+import lombok.Data;
5
+
6
+/**
7
+ * 考勤统计数据返回对象
8
+ *
9
+ * @author ruoyi
10
+ */
11
+@Data
12
+public class AttendanceStatsResult {
13
+    // 安检员统计
14
+    @ApiModelProperty("安检员统计")
15
+    private SecurityCheckStats securityCheckStats;
16
+    // 班组统计
17
+    @ApiModelProperty("班组统计")
18
+    private TeamLeaderStats teamLeaderStats;
19
+    // 科室统计
20
+    @ApiModelProperty("科室统计")
21
+    private SectionLeaderStats sectionLeaderStats;
22
+    // 大队统计
23
+    @ApiModelProperty("大队统计")
24
+    private BrigadeLeaderStats brigadeLeaderStats;
25
+    // 站长统计
26
+    @ApiModelProperty("站长统计")
27
+    private StationLeaderStats stationLeaderStats;
28
+}

+ 43 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/BrigadeLeaderStats.java

@@ -0,0 +1,43 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import io.swagger.annotations.ApiModelProperty;
4
+import lombok.Data;
5
+
6
+/**
7
+ * 经理/大队行政考勤统计数据对象
8
+ *
9
+ * @author ruoyi
10
+ */
11
+@Data
12
+public class BrigadeLeaderStats {
13
+    /**
14
+     * 在岗班组数量
15
+     */
16
+    @ApiModelProperty("在岗班组数量")
17
+    private String onDutyTeamCount;
18
+    /**
19
+     * 在岗人员数量
20
+     */
21
+    @ApiModelProperty("在岗人员数量")
22
+    private String onDutyPersonnelCount;
23
+    /**
24
+     * 通道开放统计
25
+     */
26
+    @ApiModelProperty("通道开放统计")
27
+    private ChannelOpenStats channelOpenStats;
28
+    /**
29
+     * 今日查获上报数量
30
+     */
31
+    @ApiModelProperty("今日查获上报数量")
32
+    private Integer todaySeizureReportCount;
33
+    /**
34
+     * 今日巡检问题数
35
+     */
36
+    @ApiModelProperty("今日巡检问题数")
37
+    private Integer todayCheckCorrectionCount;
38
+    /**
39
+     * 在岗大队
40
+     */
41
+    @ApiModelProperty("在岗大队")
42
+    private String dutyDeptName;
43
+}

+ 14 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/ChannelOpenStats.java

@@ -0,0 +1,14 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import lombok.Data;
4
+
5
+/**
6
+ * 通道开放统计对象
7
+ *
8
+ * @author ruoyi
9
+ */
10
+@Data
11
+public class ChannelOpenStats {
12
+    private Integer openChannels; // 开放通道数
13
+    private Integer totalChannels; // 总通道数
14
+}

+ 93 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/DutyScheduleGenerateReq.java

@@ -0,0 +1,93 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+
5
+import java.util.Date;
6
+
7
+/**
8
+ * 排班计划生成请求参数对象
9
+ *
10
+ * @author wangxx
11
+ * @date 2026-01-23
12
+ */
13
+public class DutyScheduleGenerateReq {
14
+
15
+    /**
16
+     * 开始日期
17
+     */
18
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
19
+    private Date startDate;
20
+
21
+    /**
22
+     * 开始部门ID
23
+     */
24
+    private Long startDeptId;
25
+
26
+    /**
27
+     * 部门列表,逗号分隔的部门ID
28
+     */
29
+    private String deptIds;
30
+
31
+    /**
32
+     * 班次安排,格式:9-17,17-9 或 9-9(跨天)
33
+     */
34
+    private String shifts;
35
+
36
+    /**
37
+     * 生成天数(默认365天)
38
+     */
39
+    private Integer daysToGenerate = 365;
40
+
41
+    /**
42
+     * 站级部门ID
43
+     */
44
+    private Long stationId;
45
+
46
+    public Date getStartDate() {
47
+        return startDate;
48
+    }
49
+
50
+    public void setStartDate(Date startDate) {
51
+        this.startDate = startDate;
52
+    }
53
+
54
+    public Long getStartDeptId() {
55
+        return startDeptId;
56
+    }
57
+
58
+    public void setStartDeptId(Long startDeptId) {
59
+        this.startDeptId = startDeptId;
60
+    }
61
+
62
+    public String getDeptIds() {
63
+        return deptIds;
64
+    }
65
+
66
+    public void setDeptIds(String deptIds) {
67
+        this.deptIds = deptIds;
68
+    }
69
+
70
+    public String getShifts() {
71
+        return shifts;
72
+    }
73
+
74
+    public void setShifts(String shifts) {
75
+        this.shifts = shifts;
76
+    }
77
+
78
+    public Integer getDaysToGenerate() {
79
+        return daysToGenerate;
80
+    }
81
+
82
+    public void setDaysToGenerate(Integer daysToGenerate) {
83
+        this.daysToGenerate = daysToGenerate;
84
+    }
85
+
86
+    public Long getStationId() {
87
+        return stationId;
88
+    }
89
+
90
+    public void setStationId(Long stationId) {
91
+        this.stationId = stationId;
92
+    }
93
+}

+ 47 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/SectionLeaderStats.java

@@ -0,0 +1,47 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import io.swagger.annotations.ApiModelProperty;
5
+import lombok.Data;
6
+
7
+import java.util.Date;
8
+
9
+/**
10
+ * 科长考勤统计数据对象
11
+ *
12
+ * @author ruoyi
13
+ */
14
+@Data
15
+public class SectionLeaderStats {
16
+    /**
17
+     * 出勤时间
18
+     */
19
+    @ApiModelProperty("出勤时间")
20
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
21
+    private Date attendanceTime;
22
+    /**
23
+     * 出勤时间提示
24
+     */
25
+    @ApiModelProperty("出勤时间提示(打卡/未打卡)")
26
+    private String attendanceTimeTips;
27
+    /**
28
+     * 在岗班组数量
29
+     */
30
+    @ApiModelProperty("在岗班组数量")
31
+    private String onDutyTeamCount;
32
+    /**
33
+     * 在岗人员数量
34
+     */
35
+    @ApiModelProperty("在岗人员数量")
36
+    private String onDutyPersonnelCount;
37
+    /**
38
+     * 今日查获上报数量
39
+     */
40
+    @ApiModelProperty("今日查获上报数量")
41
+    private Integer todaySeizureReportCount;
42
+    /**
43
+     * 今日巡检问题数
44
+     */
45
+    @ApiModelProperty("今日巡检问题数")
46
+    private Integer todayCheckCorrectionCount;
47
+}

+ 29 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/SecurityCheckStats.java

@@ -0,0 +1,29 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import io.swagger.annotations.ApiModelProperty;
5
+import lombok.Data;
6
+
7
+import java.util.Date;
8
+
9
+/**
10
+ * 安检员考勤统计数据对象
11
+ *
12
+ * @author ruoyi
13
+ */
14
+@Data
15
+public class SecurityCheckStats {
16
+    // 出勤班组
17
+    @ApiModelProperty("出勤班组")
18
+    private String attendanceTeamName;
19
+    // 出勤通道
20
+    @ApiModelProperty("出勤通道")
21
+    private String attendanceChannel;
22
+    // 出勤时间
23
+    @ApiModelProperty("出勤时间")
24
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
25
+    private Date attendanceTime;
26
+    // 出勤时间提示
27
+    @ApiModelProperty("出勤时间提示(打卡/未打卡)")
28
+    private String attendanceTimeTips;
29
+}

+ 43 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/StationLeaderStats.java

@@ -0,0 +1,43 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import io.swagger.annotations.ApiModelProperty;
4
+import lombok.Data;
5
+
6
+/**
7
+ * 站长/质检科考勤统计数据对象
8
+ *
9
+ * @author ruoyi
10
+ */
11
+@Data
12
+public class StationLeaderStats {
13
+    /**
14
+     * 在岗班组数量
15
+     */
16
+    @ApiModelProperty("在岗班组数量")
17
+    private String onDutyTeamCount;
18
+    /**
19
+     * 在岗人员数量
20
+     */
21
+    @ApiModelProperty("在岗人员数量")
22
+    private String onDutyPersonnelCount;
23
+    /**
24
+     * 通道开放统计
25
+     */
26
+    @ApiModelProperty("通道开放统计")
27
+    private ChannelOpenStats channelOpenStats;
28
+    /**
29
+     * 今日查获上报数量
30
+     */
31
+    @ApiModelProperty("今日查获上报数量")
32
+    private Integer todaySeizureReportCount;
33
+    /**
34
+     * 今日巡检问题数
35
+     */
36
+    @ApiModelProperty("今日巡检问题数")
37
+    private Integer todayCheckCorrectionCount;
38
+    /**
39
+     * 在岗科室
40
+     */
41
+    @ApiModelProperty("在岗科室")
42
+    private String dutyDeptName;
43
+}

+ 37 - 0
airport-common/src/main/java/com/sundot/airport/common/dto/TeamLeaderStats.java

@@ -0,0 +1,37 @@
1
+package com.sundot.airport.common.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import io.swagger.annotations.ApiModelProperty;
5
+import lombok.Data;
6
+
7
+import java.util.Date;
8
+
9
+/**
10
+ * 班组长考勤统计数据对象
11
+ *
12
+ * @author ruoyi
13
+ */
14
+@Data
15
+public class TeamLeaderStats {
16
+    /**
17
+     * 出勤班组
18
+     */
19
+    @ApiModelProperty("出勤班组")
20
+    private String attendanceTeamName;
21
+    /**
22
+     * 出勤通道
23
+     */
24
+    @ApiModelProperty("出勤通道")
25
+    private String attendanceChannel;
26
+    /**
27
+     * 出勤时间
28
+     */
29
+    @ApiModelProperty("出勤时间")
30
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
31
+    private Date attendanceTime;
32
+    /**
33
+     * 出勤时间提示
34
+     */
35
+    @ApiModelProperty("出勤时间提示(打卡/未打卡)")
36
+    private String attendanceTimeTips;
37
+}

+ 139 - 0
airport-item/src/main/java/com/sundot/airport/item/controller/SimpleDutyScheduleController.java

@@ -0,0 +1,139 @@
1
+package com.sundot.airport.item.controller;
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.core.domain.entity.SysDept;
7
+import com.sundot.airport.common.core.page.TableDataInfo;
8
+import com.sundot.airport.common.dto.DutyScheduleGenerateReq;
9
+import com.sundot.airport.common.enums.BusinessType;
10
+import com.sundot.airport.common.utils.poi.ExcelUtil;
11
+import com.sundot.airport.item.domain.SimpleDutySchedule;
12
+import com.sundot.airport.item.service.ISimpleDutyScheduleService;
13
+import com.sundot.airport.system.service.ISysDeptService;
14
+import org.springframework.beans.factory.annotation.Autowired;
15
+import org.springframework.web.bind.annotation.DeleteMapping;
16
+import org.springframework.web.bind.annotation.GetMapping;
17
+import org.springframework.web.bind.annotation.PathVariable;
18
+import org.springframework.web.bind.annotation.PostMapping;
19
+import org.springframework.web.bind.annotation.PutMapping;
20
+import org.springframework.web.bind.annotation.RequestBody;
21
+import org.springframework.web.bind.annotation.RequestMapping;
22
+import org.springframework.web.bind.annotation.RestController;
23
+
24
+import java.util.ArrayList;
25
+import java.util.List;
26
+
27
+/**
28
+ * 值班安排Controller
29
+ *
30
+ * @author wangxx
31
+ * @date 2026-01-14
32
+ */
33
+@RestController
34
+@RequestMapping("/item/simpleDutySchedule")
35
+public class SimpleDutyScheduleController extends BaseController {
36
+    @Autowired
37
+    private ISimpleDutyScheduleService simpleDutyScheduleService;
38
+    @Autowired
39
+    private ISysDeptService sysDeptService;
40
+
41
+    /**
42
+     * 查询值班安排列表
43
+     */
44
+    @GetMapping("/list")
45
+    public TableDataInfo list(SimpleDutySchedule simpleDutySchedule) {
46
+        startPage();
47
+        List<SimpleDutySchedule> list = simpleDutyScheduleService.selectSimpleDutyScheduleList(simpleDutySchedule);
48
+        return getDataTable(list);
49
+    }
50
+
51
+    /**
52
+     * 导出值班安排列表
53
+     */
54
+    @Log(title = "值班安排", businessType = BusinessType.EXPORT)
55
+    @PostMapping("/export")
56
+    public AjaxResult export(SimpleDutySchedule simpleDutySchedule) {
57
+        List<SimpleDutySchedule> list = simpleDutyScheduleService.selectSimpleDutyScheduleList(simpleDutySchedule);
58
+        ExcelUtil<SimpleDutySchedule> util = new ExcelUtil<SimpleDutySchedule>(SimpleDutySchedule.class);
59
+        return util.exportExcel(list, "值班安排数据");
60
+    }
61
+
62
+
63
+    /**
64
+     * 新增值班安排
65
+     */
66
+    @Log(title = "值班安排", businessType = BusinessType.INSERT)
67
+    @PostMapping
68
+    public AjaxResult add(@RequestBody SimpleDutySchedule simpleDutySchedule) {
69
+        return toAjax(simpleDutyScheduleService.insertSimpleDutySchedule(simpleDutySchedule));
70
+    }
71
+
72
+    /**
73
+     * 修改值班安排
74
+     */
75
+    @Log(title = "值班安排", businessType = BusinessType.UPDATE)
76
+    @PutMapping
77
+    public AjaxResult edit(@RequestBody SimpleDutySchedule simpleDutySchedule) {
78
+        return toAjax(simpleDutyScheduleService.updateSimpleDutySchedule(simpleDutySchedule));
79
+    }
80
+
81
+    /**
82
+     * 删除值班安排
83
+     */
84
+    @Log(title = "值班安排", businessType = BusinessType.DELETE)
85
+    @DeleteMapping("/{scheduleIds}")
86
+    public AjaxResult remove(@PathVariable Long[] scheduleIds) {
87
+        return toAjax(simpleDutyScheduleService.deleteSimpleDutyScheduleByIds(scheduleIds));
88
+    }
89
+
90
+
91
+    /**
92
+     * 生成统一排班计划(支持单班、两班倒、多班倒等模式)
93
+     *
94
+     * @param req 排班计划生成请求参数
95
+     */
96
+    @Log(title = "生成统一排班计划", businessType = BusinessType.INSERT)
97
+    @PostMapping("/generateUnifiedSchedule")
98
+    public AjaxResult generateUnifiedSchedule(@RequestBody DutyScheduleGenerateReq req) {
99
+        // 查询循环部门
100
+        String[] deptParts = req.getDeptIds().split(",");
101
+        List<SysDept> departmentList = new ArrayList<>();
102
+        SysDept startDepartment = new SysDept();
103
+        for (String deptId : deptParts) {
104
+            long id = Long.parseLong(deptId);
105
+            //根据部门id 查询部门信息
106
+            SysDept dept = sysDeptService.selectDeptById(id);
107
+            departmentList.add(dept);
108
+            if (id == req.getStartDeptId()) {
109
+                startDepartment = dept;
110
+            }
111
+        }
112
+
113
+        // 解析班次安排字符串,格式:"9-17,17-9" 或 "9-9"(跨天)
114
+        String[] shiftPairs = req.getShifts().split(",");
115
+        int[][] shiftsArray = new int[shiftPairs.length][2];
116
+
117
+        for (int i = 0; i < shiftPairs.length; i++) {
118
+            String[] times = shiftPairs[i].split("-");
119
+            if (times.length != 2) {
120
+                throw new IllegalArgumentException("班次格式错误,应为 HH-HH 格式,如 9-17");
121
+            }
122
+            shiftsArray[i][0] = Integer.parseInt(times[0]); // 开始时间
123
+            shiftsArray[i][1] = Integer.parseInt(times[1]); // 结束时间
124
+        }
125
+
126
+        SysDept stationDepartment = sysDeptService.selectDeptById(req.getStationId());
127
+
128
+        List<SimpleDutySchedule> scheduleList = simpleDutyScheduleService.generateUnifiedSchedule(
129
+                req.getStartDate(), startDepartment, departmentList, shiftsArray, req.getDaysToGenerate(), stationDepartment);
130
+
131
+        // 批量插入或更新排班安排(如果已存在则覆盖)
132
+        int successCount = 0;
133
+        if (!scheduleList.isEmpty()) {
134
+            successCount = simpleDutyScheduleService.batchInsertOrUpdateSimpleDutySchedule(scheduleList);
135
+        }
136
+
137
+        return AjaxResult.success("成功生成 " + successCount + " 条排班安排");
138
+    }
139
+}

+ 185 - 0
airport-item/src/main/java/com/sundot/airport/item/domain/SimpleDutySchedule.java

@@ -0,0 +1,185 @@
1
+package com.sundot.airport.item.domain;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import com.sundot.airport.common.core.domain.BaseEntity;
5
+import org.apache.commons.lang3.builder.ToStringBuilder;
6
+import org.apache.commons.lang3.builder.ToStringStyle;
7
+
8
+import java.util.Date;
9
+
10
+/**
11
+ * 简化值班安排对象
12
+ *
13
+ * @author sundot
14
+ * @date 2026-01-14
15
+ */
16
+public class SimpleDutySchedule extends BaseEntity {
17
+    private static final long serialVersionUID = 1L;
18
+
19
+    /**
20
+     * 主键ID
21
+     */
22
+    private Long id;
23
+
24
+    /**
25
+     * 站点ID
26
+     */
27
+    private Long stationId;
28
+
29
+    /**
30
+     * 站点名称
31
+     */
32
+    private String stationName;
33
+
34
+    /**
35
+     * 部门ID
36
+     */
37
+    private Long departmentId;
38
+
39
+    /**
40
+     * 部门名称
41
+     */
42
+    private String departmentName;
43
+
44
+    /**
45
+     * 值班日期
46
+     */
47
+    @JsonFormat(pattern = "yyyy-MM-dd")
48
+    private Date dutyDate;
49
+
50
+    /**
51
+     * 开始时间
52
+     */
53
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
54
+    private Date startTime;
55
+
56
+    /**
57
+     * 结束时间
58
+     */
59
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
60
+    private Date endTime;
61
+
62
+    /**
63
+     * 是否跨天(1跨天,0不跨天)
64
+     */
65
+    private Integer crossDay;
66
+
67
+    /**
68
+     * 状态(0正常 1停用)
69
+     */
70
+    private String status;
71
+
72
+    /**
73
+     * 备注
74
+     */
75
+    private String remark;
76
+
77
+    public Long getId() {
78
+        return id;
79
+    }
80
+
81
+    public void setId(Long id) {
82
+        this.id = id;
83
+    }
84
+
85
+    public String getStationName() {
86
+        return stationName;
87
+    }
88
+
89
+    public void setStationName(String stationName) {
90
+        this.stationName = stationName;
91
+    }
92
+
93
+    public Long getStationId() {
94
+        return stationId;
95
+    }
96
+
97
+    public void setStationId(Long stationId) {
98
+        this.stationId = stationId;
99
+    }
100
+
101
+    public String getDepartmentName() {
102
+        return departmentName;
103
+    }
104
+
105
+    public void setDepartmentName(String departmentName) {
106
+        this.departmentName = departmentName;
107
+    }
108
+
109
+    public Long getDepartmentId() {
110
+        return departmentId;
111
+    }
112
+
113
+    public void setDepartmentId(Long departmentId) {
114
+        this.departmentId = departmentId;
115
+    }
116
+
117
+    public Date getDutyDate() {
118
+        return dutyDate;
119
+    }
120
+
121
+    public void setDutyDate(Date dutyDate) {
122
+        this.dutyDate = dutyDate;
123
+    }
124
+
125
+    public Date getStartTime() {
126
+        return startTime;
127
+    }
128
+
129
+    public void setStartTime(Date startTime) {
130
+        this.startTime = startTime;
131
+    }
132
+
133
+    public Date getEndTime() {
134
+        return endTime;
135
+    }
136
+
137
+    public void setEndTime(Date endTime) {
138
+        this.endTime = endTime;
139
+    }
140
+
141
+    public Integer getCrossDay() {
142
+        return crossDay;
143
+    }
144
+
145
+    public void setCrossDay(Integer crossDay) {
146
+        this.crossDay = crossDay;
147
+    }
148
+
149
+    public String getStatus() {
150
+        return status;
151
+    }
152
+
153
+    public void setStatus(String status) {
154
+        this.status = status;
155
+    }
156
+
157
+    public String getRemark() {
158
+        return remark;
159
+    }
160
+
161
+    public void setRemark(String remark) {
162
+        this.remark = remark;
163
+    }
164
+
165
+    @Override
166
+    public String toString() {
167
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
168
+                .append("id", getId())
169
+                .append("stationId", getStationId())
170
+                .append("stationName", getStationName())
171
+                .append("departmentId", getDepartmentId())
172
+                .append("departmentName", getDepartmentName())
173
+                .append("dutyDate", getDutyDate())
174
+                .append("startTime", getStartTime())
175
+                .append("endTime", getEndTime())
176
+                .append("crossDay", getCrossDay())
177
+                .append("status", getStatus())
178
+                .append("createBy", getCreateBy())
179
+                .append("createTime", getCreateTime())
180
+                .append("updateBy", getUpdateBy())
181
+                .append("updateTime", getUpdateTime())
182
+                .append("remark", getRemark())
183
+                .toString();
184
+    }
185
+}

+ 85 - 0
airport-item/src/main/java/com/sundot/airport/item/mapper/SimpleDutyScheduleMapper.java

@@ -0,0 +1,85 @@
1
+package com.sundot.airport.item.mapper;
2
+
3
+import com.sundot.airport.item.domain.SimpleDutySchedule;
4
+
5
+import java.util.Date;
6
+import java.util.List;
7
+
8
+/**
9
+ * 值班安排Mapper接口
10
+ *
11
+ * @author wangxx
12
+ * @date 2026-01-14
13
+ */
14
+public interface SimpleDutyScheduleMapper {
15
+    /**
16
+     * 查询值班安排
17
+     *
18
+     * @param id 排班ID
19
+     * @return 值班安排
20
+     */
21
+    public SimpleDutySchedule selectSimpleDutyScheduleByScheduleId(Long id);
22
+
23
+    /**
24
+     * 查询值班安排列表
25
+     *
26
+     * @param simpleDutySchedule 值班安排
27
+     * @return 值班安排集合
28
+     */
29
+    public List<SimpleDutySchedule> selectSimpleDutyScheduleList(SimpleDutySchedule simpleDutySchedule);
30
+
31
+    /**
32
+     * 根据日期查询值班安排
33
+     *
34
+     * @param dutyDate 值班日期
35
+     * @return 值班安排
36
+     */
37
+    public SimpleDutySchedule selectSimpleDutyScheduleByDate(Date dutyDate);
38
+
39
+    /**
40
+     * 根据站点和日期查询值班安排
41
+     *
42
+     * @return 值班安排
43
+     */
44
+    public SimpleDutySchedule selectSimpleDutyScheduleByStationAndDate(SimpleDutySchedule simpleDutySchedule);
45
+
46
+    /**
47
+     * 新增值班安排
48
+     *
49
+     * @param simpleDutySchedule 值班安排
50
+     * @return 结果
51
+     */
52
+    public int insertSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule);
53
+
54
+    /**
55
+     * 修改值班安排
56
+     *
57
+     * @param simpleDutySchedule 值班安排
58
+     * @return 结果
59
+     */
60
+    public int updateSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule);
61
+
62
+    /**
63
+     * 删除值班安排
64
+     *
65
+     * @param scheduleId 排班ID
66
+     * @return 结果
67
+     */
68
+    public int deleteSimpleDutyScheduleByScheduleId(Long scheduleId);
69
+
70
+    /**
71
+     * 批量删除值班安排
72
+     *
73
+     * @param scheduleIds 需要删除的数据ID
74
+     * @return 结果
75
+     */
76
+    public int deleteSimpleDutyScheduleByScheduleIds(Long[] scheduleIds);
77
+
78
+    /**
79
+     * 批量新增值班安排信息
80
+     *
81
+     * @param simpleDutyScheduleList 值班安排列表
82
+     * @return 结果
83
+     */
84
+    public int batchInsertSimpleDutySchedule(List<SimpleDutySchedule> simpleDutyScheduleList);
85
+}

+ 95 - 0
airport-item/src/main/java/com/sundot/airport/item/service/ISimpleDutyScheduleService.java

@@ -0,0 +1,95 @@
1
+package com.sundot.airport.item.service;
2
+
3
+import com.sundot.airport.common.core.domain.entity.SysDept;
4
+import com.sundot.airport.item.domain.SimpleDutySchedule;
5
+
6
+import java.util.Date;
7
+import java.util.List;
8
+
9
+/**
10
+ * 值班安排 服务层
11
+ *
12
+ * @author wangxx
13
+ * @date 2026-01-14
14
+ */
15
+public interface ISimpleDutyScheduleService {
16
+
17
+    /**
18
+     * 查询值班安排列表
19
+     *
20
+     * @param simpleDutySchedule 值班安排信息
21
+     * @return 值班安排集合
22
+     */
23
+    public List<SimpleDutySchedule> selectSimpleDutyScheduleList(SimpleDutySchedule simpleDutySchedule);
24
+
25
+    /**
26
+     * 根据日期查询值班安排
27
+     *
28
+     * @param dutyDate 值班日期
29
+     * @return 值班安排
30
+     */
31
+    public SimpleDutySchedule selectSimpleDutyScheduleByDate(Date dutyDate);
32
+
33
+
34
+    /**
35
+     * 新增值班安排
36
+     *
37
+     * @param simpleDutySchedule 值班安排信息
38
+     * @return 结果
39
+     */
40
+    public int insertSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule);
41
+
42
+    /**
43
+     * 修改值班安排
44
+     *
45
+     * @param simpleDutySchedule 值班安排信息
46
+     * @return 结果
47
+     */
48
+    public int updateSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule);
49
+
50
+    /**
51
+     * 批量删除值班安排信息
52
+     *
53
+     * @param scheduleIds 需要删除的排班ID
54
+     * @return 结果
55
+     */
56
+    public int deleteSimpleDutyScheduleByIds(Long[] scheduleIds);
57
+
58
+    /**
59
+     * 批量新增值班安排信息
60
+     *
61
+     * @param simpleDutyScheduleList 值班安排列表
62
+     * @return 结果
63
+     */
64
+    public int batchInsertSimpleDutySchedule(List<SimpleDutySchedule> simpleDutyScheduleList);
65
+
66
+    /**
67
+     * 批量插入或更新值班安排(对列表中的每条记录执行插入或更新操作)
68
+     *
69
+     * @param simpleDutyScheduleList 值班安排列表
70
+     * @return 成功处理的记录总数
71
+     */
72
+    public int batchInsertOrUpdateSimpleDutySchedule(List<SimpleDutySchedule> simpleDutyScheduleList);
73
+
74
+    /**
75
+     * 插入或更新单条值班安排(先尝试更新,如果不存在则插入)
76
+     *
77
+     * @param simpleDutySchedule 值班安排信息
78
+     * @return 结果
79
+     */
80
+    public int insertOrUpdateSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule);
81
+
82
+
83
+    /**
84
+     * 生成统一排班计划(支持单班、两班倒、多班倒等模式)
85
+     *
86
+     * @param startDate         起始日期
87
+     * @param startDepartment   起始部门对象,包含部门ID和名称
88
+     * @param departments       部门信息列表,包含部门ID和名称
89
+     * @param shifts            班次安排数组,每个元素包含[startHour, endHour]
90
+     * @param daysToGenerate    生成天数
91
+     * @param stationDepartment 站点部门对象,包含站点ID和名称
92
+     * @return SimpleDutySchedule列表
93
+     */
94
+    public List<SimpleDutySchedule> generateUnifiedSchedule(Date startDate, SysDept startDepartment, List<SysDept> departments, int[][] shifts, int daysToGenerate, SysDept stationDepartment);
95
+}

+ 154 - 0
airport-item/src/main/java/com/sundot/airport/item/service/impl/SimpleDutyScheduleServiceImpl.java

@@ -0,0 +1,154 @@
1
+package com.sundot.airport.item.service.impl;
2
+
3
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
4
+import com.sundot.airport.common.core.domain.entity.SysDept;
5
+import com.sundot.airport.common.core.domain.model.LoginUser;
6
+import com.sundot.airport.common.utils.SecurityUtils;
7
+import com.sundot.airport.item.domain.SimpleDutySchedule;
8
+import com.sundot.airport.item.mapper.SimpleDutyScheduleMapper;
9
+import com.sundot.airport.item.service.ISimpleDutyScheduleService;
10
+import com.sundot.airport.item.utils.UnifiedDutyScheduler;
11
+import org.springframework.beans.factory.annotation.Autowired;
12
+import org.springframework.stereotype.Service;
13
+
14
+import java.util.Date;
15
+import java.util.List;
16
+
17
+/**
18
+ * 值班安排 服务层实现
19
+ *
20
+ * @author wangxx
21
+ * @date 2026-01-14
22
+ */
23
+@Service
24
+public class SimpleDutyScheduleServiceImpl implements ISimpleDutyScheduleService {
25
+    @Autowired
26
+    private SimpleDutyScheduleMapper simpleDutyScheduleMapper;
27
+
28
+    /**
29
+     * 查询值班安排列表
30
+     *
31
+     * @param simpleDutySchedule 值班安排信息
32
+     * @return 值班安排集合
33
+     */
34
+    @Override
35
+    public List<SimpleDutySchedule> selectSimpleDutyScheduleList(SimpleDutySchedule simpleDutySchedule) {
36
+        return simpleDutyScheduleMapper.selectSimpleDutyScheduleList(simpleDutySchedule);
37
+    }
38
+
39
+    /**
40
+     * 根据日期查询值班安排
41
+     *
42
+     * @param dutyDate 值班日期
43
+     * @return 值班安排
44
+     */
45
+    @Override
46
+    public SimpleDutySchedule selectSimpleDutyScheduleByDate(Date dutyDate) {
47
+        return simpleDutyScheduleMapper.selectSimpleDutyScheduleByDate(dutyDate);
48
+    }
49
+
50
+
51
+    /**
52
+     * 新增值班安排
53
+     *
54
+     * @param simpleDutySchedule 值班安排信息
55
+     * @return 结果
56
+     */
57
+    @Override
58
+    public int insertSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule) {
59
+        return simpleDutyScheduleMapper.insertSimpleDutySchedule(simpleDutySchedule);
60
+    }
61
+
62
+    /**
63
+     * 修改值班安排
64
+     *
65
+     * @param simpleDutySchedule 值班安排信息
66
+     * @return 结果
67
+     */
68
+    @Override
69
+    public int updateSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule) {
70
+        return simpleDutyScheduleMapper.updateSimpleDutySchedule(simpleDutySchedule);
71
+    }
72
+
73
+    /**
74
+     * 批量删除值班安排对象
75
+     *
76
+     * @param scheduleIds 需要删除的数据ID
77
+     * @return 结果
78
+     */
79
+    @Override
80
+    public int deleteSimpleDutyScheduleByIds(Long[] scheduleIds) {
81
+        return simpleDutyScheduleMapper.deleteSimpleDutyScheduleByScheduleIds(scheduleIds);
82
+    }
83
+
84
+    /**
85
+     * 批量新增值班安排信息
86
+     *
87
+     * @param simpleDutyScheduleList 值班安排列表
88
+     * @return 结果
89
+     */
90
+    @Override
91
+    public int batchInsertSimpleDutySchedule(List<SimpleDutySchedule> simpleDutyScheduleList) {
92
+        return simpleDutyScheduleMapper.batchInsertSimpleDutySchedule(simpleDutyScheduleList);
93
+    }
94
+
95
+    /**
96
+     * 插入或更新单条值班安排(先尝试更新,如果不存在则插入)
97
+     *
98
+     * @param simpleDutySchedule 值班安排信息
99
+     * @return 结果
100
+     */
101
+    @Override
102
+    public int insertOrUpdateSimpleDutySchedule(SimpleDutySchedule simpleDutySchedule) {
103
+        // 首先检查是否存在符合条件的记录
104
+        SimpleDutySchedule existingRecord = simpleDutyScheduleMapper.selectSimpleDutyScheduleByStationAndDate(simpleDutySchedule);
105
+
106
+        if (existingRecord != null) {
107
+            // 如果存在,则更新现有记录
108
+            simpleDutySchedule.setId(existingRecord.getId());
109
+            return simpleDutyScheduleMapper.updateSimpleDutySchedule(simpleDutySchedule);
110
+        } else {
111
+            // 如果不存在,则插入新记录
112
+            return simpleDutyScheduleMapper.insertSimpleDutySchedule(simpleDutySchedule);
113
+        }
114
+    }
115
+
116
+
117
+    /**
118
+     * 批量插入或更新值班安排(对列表中的每条记录执行插入或更新操作)
119
+     *
120
+     * @param simpleDutyScheduleList 值班安排列表
121
+     * @return 成功处理的记录总数
122
+     */
123
+    @Override
124
+    public int batchInsertOrUpdateSimpleDutySchedule(List<SimpleDutySchedule> simpleDutyScheduleList) {
125
+        int successCount = 0;
126
+        LoginUser loginUser = SecurityUtils.getLoginUser();
127
+        for (SimpleDutySchedule schedule : simpleDutyScheduleList) {
128
+            //当前登录人
129
+            schedule.setCreateBy(ObjectUtils.isEmpty(loginUser) ? "admin" : loginUser.getUsername());
130
+            schedule.setCreateTime(new Date());
131
+            int result = insertOrUpdateSimpleDutySchedule(schedule);
132
+            if (result > 0) {
133
+                successCount++;
134
+            }
135
+        }
136
+        return successCount;
137
+    }
138
+
139
+    /**
140
+     * 生成统一排班计划(支持单班、两班倒、多班倒等模式)
141
+     *
142
+     * @param startDate         起始日期
143
+     * @param startDepartment   起始部门对象,包含部门ID和名称
144
+     * @param departments       部门信息列表,包含部门ID和名称
145
+     * @param shifts            班次安排数组,每个元素包含[startHour, endHour]
146
+     * @param daysToGenerate    生成天数
147
+     * @param stationDepartment 站点部门对象,包含站点ID和名称
148
+     * @return SimpleDutySchedule列表
149
+     */
150
+    @Override
151
+    public List<SimpleDutySchedule> generateUnifiedSchedule(Date startDate, SysDept startDepartment, List<SysDept> departments, int[][] shifts, int daysToGenerate, SysDept stationDepartment) {
152
+        return UnifiedDutyScheduler.generateUnifiedSchedule(startDate, startDepartment, departments, shifts, daysToGenerate, stationDepartment);
153
+    }
154
+}

+ 158 - 0
airport-item/src/main/java/com/sundot/airport/item/utils/UnifiedDutyScheduler.java

@@ -0,0 +1,158 @@
1
+package com.sundot.airport.item.utils;
2
+
3
+import com.sundot.airport.common.core.domain.entity.SysDept;
4
+import com.sundot.airport.item.domain.SimpleDutySchedule;
5
+
6
+import java.util.ArrayList;
7
+import java.util.Calendar;
8
+import java.util.Date;
9
+import java.util.List;
10
+
11
+/**
12
+ * 统一值班排班调度器
13
+ * 支持多种排班模式:
14
+ * 1. 单班模式:如9点到次日9点(24小时值班)
15
+ * 2. 两班倒模式:如9-17, 17-9
16
+ *
17
+ * @author wangxx
18
+ * @date 2026-01-14
19
+ */
20
+public class UnifiedDutyScheduler {
21
+
22
+    /**
23
+     * 生成统一排班计划
24
+     *
25
+     * @param startDate         起始日期
26
+     * @param startDepartment   起始部门
27
+     * @param departments       循环排岗部门信息列表
28
+     * @param shifts            班次安排数组,每个元素包含[startHour, endHour]
29
+     * @param daysToGenerate    生成天数
30
+     * @param stationDepartment 站点部门对象,包含站点ID和名称
31
+     * @return SimpleDutySchedule列表
32
+     */
33
+    public static List<SimpleDutySchedule> generateUnifiedSchedule(Date startDate, SysDept startDepartment, List<SysDept> departments, int[][] shifts, int daysToGenerate, SysDept stationDepartment) {
34
+        List<SimpleDutySchedule> scheduleList = new ArrayList<>();
35
+
36
+        // 提取部门名称数组和部门ID数组
37
+        String[] deptNames = departments.stream()
38
+                .map(SysDept::getDeptName)
39
+                .toArray(String[]::new);
40
+        Long[] departmentIds = departments.stream()
41
+                .map(SysDept::getDeptId)
42
+                .toArray(Long[]::new);
43
+
44
+        // 验证部门ID数组长度是否与部门名称数组一致
45
+        if (departmentIds != null && departmentIds.length != deptNames.length) {
46
+            throw new IllegalArgumentException("部门ID数组长度与部门名称数组长度不匹配");
47
+        }
48
+
49
+        // 获取起始部门名称
50
+        String startDeptName = startDepartment != null ? startDepartment.getDeptName() : null;
51
+
52
+        if (startDeptName == null) {
53
+            throw new IllegalArgumentException("未找到指定的起始部门名称");
54
+        }
55
+
56
+        // 找到起始科室的索引
57
+        int startIndex = -1;
58
+        for (int i = 0; i < deptNames.length; i++) {
59
+            if (deptNames[i].equals(startDeptName)) {
60
+                startIndex = i;
61
+                break;
62
+            }
63
+        }
64
+
65
+        if (startIndex == -1) {
66
+            throw new IllegalArgumentException("未找到指定的起始科室: " + startDeptName);
67
+        }
68
+
69
+        // 从起始日期开始生成排班
70
+        Calendar calendar = Calendar.getInstance();
71
+        calendar.setTime(startDate);
72
+
73
+        int currentDeptIndex = startIndex;
74
+
75
+        // 计算总的班次数
76
+        int totalShifts = daysToGenerate * shifts.length;
77
+
78
+        for (int i = 0; i < totalShifts; i++) {
79
+            // 确定当前是哪一天的第几个班次
80
+            int dayIndex = i / shifts.length; // 第几天
81
+            int shiftIndex = i % shifts.length; // 当天的第几个班次
82
+
83
+            // 获取班次信息 [startHour, endHour]
84
+            int[] shift = shifts[shiftIndex];
85
+            int startTime = shift[0];
86
+            int endTime = shift[1];
87
+
88
+            // 计算班次日期
89
+            Calendar shiftCalendar = (Calendar) calendar.clone();
90
+            shiftCalendar.add(Calendar.DAY_OF_MONTH, dayIndex);
91
+
92
+            // 判断是否跨天
93
+            boolean isCrossDay = false;
94
+            if (startTime > endTime) {
95
+                // 如果开始时间大于结束时间,说明跨天,例如22点到次日6点
96
+                isCrossDay = true;
97
+            } else if (startTime == endTime && shifts.length == 1) {
98
+                // 对于24小时连续值班情况,如9点到次日9点,由于是一整天(24小时)值班,所以肯定跨天
99
+                isCrossDay = true;
100
+            }
101
+
102
+            // 确定当前班次的科室
103
+            int deptIndex = (currentDeptIndex + shiftIndex) % deptNames.length; // 同一天内不同班次也轮换科室
104
+
105
+            // 创建班次安排
106
+            SimpleDutySchedule shiftSchedule = new SimpleDutySchedule();
107
+            shiftSchedule.setStationId(stationDepartment.getDeptId());
108
+            shiftSchedule.setStationName(stationDepartment.getDeptName());
109
+
110
+            if (departmentIds != null && departmentIds.length > deptIndex) {
111
+                shiftSchedule.setDepartmentId(departmentIds[deptIndex]);
112
+
113
+                // 使用部门信息列表中的名称
114
+                SysDept dept = departments.get(deptIndex);
115
+                if (dept != null) {
116
+                    shiftSchedule.setDepartmentName(dept.getDeptName());
117
+                } else {
118
+                    shiftSchedule.setDepartmentName(deptNames[deptIndex]); // fallback to name array
119
+                }
120
+            } else {
121
+                shiftSchedule.setDepartmentName(deptNames[deptIndex]);
122
+            }
123
+            shiftSchedule.setDutyDate(shiftCalendar.getTime());
124
+
125
+            // 设置完整的开始时间
126
+            Calendar startCal = (Calendar) shiftCalendar.clone();
127
+            startCal.set(Calendar.HOUR_OF_DAY, startTime);
128
+            startCal.set(Calendar.MINUTE, 0);
129
+            startCal.set(Calendar.SECOND, 0);
130
+            startCal.set(Calendar.MILLISECOND, 0);
131
+            shiftSchedule.setStartTime(startCal.getTime());
132
+
133
+            // 设置完整的结束时间
134
+            Calendar endCal = (Calendar) shiftCalendar.clone();
135
+            if (isCrossDay) {
136
+                endCal.add(Calendar.DAY_OF_MONTH, 1);
137
+            }
138
+            endCal.set(Calendar.HOUR_OF_DAY, endTime);
139
+            endCal.set(Calendar.MINUTE, 0);
140
+            endCal.set(Calendar.SECOND, 0);
141
+            endCal.set(Calendar.MILLISECOND, 0);
142
+            shiftSchedule.setEndTime(endCal.getTime());
143
+
144
+            shiftSchedule.setCrossDay(isCrossDay ? 1 : 0);
145
+            shiftSchedule.setStatus("0");
146
+            scheduleList.add(shiftSchedule);
147
+
148
+            // 根据班次安排模式决定何时轮换科室
149
+            if (shiftIndex == shifts.length - 1) {
150
+                // 每天结束后轮换科室
151
+                currentDeptIndex = (currentDeptIndex + 1) % deptNames.length;
152
+            }
153
+        }
154
+
155
+        return scheduleList;
156
+    }
157
+
158
+}

+ 154 - 0
airport-item/src/main/resources/mapper/item/SimpleDutyScheduleMapper.xml

@@ -0,0 +1,154 @@
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.item.mapper.SimpleDutyScheduleMapper">
6
+
7
+    <resultMap type="SimpleDutySchedule" id="SimpleDutyScheduleResult">
8
+        <result property="id" column="id"/>
9
+        <result property="stationId" column="station_id"/>
10
+        <result property="stationName" column="station_name"/>
11
+        <result property="departmentId" column="department_id"/>
12
+        <result property="departmentName" column="department_name"/>
13
+        <result property="dutyDate" column="duty_date"/>
14
+        <result property="startTime" column="start_time"/>
15
+        <result property="endTime" column="end_time"/>
16
+        <result property="crossDay" column="cross_day"/>
17
+        <result property="status" column="status"/>
18
+        <result property="createBy" column="create_by"/>
19
+        <result property="createTime" column="create_time"/>
20
+        <result property="updateBy" column="update_by"/>
21
+        <result property="updateTime" column="update_time"/>
22
+        <result property="remark" column="remark"/>
23
+    </resultMap>
24
+
25
+    <sql id="selectSimpleDutyScheduleVo">
26
+        select id,
27
+               station_id,
28
+               station_name,
29
+               department_id,
30
+               department_name,
31
+               duty_date,
32
+               start_time,
33
+               end_time,
34
+               cross_day,
35
+               status,
36
+               create_by,
37
+               create_time,
38
+               update_by,
39
+               update_time,
40
+               remark
41
+        from simple_duty_schedule
42
+    </sql>
43
+
44
+    <select id="selectSimpleDutyScheduleByScheduleId" parameterType="Long" resultMap="SimpleDutyScheduleResult">
45
+        <include refid="selectSimpleDutyScheduleVo"/>
46
+        where id = #{id}
47
+    </select>
48
+
49
+    <select id="selectSimpleDutyScheduleList" parameterType="SimpleDutySchedule" resultMap="SimpleDutyScheduleResult">
50
+        <include refid="selectSimpleDutyScheduleVo"/>
51
+        <where>
52
+            <if test="stationName != null  and stationName != ''">and station_name = #{stationName}</if>
53
+            <if test="departmentName != null  and departmentName != ''">and department_name = #{departmentName}</if>
54
+            <if test="dutyDate != null ">and duty_date = #{dutyDate}</if>
55
+            <if test="startTime != null ">and start_time >= #{startTime}</if>
56
+            <if test="endTime != null ">and end_time &lt;= #{endTime}</if>
57
+            <if test="crossDay != null ">and cross_day = #{crossDay}</if>
58
+            <if test="status != null  and status != ''">and status = #{status}</if>
59
+        </where>
60
+        order by duty_date asc
61
+    </select>
62
+
63
+    <select id="selectSimpleDutyScheduleByDate" parameterType="Date" resultMap="SimpleDutyScheduleResult">
64
+        <include refid="selectSimpleDutyScheduleVo"/>
65
+        where start_time &lt;= #{dutyDate} and end_time >= #{dutyDate}
66
+    </select>
67
+
68
+    <select id="selectSimpleDutyScheduleByStationAndDate" resultMap="SimpleDutyScheduleResult">
69
+        <include refid="selectSimpleDutyScheduleVo"/>
70
+        where station_id = #{stationId} and department_id = #{departmentId} and duty_date = #{dutyDate}
71
+    </select>
72
+
73
+    <insert id="insertSimpleDutySchedule" parameterType="SimpleDutySchedule" useGeneratedKeys="true" keyProperty="id">
74
+        insert into simple_duty_schedule
75
+        <trim prefix="(" suffix=")" suffixOverrides=",">
76
+            <if test="stationId != null">station_id,</if>
77
+            <if test="stationName != null and stationName != ''">station_name,</if>
78
+            <if test="departmentId != null">department_id,</if>
79
+            <if test="departmentName != null and departmentName != ''">department_name,</if>
80
+            <if test="dutyDate != null">duty_date,</if>
81
+            <if test="startTime != null">start_time,</if>
82
+            <if test="endTime != null">end_time,</if>
83
+            <if test="crossDay != null">cross_day,</if>
84
+            <if test="status != null and status != ''">status,</if>
85
+            <if test="createBy != null and createBy != ''">create_by,</if>
86
+            <if test="createTime != null">create_time,</if>
87
+            <if test="updateBy != null and updateBy != ''">update_by,</if>
88
+            <if test="updateTime != null">update_time,</if>
89
+            <if test="remark != null and remark != ''">remark,</if>
90
+        </trim>
91
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
92
+            <if test="stationId != null">#{stationId},</if>
93
+            <if test="stationName != null and stationName != ''">#{stationName},</if>
94
+            <if test="departmentId != null">#{departmentId},</if>
95
+            <if test="departmentName != null and departmentName != ''">#{departmentName},</if>
96
+            <if test="dutyDate != null">#{dutyDate},</if>
97
+            <if test="startTime != null">#{startTime},</if>
98
+            <if test="endTime != null">#{endTime},</if>
99
+            <if test="crossDay != null">#{crossDay},</if>
100
+            <if test="status != null and status != ''">#{status},</if>
101
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
102
+            <if test="createTime != null">#{createTime},</if>
103
+            <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
104
+            <if test="updateTime != null">#{updateTime},</if>
105
+            <if test="remark != null and remark != ''">#{remark},</if>
106
+        </trim>
107
+    </insert>
108
+
109
+    <update id="updateSimpleDutySchedule" parameterType="SimpleDutySchedule">
110
+        update simple_duty_schedule
111
+        <trim prefix="SET" suffixOverrides=",">
112
+            <if test="stationId != null">station_id = #{stationId},</if>
113
+            <if test="stationName != null and stationName != ''">station_name = #{stationName},</if>
114
+            <if test="departmentId != null">department_id = #{departmentId},</if>
115
+            <if test="departmentName != null and departmentName != ''">department_name = #{departmentName},</if>
116
+            <if test="dutyDate != null">duty_date = #{dutyDate},</if>
117
+            <if test="startTime != null">start_time = #{startTime},</if>
118
+            <if test="endTime != null">end_time = #{endTime},</if>
119
+            <if test="crossDay != null">cross_day = #{crossDay},</if>
120
+            <if test="status != null and status != ''">status = #{status},</if>
121
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
122
+            <if test="updateTime != null">update_time = #{updateTime},</if>
123
+            <if test="remark != null and remark != ''">remark = #{remark},</if>
124
+        </trim>
125
+        where id = #{id}
126
+    </update>
127
+
128
+    <delete id="deleteSimpleDutyScheduleByScheduleId" parameterType="Long">
129
+        delete
130
+        from simple_duty_schedule
131
+        where id = #{id}
132
+    </delete>
133
+
134
+    <delete id="deleteSimpleDutyScheduleByScheduleIds" parameterType="String">
135
+        delete from simple_duty_schedule where id in
136
+        <foreach item="id" collection="array" open="(" separator="," close=")">
137
+            #{id}
138
+        </foreach>
139
+    </delete>
140
+
141
+
142
+    <insert id="batchInsertSimpleDutySchedule" parameterType="SimpleDutySchedule">
143
+        insert into simple_duty_schedule
144
+        (station_id, station_name, department_id, department_name, duty_date, start_time, end_time,
145
+        cross_day, status, create_by, create_time, update_by, update_time, remark)
146
+        values
147
+        <foreach item="item" collection="list" separator=",">
148
+            (#{item.stationId}, #{item.stationName}, #{item.departmentId}, #{item.departmentName},
149
+            #{item.dutyDate}, #{item.startTime}, #{item.endTime}, #{item.crossDay}, #{item.status},
150
+            #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.remark})
151
+        </foreach>
152
+    </insert>
153
+
154
+</mapper>