ソースを参照

第一次提交

wangxx 5 ヶ月 前
コミット
eaddc032f3
共有42 個のファイルを変更した4376 個の追加0 個の削除を含む
  1. 53 0
      airport-attendance/pom.xml
  2. 56 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceArea.java
  3. 124 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceCheckRecord.java
  4. 249 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendancePostRecord.java
  5. 168 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceRecord.java
  6. 71 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceTeamUserRecord.java
  7. 47 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/portrait/StationDeptWorkStats.java
  8. 18 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/portrait/WorkDateStats.java
  9. 18 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/domain/portrait/WorkHoursStats.java
  10. 70 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendancePostRecordImportVO.java
  11. 27 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountDTO.java
  12. 48 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountDetailDTO.java
  13. 33 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountRegionDTO.java
  14. 35 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountWaitDTO.java
  15. 52 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordImportVO.java
  16. 42 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordTerminlCountDetailDTO.java
  17. 37 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/enums/CheckInTypeEnum.java
  18. 26 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceAreaMapper.java
  19. 61 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceCheckRecordMapper.java
  20. 135 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendancePostRecordMapper.java
  21. 61 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceRecordMapper.java
  22. 74 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceTeamUserRecordMapper.java
  23. 32 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/AttendanceModuleIndicatorResult.java
  24. 86 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/AttendanceModuleIndicatorService.java
  25. 160 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/StationWorkStatsService.java
  26. 98 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/WorkingDateIndicator.java
  27. 106 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/WorkingHoursIndicator.java
  28. 23 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/AttendanceAreaService.java
  29. 61 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceCheckRecordService.java
  30. 136 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendancePostRecordService.java
  31. 94 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceRecordService.java
  32. 84 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceTeamUserRecordService.java
  33. 52 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceAreaServiceImpl.java
  34. 90 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceCheckRecordServiceImpl.java
  35. 269 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendancePostRecordServiceImpl.java
  36. 489 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceRecordServiceImpl.java
  37. 273 0
      airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceTeamUserRecordServiceImpl.java
  38. 58 0
      airport-attendance/src/main/resources/mapper/attendance/AttendanceAreaMapper.xml
  39. 126 0
      airport-attendance/src/main/resources/mapper/attendance/AttendanceCheckRecordMapper.xml
  40. 334 0
      airport-attendance/src/main/resources/mapper/attendance/AttendancePostRecordMapper.xml
  41. 161 0
      airport-attendance/src/main/resources/mapper/attendance/AttendanceRecordMapper.xml
  42. 139 0
      airport-attendance/src/main/resources/mapper/attendance/AttendanceTeamUserRecordMapper.xml

+ 53 - 0
airport-attendance/pom.xml

@@ -0,0 +1,53 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <parent>
6
+        <groupId>com.sundot.airport</groupId>
7
+        <artifactId>airport</artifactId>
8
+        <version>3.9.0</version>
9
+    </parent>
10
+    <modelVersion>4.0.0</modelVersion>
11
+
12
+    <artifactId>airport-attendance</artifactId>
13
+
14
+    <description>
15
+        attendance 考勤管理模块
16
+    </description>
17
+
18
+    <dependencies>
19
+
20
+        <!-- 通用工具-->
21
+        <dependency>
22
+            <groupId>com.sundot.airport</groupId>
23
+            <artifactId>airport-common</artifactId>
24
+        </dependency>
25
+
26
+        <dependency>
27
+            <groupId>com.sundot.airport</groupId>
28
+            <artifactId>airport-system</artifactId>
29
+        </dependency>
30
+
31
+        <dependency>
32
+            <groupId>org.projectlombok</groupId>
33
+            <artifactId>lombok</artifactId>
34
+            <scope>provided</scope>
35
+        </dependency>
36
+
37
+        <dependency>
38
+            <groupId>com.alibaba</groupId>
39
+            <artifactId>fastjson</artifactId>
40
+        </dependency>
41
+        <dependency>
42
+            <groupId>com.sundot.airport</groupId>
43
+            <artifactId>airport-system</artifactId>
44
+        </dependency>
45
+
46
+        <!-- 考试模块,用于打卡事件监听 -->
47
+        <dependency>
48
+            <groupId>com.sundot.airport</groupId>
49
+            <artifactId>airport-exam</artifactId>
50
+        </dependency>
51
+
52
+    </dependencies>
53
+</project>

+ 56 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceArea.java

@@ -0,0 +1,56 @@
1
+package com.sundot.airport.attendance.domain;
2
+
3
+import com.sundot.airport.common.core.domain.BaseEntity;
4
+import io.swagger.annotations.ApiModel;
5
+import io.swagger.annotations.ApiModelProperty;
6
+import lombok.Data;
7
+
8
+import java.math.BigDecimal;
9
+
10
+/**
11
+ * 打卡范围
12
+ */
13
+@Data
14
+@ApiModel(value = "AttendanceArea对象",description = "打卡范围")
15
+public class AttendanceArea extends BaseEntity {
16
+    /**
17
+     * 主键id
18
+     */
19
+    @ApiModelProperty("id")
20
+    private Integer id;
21
+    /**
22
+     * 区域名称
23
+     */
24
+    @ApiModelProperty("区域名称")
25
+    private String areaName;
26
+    /**
27
+     * 详细地址
28
+     */
29
+    @ApiModelProperty("详细地址")
30
+    private String address;
31
+    /**
32
+     * 中心点经度
33
+     */
34
+    @ApiModelProperty("中心点经度")
35
+    private BigDecimal longitude;
36
+    /**
37
+     * 中心点纬度
38
+     */
39
+    @ApiModelProperty("中心点纬度")
40
+    private BigDecimal latitude;
41
+    /**
42
+     * 打卡有效半径(单位:米)
43
+     */
44
+    @ApiModelProperty("打卡有效半径(单位:米)")
45
+    private Integer radius;
46
+    /**
47
+     * 状态(0=禁用 1=启用
48
+     */
49
+    @ApiModelProperty("状态(0=禁用 1=启用")
50
+    private Boolean status;
51
+    /**
52
+     * 用于接收距离计算结果
53
+     */
54
+    @ApiModelProperty("用于接收距离计算结果")
55
+    private BigDecimal distance;
56
+}

+ 124 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceCheckRecord.java

@@ -0,0 +1,124 @@
1
+package com.sundot.airport.attendance.domain;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import com.sundot.airport.common.annotation.Excel;
5
+import com.sundot.airport.common.core.domain.BaseEntity;
6
+import lombok.Data;
7
+import org.apache.commons.lang3.builder.ToStringBuilder;
8
+import org.apache.commons.lang3.builder.ToStringStyle;
9
+
10
+import java.util.Date;
11
+
12
+/**
13
+ * 打卡记录对象 attendance_check_record
14
+ *
15
+ * @author wangchong
16
+ * @date 2025-07-10
17
+ */
18
+@Data
19
+public class AttendanceCheckRecord extends BaseEntity {
20
+    private static final long serialVersionUID = 1L;
21
+
22
+    /**
23
+     * 租户号
24
+     */
25
+    private String tenantId;
26
+
27
+    /**
28
+     * 乐观锁
29
+     */
30
+    private Long revision;
31
+
32
+    /**
33
+     * 主键
34
+     */
35
+    private Long id;
36
+
37
+    /**
38
+     * 打卡人ID
39
+     */
40
+    private Long userId;
41
+
42
+    /**
43
+     * 打卡类型
44
+     */
45
+    private String checkInType;
46
+
47
+    /**
48
+     * 打卡时间
49
+     */
50
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
51
+    @Excel(name = "打卡时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 3)
52
+    private Date checkInTime;
53
+
54
+    /**
55
+     * 打卡位置
56
+     */
57
+    private String checkInPosition;
58
+
59
+    /**
60
+     * 打卡人名称
61
+     */
62
+    @Excel(name = "打卡人姓名", sort = 1)
63
+    private String userName;
64
+
65
+    /**
66
+     * 备注
67
+     */
68
+    @Excel(name = "备注", sort = 4)
69
+    private String remark;
70
+
71
+    /**
72
+     * 打卡类型名称
73
+     */
74
+    @Excel(name = "打卡类型名称", sort = 2)
75
+    private String checkInTypeDesc;
76
+
77
+    @Override
78
+    public String toString() {
79
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
80
+                .append("tenantId", getTenantId())
81
+                .append("revision", getRevision())
82
+                .append("createBy", getCreateBy())
83
+                .append("createTime", getCreateTime())
84
+                .append("updateBy", getUpdateBy())
85
+                .append("updateTime", getUpdateTime())
86
+                .append("id", getId())
87
+                .append("userId", getUserId())
88
+                .append("checkInType", getCheckInType())
89
+                .append("checkInTime", getCheckInTime())
90
+                .append("userName", getUserName())
91
+                .append("remark", getRemark())
92
+                .append("checkInTypeDesc", getCheckInTypeDesc())
93
+                .append("checkInDate", getCheckInDate())
94
+                .toString();
95
+    }
96
+
97
+    /**
98
+     * 打卡日期
99
+     */
100
+    @JsonFormat(pattern = "yyyy-MM-dd")
101
+    private Date checkInDate;
102
+
103
+
104
+    /**
105
+     * 打卡日期
106
+     */
107
+    @JsonFormat(pattern = "yyyy-MM-dd")
108
+    private Date checkInDateStart;
109
+
110
+
111
+    /**
112
+     * 打卡日期
113
+     */
114
+    @JsonFormat(pattern = "yyyy-MM-dd")
115
+    private Date checkInDateEnd;
116
+    /**
117
+     * 是否为梭班打卡
118
+     */
119
+    private Boolean shuttleShift;
120
+    /**
121
+     * 班次类型标识,SHUTTLE"表示梭班,后期有什么标识可以扩展
122
+     */
123
+    private String shiftType;
124
+}

+ 249 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendancePostRecord.java

@@ -0,0 +1,249 @@
1
+package com.sundot.airport.attendance.domain;
2
+
3
+import java.util.Date;
4
+
5
+import com.fasterxml.jackson.annotation.JsonFormat;
6
+import lombok.Data;
7
+import org.apache.commons.lang3.builder.ToStringBuilder;
8
+import org.apache.commons.lang3.builder.ToStringStyle;
9
+import com.sundot.airport.common.annotation.Excel;
10
+import com.sundot.airport.common.core.domain.BaseEntity;
11
+
12
+
13
+/**
14
+ * 上岗记录对象 gitattendance_post_record
15
+ *
16
+ * @author wangchong
17
+ * @date 2025-07-10
18
+ */
19
+@Data
20
+public class AttendancePostRecord extends BaseEntity {
21
+    private static final long serialVersionUID = 1L;
22
+
23
+    /**
24
+     * 租户号
25
+     */
26
+    private String tenantId;
27
+
28
+    /**
29
+     * 乐观锁
30
+     */
31
+    private Long revision;
32
+
33
+    /**
34
+     * 用户ID
35
+     */
36
+    private Long userId;
37
+
38
+    /**
39
+     * 通道编码
40
+     */
41
+    private String channelCode;
42
+
43
+    /**
44
+     * 班次编码
45
+     */
46
+    private String shiftCode;
47
+
48
+    /**
49
+     * 上岗时间
50
+     */
51
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
52
+    @Excel(name = "上岗时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 5)
53
+    private Date checkInTime;
54
+
55
+    /**
56
+     * 离岗时间
57
+     */
58
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
59
+    @Excel(name = "离岗时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 6)
60
+    private Date checkOutTime;
61
+
62
+    /**
63
+     * 工作时长(分钟)
64
+     */
65
+    @Excel(name = "工作时长(分钟)", sort = 7)
66
+    private Long workDuration;
67
+
68
+    /**
69
+     * 考勤状态
70
+     */
71
+    private String status;
72
+
73
+    /**
74
+     * 考勤日期
75
+     */
76
+    @JsonFormat(pattern = "yyyy-MM-dd")
77
+    @Excel(name = "考勤日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 4)
78
+    private Date attendanceDate;
79
+
80
+    /**
81
+     * 考勤班组ID
82
+     */
83
+    private Long attendanceTeamId;
84
+
85
+    /**
86
+     * 加班时长(分钟)
87
+     */
88
+    private Long overDuration;
89
+
90
+    /**
91
+     * 主键
92
+     */
93
+    private Long id;
94
+
95
+    /**
96
+     * 考勤班组名称
97
+     */
98
+    @Excel(name = "班组名称", sort = 2)
99
+    private String attendanceTeamName;
100
+
101
+    /**
102
+     * 考勤科室ID
103
+     */
104
+    private Long attendanceDepartmentId;
105
+
106
+    /**
107
+     * 考勤科室名称
108
+     */
109
+    @Excel(name = "科室名称", sort = 3)
110
+    private String attendanceDepartmentName;
111
+
112
+    /**
113
+     * 考勤机构站ID
114
+     */
115
+    private Long attendanceStationId;
116
+
117
+    /**
118
+     * 考勤机构站名称
119
+     */
120
+    private String attendanceStationName;
121
+
122
+    /**
123
+     * 通道名称
124
+     */
125
+    @Excel(name = "通道名称", sort = 10)
126
+    private String channelName;
127
+
128
+    /**
129
+     * 区域编码
130
+     */
131
+    private String regionalCode;
132
+
133
+    /**
134
+     * 区域名称
135
+     */
136
+    @Excel(name = "区域名称", sort = 9)
137
+    private String regionalName;
138
+
139
+    /**
140
+     * 航站楼编码
141
+     */
142
+    private String terminlCode;
143
+
144
+    /**
145
+     * 航站楼名称
146
+     */
147
+    @Excel(name = "航站楼名称", sort = 8)
148
+    private String terminlName;
149
+
150
+    /**
151
+     * 用户名称
152
+     */
153
+    @Excel(name = "用户姓名", sort = 1)
154
+    private String userName;
155
+
156
+    /**
157
+     * 班次名称
158
+     */
159
+    private String shiftName;
160
+
161
+    /**
162
+     * 备注
163
+     */
164
+    @Excel(name = "备注", sort = 12)
165
+    private String remark;
166
+
167
+    /**
168
+     * 考勤状态名称
169
+     */
170
+    private String statusDesc;
171
+
172
+    /**
173
+     * 岗位编码
174
+     */
175
+    private String positionCode;
176
+
177
+    @Override
178
+    public String toString() {
179
+        return "AttendancePostRecord{" +
180
+                "tenantId='" + tenantId + '\'' +
181
+                ", revision=" + revision +
182
+                ", userId=" + userId +
183
+                ", channelCode='" + channelCode + '\'' +
184
+                ", shiftCode='" + shiftCode + '\'' +
185
+                ", checkInTime=" + checkInTime +
186
+                ", checkOutTime=" + checkOutTime +
187
+                ", workDuration=" + workDuration +
188
+                ", status='" + status + '\'' +
189
+                ", attendanceDate=" + attendanceDate +
190
+                ", attendanceTeamId=" + attendanceTeamId +
191
+                ", overDuration=" + overDuration +
192
+                ", id=" + id +
193
+                ", attendanceTeamName='" + attendanceTeamName + '\'' +
194
+                ", attendanceDepartmentId=" + attendanceDepartmentId +
195
+                ", attendanceDepartmentName='" + attendanceDepartmentName + '\'' +
196
+                ", attendanceStationId=" + attendanceStationId +
197
+                ", attendanceStationName='" + attendanceStationName + '\'' +
198
+                ", channelName='" + channelName + '\'' +
199
+                ", regionalCode='" + regionalCode + '\'' +
200
+                ", regionalName='" + regionalName + '\'' +
201
+                ", terminlCode='" + terminlCode + '\'' +
202
+                ", terminlName='" + terminlName + '\'' +
203
+                ", userName='" + userName + '\'' +
204
+                ", shiftName='" + shiftName + '\'' +
205
+                ", remark='" + remark + '\'' +
206
+                ", statusDesc='" + statusDesc + '\'' +
207
+                ", positionCode='" + positionCode + '\'' +
208
+                ", positionName='" + positionName + '\'' +
209
+                ", locked=" + locked +
210
+                ", lockTime=" + lockTime +
211
+                '}';
212
+    }
213
+
214
+    /**
215
+     * 岗位名称
216
+     */
217
+    @Excel(name = "岗位名称", sort = 11)
218
+    private String positionName;
219
+
220
+
221
+    /**
222
+     * 是否锁定
223
+     */
224
+    private Boolean locked;
225
+
226
+    /**
227
+     * 锁定时间
228
+     */
229
+    private Date lockTime;
230
+
231
+    private Date checkInTimeStart;
232
+
233
+    private Date checkInTimeEnd;
234
+
235
+    private Integer checkOutTimeType;
236
+    /**
237
+     * 考勤开始时间
238
+     */
239
+    @JsonFormat(pattern = "yyyy-MM-dd")
240
+    private Date beginTime;
241
+    /**
242
+     * 考勤结束时间
243
+     */
244
+    @JsonFormat(pattern = "yyyy-MM-dd")
245
+    private Date endTime;
246
+
247
+
248
+
249
+}

+ 168 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceRecord.java

@@ -0,0 +1,168 @@
1
+package com.sundot.airport.attendance.domain;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import com.sundot.airport.common.annotation.Excel;
5
+import com.sundot.airport.common.core.domain.BaseEntity;
6
+import lombok.Data;
7
+import org.apache.commons.lang3.builder.ToStringBuilder;
8
+import org.apache.commons.lang3.builder.ToStringStyle;
9
+
10
+import java.util.Date;
11
+
12
+/**
13
+ * 考勤记录对象 attendance_record
14
+ *
15
+ * @author wangchong
16
+ * @date 2025-07-10
17
+ */
18
+@Data
19
+public class AttendanceRecord extends BaseEntity {
20
+    private static final long serialVersionUID = 1L;
21
+
22
+    /**
23
+     * 租户号
24
+     */
25
+    private String tenantId;
26
+
27
+    /**
28
+     * 乐观锁
29
+     */
30
+    private Long revision;
31
+
32
+    /**
33
+     * 主键
34
+     */
35
+    private Long id;
36
+
37
+    /**
38
+     * 用户ID
39
+     */
40
+    private Long userId;
41
+
42
+    /**
43
+     * 签到时间
44
+     */
45
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
46
+    @Excel(name = "签到时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 5)
47
+    private Date checkInTime;
48
+
49
+    /**
50
+     * 签退时间
51
+     */
52
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
53
+    @Excel(name = "签退时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 6)
54
+    private Date checkOutTime;
55
+
56
+    /**
57
+     * 工作时长(分钟)
58
+     */
59
+    @Excel(name = "工作时长(分钟)", sort = 7)
60
+    private Long workDuration;
61
+
62
+    /**
63
+     * 考勤状态
64
+     */
65
+    private String status;
66
+
67
+    /**
68
+     * 班组编码
69
+     */
70
+    private String teamCode;
71
+
72
+    /**
73
+     * 班组名称
74
+     */
75
+    @Excel(name = "班组名称", sort = 3)
76
+    private String teamName;
77
+
78
+    /**
79
+     * 科室编码
80
+     */
81
+    private String departmentCode;
82
+
83
+    /**
84
+     * 科室名称
85
+     */
86
+    @Excel(name = "科室名称", sort = 2)
87
+    private String departmentName;
88
+
89
+    /**
90
+     * 机构站编码
91
+     */
92
+    private String stationCode;
93
+
94
+    /**
95
+     * 机构站名称
96
+     */
97
+    private String stationName;
98
+
99
+    /**
100
+     * 用户名称
101
+     */
102
+    @Excel(name = "用户姓名", sort = 1)
103
+    private String userName;
104
+
105
+    /**
106
+     * 备注
107
+     */
108
+    @Excel(name = "备注", sort = 8)
109
+    private String remark;
110
+
111
+    /**
112
+     * 考勤日期
113
+     */
114
+    @JsonFormat(pattern = "yyyy-MM-dd")
115
+    @Excel(name = "考勤日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 4)
116
+    private Date attendanceDate;
117
+
118
+    /**
119
+     * 加班时长(分钟)
120
+     */
121
+    private Long overDuration;
122
+
123
+    /**
124
+     * 考勤状态名称
125
+     */
126
+    private String statusDesc;
127
+
128
+    @Override
129
+    public String toString() {
130
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
131
+                .append("tenantId", getTenantId())
132
+                .append("revision", getRevision())
133
+                .append("createBy", getCreateBy())
134
+                .append("createTime", getCreateTime())
135
+                .append("updateBy", getUpdateBy())
136
+                .append("updateTime", getUpdateTime())
137
+                .append("id", getId())
138
+                .append("userId", getUserId())
139
+                .append("checkInTime", getCheckInTime())
140
+                .append("checkOutTime", getCheckOutTime())
141
+                .append("workDuration", getWorkDuration())
142
+                .append("status", getStatus())
143
+                .append("teamCode", getTeamCode())
144
+                .append("teamName", getTeamName())
145
+                .append("departmentCode", getDepartmentCode())
146
+                .append("departmentName", getDepartmentName())
147
+                .append("stationCode", getStationCode())
148
+                .append("stationName", getStationName())
149
+                .append("userName", getUserName())
150
+                .append("remark", getRemark())
151
+                .append("attendanceDate", getAttendanceDate())
152
+                .append("overDuration", getOverDuration())
153
+                .append("statusDesc", getStatusDesc())
154
+                .toString();
155
+    }
156
+
157
+    /**
158
+     * 考勤日期
159
+     */
160
+    @JsonFormat(pattern = "yyyy-MM-dd")
161
+    private Date attendanceDateStart;
162
+
163
+    /**
164
+     * 考勤日期
165
+     */
166
+    @JsonFormat(pattern = "yyyy-MM-dd")
167
+    private Date attendanceDateEnd;
168
+}

+ 71 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/AttendanceTeamUserRecord.java

@@ -0,0 +1,71 @@
1
+package com.sundot.airport.attendance.domain;
2
+
3
+import java.util.Date;
4
+import com.fasterxml.jackson.annotation.JsonFormat;
5
+import io.swagger.annotations.ApiModelProperty;
6
+import lombok.Data;
7
+import lombok.EqualsAndHashCode;
8
+import org.apache.commons.lang3.builder.ToStringBuilder;
9
+import org.apache.commons.lang3.builder.ToStringStyle;
10
+import com.sundot.airport.common.annotation.Excel;
11
+import com.sundot.airport.common.core.domain.BaseEntity;
12
+
13
+/**
14
+ * 考勤班组成员对象 attendance_team_user_record
15
+ * 
16
+ * @author ruoyi
17
+ * @date 2025-09-06
18
+ */
19
+@EqualsAndHashCode(callSuper = true)
20
+@Data
21
+public class AttendanceTeamUserRecord extends BaseEntity
22
+{
23
+    private static final long serialVersionUID = 1L;
24
+
25
+    /** 用户ID */
26
+    @Excel(name = "用户ID")
27
+    @ApiModelProperty("用户ID")
28
+    private Long userId;
29
+
30
+    /** 用户code */
31
+    @Excel(name = "用户code")
32
+    @ApiModelProperty("用户code")
33
+    private String userCode;
34
+
35
+    /** 用户名称 */
36
+    @Excel(name = "用户名称")
37
+    @ApiModelProperty("用户名称")
38
+    private String userName;
39
+
40
+    /** 航站楼编码 */
41
+    @Excel(name = "航站楼编码")
42
+    @ApiModelProperty("航站楼编码")
43
+    private String terminlCode;
44
+
45
+    /** 航站楼名称 */
46
+    @Excel(name = "航站楼名称")
47
+    @ApiModelProperty("航站楼名称")
48
+    private String terminlName;
49
+
50
+    /** 考勤日期 */
51
+    @JsonFormat(pattern = "yyyy-MM-dd")
52
+    @Excel(name = "考勤日期", width = 30, dateFormat = "yyyy-MM-dd")
53
+    @ApiModelProperty("考勤日期")
54
+    private Date attendanceDate;
55
+
56
+    /** 考勤班组ID */
57
+    @Excel(name = "考勤班组ID")
58
+    @ApiModelProperty("考勤班组ID")
59
+    private Long attendanceTeamId;
60
+
61
+    /** 考勤班组名称 */
62
+    @Excel(name = "考勤班组名称")
63
+    @ApiModelProperty("考勤班组名称")
64
+    private String attendanceTeamName;
65
+
66
+    private  Date attendanceDateStart;
67
+
68
+    private  Date attendanceDateEnd;
69
+
70
+
71
+}

+ 47 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/portrait/StationDeptWorkStats.java

@@ -0,0 +1,47 @@
1
+package com.sundot.airport.attendance.domain.portrait;
2
+
3
+import lombok.Data;
4
+
5
+import java.math.BigDecimal;
6
+
7
+/**
8
+ * 站级和科级工作指标统计信息
9
+ * 用于表示站级和下属科级的工作指标统计信息
10
+ */
11
+@Data
12
+public class StationDeptWorkStats {
13
+    /**
14
+     * 统计维度(站和科)
15
+     */
16
+    private String dimension;
17
+    
18
+    /**
19
+     * 部门ID
20
+     */
21
+    private Long deptId;
22
+    
23
+    /**
24
+     * 部门名称
25
+     */
26
+    private String deptName;
27
+    
28
+    /**
29
+     * 人均出勤天数
30
+     */
31
+    private BigDecimal avgWorkingDays;
32
+    
33
+    /**
34
+     * 人均上岗时长
35
+     * 单位:小时
36
+     */
37
+    private BigDecimal avgWorkingHours;
38
+    
39
+    // 添加setter方法
40
+    public void setDeptId(Long deptId) {
41
+        this.deptId = deptId;
42
+    }
43
+    
44
+    public void setDeptName(String deptName) {
45
+        this.deptName = deptName;
46
+    }
47
+}

+ 18 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/portrait/WorkDateStats.java

@@ -0,0 +1,18 @@
1
+package com.sundot.airport.attendance.domain.portrait;
2
+
3
+import lombok.Data;
4
+
5
+import java.math.BigDecimal;
6
+
7
+/**
8
+ * 出勤天数统计信息
9
+ * 用于表示出勤天数的统计信息,包括总出勤天数和平均人数
10
+ */
11
+@Data
12
+public class WorkDateStats {
13
+    /** 总出勤天数 */
14
+    private Integer totalWorkingDays;
15
+    
16
+    /** 平均人数 */
17
+    private BigDecimal averagePersonnel;
18
+}

+ 18 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/domain/portrait/WorkHoursStats.java

@@ -0,0 +1,18 @@
1
+package com.sundot.airport.attendance.domain.portrait;
2
+
3
+import lombok.Data;
4
+
5
+import java.math.BigDecimal;
6
+
7
+/**
8
+ * 上岗时长统计信息
9
+ * 用于表示上岗时长的统计信息,包括总上岗时长和平均人数
10
+ */
11
+@Data
12
+public class WorkHoursStats {
13
+    /** 总上岗时长(小时) */
14
+    private Long totalWorkingHours;
15
+    
16
+    /** 平均上岗时长 */
17
+    private BigDecimal averagePersonnel;
18
+}

+ 70 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendancePostRecordImportVO.java

@@ -0,0 +1,70 @@
1
+package com.sundot.airport.attendance.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import com.sundot.airport.common.annotation.Excel;
5
+import lombok.Data;
6
+
7
+import java.io.Serializable;
8
+import java.util.Date;
9
+
10
+/**
11
+ * 上岗记录导入
12
+ *
13
+ * @Author: wangchong
14
+ * @Date: 2025/7/16 14:55
15
+ **/
16
+@Data
17
+public class AttendancePostRecordImportVO implements Serializable {
18
+    private static final long serialVersionUID = 4927088909940841778L;
19
+
20
+    /**
21
+     * 用户名称
22
+     */
23
+    @Excel(name = "用户姓名(登录账号)", width = 30, sort = 1)
24
+    private String userName;
25
+
26
+    /**
27
+     * 考勤日期
28
+     */
29
+    @JsonFormat(pattern = "yyyy-MM-dd")
30
+    @Excel(name = "考勤日期(yyyy-MM-dd)", width = 30, dateFormat = "yyyy-MM-dd", sort = 2)
31
+    private Date attendanceDate;
32
+
33
+    /**
34
+     * 上岗时间
35
+     */
36
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
37
+    @Excel(name = "上岗时间(yyyy-MM-dd HH:mm:ss)", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 3)
38
+    private Date checkInTime;
39
+
40
+    /**
41
+     * 离岗时间
42
+     */
43
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
44
+    @Excel(name = "离岗时间(yyyy-MM-dd HH:mm:ss)", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 4)
45
+    private Date checkOutTime;
46
+
47
+    /**
48
+     * 考勤班组名称
49
+     */
50
+    @Excel(name = "班组(机构站/科室/班组)", width = 30, sort = 5)
51
+    private String attendanceTeamName;
52
+
53
+    /**
54
+     * 通道名称
55
+     */
56
+    @Excel(name = "通道(航站楼/区域/通道)", width = 30, sort = 6)
57
+    private String channelName;
58
+
59
+    /**
60
+     * 岗位名称
61
+     */
62
+    @Excel(name = "岗位名称", width = 30, sort = 7)
63
+    private String positionName;
64
+
65
+    /**
66
+     * 备注
67
+     */
68
+    @Excel(name = "备注", width = 30, sort = 8)
69
+    private String remark;
70
+}

+ 27 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountDTO.java

@@ -0,0 +1,27 @@
1
+package com.sundot.airport.attendance.dto;
2
+
3
+import io.swagger.annotations.ApiModelProperty;
4
+import lombok.Data;
5
+
6
+import java.util.List;
7
+
8
+
9
+@Data
10
+public class AttendanceRecordCountDTO {
11
+
12
+
13
+    @ApiModelProperty("通道总数")
14
+    private Integer channelCount;
15
+
16
+
17
+    @ApiModelProperty("维护人数")
18
+    private Integer maintainCount;
19
+
20
+    @ApiModelProperty("在岗人数(上通道人数)")
21
+    private Integer onDutyCount;
22
+
23
+
24
+    @ApiModelProperty("航站楼详细在岗信息")
25
+    private List<AttendanceRecordTerminlCountDetailDTO> terminlDetail;
26
+
27
+}

+ 48 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountDetailDTO.java

@@ -0,0 +1,48 @@
1
+package com.sundot.airport.attendance.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
+@Data
10
+public class AttendanceRecordCountDetailDTO {
11
+
12
+    @ApiModelProperty("航站楼编码")
13
+    private String terminlCode;
14
+
15
+    @ApiModelProperty("航站楼名称")
16
+    private String terminlName;
17
+
18
+    @ApiModelProperty("区域code")
19
+    private String regionalCode;
20
+
21
+    @ApiModelProperty("区域名称")
22
+    private String regionalName;
23
+
24
+    @ApiModelProperty("通道名称")
25
+    private String channelName;
26
+
27
+    @ApiModelProperty("通道编码")
28
+    private String channelCode;
29
+
30
+    @ApiModelProperty("班级名称")
31
+    private String attendanceTeamName;
32
+
33
+    @ApiModelProperty("班级编码")
34
+    private String attendanceTeamCode;
35
+
36
+    @ApiModelProperty("状态(0:关闭 1:开启)")
37
+    private String status;
38
+
39
+    @ApiModelProperty("在岗人数")
40
+    private Integer onDutyCount;
41
+
42
+    @ApiModelProperty("在岗班组")
43
+     private String onDutyTeamName;
44
+
45
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
46
+    @ApiModelProperty("上岗时间")
47
+    private Date onDutyTime;
48
+}

+ 33 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountRegionDTO.java

@@ -0,0 +1,33 @@
1
+package com.sundot.airport.attendance.dto;
2
+
3
+import io.swagger.annotations.ApiModelProperty;
4
+import lombok.Data;
5
+
6
+import java.util.List;
7
+
8
+@Data
9
+public class AttendanceRecordCountRegionDTO {
10
+    @ApiModelProperty("航站楼编码")
11
+    private String terminlCode;
12
+
13
+    @ApiModelProperty("航站楼名称")
14
+    private String terminlName;
15
+
16
+    @ApiModelProperty("区域code")
17
+    private String regionalCode;
18
+
19
+    @ApiModelProperty("区域名称")
20
+    private String regionalName;
21
+
22
+    @ApiModelProperty("开放人数量")
23
+    private Integer  openUserCount;
24
+
25
+    @ApiModelProperty("开放通道数")
26
+    private Integer openChannelCount;
27
+
28
+
29
+    @ApiModelProperty("各个通道下各个班组在岗详情")
30
+    private List<AttendanceRecordCountDetailDTO> detailList;
31
+
32
+
33
+}

+ 35 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordCountWaitDTO.java

@@ -0,0 +1,35 @@
1
+package com.sundot.airport.attendance.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
+@Data
10
+public class AttendanceRecordCountWaitDTO {
11
+    @ApiModelProperty("航站楼编码")
12
+    private String terminlCode;
13
+
14
+    @ApiModelProperty("航站楼名称")
15
+    private String terminlName;
16
+
17
+    @ApiModelProperty("区域code")
18
+    private Long regionalCode;
19
+
20
+    @ApiModelProperty("区域名称")
21
+    private String regionalName;
22
+
23
+    @ApiModelProperty("待岗人数")
24
+    private Integer waitCount;
25
+
26
+    @ApiModelProperty("待岗班组")
27
+    private String waitTeamName;
28
+
29
+    @ApiModelProperty("待岗班组code")
30
+    private Long waitTeamCode;
31
+
32
+    @ApiModelProperty("下岗时间")
33
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
34
+    private Date checkOutTime;
35
+}

+ 52 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordImportVO.java

@@ -0,0 +1,52 @@
1
+package com.sundot.airport.attendance.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import com.sundot.airport.common.annotation.Excel;
5
+import lombok.Data;
6
+
7
+import java.io.Serializable;
8
+import java.util.Date;
9
+
10
+/**
11
+ * 考勤记录导入
12
+ *
13
+ * @Author: wangchong
14
+ * @Date: 2025/7/16 14:54
15
+ **/
16
+@Data
17
+public class AttendanceRecordImportVO implements Serializable {
18
+    private static final long serialVersionUID = 2245380666800347159L;
19
+
20
+    /**
21
+     * 用户姓名(登录账号)
22
+     */
23
+    @Excel(name = "用户姓名(登录账号)", width = 30, sort = 1)
24
+    private String userName;
25
+
26
+    /**
27
+     * 考勤日期
28
+     */
29
+    @JsonFormat(pattern = "yyyy-MM-dd")
30
+    @Excel(name = "考勤日期(yyyy-MM-dd)", width = 30, dateFormat = "yyyy-MM-dd", sort = 2)
31
+    private Date attendanceDate;
32
+
33
+    /**
34
+     * 签到时间
35
+     */
36
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
37
+    @Excel(name = "签到时间(yyyy-MM-dd HH:mm:ss)", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 3)
38
+    private Date checkInTime;
39
+
40
+    /**
41
+     * 签退时间
42
+     */
43
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
44
+    @Excel(name = "签退时间(yyyy-MM-dd HH:mm:ss)", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 4)
45
+    private Date checkOutTime;
46
+
47
+    /**
48
+     * 备注
49
+     */
50
+    @Excel(name = "备注", sort = 5)
51
+    private String remark;
52
+}

+ 42 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/dto/AttendanceRecordTerminlCountDetailDTO.java

@@ -0,0 +1,42 @@
1
+package com.sundot.airport.attendance.dto;
2
+
3
+import io.swagger.annotations.ApiModelProperty;
4
+import lombok.Data;
5
+
6
+import java.util.List;
7
+
8
+/**
9
+ * 各个航站楼下的信息
10
+ */
11
+@Data
12
+public class AttendanceRecordTerminlCountDetailDTO {
13
+
14
+    @ApiModelProperty("航站楼编码")
15
+    private String terminlCode;
16
+
17
+    @ApiModelProperty("航站楼名称")
18
+    private String terminlName;
19
+
20
+    @ApiModelProperty("总人数")
21
+    private Integer openChannelCount;
22
+
23
+    @ApiModelProperty("在岗人数")
24
+    private Integer onDutyCount;
25
+
26
+    @ApiModelProperty("总通道数")
27
+    private Integer channelCount;
28
+
29
+    @ApiModelProperty("在岗通道数")
30
+    private Integer onDutyChannelCount;
31
+
32
+    @ApiModelProperty("航站楼下各个区域在岗详情")
33
+    private List<AttendanceRecordCountRegionDTO> region;
34
+
35
+    @ApiModelProperty("航站楼下各个班组待岗详情")
36
+    private List<AttendanceRecordCountWaitDTO> waitList;
37
+
38
+
39
+
40
+
41
+
42
+}

+ 37 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/enums/CheckInTypeEnum.java

@@ -0,0 +1,37 @@
1
+package com.sundot.airport.attendance.enums;
2
+
3
+import lombok.AllArgsConstructor;
4
+import lombok.Getter;
5
+import lombok.Setter;
6
+
7
+/**
8
+ * 打卡类型
9
+ *
10
+ * @Author: wangchong
11
+ * @Date: 2025/7/11 09:37
12
+ **/
13
+@AllArgsConstructor
14
+@Getter
15
+public enum CheckInTypeEnum {
16
+
17
+    CLOCK_IN(136, "上班", "CLOCK_IN"),
18
+    CLOCK_OUT(137, "下班", "CLOCK_OUT"),
19
+    POST_IN(138, "上岗", "POST_IN"),
20
+    POST_OUT(139, "下岗", "POST_OUT"),
21
+    ;
22
+
23
+    /**
24
+     * code
25
+     */
26
+    private final Integer code;
27
+
28
+    /**
29
+     * name
30
+     */
31
+    private final String name;
32
+
33
+    /**
34
+     * value
35
+     */
36
+    private final String value;
37
+}

+ 26 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceAreaMapper.java

@@ -0,0 +1,26 @@
1
+package com.sundot.airport.attendance.mapper;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceArea;
4
+import org.apache.ibatis.annotations.Param;
5
+
6
+import java.math.BigDecimal;
7
+import java.util.List;
8
+
9
+public interface AttendanceAreaMapper {
10
+
11
+    /**
12
+     *
13
+     * @param lng 用户经度
14
+     * @param lat 用户纬度
15
+     * @return 符合条件的考勤区域列表
16
+     */
17
+    List<AttendanceArea> findValidAreasEfficiently(
18
+            @Param("lng") BigDecimal lng,
19
+            @Param("lat") BigDecimal lat);
20
+
21
+    /**
22
+     * 查询考勤列表
23
+     * @return
24
+     */
25
+    List<AttendanceArea> areaList();
26
+}

+ 61 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendanceCheckRecordMapper.java

@@ -0,0 +1,61 @@
1
+package com.sundot.airport.attendance.mapper;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceCheckRecord;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * 打卡记录Mapper接口
9
+ *
10
+ * @author wangchong
11
+ * @date 2025-07-10
12
+ */
13
+public interface AttendanceCheckRecordMapper {
14
+    /**
15
+     * 查询打卡记录
16
+     *
17
+     * @param id 打卡记录主键
18
+     * @return 打卡记录
19
+     */
20
+    public AttendanceCheckRecord selectAttendanceCheckRecordById(Long id);
21
+
22
+    /**
23
+     * 查询打卡记录列表
24
+     *
25
+     * @param attendanceCheckRecord 打卡记录
26
+     * @return 打卡记录集合
27
+     */
28
+    public List<AttendanceCheckRecord> selectAttendanceCheckRecordList(AttendanceCheckRecord attendanceCheckRecord);
29
+
30
+    /**
31
+     * 新增打卡记录
32
+     *
33
+     * @param attendanceCheckRecord 打卡记录
34
+     * @return 结果
35
+     */
36
+    public int insertAttendanceCheckRecord(AttendanceCheckRecord attendanceCheckRecord);
37
+
38
+    /**
39
+     * 修改打卡记录
40
+     *
41
+     * @param attendanceCheckRecord 打卡记录
42
+     * @return 结果
43
+     */
44
+    public int updateAttendanceCheckRecord(AttendanceCheckRecord attendanceCheckRecord);
45
+
46
+    /**
47
+     * 删除打卡记录
48
+     *
49
+     * @param id 打卡记录主键
50
+     * @return 结果
51
+     */
52
+    public int deleteAttendanceCheckRecordById(Long id);
53
+
54
+    /**
55
+     * 批量删除打卡记录
56
+     *
57
+     * @param ids 需要删除的数据主键集合
58
+     * @return 结果
59
+     */
60
+    public int deleteAttendanceCheckRecordByIds(Long[] ids);
61
+}

+ 135 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/mapper/AttendancePostRecordMapper.java

@@ -0,0 +1,135 @@
1
+package com.sundot.airport.attendance.mapper;
2
+
3
+import com.sundot.airport.attendance.domain.AttendancePostRecord;
4
+import org.apache.ibatis.annotations.Param;
5
+
6
+import java.util.Date;
7
+import java.util.List;
8
+
9
+/**
10
+ * 上岗记录Mapper接口
11
+ *
12
+ * @author wangchong
13
+ * @date 2025-07-10
14
+ */
15
+public interface AttendancePostRecordMapper {
16
+    /**
17
+     * 查询上岗记录
18
+     *
19
+     * @param id 上岗记录主键
20
+     * @return 上岗记录
21
+     */
22
+    public AttendancePostRecord selectAttendancePostRecordById(Long id);
23
+
24
+    /**
25
+     * 查询上岗记录列表
26
+     *
27
+     * @param attendancePostRecord 上岗记录
28
+     * @return 上岗记录集合
29
+     */
30
+    public List<AttendancePostRecord> selectAttendancePostRecordList(AttendancePostRecord attendancePostRecord);
31
+
32
+    /**
33
+     * 新增上岗记录
34
+     *
35
+     * @param attendancePostRecord 上岗记录
36
+     * @return 结果
37
+     */
38
+    public int insertAttendancePostRecord(AttendancePostRecord attendancePostRecord);
39
+
40
+    /**
41
+     * 修改上岗记录
42
+     *
43
+     * @param attendancePostRecord 上岗记录
44
+     * @return 结果
45
+     */
46
+    public int updateAttendancePostRecord(AttendancePostRecord attendancePostRecord);
47
+
48
+    /**
49
+     * 删除上岗记录
50
+     *
51
+     * @param id 上岗记录主键
52
+     * @return 结果
53
+     */
54
+    public int deleteAttendancePostRecordById(Long id);
55
+
56
+    /**
57
+     * 批量删除上岗记录
58
+     *
59
+     * @param ids 需要删除的数据主键集合
60
+     * @return 结果
61
+     */
62
+    public int deleteAttendancePostRecordByIds(Long[] ids);
63
+
64
+    /**
65
+     * 查询指定用户当天的上岗记录
66
+     *
67
+     * @param userId 用户ID
68
+     * @param today 当天日期
69
+     * @return 上岗记录集合
70
+     */
71
+    public List<AttendancePostRecord> selectTodayRecordsByUserId(@Param("userId") Long userId, @Param("today") Date today);
72
+
73
+    /**
74
+     * 查询指定班次代码当天的上岗记录
75
+     *
76
+     * @param today 当天日期
77
+     * @param shiftCodes 班次代码列表
78
+     * @return 上岗记录集合
79
+     */
80
+    public List<AttendancePostRecord> selectTodayRecordsByShiftCodes(@Param("today") Date today, @Param("shiftCodes") List<String> shiftCodes);
81
+
82
+    /**
83
+     * 根据指定时间获取用户的在岗位置信息
84
+     *
85
+     * @param userId 用户ID
86
+     * @param queryTime 查询时间
87
+     * @return 上岗记录集合
88
+     */
89
+    public List<AttendancePostRecord> selectLocationsByTime(@Param("userId") Long userId, @Param("queryTime") Date queryTime);
90
+
91
+    /**
92
+     *  根据通道名称和指定时间获取通道的在岗位置信息
93
+     * @param channelCodeList 通道code
94
+     * @param queryTime 查询时间
95
+     * @return 上岗记录集合
96
+     */
97
+    List<AttendancePostRecord> selectLocationsByTimeAndChannelName(@Param("channelCodeList") List<String> channelCodeList, @Param("queryTime") Date queryTime, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
98
+
99
+    /**
100
+     *  查询指定班次代码的下班时间
101
+     * @param today
102
+     * @param shiftCodes
103
+     * @return
104
+     */
105
+    List<AttendancePostRecord> selectCheckOutTimeByShiftCodes(@Param("today") Date today, @Param("shiftCodes") List<String> shiftCodes);
106
+
107
+    /**
108
+     *
109
+     * @return
110
+     */
111
+    List<AttendancePostRecord> selectKezhangRecordCount(@Param("userId") Long userId, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
112
+
113
+    /**
114
+     * 查询指定用户指定时间最后的记录
115
+     * @param userId 用户ID
116
+     * @return 上岗记录集合
117
+     */
118
+    List<AttendancePostRecord> queryLastTime(@Param("userId") Long userId);
119
+
120
+    /**
121
+     *  查询指定时间段内所有考勤记录的ke_id
122
+     * @param startTime  开始时间
123
+     * @param endTime 结束时间
124
+     * @return
125
+     */
126
+    List<Long> queryKeIds(@Param("startTime")Date startTime, @Param("endTime") Date endTime);
127
+
128
+    /**
129
+     *  查询指定时间段内班组内所有考勤记录
130
+     * @param date
131
+     * @param attendanceTeamId
132
+     * @return
133
+     */
134
+    List<AttendancePostRecord> selectTeamOnList( @Param("date") Date date,@Param("attendanceTeamId") Long attendanceTeamId);
135
+}

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

@@ -0,0 +1,61 @@
1
+package com.sundot.airport.attendance.mapper;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceRecord;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * 考勤记录Mapper接口
9
+ *
10
+ * @author wangchong
11
+ * @date 2025-07-10
12
+ */
13
+public interface AttendanceRecordMapper {
14
+    /**
15
+     * 查询考勤记录
16
+     *
17
+     * @param id 考勤记录主键
18
+     * @return 考勤记录
19
+     */
20
+    public AttendanceRecord selectAttendanceRecordById(Long id);
21
+
22
+    /**
23
+     * 查询考勤记录列表
24
+     *
25
+     * @param attendanceRecord 考勤记录
26
+     * @return 考勤记录集合
27
+     */
28
+    public List<AttendanceRecord> selectAttendanceRecordList(AttendanceRecord attendanceRecord);
29
+
30
+    /**
31
+     * 新增考勤记录
32
+     *
33
+     * @param attendanceRecord 考勤记录
34
+     * @return 结果
35
+     */
36
+    public int insertAttendanceRecord(AttendanceRecord attendanceRecord);
37
+
38
+    /**
39
+     * 修改考勤记录
40
+     *
41
+     * @param attendanceRecord 考勤记录
42
+     * @return 结果
43
+     */
44
+    public int updateAttendanceRecord(AttendanceRecord attendanceRecord);
45
+
46
+    /**
47
+     * 删除考勤记录
48
+     *
49
+     * @param id 考勤记录主键
50
+     * @return 结果
51
+     */
52
+    public int deleteAttendanceRecordById(Long id);
53
+
54
+    /**
55
+     * 批量删除考勤记录
56
+     *
57
+     * @param ids 需要删除的数据主键集合
58
+     * @return 结果
59
+     */
60
+    public int deleteAttendanceRecordByIds(Long[] ids);
61
+}

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

@@ -0,0 +1,74 @@
1
+package com.sundot.airport.attendance.mapper;
2
+
3
+import java.util.Date;
4
+import java.util.List;
5
+import com.sundot.airport.attendance.domain.AttendanceTeamUserRecord;
6
+import org.apache.ibatis.annotations.Param;
7
+
8
+/**
9
+ * 考勤班组成员Mapper接口
10
+ * 
11
+ * @author ruoyi
12
+ * @date 2025-09-06
13
+ */
14
+public interface AttendanceTeamUserRecordMapper 
15
+{
16
+    /**
17
+     * 查询考勤班组成员
18
+     * 
19
+     * @param userId 考勤班组成员主键
20
+     * @return 考勤班组成员
21
+     */
22
+    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(@Param("userId")  Long userId, @Param("attendanceDate") Date attendanceDate);
23
+
24
+    /**
25
+     * 查询考勤班组成员列表
26
+     * 
27
+     * @param attendanceTeamUserRecord 考勤班组成员
28
+     * @return 考勤班组成员集合
29
+     */
30
+    public List<AttendanceTeamUserRecord> selectAttendanceTeamUserRecordList(AttendanceTeamUserRecord attendanceTeamUserRecord);
31
+
32
+    /**
33
+     * 新增考勤班组成员
34
+     * 
35
+     * @param attendanceTeamUserRecord 考勤班组成员
36
+     * @return 结果
37
+     */
38
+    public int insertAttendanceTeamUserRecord(AttendanceTeamUserRecord attendanceTeamUserRecord);
39
+
40
+    /**
41
+     * 修改考勤班组成员
42
+     * 
43
+     * @param attendanceTeamUserRecord 考勤班组成员
44
+     * @return 结果
45
+     */
46
+    public int updateAttendanceTeamUserRecord(AttendanceTeamUserRecord attendanceTeamUserRecord);
47
+
48
+    /**
49
+     * 删除考勤班组成员
50
+     * 
51
+     * @param userId 考勤班组成员主键
52
+     * @return 结果
53
+     */
54
+    public int deleteAttendanceTeamUserRecordByUserId(Long userId);
55
+
56
+    /**
57
+     * 批量删除考勤班组成员
58
+     * 
59
+     * @param userIds 需要删除的数据主键集合
60
+     * @return 结果
61
+     */
62
+    public int deleteAttendanceTeamUserRecordByUserIds(Long[] userIds);
63
+
64
+    int insertAttendanceTeamUserRecordList(List<AttendanceTeamUserRecord> attendanceTeamUserRecord);
65
+    /**
66
+     * 根据用户ID和时间范围查询考勤班组成员
67
+     *
68
+     * @param userId 用户ID
69
+     * @param startDate 开始日期
70
+     * @param endDate 结束日期
71
+     * @return 考勤班组成员列表
72
+     */
73
+    List<AttendanceTeamUserRecord> selectAttendanceTeamUserRecordByUserIdAndDateRange(Long userId, Date startDate, Date endDate);
74
+}

+ 32 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/AttendanceModuleIndicatorResult.java

@@ -0,0 +1,32 @@
1
+package com.sundot.airport.attendance.portrait;
2
+
3
+import com.sundot.airport.attendance.domain.portrait.StationDeptWorkStats;
4
+import com.sundot.airport.common.domain.portrait.BaseModuleIndicatorResult;
5
+import io.swagger.annotations.ApiModelProperty;
6
+import lombok.Data;
7
+
8
+import java.util.List;
9
+
10
+/**
11
+ * 考勤模块指标结果类
12
+ * 用于存储考勤模块的指标数据
13
+ */
14
+@Data
15
+public class AttendanceModuleIndicatorResult extends BaseModuleIndicatorResult {
16
+    
17
+    /** 出勤天数 */
18
+    @ApiModelProperty(value = "出勤天数")
19
+    private Object workingDate;
20
+    
21
+    /** 上岗时长 */
22
+    @ApiModelProperty(value = "出勤天数")
23
+    private Object workingHours;
24
+
25
+    /** 站的统计列表 */
26
+    @ApiModelProperty(value = "出勤天数")
27
+    private List<StationDeptWorkStats> stationDeptWorkStats;
28
+    
29
+    public AttendanceModuleIndicatorResult() {
30
+        setModuleName("attendance");
31
+    }
32
+}

+ 86 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/AttendanceModuleIndicatorService.java

@@ -0,0 +1,86 @@
1
+package com.sundot.airport.attendance.portrait;
2
+
3
+import cn.hutool.core.collection.CollectionUtil;
4
+import com.sundot.airport.attendance.domain.AttendancePostRecord;
5
+import com.sundot.airport.attendance.domain.AttendanceRecord;
6
+import com.sundot.airport.attendance.domain.portrait.StationDeptWorkStats;
7
+import com.sundot.airport.attendance.mapper.AttendancePostRecordMapper;
8
+import com.sundot.airport.attendance.mapper.AttendanceRecordMapper;
9
+import com.sundot.airport.common.core.domain.entity.SysDept;
10
+import com.sundot.airport.common.domain.portrait.IndicatorCalculateParams;
11
+import com.sundot.airport.common.enums.DeptTypeEnum;
12
+import com.sundot.airport.common.enums.portrait.IndicatorType;
13
+import com.sundot.airport.common.enums.portrait.UserType;
14
+import com.sundot.airport.common.service.portrait.AbstractModuleIndicatorService;
15
+import com.sundot.airport.common.service.portrait.Indicator;
16
+import com.sundot.airport.system.service.ISysDeptService;
17
+import org.springframework.beans.factory.annotation.Autowired;
18
+import org.springframework.stereotype.Service;
19
+
20
+import java.util.List;
21
+import java.math.BigDecimal;
22
+import java.util.Set;
23
+import java.util.HashSet;
24
+import java.util.stream.Collectors;
25
+import javax.annotation.PostConstruct;
26
+
27
+/**
28
+ * 考勤模块指标服务实现
29
+ * 处理考勤相关的指标计算
30
+ */
31
+@Service
32
+public class AttendanceModuleIndicatorService extends AbstractModuleIndicatorService<AttendanceModuleIndicatorResult> {
33
+    
34
+    @Autowired
35
+    private Set<Indicator<?>> indicators;
36
+    
37
+    @Autowired
38
+    private AttendanceRecordMapper attendanceRecordMapper;
39
+
40
+    @Autowired
41
+    private AttendancePostRecordMapper attendancePostRecordMapper;
42
+
43
+    @Autowired
44
+    private ISysDeptService sysDeptService;
45
+    
46
+    private Set<IndicatorType> supportedIndicatorTypes = new HashSet<>();
47
+    
48
+    @Override
49
+    public String getModuleName() {
50
+        return "attendance";
51
+    }
52
+    
53
+    @PostConstruct
54
+    public void initSupportedIndicatorTypes() {
55
+        // 初始化支持的指标类型
56
+        if (indicators != null) {
57
+            for (Indicator<?> indicator : indicators) {
58
+                IndicatorType type = IndicatorType.fromCode(indicator.getName());
59
+                if (type != null && !supportedIndicatorTypes.contains(type) && type.getModule().equals(getModuleName())) {
60
+                    supportedIndicatorTypes.add(type);
61
+                }
62
+            }
63
+        }
64
+    }
65
+    
66
+    @Override
67
+    protected AttendanceModuleIndicatorResult createResult() {
68
+        return new AttendanceModuleIndicatorResult();
69
+    }
70
+    
71
+    @Override
72
+    protected Set<IndicatorType> getSupportedIndicatorTypes() {
73
+        return supportedIndicatorTypes;
74
+    }
75
+    
76
+    @Override
77
+    protected Set<Indicator<?>> getIndicators() {
78
+        return indicators;
79
+    }
80
+    
81
+    @Override
82
+    protected void setIndicatorResult(Indicator<?> indicator, AttendanceModuleIndicatorResult result) {
83
+        // 直接调用指标的setResult方法,将值设置到结果对象中
84
+        indicator.setResult(result);
85
+    }
86
+}

+ 160 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/StationWorkStatsService.java

@@ -0,0 +1,160 @@
1
+package com.sundot.airport.attendance.portrait;
2
+
3
+import cn.hutool.core.collection.CollectionUtil;
4
+import cn.hutool.core.util.StrUtil;
5
+import com.sundot.airport.attendance.domain.portrait.StationDeptWorkStats;
6
+import com.sundot.airport.attendance.domain.portrait.WorkDateStats;
7
+import com.sundot.airport.attendance.domain.portrait.WorkHoursStats;
8
+import com.sundot.airport.common.core.domain.entity.SysDept;
9
+import com.sundot.airport.common.core.domain.entity.SysUser;
10
+import com.sundot.airport.common.domain.portrait.IndicatorCalculateParams;
11
+import com.sundot.airport.common.domain.portrait.ModularIndicatorResult;
12
+import com.sundot.airport.common.enums.DeptTypeEnum;
13
+import com.sundot.airport.common.enums.RoleTypeEnum;
14
+import com.sundot.airport.common.enums.portrait.UserType;
15
+import com.sundot.airport.system.service.ISysDeptService;
16
+import com.sundot.airport.system.service.ISysUserService;
17
+import com.sundot.airport.system.service.portrait.UserPortraitService;
18
+import org.springframework.beans.factory.annotation.Autowired;
19
+import org.springframework.stereotype.Service;
20
+
21
+import java.math.BigDecimal;
22
+import java.util.Arrays;
23
+import java.util.List;
24
+import java.util.Optional;
25
+import java.util.stream.Collectors;
26
+
27
+/**
28
+ * 站级工作统计服务
29
+ * 专门处理考勤模块中站级部门的特殊统计逻辑
30
+ */
31
+@Service
32
+public class StationWorkStatsService {
33
+
34
+    
35
+    @Autowired
36
+    private ISysDeptService sysDeptService;
37
+
38
+    @Autowired
39
+    private UserPortraitService userPortraitService;
40
+
41
+    @Autowired
42
+    private ISysUserService iSysUserService;
43
+    
44
+    /**
45
+     * 计算站级部门及其下属部门的工作统计数据
46
+     * @param params 指定部门
47
+     * @return 工作统计数据列表
48
+     */
49
+    public List<StationDeptWorkStats> calculateStationWorkStats(IndicatorCalculateParams params) {
50
+        Long stationId = params.getDeptId();
51
+        if (stationId == null) {
52
+            return null;
53
+        }
54
+
55
+        // 直接查询部门信息
56
+        SysDept queryDept = new SysDept();
57
+        queryDept.setDeptId(stationId);
58
+        queryDept.setDeptType(DeptTypeEnum.DEPARTMENT.getCode());
59
+        List<SysDept> sysDeptList = sysDeptService.selectDeptInfo(queryDept);
60
+        
61
+        if (CollectionUtil.isEmpty(sysDeptList)) {
62
+            return null;
63
+        }
64
+        IndicatorCalculateParams indicatorCalculateParams = this.appendParams(params);
65
+        // 为每个部门生成统计信息
66
+        return sysDeptList.stream()
67
+                .map(dept -> getStationDeptWorkStats( dept,indicatorCalculateParams))
68
+                .filter(java.util.Objects::nonNull)
69
+                .collect(Collectors.toList());
70
+    }
71
+
72
+    private IndicatorCalculateParams appendParams(IndicatorCalculateParams dto) {
73
+        IndicatorCalculateParams params = new IndicatorCalculateParams();
74
+        params.setIndicatorTypes(Arrays.asList("qualificationLevel", "workingDate", "workingHours", "effectiveSeizureCount", "workYears", "securityWorkYears"));
75
+        // 设置默认时间范围
76
+        java.util.Date endTime = Optional.ofNullable(dto.getEndTime()).orElse(new java.util.Date());
77
+        java.util.Date startTime = Optional.ofNullable(dto.getStartTime())
78
+                .orElse(java.util.Date.from(endTime.toInstant().minusSeconds(7862400L))); // 91天
79
+
80
+        params.setStartTime(startTime);
81
+        params.setEndTime(endTime);
82
+        return params;
83
+    }
84
+    
85
+    /**
86
+     * 为特定部门生成工作统计信息
87
+     * @param dept 部门信息
88
+     * @return 部门工作统计信息
89
+     */
90
+    private StationDeptWorkStats getStationDeptWorkStats(SysDept dept, IndicatorCalculateParams indicatorCalculateParams) {
91
+        if (dept == null) {
92
+            return null;
93
+        }
94
+        
95
+        StationDeptWorkStats stationStats = new StationDeptWorkStats();
96
+        stationStats.setDimension(dept.getDeptName());
97
+        stationStats.setDeptId(dept.getDeptId());
98
+        stationStats.setDeptName(dept.getDeptName());
99
+
100
+        indicatorCalculateParams.setUserType(StrUtil.equals(DeptTypeEnum.STATION.getCode(), dept.getDeptType()) ? UserType.STATION : UserType.DEPARTMENT);
101
+
102
+
103
+        //获取指标
104
+        indicatorCalculateParams.setDeptId(dept.getDeptId());
105
+        List<SysUser> sysUsers = iSysUserService.selectUserListByRoleKeyAndDeptId(Arrays.asList(RoleTypeEnum.banzuzhang.getCode(), RoleTypeEnum.SecurityCheck.getCode()), dept.getDeptId());
106
+        indicatorCalculateParams.setUserIds(sysUsers.stream().map(SysUser::getUserId).collect(Collectors.toList()));
107
+        // 计算人均出勤天数
108
+        stationStats.setAvgWorkingDays(avgWorkingDays(indicatorCalculateParams));
109
+
110
+        // 计算人均上岗时长
111
+        stationStats.setAvgWorkingHours(avgWorkingHours(indicatorCalculateParams));
112
+
113
+        return stationStats;
114
+    }
115
+
116
+
117
+    private BigDecimal  avgWorkingDays(IndicatorCalculateParams params) {
118
+        BigDecimal avgWorkingDays = BigDecimal.ZERO;
119
+                ModularIndicatorResult indicators = userPortraitService.getIndicators(params, Arrays.asList("attendance"));
120
+        if (indicators == null) {
121
+            return avgWorkingDays;
122
+        }
123
+
124
+        AttendanceModuleIndicatorResult attendanceResult = (AttendanceModuleIndicatorResult) indicators.getModuleResult("attendance");
125
+        if (attendanceResult == null) {
126
+            return avgWorkingDays;
127
+        }
128
+
129
+        Object workingDate = attendanceResult.getWorkingDate();
130
+        if (workingDate instanceof WorkDateStats) {
131
+            WorkDateStats workDateStats = (WorkDateStats) workingDate;
132
+             avgWorkingDays =    workDateStats.getAveragePersonnel() != null ?
133
+                    workDateStats.getAveragePersonnel() : BigDecimal.ZERO;
134
+        }
135
+        return avgWorkingDays;
136
+    }
137
+
138
+
139
+    private BigDecimal avgWorkingHours(IndicatorCalculateParams params) {
140
+        BigDecimal avgWorkingHours = BigDecimal.ZERO;
141
+        ModularIndicatorResult indicators = userPortraitService.getIndicators(params, Arrays.asList("attendance"));
142
+        if (indicators == null) {
143
+            return avgWorkingHours;
144
+        }
145
+
146
+        AttendanceModuleIndicatorResult attendanceResult = (AttendanceModuleIndicatorResult) indicators.getModuleResult("attendance");
147
+        if (attendanceResult == null) {
148
+            return avgWorkingHours;
149
+        }
150
+        Object workingHours = attendanceResult.getWorkingHours();
151
+        if (workingHours instanceof WorkHoursStats) {
152
+            WorkHoursStats workHoursStats = (WorkHoursStats) workingHours;
153
+            avgWorkingHours=workHoursStats.getAveragePersonnel() != null ?
154
+                    workHoursStats.getAveragePersonnel() : BigDecimal.ZERO;
155
+        }
156
+
157
+        return avgWorkingHours;
158
+    }
159
+
160
+}

+ 98 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/WorkingDateIndicator.java

@@ -0,0 +1,98 @@
1
+package com.sundot.airport.attendance.portrait;
2
+
3
+import cn.hutool.core.collection.CollectionUtil;
4
+import com.sundot.airport.attendance.domain.AttendanceRecord;
5
+import com.sundot.airport.attendance.mapper.AttendanceRecordMapper;
6
+import com.sundot.airport.common.domain.portrait.BaseModuleIndicatorResult;
7
+import com.sundot.airport.common.domain.portrait.IndicatorCalculateParams;
8
+import com.sundot.airport.attendance.domain.portrait.WorkDateStats;
9
+import com.sundot.airport.common.enums.portrait.IndicatorType;
10
+import com.sundot.airport.common.enums.portrait.UserType;
11
+import com.sundot.airport.common.service.portrait.Indicator;
12
+import org.springframework.beans.factory.annotation.Autowired;
13
+import org.springframework.stereotype.Component;
14
+
15
+import java.math.BigDecimal;
16
+import java.util.List;
17
+
18
+/**
19
+ * 出勤天数
20
+ * 个人:周期内出勤天数
21
+ * 组织:周期内出勤总天数/平均人数
22
+ */
23
+@Component
24
+public class WorkingDateIndicator implements Indicator<Object> {
25
+    
26
+    @Autowired
27
+    private AttendanceRecordMapper attendanceRecordMapper;
28
+    
29
+    private Object value;
30
+    
31
+    @Override
32
+    public String getName() {
33
+        return IndicatorType.WORKING_DATE.getCode();
34
+    }
35
+
36
+    @Override
37
+    public Object getValue() {
38
+        return value;
39
+    }
40
+
41
+    @Override
42
+    public void calculate(IndicatorCalculateParams params) {
43
+        Long userId = params.getUserId();
44
+        UserType userType = params.getUserType();
45
+        if (UserType.PERSONAL.equals(userType)) {
46
+            // 个人:周期内出勤天数
47
+            AttendanceRecord record = new AttendanceRecord();
48
+            record.setUserId(userId);
49
+            record.setAttendanceDateStart(params.getStartTime());
50
+            record.setAttendanceDateEnd(params.getEndTime());
51
+            List<AttendanceRecord> records = attendanceRecordMapper.selectAttendanceRecordList(record);
52
+            this.value = records.size();
53
+        } else {
54
+            // 组织:周期内出勤总天数/平均人数
55
+            WorkDateStats stats = new WorkDateStats();
56
+
57
+            // 部门id
58
+            Long deptId = params.getDeptId();
59
+
60
+            // 查询该部门下所有用户的考勤记录
61
+            AttendanceRecord record = new AttendanceRecord();
62
+            switch (userType) {
63
+                case TEAM:
64
+                    record.setTeamCode(deptId.toString());
65
+                    break;
66
+                case DEPARTMENT:
67
+                    record.setDepartmentCode(deptId.toString());
68
+                    break;
69
+                case STATION:
70
+                    record.setStationCode(deptId.toString());
71
+                    break;
72
+                default:
73
+                    break;
74
+            }
75
+            record.setAttendanceDateStart(params.getStartTime());
76
+            record.setAttendanceDateEnd(params.getEndTime());
77
+
78
+            List<AttendanceRecord> records = attendanceRecordMapper.selectAttendanceRecordList(record);
79
+
80
+            // 计算总出勤天数
81
+            int totalWorkingDays = records.size();
82
+
83
+
84
+            stats.setTotalWorkingDays(totalWorkingDays);
85
+            stats.setAveragePersonnel(CollectionUtil.isNotEmpty(params.getUserIds()) ? new BigDecimal(totalWorkingDays).divide(new BigDecimal(params.getUserIds().size()), 2, BigDecimal.ROUND_HALF_UP) : BigDecimal.ZERO);
86
+
87
+            this.value = stats;
88
+        }
89
+    }
90
+
91
+    @Override
92
+    public void setResult(BaseModuleIndicatorResult result) {
93
+        if (result instanceof AttendanceModuleIndicatorResult) {
94
+            AttendanceModuleIndicatorResult attendanceResult = (AttendanceModuleIndicatorResult) result;
95
+            attendanceResult.setWorkingDate(this.value);
96
+        }
97
+    }
98
+}

+ 106 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/portrait/WorkingHoursIndicator.java

@@ -0,0 +1,106 @@
1
+package com.sundot.airport.attendance.portrait;
2
+
3
+import cn.hutool.core.collection.CollectionUtil;
4
+import com.sundot.airport.attendance.domain.AttendancePostRecord;
5
+import com.sundot.airport.attendance.mapper.AttendancePostRecordMapper;
6
+import com.sundot.airport.common.domain.portrait.BaseModuleIndicatorResult;
7
+import com.sundot.airport.common.domain.portrait.IndicatorCalculateParams;
8
+import com.sundot.airport.attendance.domain.portrait.WorkHoursStats;
9
+import com.sundot.airport.common.enums.portrait.IndicatorType;
10
+import com.sundot.airport.common.enums.portrait.UserType;
11
+import com.sundot.airport.common.service.portrait.Indicator;
12
+import org.springframework.beans.factory.annotation.Autowired;
13
+import org.springframework.stereotype.Component;
14
+
15
+import java.math.BigDecimal;
16
+import java.util.List;
17
+
18
+/**
19
+ * 上岗时长
20
+ * 个人:周期内上岗时数
21
+ * 组织:周期内上岗总时数/平均人数
22
+ */
23
+@Component
24
+public class WorkingHoursIndicator implements Indicator<Object> {
25
+
26
+    @Autowired
27
+    private AttendancePostRecordMapper attendancePostRecordMapper;
28
+
29
+    private Object value;
30
+
31
+    @Override
32
+    public String getName() {
33
+        return IndicatorType.WORKING_HOURS.getCode();
34
+    }
35
+
36
+    @Override
37
+    public Object getValue() {
38
+        return value;
39
+    }
40
+
41
+    @Override
42
+    public void calculate(IndicatorCalculateParams params) {
43
+        Long userId = params.getUserId();
44
+        UserType userType = params.getUserType();
45
+        if (UserType.PERSONAL.equals(userType)) {
46
+            // 个人:周期内上岗时数(分钟)
47
+            AttendancePostRecord record = new AttendancePostRecord();
48
+            record.setUserId(userId);
49
+            record.setCheckInTimeStart(params.getStartTime());
50
+            record.setCheckInTimeEnd(params.getEndTime());
51
+            List<AttendancePostRecord> records = attendancePostRecordMapper.selectAttendancePostRecordList(record);
52
+
53
+            // 计算总工作时长
54
+            long totalWorkingHours = records.stream()
55
+                    .mapToLong(AttendancePostRecord::getWorkDuration)
56
+                    .filter(workDuration -> workDuration > 0)
57
+                    .sum();
58
+            totalWorkingHours = totalWorkingHours > 0 ? totalWorkingHours / 60 : 0;
59
+            this.value = totalWorkingHours;
60
+        } else {
61
+            // 组织:周期内上岗总时数/平均人数
62
+            WorkHoursStats stats = new WorkHoursStats();
63
+
64
+            // 部门id
65
+            Long deptId = params.getDeptId();
66
+
67
+            // 查询部门下所有用户的上岗记录
68
+            AttendancePostRecord record = new AttendancePostRecord();
69
+            switch (userType) {
70
+                case TEAM:
71
+                    record.setAttendanceTeamId(deptId);
72
+                    break;
73
+                case DEPARTMENT:
74
+                    record.setAttendanceDepartmentId(deptId);
75
+                    break;
76
+                case STATION:
77
+                    record.setAttendanceStationId(deptId);
78
+                    break;
79
+                default:
80
+                    break;
81
+            }
82
+            record.setCheckInTimeStart(params.getStartTime());
83
+            record.setCheckInTimeEnd(params.getEndTime());
84
+            List<AttendancePostRecord> records = attendancePostRecordMapper.selectAttendancePostRecordList(record);
85
+
86
+            // 计算总工作时长
87
+            long totalWorkingHours = records.stream()
88
+                    .mapToLong(AttendancePostRecord::getWorkDuration)
89
+                    .filter(workDuration -> workDuration > 0)
90
+                    .sum();
91
+            totalWorkingHours = totalWorkingHours > 0 ? totalWorkingHours / 60 : 0;
92
+            
93
+            stats.setTotalWorkingHours(totalWorkingHours);
94
+            stats.setAveragePersonnel(CollectionUtil.isNotEmpty(params.getUserIds())? new BigDecimal(totalWorkingHours).divide(new BigDecimal(params.getUserIds().size()), 2, BigDecimal.ROUND_HALF_UP) : BigDecimal.ZERO);
95
+            this.value = stats;
96
+        }
97
+    }
98
+    
99
+    @Override
100
+    public void setResult(BaseModuleIndicatorResult result) {
101
+        if (result instanceof AttendanceModuleIndicatorResult) {
102
+            AttendanceModuleIndicatorResult attendanceResult = (AttendanceModuleIndicatorResult) result;
103
+            attendanceResult.setWorkingHours(this.value);
104
+        }
105
+    }
106
+}

+ 23 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/AttendanceAreaService.java

@@ -0,0 +1,23 @@
1
+package com.sundot.airport.attendance.service;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceArea;
4
+
5
+import java.math.BigDecimal;
6
+import java.util.List;
7
+
8
+public interface AttendanceAreaService {
9
+    /**
10
+     *
11
+     * 查询是否在考勤范围
12
+     * @param lng 经度
13
+     * @param lat 纬度
14
+     * @return
15
+     */
16
+    List<AttendanceArea> checkUserInValidArea(BigDecimal lng, BigDecimal lat);
17
+
18
+    /**
19
+     *
20
+     * @return
21
+     */
22
+    List<AttendanceArea> areaList();
23
+}

+ 61 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendanceCheckRecordService.java

@@ -0,0 +1,61 @@
1
+package com.sundot.airport.attendance.service;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceCheckRecord;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * 打卡记录Service接口
9
+ *
10
+ * @author wangchong
11
+ * @date 2025-07-10
12
+ */
13
+public interface IAttendanceCheckRecordService {
14
+    /**
15
+     * 查询打卡记录
16
+     *
17
+     * @param id 打卡记录主键
18
+     * @return 打卡记录
19
+     */
20
+    public AttendanceCheckRecord selectAttendanceCheckRecordById(Long id);
21
+
22
+    /**
23
+     * 查询打卡记录列表
24
+     *
25
+     * @param attendanceCheckRecord 打卡记录
26
+     * @return 打卡记录集合
27
+     */
28
+    public List<AttendanceCheckRecord> selectAttendanceCheckRecordList(AttendanceCheckRecord attendanceCheckRecord);
29
+
30
+    /**
31
+     * 新增打卡记录
32
+     *
33
+     * @param attendanceCheckRecord 打卡记录
34
+     * @return 结果
35
+     */
36
+    public int insertAttendanceCheckRecord(AttendanceCheckRecord attendanceCheckRecord);
37
+
38
+    /**
39
+     * 修改打卡记录
40
+     *
41
+     * @param attendanceCheckRecord 打卡记录
42
+     * @return 结果
43
+     */
44
+    public int updateAttendanceCheckRecord(AttendanceCheckRecord attendanceCheckRecord);
45
+
46
+    /**
47
+     * 批量删除打卡记录
48
+     *
49
+     * @param ids 需要删除的打卡记录主键集合
50
+     * @return 结果
51
+     */
52
+    public int deleteAttendanceCheckRecordByIds(Long[] ids);
53
+
54
+    /**
55
+     * 删除打卡记录信息
56
+     *
57
+     * @param id 打卡记录主键
58
+     * @return 结果
59
+     */
60
+    public int deleteAttendanceCheckRecordById(Long id);
61
+}

+ 136 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/IAttendancePostRecordService.java

@@ -0,0 +1,136 @@
1
+package com.sundot.airport.attendance.service;
2
+
3
+import com.sundot.airport.attendance.domain.AttendancePostRecord;
4
+import com.sundot.airport.attendance.dto.AttendanceRecordCountDTO;
5
+
6
+import java.util.Date;
7
+import java.util.List;
8
+import java.util.Map;
9
+
10
+/**
11
+ * 上岗记录Service接口
12
+ *
13
+ * @author wangchong
14
+ * @date 2025-07-10
15
+ */
16
+public interface IAttendancePostRecordService {
17
+    /**
18
+     * 查询上岗记录
19
+     *
20
+     * @param id 上岗记录主键
21
+     * @return 上岗记录
22
+     */
23
+    public AttendancePostRecord selectAttendancePostRecordById(Long id);
24
+
25
+    /**
26
+     * 查询上岗记录列表
27
+     *
28
+     * @param attendancePostRecord 上岗记录
29
+     * @return 上岗记录集合
30
+     */
31
+    public List<AttendancePostRecord> selectAttendancePostRecordList(AttendancePostRecord attendancePostRecord);
32
+
33
+    /**
34
+     * 新增上岗记录
35
+     *
36
+     * @param attendancePostRecord 上岗记录
37
+     * @return 结果
38
+     */
39
+    public int insertAttendancePostRecord(AttendancePostRecord attendancePostRecord);
40
+
41
+    /**
42
+     * 修改上岗记录
43
+     *
44
+     * @param attendancePostRecord 上岗记录
45
+     * @return 结果
46
+     */
47
+    public int updateAttendancePostRecord(AttendancePostRecord attendancePostRecord);
48
+
49
+    /**
50
+     * 批量删除上岗记录
51
+     *
52
+     * @param ids 需要删除的上岗记录主键集合
53
+     * @return 结果
54
+     */
55
+    public int deleteAttendancePostRecordByIds(Long[] ids);
56
+
57
+    /**
58
+     * 删除上岗记录信息
59
+     *
60
+     * @param id 上岗记录主键
61
+     * @return 结果
62
+     */
63
+    public int deleteAttendancePostRecordById(Long id);
64
+
65
+    /**
66
+     * 查询指定用户当天的上岗记录
67
+     *
68
+     * @param userId 用户ID
69
+     * @param today 当天日期
70
+     * @return 上岗记录集合
71
+     */
72
+    public List<AttendancePostRecord> selectTodayRecordsByUserId(Long userId, Date today);
73
+
74
+    /**
75
+     * 查询指定班次代码当天的上岗记录
76
+     *
77
+     * @param today 当天日期
78
+     * @param shiftCodes 班次代码列表
79
+     * @return 上岗记录集合
80
+     */
81
+    public List<AttendancePostRecord> selectTodayRecordsByShiftCodes(Date today, List<String> shiftCodes);
82
+
83
+    /**
84
+     * 根据指定时间获取用户的在岗位置信息
85
+     *
86
+     * @param userId 用户ID
87
+     * @param queryTime 查询时间
88
+     * @return 上岗记录集合
89
+     */
90
+    public List<AttendancePostRecord> selectLocationsByTime(Long userId, Date queryTime);
91
+
92
+    /**
93
+     * 根据指定时间获取指定通道的在岗位置信息
94
+     *
95
+     * @param channelCodeList 通道code
96
+     * @return 上岗记录集合
97
+     */
98
+    List<AttendancePostRecord> selectLocationsByTimeAndChannelName(List<String> channelCodeList, Date nowDate,Date startTime,Date endTime);
99
+
100
+    /**
101
+     * 查询指定班次代码当天的下岗记录
102
+     * @param today
103
+     * @param shiftCodes
104
+     * @return
105
+     */
106
+    public List<AttendancePostRecord> selectCheckOutTimeByShiftCodes(Date today, List<String> shiftCodes);
107
+
108
+    /**
109
+     *  获取当前时间下科长的在岗区域
110
+     * @return 区域集合
111
+     */
112
+    List<AttendancePostRecord> selectKezhangRecordCount(Long userId,Date startTime,Date endTime);
113
+
114
+    /**
115
+     * 获取当前时间下指定用户最后的在岗时间
116
+     * @param userId 用户ID
117
+     * @return 最后的在岗时间
118
+     */
119
+    List<AttendancePostRecord>  queryLastTime(Long userId);
120
+
121
+    /**
122
+     * 获取当前班次下科长上岗的id
123
+     * @return
124
+     */
125
+    List<Long> queryKeIds();
126
+    /**
127
+     * 查询当前班组下所有在岗记录
128
+     */
129
+    List<AttendancePostRecord>  selectTeamOnList(Date date, Long attendanceTeamId);
130
+
131
+    /**
132
+     * 获取当前班次时间
133
+     * @return  时间
134
+     */
135
+    Map<String, Date> getShiftTime();
136
+}

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

@@ -0,0 +1,94 @@
1
+package com.sundot.airport.attendance.service;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceCheckRecord;
4
+import com.sundot.airport.attendance.domain.AttendanceRecord;
5
+import com.sundot.airport.common.dto.AttendanceRecordDTO;
6
+import com.sundot.airport.common.dto.AttendanceRecordRep;
7
+import com.sundot.airport.common.dto.AttendanceRecordReq;
8
+import com.sundot.airport.common.dto.UserInfo;
9
+
10
+import java.util.Date;
11
+import java.util.List;
12
+
13
+/**
14
+ * 考勤记录Service接口
15
+ *
16
+ * @author wangchong
17
+ * @date 2025-07-10
18
+ */
19
+public interface IAttendanceRecordService {
20
+    /**
21
+     * 查询考勤记录
22
+     *
23
+     * @param id 考勤记录主键
24
+     * @return 考勤记录
25
+     */
26
+    public AttendanceRecord selectAttendanceRecordById(Long id);
27
+
28
+    /**
29
+     * 查询考勤记录列表
30
+     *
31
+     * @param attendanceRecord 考勤记录
32
+     * @return 考勤记录集合
33
+     */
34
+    public List<AttendanceRecord> selectAttendanceRecordList(AttendanceRecord attendanceRecord);
35
+
36
+    /**
37
+     * 新增考勤记录
38
+     *
39
+     * @param attendanceRecord 考勤记录
40
+     * @return 结果
41
+     */
42
+    public int insertAttendanceRecord(AttendanceRecord attendanceRecord);
43
+
44
+    /**
45
+     * 修改考勤记录
46
+     *
47
+     * @param attendanceRecord 考勤记录
48
+     * @return 结果
49
+     */
50
+    public int updateAttendanceRecord(AttendanceRecord attendanceRecord);
51
+
52
+    /**
53
+     * 批量删除考勤记录
54
+     *
55
+     * @param ids 需要删除的考勤记录主键集合
56
+     * @return 结果
57
+     */
58
+    public int deleteAttendanceRecordByIds(Long[] ids);
59
+
60
+    /**
61
+     * 删除考勤记录信息
62
+     *
63
+     * @param id 考勤记录主键
64
+     * @return 结果
65
+     */
66
+    public int deleteAttendanceRecordById(Long id);
67
+
68
+    /**
69
+     * 打卡签到
70
+     *
71
+     * @param dto      打卡信息
72
+     * @param userInfo 登录用户
73
+     * @return 今日打卡历史
74
+     */
75
+    List<AttendanceCheckRecord> record(AttendanceRecordDTO dto, UserInfo userInfo);
76
+
77
+    /**
78
+     * 获取打卡记录
79
+     *
80
+     * @param dto 打卡记录参数
81
+     * @return 打卡记录
82
+     */
83
+    List<AttendanceRecordRep> recordList(AttendanceRecordReq dto);
84
+
85
+    /**
86
+     * 计算考勤日期
87
+     * @param checkInTime 实际打卡时间,即员工实际进行打卡操作的时间点
88
+     * @param shiftStartTime 班次开始时间,格式为 "HH:mm",如 "21:00" 表示晚上9点
89
+     * @param shiftEndTime 班次结束时间,格式为 "HH:mm",如 "21:00" 表示晚上9点
90
+     * @param checkInType  1-上班打卡,2-下班打卡
91
+     * @return 计算出的考勤日期,表示该次打卡记录应归属的考勤周期日期
92
+     */
93
+    Date calculateAttendanceDate(Date checkInTime, String shiftStartTime, String shiftEndTime,String checkInType);
94
+}

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

@@ -0,0 +1,84 @@
1
+package com.sundot.airport.attendance.service;
2
+
3
+import java.util.Date;
4
+import java.util.List;
5
+import com.sundot.airport.attendance.domain.AttendanceTeamUserRecord;
6
+
7
+/**
8
+ * 考勤班组成员Service接口
9
+ * 
10
+ * @author ruoyi
11
+ * @date 2025-09-06
12
+ */
13
+public interface IAttendanceTeamUserRecordService 
14
+{
15
+    /**
16
+     * 查询考勤班组成员
17
+     * 
18
+     * @param userId 考勤班组成员主键
19
+     * @return 考勤班组成员
20
+     */
21
+    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(Long userId, Date attendanceDate);
22
+
23
+    /**
24
+     * 查询考勤班组成员列表
25
+     * @param attendanceTeamUserRecord
26
+     * @return
27
+     */
28
+
29
+    List<AttendanceTeamUserRecord> selectAttendanceTeamUserRecordPageList(AttendanceTeamUserRecord attendanceTeamUserRecord);
30
+
31
+    /**
32
+     * 上岗记录中查询考勤班组成员列表
33
+     * 
34
+     * @param attendanceTeamUserRecord 考勤班组成员
35
+     * @return 考勤班组成员集合
36
+     */
37
+    public List<AttendanceTeamUserRecord> selectAttendanceTeamUserRecordList(AttendanceTeamUserRecord attendanceTeamUserRecord);
38
+
39
+    /**
40
+     * 新增考勤班组成员
41
+     * 
42
+     * @param attendanceTeamUserRecord 考勤班组成员
43
+     * @return 结果
44
+     */
45
+    public int insertAttendanceTeamUserRecord(AttendanceTeamUserRecord attendanceTeamUserRecord);
46
+
47
+    /**
48
+     * 修改考勤班组成员
49
+     * 
50
+     * @param attendanceTeamUserRecord 考勤班组成员
51
+     * @return 结果
52
+     */
53
+    public int updateAttendanceTeamUserRecord(AttendanceTeamUserRecord attendanceTeamUserRecord);
54
+
55
+    /**
56
+     * 批量删除考勤班组成员
57
+     * 
58
+     * @param userIds 需要删除的考勤班组成员主键集合
59
+     * @return 结果
60
+     */
61
+    public int deleteAttendanceTeamUserRecordByUserIds(Long[] userIds);
62
+
63
+    /**
64
+     * 删除考勤班组成员信息
65
+     * 
66
+     * @param userId 考勤班组成员主键
67
+     * @return 结果
68
+     */
69
+    public int deleteAttendanceTeamUserRecordByUserId(Long userId);
70
+
71
+    /**
72
+     *  批量新增考勤班组成员
73
+     * @param attendanceTeamUserRecord
74
+     * @return
75
+     */
76
+    int insertAttendanceTeamUserRecordList(List<AttendanceTeamUserRecord> attendanceTeamUserRecord);
77
+
78
+    /**
79
+     * 查询考勤班组组长id
80
+     */
81
+    List<Long> selectAttendanceTeamLeaderId();
82
+
83
+
84
+}

+ 52 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceAreaServiceImpl.java

@@ -0,0 +1,52 @@
1
+package com.sundot.airport.attendance.service.impl;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceArea;
4
+import com.sundot.airport.attendance.mapper.AttendanceAreaMapper;
5
+import com.sundot.airport.attendance.service.AttendanceAreaService;
6
+import com.sundot.airport.common.exception.ServiceException;
7
+import org.springframework.beans.factory.annotation.Autowired;
8
+import org.springframework.stereotype.Service;
9
+
10
+import java.math.BigDecimal;
11
+import java.util.List;
12
+
13
+@Service
14
+public class AttendanceAreaServiceImpl implements AttendanceAreaService {
15
+    @Autowired
16
+    private AttendanceAreaMapper attendanceAreaMapper;
17
+
18
+    /**
19
+     * 查询是否在考勤范围
20
+     * @param lng 经度
21
+     * @param lat 纬度
22
+     * @return
23
+     */
24
+    @Override
25
+    public List<AttendanceArea> checkUserInValidArea(BigDecimal lng, BigDecimal lat) {
26
+        // 验证经纬度有效性
27
+        if (lng == null || lat == null) {
28
+            throw new ServiceException("经纬度不能为空");
29
+        }
30
+
31
+        // 检查经纬度范围(经度:-180到180,纬度:-90到90)
32
+        if (lng.compareTo(new BigDecimal("-180")) < 0 ||
33
+                lng.compareTo(new BigDecimal("180")) > 0) {
34
+            throw new ServiceException("经度必须在-180到180之间");
35
+        }
36
+
37
+        if (lat.compareTo(new BigDecimal("-90")) < 0 ||
38
+                lat.compareTo(new BigDecimal("90")) > 0) {
39
+            throw new ServiceException("纬度必须在-90到90之间");
40
+        }
41
+        return attendanceAreaMapper.findValidAreasEfficiently(lng, lat);
42
+    }
43
+
44
+    /**
45
+     * 查询所有的考勤范围点
46
+     * @return
47
+     */
48
+    @Override
49
+    public List<AttendanceArea> areaList() {
50
+        return attendanceAreaMapper.areaList();
51
+    }
52
+}

+ 90 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendanceCheckRecordServiceImpl.java

@@ -0,0 +1,90 @@
1
+package com.sundot.airport.attendance.service.impl;
2
+
3
+import com.sundot.airport.attendance.domain.AttendanceCheckRecord;
4
+import com.sundot.airport.attendance.mapper.AttendanceCheckRecordMapper;
5
+import com.sundot.airport.attendance.service.IAttendanceCheckRecordService;
6
+import com.sundot.airport.common.utils.DateUtils;
7
+import org.springframework.beans.factory.annotation.Autowired;
8
+import org.springframework.stereotype.Service;
9
+
10
+import java.util.List;
11
+
12
+/**
13
+ * 打卡记录Service业务层处理
14
+ *
15
+ * @author wangchong
16
+ * @date 2025-07-10
17
+ */
18
+@Service
19
+public class AttendanceCheckRecordServiceImpl implements IAttendanceCheckRecordService {
20
+    @Autowired
21
+    private AttendanceCheckRecordMapper attendanceCheckRecordMapper;
22
+
23
+    /**
24
+     * 查询打卡记录
25
+     *
26
+     * @param id 打卡记录主键
27
+     * @return 打卡记录
28
+     */
29
+    @Override
30
+    public AttendanceCheckRecord selectAttendanceCheckRecordById(Long id) {
31
+        return attendanceCheckRecordMapper.selectAttendanceCheckRecordById(id);
32
+    }
33
+
34
+    /**
35
+     * 查询打卡记录列表
36
+     *
37
+     * @param attendanceCheckRecord 打卡记录
38
+     * @return 打卡记录
39
+     */
40
+    @Override
41
+    public List<AttendanceCheckRecord> selectAttendanceCheckRecordList(AttendanceCheckRecord attendanceCheckRecord) {
42
+        return attendanceCheckRecordMapper.selectAttendanceCheckRecordList(attendanceCheckRecord);
43
+    }
44
+
45
+    /**
46
+     * 新增打卡记录
47
+     *
48
+     * @param attendanceCheckRecord 打卡记录
49
+     * @return 结果
50
+     */
51
+    @Override
52
+    public int insertAttendanceCheckRecord(AttendanceCheckRecord attendanceCheckRecord) {
53
+        attendanceCheckRecord.setCreateTime(DateUtils.getNowDate());
54
+        return attendanceCheckRecordMapper.insertAttendanceCheckRecord(attendanceCheckRecord);
55
+    }
56
+
57
+    /**
58
+     * 修改打卡记录
59
+     *
60
+     * @param attendanceCheckRecord 打卡记录
61
+     * @return 结果
62
+     */
63
+    @Override
64
+    public int updateAttendanceCheckRecord(AttendanceCheckRecord attendanceCheckRecord) {
65
+        attendanceCheckRecord.setUpdateTime(DateUtils.getNowDate());
66
+        return attendanceCheckRecordMapper.updateAttendanceCheckRecord(attendanceCheckRecord);
67
+    }
68
+
69
+    /**
70
+     * 批量删除打卡记录
71
+     *
72
+     * @param ids 需要删除的打卡记录主键
73
+     * @return 结果
74
+     */
75
+    @Override
76
+    public int deleteAttendanceCheckRecordByIds(Long[] ids) {
77
+        return attendanceCheckRecordMapper.deleteAttendanceCheckRecordByIds(ids);
78
+    }
79
+
80
+    /**
81
+     * 删除打卡记录信息
82
+     *
83
+     * @param id 打卡记录主键
84
+     * @return 结果
85
+     */
86
+    @Override
87
+    public int deleteAttendanceCheckRecordById(Long id) {
88
+        return attendanceCheckRecordMapper.deleteAttendanceCheckRecordById(id);
89
+    }
90
+}

+ 269 - 0
airport-attendance/src/main/java/com/sundot/airport/attendance/service/impl/AttendancePostRecordServiceImpl.java

@@ -0,0 +1,269 @@
1
+package com.sundot.airport.attendance.service.impl;
2
+
3
+import cn.hutool.core.collection.CollectionUtil;
4
+import com.sundot.airport.attendance.domain.AttendancePostRecord;
5
+import com.sundot.airport.attendance.mapper.AttendancePostRecordMapper;
6
+import com.sundot.airport.attendance.service.IAttendancePostRecordService;
7
+import com.sundot.airport.common.utils.DateUtils;
8
+import com.sundot.airport.common.utils.StringUtils;
9
+import com.sundot.airport.system.service.ISysConfigService;
10
+import org.springframework.beans.factory.annotation.Autowired;
11
+import org.springframework.stereotype.Service;
12
+
13
+import java.util.*;
14
+import java.util.stream.Collectors;
15
+
16
+/**
17
+ * 上岗记录Service业务层处理
18
+ *
19
+ * @author wangchong
20
+ * @date 2025-07-10
21
+ */
22
+@Service
23
+public class AttendancePostRecordServiceImpl implements IAttendancePostRecordService {
24
+    @Autowired
25
+    private AttendancePostRecordMapper attendancePostRecordMapper;
26
+
27
+    @Autowired
28
+    private ISysConfigService configService;
29
+
30
+    /**
31
+     * 查询上岗记录
32
+     *
33
+     * @param id 上岗记录主键
34
+     * @return 上岗记录
35
+     */
36
+    @Override
37
+    public AttendancePostRecord selectAttendancePostRecordById(Long id) {
38
+        return attendancePostRecordMapper.selectAttendancePostRecordById(id);
39
+    }
40
+
41
+    /**
42
+     * 查询上岗记录列表
43
+     *
44
+     * @param attendancePostRecord 上岗记录
45
+     * @return 上岗记录
46
+     */
47
+    @Override
48
+    public List<AttendancePostRecord> selectAttendancePostRecordList(AttendancePostRecord attendancePostRecord) {
49
+        List<AttendancePostRecord> records = attendancePostRecordMapper.selectAttendancePostRecordList(attendancePostRecord);
50
+        if (CollectionUtil.isEmpty(records)) {
51
+            return new ArrayList<>();
52
+        }
53
+        records = records.stream().map(record -> {
54
+            //下通道时间是2001年1月1日00:00:00,所以这里要设置为null
55
+            if(record.getCheckOutTime() != null && record.getCheckOutTime().getTime() == 978352000000L){
56
+                record.setCheckOutTime(null);
57
+            }
58
+            if(record.getWorkDuration() !=null && record.getWorkDuration() < 0){
59
+                record.setWorkDuration(0L);
60
+            }
61
+            return record;
62
+        }).collect(Collectors.toList());
63
+        return records;
64
+    }
65
+
66
+    /**
67
+     * 新增上岗记录
68
+     *
69
+     * @param attendancePostRecord 上岗记录
70
+     * @return 结果
71
+     */
72
+    @Override
73
+    public int insertAttendancePostRecord(AttendancePostRecord attendancePostRecord) {
74
+        attendancePostRecord.setCreateTime(DateUtils.getNowDate());
75
+        return attendancePostRecordMapper.insertAttendancePostRecord(attendancePostRecord);
76
+    }
77
+
78
+    /**
79
+     * 修改上岗记录
80
+     *
81
+     * @param attendancePostRecord 上岗记录
82
+     * @return 结果
83
+     */
84
+    @Override
85
+    public int updateAttendancePostRecord(AttendancePostRecord attendancePostRecord) {
86
+        attendancePostRecord.setUpdateTime(DateUtils.getNowDate());
87
+        return attendancePostRecordMapper.updateAttendancePostRecord(attendancePostRecord);
88
+    }
89
+
90
+    /**
91
+     * 批量删除上岗记录
92
+     *
93
+     * @param ids 需要删除的上岗记录主键
94
+     * @return 结果
95
+     */
96
+    @Override
97
+    public int deleteAttendancePostRecordByIds(Long[] ids) {
98
+        return attendancePostRecordMapper.deleteAttendancePostRecordByIds(ids);
99
+    }
100
+
101
+    /**
102
+     * 删除上岗记录信息
103
+     *
104
+     * @param id 上岗记录主键
105
+     * @return 结果
106
+     */
107
+    @Override
108
+    public int deleteAttendancePostRecordById(Long id) {
109
+        return attendancePostRecordMapper.deleteAttendancePostRecordById(id);
110
+    }
111
+
112
+    /**
113
+     * 查询指定用户当天的上岗记录
114
+     *
115
+     * @param userId 用户ID
116
+     * @param today 当天日期
117
+     * @return 上岗记录集合
118
+     */
119
+    @Override
120
+    public List<AttendancePostRecord> selectTodayRecordsByUserId(Long userId, Date today) {
121
+        return attendancePostRecordMapper.selectTodayRecordsByUserId(userId, today);
122
+    }
123
+
124
+    /**
125
+     * 查询指定班次代码当天的上岗记录
126
+     *
127
+     * @param today 当天日期
128
+     * @param shiftCodes 班次代码列表
129
+     * @return 上岗记录集合
130
+     */
131
+    @Override
132
+    public List<AttendancePostRecord> selectTodayRecordsByShiftCodes(Date today, List<String> shiftCodes) {
133
+        return attendancePostRecordMapper.selectTodayRecordsByShiftCodes(today, shiftCodes);
134
+    }
135
+
136
+    /**
137
+     * 根据指定时间获取用户的在岗位置信息
138
+     *
139
+     * @param userId 用户ID
140
+     * @param queryTime 查询时间
141
+     * @return 上岗记录集合
142
+     */
143
+    @Override
144
+    public List<AttendancePostRecord> selectLocationsByTime(Long userId, Date queryTime) {
145
+        return attendancePostRecordMapper.selectLocationsByTime(userId, queryTime);
146
+    }
147
+
148
+    /**
149
+     * 根据指定时间获取指定通道的在岗位置信息
150
+     *
151
+     * @param channelCodeList 通道code
152
+     * @param nowDate 查询时间
153
+     * @return 上岗记录集合
154
+     */
155
+    @Override
156
+    public List<AttendancePostRecord> selectLocationsByTimeAndChannelName(List<String> channelCodeList, Date nowDate,Date startTime,Date endTime) {
157
+        return attendancePostRecordMapper.selectLocationsByTimeAndChannelName(channelCodeList, nowDate,startTime,endTime);
158
+    }
159
+
160
+    /**
161
+     *  查询指定班次代码当天的下岗时间
162
+     * @param today
163
+     * @param shiftCodes
164
+     * @return
165
+     */
166
+    @Override
167
+    public List<AttendancePostRecord> selectCheckOutTimeByShiftCodes(Date today, List<String> shiftCodes) {
168
+        return attendancePostRecordMapper.selectCheckOutTimeByShiftCodes(today, shiftCodes);
169
+    }
170
+
171
+    /**
172
+     * 获取当前登录时间下科长的在岗区域
173
+     * @return
174
+     */
175
+    @Override
176
+    public List<AttendancePostRecord> selectKezhangRecordCount(Long userId,Date startTime,Date endTime) {
177
+        return attendancePostRecordMapper.selectKezhangRecordCount(userId, startTime, endTime);
178
+    }
179
+
180
+    @Override
181
+    public List<AttendancePostRecord> queryLastTime(Long userId) {
182
+        List<AttendancePostRecord> attendancePostRecords = attendancePostRecordMapper.queryLastTime(userId);
183
+        if (attendancePostRecords.isEmpty()) {
184
+            return new ArrayList<>();
185
+        }
186
+        // 按上岗时间分组
187
+        Map<Date, List<AttendancePostRecord>> recordsByDate = new HashMap<>();
188
+        for (AttendancePostRecord record : attendancePostRecords) {
189
+            Date checkInTime = record.getCheckInTime();
190
+            if (checkInTime != null) {
191
+                recordsByDate.computeIfAbsent(checkInTime, k -> new ArrayList<>()).add(record);
192
+            }
193
+        }
194
+
195
+        // 找到最大日期
196
+        Date maxDate = recordsByDate.keySet().stream()
197
+                .max(Date::compareTo)
198
+                .orElse(null);
199
+
200
+        // 返回最后一天的所有记录
201
+        return maxDate != null ? recordsByDate.get(maxDate) : new ArrayList<>();
202
+    }
203
+
204
+    @Override
205
+    public List<Long> queryKeIds() {
206
+        Map<String, Date> shiftTime = getShiftTime();
207
+        List<Long> longs = attendancePostRecordMapper.queryKeIds(shiftTime.get("start"), shiftTime.get("end"));
208
+        if(CollectionUtil.isNotEmpty(longs)){
209
+            return longs.stream().distinct().collect(Collectors.toList());
210
+        }
211
+        return new ArrayList<>();
212
+    }
213
+
214
+    @Override
215
+    public List<AttendancePostRecord> selectTeamOnList(Date date, Long attendanceTeamId) {
216
+        return attendancePostRecordMapper.selectTeamOnList( date,  attendanceTeamId);
217
+    }
218
+
219
+    /**
220
+     *  获取当前班次周期的开始和结束时间
221
+     * @return  map  startTime:开始时间  endTime:结束时间
222
+     */
223
+    @Override
224
+    public Map<String, Date> getShiftTime() {
225
+        try {
226
+            Date now = DateUtils.getNowDate();
227
+            String todayStr = DateUtils.getDate();
228
+            String startTime = configService.selectConfigByKey("attendance.post.record.start.time");
229
+            String endTime = configService.selectConfigByKey("attendance.post.record.end.time");
230
+
231
+            // 参数校验
232
+            if (StringUtils.isEmpty(startTime) || StringUtils.isEmpty(endTime)) {
233
+                throw new RuntimeException("班次时间配置缺失");
234
+            }
235
+
236
+            // 构造昨天的班次开始时间
237
+            Date yesterday = DateUtils.addDays(now, -1);
238
+            String yesterdayStr = DateUtils.parseDateToStr("yyyy-MM-dd", yesterday);
239
+            Date shiftStartTime = DateUtils.parseDate(yesterdayStr + " " + startTime);
240
+
241
+            // 构造今天的班次结束时间
242
+            Date shiftEndTime = DateUtils.parseDate(todayStr + " " + endTime);
243
+
244
+            // 如果当前时间在昨天班次时间段内,则属于昨天班次
245
+            // 否则属于今天班次(需要重新计算今天的班次时间段)
246
+            if (now.compareTo(shiftStartTime) >= 0 && now.compareTo(shiftEndTime) < 0) {
247
+                // 属于昨天班次,直接返回已计算的时间段
248
+                Map<String, Date> map = new HashMap<>();
249
+                map.put("start", shiftStartTime);
250
+                map.put("end", shiftEndTime);
251
+                return map;
252
+            } else {
253
+                // 属于今天班次,重新计算时间段
254
+                Date todayShiftStart = DateUtils.parseDate(todayStr + " " + startTime);
255
+                Date tomorrowShiftEnd = DateUtils.addDays(todayShiftStart, 1);
256
+
257
+                Map<String, Date> map = new HashMap<>();
258
+                map.put("start", todayShiftStart);
259
+                map.put("end", tomorrowShiftEnd);
260
+                return map;
261
+            }
262
+        } catch (Exception e) {
263
+            throw new RuntimeException("班次时间计算错误: " + e.getMessage(), e);
264
+        }
265
+    }
266
+
267
+
268
+
269
+}

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

@@ -0,0 +1,489 @@
1
+package com.sundot.airport.attendance.service.impl;
2
+
3
+import cn.hutool.core.bean.BeanUtil;
4
+import cn.hutool.core.collection.CollectionUtil;
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.AttendanceRecord;
9
+import com.sundot.airport.attendance.enums.CheckInTypeEnum;
10
+import com.sundot.airport.attendance.mapper.AttendanceCheckRecordMapper;
11
+import com.sundot.airport.attendance.mapper.AttendanceRecordMapper;
12
+import com.sundot.airport.attendance.service.IAttendanceRecordService;
13
+import com.sundot.airport.common.dto.AttendanceRecordDTO;
14
+import com.sundot.airport.common.dto.AttendanceRecordRep;
15
+import com.sundot.airport.common.dto.AttendanceRecordReq;
16
+import com.sundot.airport.common.dto.UserInfo;
17
+import com.sundot.airport.common.exception.ServiceException;
18
+import com.sundot.airport.common.utils.DateUtils;
19
+import com.sundot.airport.common.utils.SecurityUtils;
20
+import com.sundot.airport.exam.event.ClockInEvent;
21
+import com.sundot.airport.system.service.ISysConfigService;
22
+import org.apache.commons.compress.utils.Lists;
23
+import org.springframework.beans.factory.annotation.Autowired;
24
+import org.springframework.context.ApplicationEventPublisher;
25
+import org.springframework.stereotype.Service;
26
+
27
+import java.time.LocalDate;
28
+import java.time.LocalDateTime;
29
+import java.time.LocalTime;
30
+import java.time.ZoneId;
31
+import java.util.Date;
32
+import java.util.List;
33
+import java.util.Objects;
34
+import java.util.stream.Collectors;
35
+
36
+/**
37
+ * 考勤记录Service业务层处理
38
+ *
39
+ * @author wangchong
40
+ * @date 2025-07-10
41
+ */
42
+@Service
43
+public class AttendanceRecordServiceImpl implements IAttendanceRecordService {
44
+    @Autowired
45
+    private AttendanceRecordMapper attendanceRecordMapper;
46
+
47
+    @Autowired
48
+    private AttendanceCheckRecordMapper attendanceCheckRecordMapper;
49
+
50
+    @Autowired
51
+    private ApplicationEventPublisher eventPublisher;
52
+
53
+    // 梭班时间窗口(2小时内)
54
+    private static final long SHUTTLE_TIME_WINDOW = 2 * 60 * 60 * 1000;
55
+
56
+
57
+    @Autowired
58
+    private ISysConfigService configService;
59
+
60
+    /**
61
+     * 查询考勤记录
62
+     *
63
+     * @param id 考勤记录主键
64
+     * @return 考勤记录
65
+     */
66
+    @Override
67
+    public AttendanceRecord selectAttendanceRecordById(Long id) {
68
+        return attendanceRecordMapper.selectAttendanceRecordById(id);
69
+    }
70
+
71
+    /**
72
+     * 查询考勤记录列表
73
+     *
74
+     * @param attendanceRecord 考勤记录
75
+     * @return 考勤记录
76
+     */
77
+    @Override
78
+    public List<AttendanceRecord> selectAttendanceRecordList(AttendanceRecord attendanceRecord) {
79
+        return attendanceRecordMapper.selectAttendanceRecordList(attendanceRecord);
80
+    }
81
+
82
+    /**
83
+     * 新增考勤记录
84
+     *
85
+     * @param attendanceRecord 考勤记录
86
+     * @return 结果
87
+     */
88
+    @Override
89
+    public int insertAttendanceRecord(AttendanceRecord attendanceRecord) {
90
+        attendanceRecord.setCreateTime(DateUtils.getNowDate());
91
+        return attendanceRecordMapper.insertAttendanceRecord(attendanceRecord);
92
+    }
93
+
94
+    /**
95
+     * 修改考勤记录
96
+     *
97
+     * @param attendanceRecord 考勤记录
98
+     * @return 结果
99
+     */
100
+    @Override
101
+    public int updateAttendanceRecord(AttendanceRecord attendanceRecord) {
102
+        attendanceRecord.setUpdateTime(DateUtils.getNowDate());
103
+        return attendanceRecordMapper.updateAttendanceRecord(attendanceRecord);
104
+    }
105
+
106
+    /**
107
+     * 批量删除考勤记录
108
+     *
109
+     * @param ids 需要删除的考勤记录主键
110
+     * @return 结果
111
+     */
112
+    @Override
113
+    public int deleteAttendanceRecordByIds(Long[] ids) {
114
+        return attendanceRecordMapper.deleteAttendanceRecordByIds(ids);
115
+    }
116
+
117
+    /**
118
+     * 删除考勤记录信息
119
+     *
120
+     * @param id 考勤记录主键
121
+     * @return 结果
122
+     */
123
+    @Override
124
+    public int deleteAttendanceRecordById(Long id) {
125
+        return attendanceRecordMapper.deleteAttendanceRecordById(id);
126
+    }
127
+
128
+    /**
129
+     * 打卡签到
130
+     *
131
+     * @param dto      打卡信息
132
+     * @param userInfo 登录用户
133
+     * @return 今日打卡历史
134
+     */
135
+    @Override
136
+    public List<AttendanceCheckRecord> record(AttendanceRecordDTO dto, UserInfo userInfo) {
137
+        //确定打卡时间
138
+        if(ObjectUtil.isNotEmpty(dto.getCheckInDate())){
139
+            String startTime = configService.selectConfigByKey("attendance.record.start.time");
140
+            String endTime = configService.selectConfigByKey("attendance.record.end.time");
141
+            Date checkInTime = this.calculateAttendanceDate(new Date(), startTime, endTime, dto.getCheckInType());
142
+            dto.setCheckInDate(checkInTime);
143
+        }
144
+        //如果下班卡,直接取最近一条上班打卡记录
145
+        if(StrUtil.equals("2", dto.getCheckInType())){
146
+            AttendanceRecordReq dtoReq=new AttendanceRecordReq();
147
+            dtoReq.setUserId(userInfo.getUserId());
148
+            dtoReq.setCheckInDate(dto.getCheckInDate());
149
+            List<AttendanceRecord> attendanceRecords = queryRecordList(dtoReq);
150
+            if(CollectionUtil.isNotEmpty(attendanceRecords)){
151
+                AttendanceRecord lastRecord = attendanceRecords.get(0);
152
+                dto.setCheckInDate(lastRecord.getAttendanceDate());
153
+            }
154
+
155
+        }
156
+        // 处理打卡记录
157
+        AttendanceCheckRecord checkRecord = new AttendanceCheckRecord();
158
+        checkRecord.setRevision(1L);
159
+        checkRecord.setUserId(userInfo.getUserId());
160
+        checkRecord.setCheckInTime(new Date());
161
+        checkRecord.setCheckInDate(dto.getCheckInDate());
162
+        checkRecord.setCheckInType(StrUtil.equals("1", dto.getCheckInType()) ? CheckInTypeEnum.CLOCK_IN.getValue() : CheckInTypeEnum.CLOCK_OUT.getValue());
163
+        checkRecord.setCheckInPosition(dto.getCheckInPosition());
164
+        checkRecord.setUserName(userInfo.getNickName());
165
+        checkRecord.setRemark(dto.getRemark());
166
+        checkRecord.setCheckInTypeDesc(StrUtil.equals("1", dto.getCheckInType()) ? CheckInTypeEnum.CLOCK_IN.getName() : CheckInTypeEnum.CLOCK_OUT.getName());
167
+        checkRecord.setCreateBy(userInfo.getUserId().toString());
168
+        checkRecord.setCreateTime(new Date());
169
+
170
+        // 处理梭班
171
+        processShuttleShiftLogic(checkRecord, userInfo);
172
+        // 保存打卡记录
173
+        attendanceCheckRecordMapper.insertAttendanceCheckRecord(checkRecord);
174
+
175
+        // 如果是上班打卡,发布打卡事件,触发每日答题任务生成
176
+        if (StrUtil.equals("1", dto.getCheckInType())) {
177
+            try {
178
+                ClockInEvent event = new ClockInEvent(
179
+                    this,
180
+                    userInfo.getUserId(),
181
+                    userInfo.getDepartmentId()
182
+                );
183
+                eventPublisher.publishEvent(event);
184
+            } catch (Exception e) {
185
+                // 事件发布失败不影响打卡主流程
186
+                // 仅记录日志
187
+            }
188
+        }
189
+
190
+        // 处理考勤记录
191
+        AttendanceRecord search = new AttendanceRecord();
192
+        search.setUserId(userInfo.getUserId());
193
+        search.setAttendanceDate(dto.getCheckInDate());
194
+        List<AttendanceRecord> attendanceCheckRecords = attendanceRecordMapper.selectAttendanceRecordList(search);
195
+        if (CollectionUtil.isEmpty(attendanceCheckRecords)) {
196
+            AttendanceRecord attendanceRecord = getAttendanceRecord(dto, userInfo);
197
+            attendanceRecordMapper.insertAttendanceRecord(attendanceRecord);
198
+        } else {
199
+            AttendanceRecord attendanceRecord = getAttendanceRecord(userInfo, attendanceCheckRecords);
200
+            attendanceRecord.setRemark(attendanceRecord.getRemark() + "\n" + dto.getRemark());
201
+            attendanceRecordMapper.updateAttendanceRecord(attendanceRecord);
202
+        }
203
+
204
+        AttendanceCheckRecord checkSearch = new AttendanceCheckRecord();
205
+        checkSearch.setUserId(userInfo.getUserId());
206
+        checkRecord.setCheckInDate(dto.getCheckInDate());
207
+        return attendanceCheckRecordMapper.selectAttendanceCheckRecordList(checkSearch);
208
+    }
209
+
210
+    private static AttendanceRecord getAttendanceRecord(AttendanceRecordDTO dto, UserInfo userInfo) {
211
+        AttendanceRecord attendanceRecord = new AttendanceRecord();
212
+        attendanceRecord.setRevision(1L);
213
+        attendanceRecord.setOverDuration(0L);
214
+        attendanceRecord.setUserId(userInfo.getUserId());
215
+        if (Objects.nonNull(userInfo.getTeamsId())) {
216
+            attendanceRecord.setTeamCode(userInfo.getTeamsId().toString());
217
+        }
218
+        attendanceRecord.setTeamName(userInfo.getTeamsName());
219
+        if (Objects.nonNull(userInfo.getDepartmentId())) {
220
+            attendanceRecord.setDepartmentCode(userInfo.getDepartmentId().toString());
221
+        }
222
+        attendanceRecord.setDepartmentName(userInfo.getDepartmentName());
223
+        if (Objects.nonNull(userInfo.getStationId())) {
224
+            attendanceRecord.setStationCode(userInfo.getStationId().toString());
225
+        }
226
+        attendanceRecord.setStationName(userInfo.getStationName());
227
+        attendanceRecord.setUserName(userInfo.getNickName());
228
+        attendanceRecord.setWorkDuration(0L);
229
+        attendanceRecord.setAttendanceDate(dto.getCheckInDate());
230
+        attendanceRecord.setCreateBy(userInfo.getUserId().toString());
231
+        attendanceRecord.setCreateTime(new Date());
232
+        attendanceRecord.setCheckInTime(new Date());
233
+        attendanceRecord.setRemark(dto.getRemark());
234
+        return attendanceRecord;
235
+    }
236
+
237
+    private static AttendanceRecord getAttendanceRecord(UserInfo userInfo, List<AttendanceRecord> attendanceCheckRecords) {
238
+        AttendanceRecord attendanceRecord = attendanceCheckRecords.get(0);
239
+        // 如果有记录则默认按照签退
240
+        attendanceRecord.setCheckOutTime(new Date());
241
+        attendanceRecord.setWorkDuration((attendanceRecord.getCheckOutTime().getTime() - attendanceRecord.getCheckInTime().getTime()) / 1000 / 60);
242
+        attendanceRecord.setUpdateBy(userInfo.getUserId().toString());
243
+        attendanceRecord.setUpdateTime(new Date());
244
+        attendanceRecord.setRevision(attendanceRecord.getRevision() + 1);
245
+        return attendanceRecord;
246
+    }
247
+
248
+    /**
249
+     * 获取打卡记录
250
+     *
251
+     * @param dto 打卡记录参数
252
+     * @return 打卡记录
253
+     */
254
+    @Override
255
+    public List<AttendanceRecordRep> recordList(AttendanceRecordReq dto) {
256
+        List<AttendanceRecordRep> result = Lists.newArrayList();
257
+        AttendanceRecord recordSearch = new AttendanceRecord();
258
+        recordSearch.setUserId(dto.getUserId());
259
+        if (StrUtil.equals("1", dto.getType())) {
260
+            // 单日查询场景
261
+            Date targetAttendanceDate = dto.getCheckInDate();
262
+            // 计算查询时间范围
263
+            Date yesterday = DateUtils.addDays(targetAttendanceDate, -1);
264
+            // 设置查询条件
265
+            recordSearch.setAttendanceDateStart(yesterday);
266
+            recordSearch.setAttendanceDateEnd(targetAttendanceDate);
267
+        } else {
268
+            recordSearch.setAttendanceDateStart(dto.getStartDate());
269
+            recordSearch.setAttendanceDateEnd(dto.getEndDate());
270
+        }
271
+        List<AttendanceRecord> attendanceRecords = attendanceRecordMapper.selectAttendanceRecordList(recordSearch);
272
+        if (CollectionUtil.isEmpty(attendanceRecords)) {
273
+            return result;
274
+        }
275
+        AttendanceCheckRecord checkSearch = new AttendanceCheckRecord();
276
+        checkSearch.setUserId(dto.getUserId());
277
+        if (StrUtil.equals("1", dto.getType())) {
278
+            checkSearch.setCheckInDate(attendanceRecords.get(0).getAttendanceDate());
279
+        } else {
280
+            checkSearch.setCheckInDateStart(dto.getStartDate());
281
+            checkSearch.setCheckInDateEnd(dto.getEndDate());
282
+        }
283
+        List<AttendanceCheckRecord> attendanceCheckRecords = attendanceCheckRecordMapper.selectAttendanceCheckRecordList(checkSearch);
284
+
285
+        attendanceRecords.forEach(record -> {
286
+            AttendanceRecordRep main = new AttendanceRecordRep();
287
+            BeanUtil.copyProperties(record, main);
288
+            List<AttendanceCheckRecord> collect = attendanceCheckRecords.stream().filter(x -> Objects.equals(x.getCheckInDate(), record.getAttendanceDate())).collect(Collectors.toList());
289
+            if (CollectionUtil.isNotEmpty(collect)) {
290
+                collect.forEach(c -> {
291
+                    AttendanceRecordRep.RecordItem item = new AttendanceRecordRep.RecordItem();
292
+                    BeanUtil.copyProperties(c, item);
293
+                    main.getItems().add(item);
294
+                });
295
+            }
296
+            result.add(main);
297
+        });
298
+        return result;
299
+    }
300
+
301
+
302
+    /**
303
+     * 根据实际打卡时间和班次时间计算所属考勤日期
304
+     *
305
+     * @param checkInTime 实际打卡时间,即员工实际进行打卡操作的时间点
306
+     * @param shiftStartTime 班次开始时间,格式为 "HH:mm"
307
+     * @param shiftEndTime 班次结束时间,格式为 "HH:mm"
308
+     * @param checkInType 打卡类型:1-上班打卡,2-下班打卡
309
+     * @return 计算出的考勤日期,该次打卡记录应归属的考勤周期日期
310
+     */
311
+    public Date calculateAttendanceDate(Date checkInTime, String shiftStartTime, String shiftEndTime, String checkInType) {
312
+        // 参数校验
313
+        if (checkInTime == null || shiftStartTime == null || shiftEndTime == null) {
314
+            throw new ServiceException("参数不能为空: checkInTime, shiftStartTime, shiftEndTime");
315
+        }
316
+
317
+        final String DATE_FORMAT = "yyyy-MM-dd";
318
+        final String TIME_FORMAT = "HH:mm";
319
+
320
+        try {
321
+            String checkInDateStr = DateUtils.parseDateToStr(DATE_FORMAT, checkInTime);
322
+            Date checkInDateTime = DateUtils.parseDate(checkInDateStr + " " + DateUtils.parseDateToStr(TIME_FORMAT, checkInTime));
323
+
324
+            // 构造昨天的班次时间段
325
+            Date yesterday = DateUtils.addDays(checkInTime, -1);
326
+            String yesterdayStr = DateUtils.parseDateToStr(DATE_FORMAT, yesterday);
327
+            Date yesterdayShiftStart = DateUtils.parseDate(yesterdayStr + " " + shiftStartTime);
328
+            Date todayShiftEnd = DateUtils.parseDate(checkInDateStr + " " + shiftEndTime);
329
+
330
+            boolean isWithinYesterdayShift = checkInDateTime.compareTo(yesterdayShiftStart) >= 0 &&
331
+                    checkInDateTime.compareTo(todayShiftEnd) <= 0;
332
+
333
+            if (StrUtil.isBlank(checkInType)) {
334
+                // 默认规则:若在昨天班次时间内,则归属昨天
335
+                if (isWithinYesterdayShift) {
336
+                    return DateUtils.parseDate(yesterdayStr);
337
+                }
338
+                return DateUtils.parseDate(checkInDateStr);
339
+            } else {
340
+                if (StrUtil.equals("1", checkInType)) {
341
+                    // 类型1:如果早于昨天班次开始时间,则归属昨天
342
+                    if (checkInDateTime.before(yesterdayShiftStart)) {
343
+                        return DateUtils.parseDate(yesterdayStr);
344
+                    }
345
+                    return DateUtils.parseDate(checkInDateStr);
346
+                } else {
347
+                    // 如果早于今日班次结束时间,则归属昨天
348
+                    if (checkInDateTime.before(todayShiftEnd)) {
349
+                        return DateUtils.parseDate(yesterdayStr);
350
+                    }
351
+                    return DateUtils.parseDate(checkInDateStr);
352
+                }
353
+            }
354
+
355
+        } catch (Exception e) {
356
+            throw new ServiceException("日期解析失败: " + e.getMessage());
357
+        }
358
+    }
359
+
360
+
361
+
362
+    /**
363
+     * 处理梭班
364
+     *
365
+     * @param checkRecord 打卡记录
366
+     * @param userInfo 用户信息
367
+     */
368
+    private void processShuttleShiftLogic(AttendanceCheckRecord checkRecord, UserInfo userInfo) {
369
+        // 获取用户最近一次打卡记录
370
+        AttendanceCheckRecord lastRecord = getLastCheckRecord(userInfo.getUserId());
371
+
372
+        // 判断是否为梭班场景(先下后上且在时间窗口内)
373
+        if (isShuttleShiftScenario(lastRecord, checkRecord)) {
374
+            // 标记为梭班打卡
375
+            checkRecord.setShuttleShift(true);
376
+            checkRecord.setShiftType("SHUTTLE");
377
+
378
+            // 如果是上班卡,归属到第二天考勤(相对于上次考勤日期)
379
+            if (checkRecord.getCheckInType().equals(CheckInTypeEnum.CLOCK_IN.getValue())) {
380
+                Date nextDayAttendanceDate = calculateNextDayAttendanceDateForShuttleShift(lastRecord);
381
+                checkRecord.setCheckInDate(nextDayAttendanceDate);
382
+            }
383
+        }
384
+
385
+    }
386
+
387
+
388
+
389
+    /**
390
+     * 判断是否为梭班场景
391
+     *
392
+     * @param lastRecord 最近打卡记录
393
+     * @param currentRecord 当前打卡记录
394
+     * @return 是否为梭班场景
395
+     */
396
+    private boolean isShuttleShiftScenario(AttendanceCheckRecord lastRecord, AttendanceCheckRecord currentRecord) {
397
+        // 需要满足以下条件:
398
+        // 1. 存在上一次打卡记录
399
+        // 2. 上次是下班卡,本次是上班卡
400
+        // 3. 两次打卡时间间隔在梭班时间窗口内(2小时内)
401
+        if (lastRecord == null) {
402
+            return false;
403
+        }
404
+
405
+        boolean isSequenceValid = lastRecord.getCheckInType().equals(CheckInTypeEnum.CLOCK_OUT.getValue())
406
+                && currentRecord.getCheckInType().equals(CheckInTypeEnum.CLOCK_IN.getValue());
407
+
408
+        long timeDiff = currentRecord.getCheckInTime().getTime() - lastRecord.getCheckInTime().getTime();
409
+        boolean isTimeValid = timeDiff > 0 && timeDiff <= SHUTTLE_TIME_WINDOW;
410
+
411
+        return isSequenceValid && isTimeValid;
412
+    }
413
+
414
+
415
+
416
+    /**
417
+     * 获取用户最近一次打卡记录
418
+     *
419
+     * @param userId 用户ID
420
+     * @return 最近打卡记录
421
+     */
422
+    private AttendanceCheckRecord getLastCheckRecord(Long userId) {
423
+        AttendanceCheckRecord search = new AttendanceCheckRecord();
424
+        search.setUserId(userId);
425
+        List<AttendanceCheckRecord> records = attendanceCheckRecordMapper.selectAttendanceCheckRecordList(search);
426
+
427
+        if (CollectionUtil.isNotEmpty(records)) {
428
+            // 返回最近的打卡记录
429
+            return records.stream()
430
+                    .sorted((r1, r2) -> r2.getCheckInTime().compareTo(r1.getCheckInTime()))
431
+                    .findFirst()
432
+                    .orElse(null);
433
+        }
434
+        return null;
435
+    }
436
+
437
+
438
+
439
+    /**
440
+     * 计算梭班场景下的次日考勤日期(基于上次考勤日期)
441
+     *
442
+     * @param lastRecord 最近一次打卡日期
443
+     * @return 次日考勤日期
444
+     */
445
+    private Date calculateNextDayAttendanceDateForShuttleShift(AttendanceCheckRecord lastRecord) {
446
+        // 查询用户最近的考勤记录
447
+        if (ObjectUtil.isNotEmpty(lastRecord)) {
448
+            LocalDateTime lastAttendanceDate = lastRecord.getCheckInDate().toInstant()
449
+                    .atZone(ZoneId.systemDefault())
450
+                    .toLocalDateTime();
451
+
452
+            // 返回上次考勤日期的次日
453
+            return Date.from(
454
+                    lastAttendanceDate.toLocalDate()
455
+                            .plusDays(1)
456
+                            .atStartOfDay(ZoneId.systemDefault())
457
+                            .toInstant()
458
+            );
459
+        } else {
460
+            // 如果没有历史记录,则使用当前日期
461
+            return Date.from(
462
+                    LocalDate.now()
463
+                            .atStartOfDay(ZoneId.systemDefault())
464
+                            .toInstant()
465
+            );
466
+        }
467
+    }
468
+
469
+
470
+    private List<AttendanceRecord> queryRecordList(AttendanceRecordReq dto) {
471
+        AttendanceRecord recordSearch = new AttendanceRecord();
472
+        recordSearch.setUserId(dto.getUserId());
473
+        // 单日查询场景
474
+        Date targetAttendanceDate = dto.getCheckInDate();
475
+        // 计算查询时间范围
476
+        Date yesterday = DateUtils.addDays(targetAttendanceDate, -1);
477
+        // 设置查询条件
478
+        recordSearch.setAttendanceDateStart(yesterday);
479
+        recordSearch.setAttendanceDateEnd(targetAttendanceDate);
480
+        return attendanceRecordMapper.selectAttendanceRecordList(recordSearch);
481
+    }
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+}

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

@@ -0,0 +1,273 @@
1
+package com.sundot.airport.attendance.service.impl;
2
+
3
+import java.text.ParseException;
4
+import java.text.SimpleDateFormat;
5
+import java.util.*;
6
+import java.util.stream.Collectors;
7
+
8
+import cn.hutool.core.collection.CollectionUtil;
9
+import cn.hutool.core.util.StrUtil;
10
+import com.alibaba.fastjson2.JSON;
11
+import com.alibaba.fastjson2.JSONArray;
12
+import com.sundot.airport.common.core.domain.entity.SysUser;
13
+import com.sundot.airport.common.exception.ServiceException;
14
+import com.sundot.airport.common.utils.DateUtils;
15
+import com.sundot.airport.system.mapper.SysUserMapper;
16
+import com.sundot.airport.system.service.ISysConfigService;
17
+import org.springframework.beans.factory.annotation.Autowired;
18
+import org.springframework.stereotype.Service;
19
+import com.sundot.airport.attendance.mapper.AttendanceTeamUserRecordMapper;
20
+import com.sundot.airport.attendance.domain.AttendanceTeamUserRecord;
21
+import com.sundot.airport.attendance.service.IAttendanceTeamUserRecordService;
22
+
23
+import static com.sundot.airport.common.utils.SecurityUtils.getUsername;
24
+
25
+/**
26
+ * 考勤班组成员Service业务层处理
27
+ * 
28
+ * @author ruoyi
29
+ * @date 2025-09-06
30
+ */
31
+@Service
32
+public class AttendanceTeamUserRecordServiceImpl implements IAttendanceTeamUserRecordService 
33
+{
34
+    @Autowired
35
+    private AttendanceTeamUserRecordMapper attendanceTeamUserRecordMapper;
36
+
37
+    @Autowired
38
+    private ISysConfigService configService;
39
+
40
+    @Autowired
41
+    private SysUserMapper userMapper;
42
+
43
+    /**
44
+     * 查询考勤班组成员
45
+     * 
46
+     * @param userId 考勤班组成员主键
47
+     * @return 考勤班组成员
48
+     */
49
+    @Override
50
+    public AttendanceTeamUserRecord selectAttendanceTeamUserRecordByUserId(Long userId, Date attendanceDate)
51
+    {
52
+        return attendanceTeamUserRecordMapper.selectAttendanceTeamUserRecordByUserId(userId, attendanceDate);
53
+    }
54
+
55
+    @Override
56
+    public List<AttendanceTeamUserRecord> selectAttendanceTeamUserRecordPageList(AttendanceTeamUserRecord attendanceTeamUserRecord) {
57
+        return attendanceTeamUserRecordMapper.selectAttendanceTeamUserRecordList(attendanceTeamUserRecord);
58
+    }
59
+
60
+    /**
61
+     * 上岗记录中查询考勤班组成员列表
62
+     * 
63
+     * @param attendanceTeamUserRecord 考勤班组成员
64
+     * @return 考勤班组成员
65
+     */
66
+    @Override
67
+    public List<AttendanceTeamUserRecord> selectAttendanceTeamUserRecordList(AttendanceTeamUserRecord attendanceTeamUserRecord)
68
+    {
69
+        // 如果指定了考勤日期,则调整查询时间范围
70
+        if (attendanceTeamUserRecord.getAttendanceDate() != null) {
71
+            try {
72
+                String display = configService.selectConfigByKey("attendance.team.user.record.display.end.time");
73
+                Date date = getDate(new Date(), display);
74
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
75
+                String baseDateStr = sdf.format(date);
76
+                String nextDateStr = sdf.format(DateUtils.addDays(date, 1));
77
+
78
+                // 显示时间范围:当日7:00到次日9:00
79
+                Date displayStartDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(baseDateStr + " " + configService.selectConfigByKey("attendance.team.user.record.start.time"));
80
+                Date displayEndDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(nextDateStr + " " + display);
81
+
82
+                // 设置查询时间范围
83
+                attendanceTeamUserRecord.setAttendanceDateStart(displayStartDate);
84
+                attendanceTeamUserRecord.setAttendanceDateEnd(displayEndDate);
85
+                // 清除原始的attendanceDate,避免冲突
86
+                attendanceTeamUserRecord.setAttendanceDate(null);
87
+            } catch (Exception e) {
88
+                throw new ServiceException("日期格式化错误");
89
+            }
90
+        }
91
+        return attendanceTeamUserRecordMapper.selectAttendanceTeamUserRecordList(attendanceTeamUserRecord);
92
+    }
93
+
94
+
95
+    /**
96
+     * 新增考勤班组成员
97
+     * 
98
+     * @param attendanceTeamUserRecord 考勤班组成员
99
+     * @return 结果
100
+     */
101
+    @Override
102
+    public int insertAttendanceTeamUserRecord(AttendanceTeamUserRecord attendanceTeamUserRecord)
103
+    {
104
+        attendanceTeamUserRecord.setCreateTime(DateUtils.getNowDate());
105
+        return attendanceTeamUserRecordMapper.insertAttendanceTeamUserRecord(attendanceTeamUserRecord);
106
+    }
107
+
108
+    /**
109
+     * 修改考勤班组成员
110
+     * 
111
+     * @param attendanceTeamUserRecord 考勤班组成员
112
+     * @return 结果
113
+     */
114
+    @Override
115
+    public int updateAttendanceTeamUserRecord(AttendanceTeamUserRecord attendanceTeamUserRecord)
116
+    {
117
+        attendanceTeamUserRecord.setUpdateTime(DateUtils.getNowDate());
118
+        return attendanceTeamUserRecordMapper.updateAttendanceTeamUserRecord(attendanceTeamUserRecord);
119
+    }
120
+
121
+    /**
122
+     * 批量删除考勤班组成员
123
+     * 
124
+     * @param userIds 需要删除的考勤班组成员主键
125
+     * @return 结果
126
+     */
127
+    @Override
128
+    public int deleteAttendanceTeamUserRecordByUserIds(Long[] userIds)
129
+    {
130
+        return attendanceTeamUserRecordMapper.deleteAttendanceTeamUserRecordByUserIds(userIds);
131
+    }
132
+
133
+    /**
134
+     * 删除考勤班组成员信息
135
+     * 
136
+     * @param userId 考勤班组成员主键
137
+     * @return 结果
138
+     */
139
+    @Override
140
+    public int deleteAttendanceTeamUserRecordByUserId(Long userId)
141
+    {
142
+        return attendanceTeamUserRecordMapper.deleteAttendanceTeamUserRecordByUserId(userId);
143
+    }
144
+
145
+    /**
146
+     *  批量插入考勤班组成员
147
+     * @param attendanceTeamUserRecord
148
+     * @return
149
+     */
150
+    @Override
151
+    public int insertAttendanceTeamUserRecordList(List<AttendanceTeamUserRecord> attendanceTeamUserRecord) {
152
+        List<String> userName = new ArrayList<>();
153
+        attendanceTeamUserRecord.forEach(record -> {
154
+            record.setCreateTime(DateUtils.getNowDate());
155
+            record.setCreateBy(getUsername());
156
+            if(Objects.isNull(record.getAttendanceDate())){
157
+                record.setAttendanceDate(DateUtils.getNowDate());
158
+            }
159
+            if(Objects.isNull(record.getUserId())){
160
+                throw new ServiceException("用户id不能为空");
161
+            }
162
+
163
+            // 计算班次周期范围:前一天到后一天
164
+            Date startDate = DateUtils.addDays(record.getAttendanceDate(), -1);
165
+            Date endDate = DateUtils.addDays(record.getAttendanceDate(), 1);
166
+
167
+            // 构建查询条件
168
+            AttendanceTeamUserRecord searchRecord = new AttendanceTeamUserRecord();
169
+            searchRecord.setUserId(record.getUserId());
170
+            searchRecord.setAttendanceDateStart(startDate);
171
+            searchRecord.setAttendanceDateEnd(endDate);
172
+
173
+            // 查询时间范围内的记录
174
+            List<AttendanceTeamUserRecord> existingRecords = attendanceTeamUserRecordMapper
175
+                    .selectAttendanceTeamUserRecordList(searchRecord);
176
+
177
+            // 检查是否存在班次周期内的重复记录(当日7:00 - 次日7:00)
178
+            if (hasRecordInShiftPeriod(existingRecords, record.getAttendanceDate())) {
179
+                // 获取第一个重复记录的用户名
180
+                AttendanceTeamUserRecord firstDuplicate = existingRecords.stream()
181
+                        .filter(r -> isWithinShiftPeriod(r.getCreateTime(), new Date()))
182
+                        .findFirst()
183
+                        .orElse(null);
184
+                if (firstDuplicate != null) {
185
+                    userName.add(firstDuplicate.getUserName());
186
+                }
187
+            }
188
+        });
189
+        if(userName.size()>0){
190
+            throw new ServiceException(StrUtil.format("用户{}在当前班次周期内已维护", JSONArray.toJSONString(userName)));
191
+        }
192
+        return attendanceTeamUserRecordMapper.insertAttendanceTeamUserRecordList(attendanceTeamUserRecord);
193
+    }
194
+
195
+    @Override
196
+    public List<Long> selectAttendanceTeamLeaderId() {
197
+        AttendanceTeamUserRecord attendanceTeamUserRecord = new AttendanceTeamUserRecord();
198
+        attendanceTeamUserRecord.setAttendanceDate(DateUtils.getNowDate());
199
+        List<AttendanceTeamUserRecord> attendanceTeamUserRecords = selectAttendanceTeamUserRecordList(attendanceTeamUserRecord);
200
+        List<Long> collect = attendanceTeamUserRecords.stream().map(AttendanceTeamUserRecord::getAttendanceTeamId).collect(Collectors.toList());
201
+        if(CollectionUtil.isEmpty( collect)){
202
+            return new ArrayList<>() ;
203
+        }
204
+        List<Long> longs = userMapper.selectAttendanceTeamLeaderId(collect);
205
+        if(CollectionUtil.isEmpty(longs)){
206
+            return new ArrayList<>();
207
+        }
208
+        return longs.stream().distinct().collect(Collectors.toList());
209
+    }
210
+
211
+
212
+
213
+    /**
214
+     * 判断是否存在班次周期内的记录
215
+     * @param records 查询到的记录列表
216
+     * @param targetDate 目标日期
217
+     * @return 是否存在班次周期内的记录
218
+     */
219
+    private boolean hasRecordInShiftPeriod(List<AttendanceTeamUserRecord> records, Date targetDate) {
220
+        return records.stream().anyMatch(record -> isWithinShiftPeriod(record.getCreateTime(), targetDate));
221
+    }
222
+
223
+    /**
224
+     * 判断指定日期是否在目标日期的班次周期内(当日7:00 - 次日7:00)
225
+     * @param recordDate 记录日期
226
+     * @param targetDate 目标日期
227
+     * @return 是否在班次周期内
228
+     */
229
+    private boolean isWithinShiftPeriod(Date recordDate, Date targetDate) {
230
+        if (recordDate == null || targetDate == null) {
231
+            return false;
232
+        }
233
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
234
+        try {
235
+            String end = configService.selectConfigByKey("attendance.team.user.record.end.time");
236
+            targetDate = getDate(targetDate, end);
237
+            String targetDateStr = sdf.format(targetDate);
238
+            String nextDateStr = sdf.format(DateUtils.addDays(targetDate, 1));
239
+            String recordDateStr = sdf.format(recordDate);
240
+
241
+            // 班次的开始时间(targetDate 07:00:00)
242
+            Date shiftStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(targetDateStr + " " + configService.selectConfigByKey("attendance.team.user.record.start.time"));
243
+
244
+            // 班次的结束时间(targetDate+1天 07:00:00)
245
+            Date shiftEndTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(nextDateStr + " " + end);
246
+
247
+            // 构造记录日期的代表时间(recordDate 00:00:00)
248
+            Date recordDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(recordDateStr + " 00:00:00");
249
+
250
+            // 判断记录日期是否在班次周期内
251
+            return !(recordDateTime.before(shiftStartTime) || recordDateTime.after(shiftEndTime));
252
+        } catch (Exception e) {
253
+            return false;
254
+        }
255
+    }
256
+
257
+    private Date getDate(Date targetDate, String time) {
258
+        if (targetDate == null) {
259
+            return null;
260
+        }
261
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
262
+        try {
263
+            //判断targetDate是否在7点之前
264
+            if (targetDate.before(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(sdf.format(targetDate) +" "+ time))) {
265
+                targetDate=DateUtils.addDays(targetDate, -1);
266
+            }
267
+            return targetDate;
268
+        } catch (ParseException e) {
269
+            throw new ServiceException("日期解析失败");
270
+        }
271
+    }
272
+
273
+}

+ 58 - 0
airport-attendance/src/main/resources/mapper/attendance/AttendanceAreaMapper.xml

@@ -0,0 +1,58 @@
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.attendance.mapper.AttendanceAreaMapper">
6
+
7
+    <!-- 通用结果映射 -->
8
+    <resultMap id="AttendanceAreaResult" type="AttendanceArea">
9
+        <id column="id" property="id" jdbcType="INTEGER"/>
10
+        <result column="area_name" property="areaName" jdbcType="VARCHAR"/>
11
+        <result column="address" property="address" jdbcType="VARCHAR"/>
12
+        <result column="longitude" property="longitude" jdbcType="DECIMAL"/>
13
+        <result column="latitude" property="latitude" jdbcType="DECIMAL"/>
14
+        <result column="radius" property="radius" jdbcType="INTEGER"/>
15
+        <result column="status" property="status" jdbcType="TINYINT"/>
16
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
17
+        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
18
+    </resultMap>
19
+
20
+    <!-- 带距离计算的结果映射 -->
21
+    <resultMap id="DistanceResultMap" type="AttendanceArea"
22
+               extends="AttendanceAreaResult">
23
+        <result column="distance" property="distance" jdbcType="DECIMAL"/>
24
+    </resultMap>
25
+
26
+    <sql id="selectAttendanceCheckRecordVo">
27
+        select id, area_name, address, longitude, latitude, radius, status, create_time, update_time from attendance_area
28
+    </sql>
29
+
30
+    <select id="findValidAreasEfficiently" resultMap="DistanceResultMap">
31
+        SELECT
32
+            id, area_name, address,
33
+            longitude, latitude, radius, status,
34
+            create_time, update_time,
35
+            ST_Distance_Sphere(
36
+                    POINT(longitude, latitude),
37
+                    POINT(#{lng}, #{lat})
38
+            ) AS distance
39
+        FROM attendance_area
40
+        WHERE
41
+            status = 1
42
+          AND longitude IS NOT NULL
43
+          AND latitude IS NOT NULL
44
+          AND longitude BETWEEN -180 AND 180
45
+          AND latitude BETWEEN -90 AND 90
46
+          AND ST_Distance_Sphere(
47
+                POINT(longitude, latitude),
48
+                POINT(#{lng}, #{lat})
49
+              ) BETWEEN 0 AND radius
50
+    </select>
51
+
52
+    <select id="areaList"  resultMap="DistanceResultMap">
53
+        <include refid="selectAttendanceCheckRecordVo"/>
54
+        WHERE
55
+        status = 1
56
+    </select>
57
+
58
+</mapper>

+ 126 - 0
airport-attendance/src/main/resources/mapper/attendance/AttendanceCheckRecordMapper.xml

@@ -0,0 +1,126 @@
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.attendance.mapper.AttendanceCheckRecordMapper">
6
+
7
+    <resultMap type="AttendanceCheckRecord" id="AttendanceCheckRecordResult">
8
+        <result property="tenantId"    column="tenant_id"    />
9
+        <result property="revision"    column="revision"    />
10
+        <result property="createBy"    column="create_by"    />
11
+        <result property="createTime"    column="create_time"    />
12
+        <result property="updateBy"    column="update_by"    />
13
+        <result property="updateTime"    column="update_time"    />
14
+        <result property="id"    column="id"    />
15
+        <result property="userId"    column="user_id"    />
16
+        <result property="checkInType"    column="check_in_type"    />
17
+        <result property="checkInTime"    column="check_in_time"    />
18
+        <result property="userName"    column="user_name"    />
19
+        <result property="remark"    column="remark"    />
20
+        <result property="checkInTypeDesc"    column="check_in_type_desc"    />
21
+        <result property="checkInPosition"    column="check_in_position"    />
22
+        <result property="checkInDate"    column="check_in_date"    />
23
+    </resultMap>
24
+
25
+    <sql id="selectAttendanceCheckRecordVo">
26
+        select tenant_id, revision, create_by, create_time, update_by, update_time, id, user_id, check_in_type, check_in_time, user_name, remark, check_in_type_desc, check_in_position, check_in_date from attendance_check_record
27
+    </sql>
28
+
29
+    <select id="selectAttendanceCheckRecordList" parameterType="AttendanceCheckRecord" resultMap="AttendanceCheckRecordResult">
30
+        <include refid="selectAttendanceCheckRecordVo"/>
31
+        <where>
32
+            <if test="tenantId != null  and tenantId != ''"> and tenant_id = #{tenantId}</if>
33
+            <if test="revision != null "> and revision = #{revision}</if>
34
+            <if test="userId != null "> and user_id = #{userId}</if>
35
+            <if test="checkInType != null  and checkInType != ''"> and check_in_type = #{checkInType}</if>
36
+            <if test="checkInTime != null "> and check_in_time = #{checkInTime}</if>
37
+            <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
38
+            <if test="remark != null  and remark != ''"> and remark = #{remark}</if>
39
+            <if test="checkInTypeDesc != null  and checkInTypeDesc != ''"> and check_in_type_desc = #{checkInTypeDesc}</if>
40
+            <if test="checkInPosition != null  and checkInPosition != ''"> and check_in_position = #{checkInPosition}</if>
41
+            <if test="checkInDate != null "> and check_in_date = #{checkInDate}</if>
42
+            <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
43
+                and date_format(check_in_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
44
+            </if>
45
+            <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
46
+                and date_format(check_in_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
47
+            </if>
48
+            <if test="checkInDateStart != null "> and check_in_date &gt;= concat(#{checkInDateStart}, ' 00:00:00')</if>
49
+            <if test="checkInDateEnd != null "> and check_in_date &lt;= concat(#{checkInDateEnd}, ' 23:59:59')</if>
50
+        </where>
51
+        order by create_time desc
52
+    </select>
53
+
54
+    <select id="selectAttendanceCheckRecordById" parameterType="Long" resultMap="AttendanceCheckRecordResult">
55
+        <include refid="selectAttendanceCheckRecordVo"/>
56
+        where id = #{id}
57
+    </select>
58
+
59
+    <insert id="insertAttendanceCheckRecord" parameterType="AttendanceCheckRecord" useGeneratedKeys="true" keyProperty="id">
60
+        insert into attendance_check_record
61
+        <trim prefix="(" suffix=")" suffixOverrides=",">
62
+            <if test="tenantId != null">tenant_id,</if>
63
+            <if test="revision != null">revision,</if>
64
+            <if test="createBy != null">create_by,</if>
65
+            <if test="createTime != null">create_time,</if>
66
+            <if test="updateBy != null">update_by,</if>
67
+            <if test="updateTime != null">update_time,</if>
68
+            <if test="userId != null">user_id,</if>
69
+            <if test="checkInType != null and checkInType != ''">check_in_type,</if>
70
+            <if test="checkInTime != null">check_in_time,</if>
71
+            <if test="userName != null and userName != ''">user_name,</if>
72
+            <if test="remark != null">remark,</if>
73
+            <if test="checkInTypeDesc != null and checkInTypeDesc != ''">check_in_type_desc,</if>
74
+            <if test="checkInPosition != null">check_in_position,</if>
75
+            <if test="checkInDate != null">check_in_date,</if>
76
+        </trim>
77
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
78
+            <if test="tenantId != null">#{tenantId},</if>
79
+            <if test="revision != null">#{revision},</if>
80
+            <if test="createBy != null">#{createBy},</if>
81
+            <if test="createTime != null">#{createTime},</if>
82
+            <if test="updateBy != null">#{updateBy},</if>
83
+            <if test="updateTime != null">#{updateTime},</if>
84
+            <if test="userId != null">#{userId},</if>
85
+            <if test="checkInType != null and checkInType != ''">#{checkInType},</if>
86
+            <if test="checkInTime != null">#{checkInTime},</if>
87
+            <if test="userName != null and userName != ''">#{userName},</if>
88
+            <if test="remark != null">#{remark},</if>
89
+            <if test="checkInTypeDesc != null and checkInTypeDesc != ''">#{checkInTypeDesc},</if>
90
+            <if test="checkInPosition != null">#{checkInPosition},</if>
91
+            <if test="checkInDate != null">#{checkInDate},</if>
92
+        </trim>
93
+    </insert>
94
+
95
+    <update id="updateAttendanceCheckRecord" parameterType="AttendanceCheckRecord">
96
+        update attendance_check_record
97
+        <trim prefix="SET" suffixOverrides=",">
98
+            <if test="tenantId != null">tenant_id = #{tenantId},</if>
99
+            <if test="revision != null">revision = #{revision},</if>
100
+            <if test="createBy != null">create_by = #{createBy},</if>
101
+            <if test="createTime != null">create_time = #{createTime},</if>
102
+            <if test="updateBy != null">update_by = #{updateBy},</if>
103
+            <if test="updateTime != null">update_time = #{updateTime},</if>
104
+            <if test="userId != null">user_id = #{userId},</if>
105
+            <if test="checkInType != null and checkInType != ''">check_in_type = #{checkInType},</if>
106
+            <if test="checkInTime != null">check_in_time = #{checkInTime},</if>
107
+            <if test="userName != null and userName != ''">user_name = #{userName},</if>
108
+            <if test="remark != null">remark = #{remark},</if>
109
+            <if test="checkInTypeDesc != null and checkInTypeDesc != ''">check_in_type_desc = #{checkInTypeDesc},</if>
110
+            <if test="checkInPosition != null">check_in_position = #{checkInPosition},</if>
111
+            <if test="checkInDate != null">check_in_date = #{checkInDate},</if>
112
+        </trim>
113
+        where id = #{id}
114
+    </update>
115
+
116
+    <delete id="deleteAttendanceCheckRecordById" parameterType="Long">
117
+        delete from attendance_check_record where id = #{id}
118
+    </delete>
119
+
120
+    <delete id="deleteAttendanceCheckRecordByIds" parameterType="String">
121
+        delete from attendance_check_record where id in
122
+        <foreach item="id" collection="array" open="(" separator="," close=")">
123
+            #{id}
124
+        </foreach>
125
+    </delete>
126
+</mapper>

File diff suppressed because it is too large
+ 334 - 0
airport-attendance/src/main/resources/mapper/attendance/AttendancePostRecordMapper.xml


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

@@ -0,0 +1,161 @@
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.attendance.mapper.AttendanceRecordMapper">
6
+    
7
+    <resultMap type="AttendanceRecord" id="AttendanceRecordResult">
8
+        <result property="tenantId"    column="tenant_id"    />
9
+        <result property="revision"    column="revision"    />
10
+        <result property="createBy"    column="create_by"    />
11
+        <result property="createTime"    column="create_time"    />
12
+        <result property="updateBy"    column="update_by"    />
13
+        <result property="updateTime"    column="update_time"    />
14
+        <result property="id"    column="id"    />
15
+        <result property="userId"    column="user_id"    />
16
+        <result property="checkInTime"    column="check_in_time"    />
17
+        <result property="checkOutTime"    column="check_out_time"    />
18
+        <result property="workDuration"    column="work_duration"    />
19
+        <result property="status"    column="status"    />
20
+        <result property="teamCode"    column="team_code"    />
21
+        <result property="teamName"    column="team_name"    />
22
+        <result property="departmentCode"    column="department_code"    />
23
+        <result property="departmentName"    column="department_name"    />
24
+        <result property="stationCode"    column="station_code"    />
25
+        <result property="stationName"    column="station_name"    />
26
+        <result property="userName"    column="user_name"    />
27
+        <result property="remark"    column="remark"    />
28
+        <result property="attendanceDate"    column="attendance_date"    />
29
+        <result property="overDuration"    column="over_duration"    />
30
+        <result property="statusDesc"    column="status_desc"    />
31
+    </resultMap>
32
+
33
+    <sql id="selectAttendanceRecordVo">
34
+        select tenant_id, revision, create_by, create_time, update_by, update_time, id, user_id, check_in_time, check_out_time, work_duration, status, team_code, team_name, department_code, department_name, station_code, station_name, user_name, remark, attendance_date, over_duration, status_desc from attendance_record
35
+    </sql>
36
+
37
+    <select id="selectAttendanceRecordList" parameterType="AttendanceRecord" resultMap="AttendanceRecordResult">
38
+        <include refid="selectAttendanceRecordVo"/>
39
+        <where>  
40
+            <if test="tenantId != null  and tenantId != ''"> and tenant_id = #{tenantId}</if>
41
+            <if test="revision != null "> and revision = #{revision}</if>
42
+            <if test="userId != null "> and user_id = #{userId}</if>
43
+            <if test="checkInTime != null "> and check_in_time = #{checkInTime}</if>
44
+            <if test="checkOutTime != null "> and check_out_time = #{checkOutTime}</if>
45
+            <if test="workDuration != null "> and work_duration = #{workDuration}</if>
46
+            <if test="status != null  and status != ''"> and status = #{status}</if>
47
+            <if test="teamCode != null  and teamCode != ''"> and team_code = #{teamCode}</if>
48
+            <if test="teamName != null  and teamName != ''"> and team_name like concat('%', #{teamName}, '%')</if>
49
+            <if test="departmentCode != null  and departmentCode != ''"> and department_code = #{departmentCode}</if>
50
+            <if test="departmentName != null  and departmentName != ''"> and department_name like concat('%', #{departmentName}, '%')</if>
51
+            <if test="stationCode != null  and stationCode != ''"> and station_code = #{stationCode}</if>
52
+            <if test="stationName != null  and stationName != ''"> and station_name like concat('%', #{stationName}, '%')</if>
53
+            <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
54
+            <if test="remark != null  and remark != ''"> and remark = #{remark}</if>
55
+            <if test="attendanceDate != null "> and attendance_date = #{attendanceDate}</if>
56
+            <if test="overDuration != null "> and over_duration = #{overDuration}</if>
57
+            <if test="statusDesc != null  and statusDesc != ''"> and status_desc = #{statusDesc}</if>
58
+
59
+            <if test="attendanceDateStart != null "> and attendance_date &gt;= concat(#{attendanceDateStart}, ' 00:00:00')</if>
60
+            <if test="attendanceDateEnd != null "> and attendance_date &lt;= concat(#{attendanceDateEnd}, ' 23:59:59')</if>
61
+        </where>
62
+        order by create_time desc
63
+    </select>
64
+    
65
+    <select id="selectAttendanceRecordById" parameterType="Long" resultMap="AttendanceRecordResult">
66
+        <include refid="selectAttendanceRecordVo"/>
67
+        where id = #{id}
68
+    </select>
69
+
70
+    <insert id="insertAttendanceRecord" parameterType="AttendanceRecord" useGeneratedKeys="true" keyProperty="id">
71
+        insert into attendance_record
72
+        <trim prefix="(" suffix=")" suffixOverrides=",">
73
+            <if test="tenantId != null">tenant_id,</if>
74
+            <if test="revision != null">revision,</if>
75
+            <if test="createBy != null">create_by,</if>
76
+            <if test="createTime != null">create_time,</if>
77
+            <if test="updateBy != null">update_by,</if>
78
+            <if test="updateTime != null">update_time,</if>
79
+            <if test="userId != null">user_id,</if>
80
+            <if test="checkInTime != null">check_in_time,</if>
81
+            <if test="checkOutTime != null">check_out_time,</if>
82
+            <if test="workDuration != null">work_duration,</if>
83
+            <if test="status != null and status != ''">status,</if>
84
+            <if test="teamCode != null and teamCode != ''">team_code,</if>
85
+            <if test="teamName != null and teamName != ''">team_name,</if>
86
+            <if test="departmentCode != null and departmentCode != ''">department_code,</if>
87
+            <if test="departmentName != null and departmentName != ''">department_name,</if>
88
+            <if test="stationCode != null and stationCode != ''">station_code,</if>
89
+            <if test="stationName != null and stationName != ''">station_name,</if>
90
+            <if test="userName != null and userName != ''">user_name,</if>
91
+            <if test="remark != null">remark,</if>
92
+            <if test="attendanceDate != null">attendance_date,</if>
93
+            <if test="overDuration != null">over_duration,</if>
94
+            <if test="statusDesc != null and statusDesc != ''">status_desc,</if>
95
+         </trim>
96
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
97
+            <if test="tenantId != null">#{tenantId},</if>
98
+            <if test="revision != null">#{revision},</if>
99
+            <if test="createBy != null">#{createBy},</if>
100
+            <if test="createTime != null">#{createTime},</if>
101
+            <if test="updateBy != null">#{updateBy},</if>
102
+            <if test="updateTime != null">#{updateTime},</if>
103
+            <if test="userId != null">#{userId},</if>
104
+            <if test="checkInTime != null">#{checkInTime},</if>
105
+            <if test="checkOutTime != null">#{checkOutTime},</if>
106
+            <if test="workDuration != null">#{workDuration},</if>
107
+            <if test="status != null and status != ''">#{status},</if>
108
+            <if test="teamCode != null and teamCode != ''">#{teamCode},</if>
109
+            <if test="teamName != null and teamName != ''">#{teamName},</if>
110
+            <if test="departmentCode != null and departmentCode != ''">#{departmentCode},</if>
111
+            <if test="departmentName != null and departmentName != ''">#{departmentName},</if>
112
+            <if test="stationCode != null and stationCode != ''">#{stationCode},</if>
113
+            <if test="stationName != null and stationName != ''">#{stationName},</if>
114
+            <if test="userName != null and userName != ''">#{userName},</if>
115
+            <if test="remark != null">#{remark},</if>
116
+            <if test="attendanceDate != null">#{attendanceDate},</if>
117
+            <if test="overDuration != null">#{overDuration},</if>
118
+            <if test="statusDesc != null and statusDesc != ''">#{statusDesc},</if>
119
+         </trim>
120
+    </insert>
121
+
122
+    <update id="updateAttendanceRecord" parameterType="AttendanceRecord">
123
+        update attendance_record
124
+        <trim prefix="SET" suffixOverrides=",">
125
+            <if test="tenantId != null">tenant_id = #{tenantId},</if>
126
+            <if test="revision != null">revision = #{revision},</if>
127
+            <if test="createBy != null">create_by = #{createBy},</if>
128
+            <if test="createTime != null">create_time = #{createTime},</if>
129
+            <if test="updateBy != null">update_by = #{updateBy},</if>
130
+            <if test="updateTime != null">update_time = #{updateTime},</if>
131
+            <if test="userId != null">user_id = #{userId},</if>
132
+            <if test="checkInTime != null">check_in_time = #{checkInTime},</if>
133
+            <if test="checkOutTime != null">check_out_time = #{checkOutTime},</if>
134
+            <if test="workDuration != null">work_duration = #{workDuration},</if>
135
+            <if test="status != null and status != ''">status = #{status},</if>
136
+            <if test="teamCode != null and teamCode != ''">team_code = #{teamCode},</if>
137
+            <if test="teamName != null and teamName != ''">team_name = #{teamName},</if>
138
+            <if test="departmentCode != null and departmentCode != ''">department_code = #{departmentCode},</if>
139
+            <if test="departmentName != null and departmentName != ''">department_name = #{departmentName},</if>
140
+            <if test="stationCode != null and stationCode != ''">station_code = #{stationCode},</if>
141
+            <if test="stationName != null and stationName != ''">station_name = #{stationName},</if>
142
+            <if test="userName != null and userName != ''">user_name = #{userName},</if>
143
+            <if test="remark != null">remark = #{remark},</if>
144
+            <if test="attendanceDate != null">attendance_date = #{attendanceDate},</if>
145
+            <if test="overDuration != null">over_duration = #{overDuration},</if>
146
+            <if test="statusDesc != null and statusDesc != ''">status_desc = #{statusDesc},</if>
147
+        </trim>
148
+        where id = #{id}
149
+    </update>
150
+
151
+    <delete id="deleteAttendanceRecordById" parameterType="Long">
152
+        delete from attendance_record where id = #{id}
153
+    </delete>
154
+
155
+    <delete id="deleteAttendanceRecordByIds" parameterType="String">
156
+        delete from attendance_record where id in 
157
+        <foreach item="id" collection="array" open="(" separator="," close=")">
158
+            #{id}
159
+        </foreach>
160
+    </delete>
161
+</mapper>

+ 139 - 0
airport-attendance/src/main/resources/mapper/attendance/AttendanceTeamUserRecordMapper.xml

@@ -0,0 +1,139 @@
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.attendance.mapper.AttendanceTeamUserRecordMapper">
6
+    
7
+    <resultMap type="AttendanceTeamUserRecord" id="AttendanceTeamUserRecordResult">
8
+        <result property="userId"    column="user_id"    />
9
+        <result property="userCode"    column="user_code"    />
10
+        <result property="userName"    column="user_name"    />
11
+        <result property="terminlCode"    column="terminl_code"    />
12
+        <result property="terminlName"    column="terminl_name"    />
13
+        <result property="attendanceDate"    column="attendance_date"    />
14
+        <result property="attendanceTeamId"    column="attendance_team_id"    />
15
+        <result property="attendanceTeamName"    column="attendance_team_name"    />
16
+        <result property="createBy"    column="create_by"    />
17
+        <result property="createTime"    column="create_time"    />
18
+        <result property="updateBy"    column="update_by"    />
19
+        <result property="updateTime"    column="update_time"    />
20
+        <result property="remark"    column="remark"    />
21
+    </resultMap>
22
+
23
+    <sql id="selectAttendanceTeamUserRecordVo">
24
+        select user_id, user_code, user_name, terminl_code, terminl_name, attendance_date, attendance_team_id, attendance_team_name, create_by, create_time, update_by, update_time, remark from attendance_team_user_record
25
+    </sql>
26
+
27
+    <select id="selectAttendanceTeamUserRecordList" parameterType="AttendanceTeamUserRecord" resultMap="AttendanceTeamUserRecordResult">
28
+        <include refid="selectAttendanceTeamUserRecordVo"/>
29
+        <where>  
30
+            <if test="userId != null "> and user_id = #{userId}</if>
31
+            <if test="userCode != null  and userCode != ''"> and user_code = #{userCode}</if>
32
+            <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
33
+            <if test="terminlCode != null  and terminlCode != ''"> and terminl_code = #{terminlCode}</if>
34
+            <if test="terminlName != null  and terminlName != ''"> and terminl_name like concat('%', #{terminlName}, '%')</if>
35
+            <if test="attendanceDate != null "> and attendance_date = #{attendanceDate}</if>
36
+            <!-- 添加时间范围查询支持 -->
37
+            <if test="attendanceDateStart != null "> and create_time >= #{attendanceDateStart}</if>
38
+            <if test="attendanceDateEnd != null "> and create_time &lt;= #{attendanceDateEnd}</if>
39
+            <if test="attendanceTeamId != null "> and attendance_team_id = #{attendanceTeamId}</if>
40
+            <if test="attendanceTeamName != null  and attendanceTeamName != ''"> and attendance_team_name like concat('%', #{attendanceTeamName}, '%')</if>
41
+        </where>
42
+        order by create_time desc
43
+    </select>
44
+    
45
+    <select id="selectAttendanceTeamUserRecordByUserId"  resultMap="AttendanceTeamUserRecordResult">
46
+        <include refid="selectAttendanceTeamUserRecordVo"/>
47
+        where user_id = #{userId}
48
+         and DATE(attendance_date) = DATE(#{attendanceDate})
49
+    </select>
50
+
51
+    <insert id="insertAttendanceTeamUserRecord" parameterType="AttendanceTeamUserRecord">
52
+        insert into attendance_team_user_record
53
+        <trim prefix="(" suffix=")" suffixOverrides=",">
54
+            <if test="userId != null">user_id,</if>
55
+            <if test="userCode != null and userCode != ''">user_code,</if>
56
+            <if test="userName != null and userName != ''">user_name,</if>
57
+            <if test="terminlCode != null">terminl_code,</if>
58
+            <if test="terminlName != null">terminl_name,</if>
59
+            <if test="attendanceDate != null">attendance_date,</if>
60
+            <if test="attendanceTeamId != null">attendance_team_id,</if>
61
+            <if test="attendanceTeamName != null">attendance_team_name,</if>
62
+            <if test="createBy != null">create_by,</if>
63
+            <if test="createTime != null">create_time,</if>
64
+            <if test="updateBy != null">update_by,</if>
65
+            <if test="updateTime != null">update_time,</if>
66
+            <if test="remark != null">remark,</if>
67
+         </trim>
68
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
69
+            <if test="userId != null">#{userId},</if>
70
+            <if test="userCode != null and userCode != ''">#{userCode},</if>
71
+            <if test="userName != null and userName != ''">#{userName},</if>
72
+            <if test="terminlCode != null">#{terminlCode},</if>
73
+            <if test="terminlName != null">#{terminlName},</if>
74
+            <if test="attendanceDate != null">#{attendanceDate},</if>
75
+            <if test="attendanceTeamId != null">#{attendanceTeamId},</if>
76
+            <if test="attendanceTeamName != null">#{attendanceTeamName},</if>
77
+            <if test="createBy != null">#{createBy},</if>
78
+            <if test="createTime != null">#{createTime},</if>
79
+            <if test="updateBy != null">#{updateBy},</if>
80
+            <if test="updateTime != null">#{updateTime},</if>
81
+            <if test="remark != null">#{remark},</if>
82
+         </trim>
83
+    </insert>
84
+
85
+    <update id="updateAttendanceTeamUserRecord" parameterType="AttendanceTeamUserRecord">
86
+        update attendance_team_user_record
87
+        <trim prefix="SET" suffixOverrides=",">
88
+            <if test="userCode != null and userCode != ''">user_code = #{userCode},</if>
89
+            <if test="userName != null and userName != ''">user_name = #{userName},</if>
90
+            <if test="terminlCode != null">terminl_code = #{terminlCode},</if>
91
+            <if test="terminlName != null">terminl_name = #{terminlName},</if>
92
+            <if test="attendanceDate != null">attendance_date = #{attendanceDate},</if>
93
+            <if test="attendanceTeamId != null">attendance_team_id = #{attendanceTeamId},</if>
94
+            <if test="attendanceTeamName != null">attendance_team_name = #{attendanceTeamName},</if>
95
+            <if test="createBy != null">create_by = #{createBy},</if>
96
+            <if test="createTime != null">create_time = #{createTime},</if>
97
+            <if test="updateBy != null">update_by = #{updateBy},</if>
98
+            <if test="updateTime != null">update_time = #{updateTime},</if>
99
+            <if test="remark != null">remark = #{remark},</if>
100
+        </trim>
101
+        where user_id = #{userId}
102
+    </update>
103
+
104
+    <delete id="deleteAttendanceTeamUserRecordByUserId" parameterType="Long">
105
+        delete from attendance_team_user_record where user_id = #{userId}
106
+    </delete>
107
+
108
+    <delete id="deleteAttendanceTeamUserRecordByUserIds" parameterType="String">
109
+        delete from attendance_team_user_record where user_id in 
110
+        <foreach item="userId" collection="array" open="(" separator="," close=")">
111
+            #{userId}
112
+        </foreach>
113
+    </delete>
114
+
115
+    <insert id="insertAttendanceTeamUserRecordList">
116
+        insert into attendance_team_user_record
117
+        (user_id, user_code, user_name, terminl_code, terminl_name, attendance_date,
118
+        attendance_team_id, attendance_team_name, create_by, create_time)
119
+        values
120
+        <foreach collection="list" item="item" separator=",">
121
+            (#{item.userId}, #{item.userCode}, #{item.userName}, #{item.terminlCode},
122
+            #{item.terminlName}, #{item.attendanceDate}, #{item.attendanceTeamId},
123
+            #{item.attendanceTeamName}, #{item.createBy}, #{item.createTime})
124
+        </foreach>
125
+    </insert>
126
+
127
+    <select id="selectAttendanceTeamUserRecordByUserIdAndDateRange" resultMap="AttendanceTeamUserRecordResult">
128
+        SELECT
129
+        tenant_id, revision, create_by, create_time, update_by, update_time,
130
+        user_id, user_name, user_code, terminl_code, terminl_name,
131
+        attendance_date, attendance_team_id, attendance_team_name, remark
132
+        FROM attendance_team_user_record
133
+        WHERE user_id = #{userId}
134
+        AND attendance_date >= #{startDate}
135
+        AND attendance_date &lt;= #{endDate}
136
+    </select>
137
+
138
+
139
+</mapper>