fix(attendance): 优化考勤状态识别逻辑并补充漏打卡处理
1. 在考勤状态筛选、汇总卡片和图例中将“迟到/早退”标签更新为“迟到/早退/缺卡”,以包含漏打卡情况 2. 后端服务中,在考勤状态常量列表新增“missed_start”、“missed_end”、“missed”三种漏打卡状态 3. 重构上下班打卡时间判断逻辑:当打卡时间晚于理论上班时间或早于理论下班时间超过30分钟时,标记为漏打卡(上班漏打卡或下班漏打卡) 4. 调整状态覆盖逻辑:上班漏打卡或下班漏打卡状态会覆盖原有的迟到/早退状态;若上下班均漏打卡则标记为全天漏打卡(missed) 5. 更新考勤记录处理:当标记为漏打卡时,对应的首次或末次打卡时间字段设为null 6. 优化旷工判定逻辑:单独处理上午段(P1)或下午段(P2)漏打卡的情况,分别标记为半天旷工(absent_half) 7. 前端新增漏打卡状态对应的日历样式(状态色块为#e6a23c)和状态描述文本(“上班漏打卡”、“下班漏打卡”)
This commit is contained in:
@@ -171,7 +171,7 @@
|
||||
<el-form-item label="总体状态">
|
||||
<el-select v-if="isEdit" v-model="editForm.overallStatus" placeholder="请选择状态">
|
||||
<el-option label="正常" value="normal" />
|
||||
<el-option label="迟到/早退" value="abnormal" />
|
||||
<el-option label="迟到/早退/缺卡" value="abnormal" />
|
||||
<el-option label="半天旷工" value="absent_half" />
|
||||
<el-option label="全天旷工" value="absent_full" />
|
||||
</el-select>
|
||||
@@ -396,7 +396,7 @@
|
||||
<el-descriptions title="考勤汇总" :column="5" border style="margin-bottom: 20px;">
|
||||
<el-descriptions-item label="姓名">{{ currentPersonData.employeeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="正常">{{ personSummary.normal }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="迟到/早退">{{ personSummary.abnormal }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="迟到/早退/缺卡">{{ personSummary.abnormal }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="半天旷工">{{ personSummary.absentHalf }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="全天旷工">{{ personSummary.absentFull }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="无记录">{{ personSummary.noRecord }}天</el-descriptions-item>
|
||||
@@ -456,7 +456,7 @@
|
||||
</div>
|
||||
<div class="calendar-legend">
|
||||
<span class="legend-item"><span class="legend-dot normal"></span>正常</span>
|
||||
<span class="legend-item"><span class="legend-dot late"></span>迟到/早退</span>
|
||||
<span class="legend-item"><span class="legend-dot late"></span>迟到/早退/缺卡</span>
|
||||
<span class="legend-item"><span class="legend-dot absent-half"></span>半天旷工</span>
|
||||
<span class="legend-item"><span class="legend-dot absent-full"></span>全天旷工</span>
|
||||
<span class="legend-item"><span class="legend-dot no-record"></span>无记录</span>
|
||||
@@ -957,6 +957,8 @@ export default {
|
||||
case 'absent_half': return 'status-absent-half'
|
||||
case 'absent_full': return 'status-absent-full'
|
||||
case 'abnormal': return 'status-abnormal'
|
||||
case 'missed_start': return 'status-missed'
|
||||
case 'missed_end': return 'status-missed'
|
||||
default: return 'status-default'
|
||||
}
|
||||
},
|
||||
@@ -972,8 +974,10 @@ export default {
|
||||
case 'early_two': return '早退II'
|
||||
case 'absent_half': return '半天旷工'
|
||||
case 'absent_full': return '全天旷工'
|
||||
case 'abnormal': return '迟到/早退'
|
||||
case 'abnormal': return '迟到/早退/缺卡'
|
||||
case 'missed': return '未打卡'
|
||||
case 'missed_start': return '上班漏打卡'
|
||||
case 'missed_end': return '下班漏打卡'
|
||||
default: return '无'
|
||||
}
|
||||
},
|
||||
@@ -1325,6 +1329,10 @@ export default {
|
||||
background-color: #c0c4cc;
|
||||
}
|
||||
|
||||
.status-missed {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.shift-name {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
|
||||
@@ -49,7 +49,9 @@ import java.util.stream.Collectors;
|
||||
public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService {
|
||||
|
||||
private static final List<String> STATUS_SEVERITY = java.util.Arrays.asList("normal", "late_warn", "early_warn",
|
||||
"late_one", "early_one", "late_two", "early_two", "absent_half");
|
||||
"late_one", "early_one", "late_two", "early_two", "absent_half", "missed_start", "missed_end", "missed");
|
||||
|
||||
private static final long LATE_EARLY_MAX_SECONDS = 30 * 60L;
|
||||
|
||||
private static final int BATCH_SIZE = 500;
|
||||
|
||||
@@ -530,6 +532,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
||||
|
||||
private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period,
|
||||
List<AttendanceRecords> periodRecords, Date expectedStart, Date expectedEnd) {
|
||||
// 旷工:0条打卡记录
|
||||
if (periodRecords.isEmpty()) {
|
||||
if (period == 1) {
|
||||
check.setP1Status("missed");
|
||||
@@ -550,53 +553,72 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
||||
int earlyMinutes = 0;
|
||||
BigDecimal deduct = BigDecimal.ZERO;
|
||||
String status = "normal";
|
||||
boolean startMissed = false;
|
||||
boolean endMissed = false;
|
||||
|
||||
if (expStart != null && firstCheck.isAfter(expStart)) {
|
||||
lateMinutes = (int) Duration.between(expStart, firstCheck).toMinutes();
|
||||
if (lateMinutes > rule.getAbsentHalfDay()) {
|
||||
status = "absent_half";
|
||||
} else if (lateMinutes > rule.getLateOne()) {
|
||||
status = "late_two";
|
||||
deduct = deduct.add(rule.getDeductTwo());
|
||||
} else if (lateMinutes > rule.getLateWarn()) {
|
||||
status = "late_one";
|
||||
deduct = deduct.add(rule.getDeductOne());
|
||||
} else {
|
||||
status = "late_warn";
|
||||
// 上班检测:晚于理论上班时间30分钟以上 → 漏打卡
|
||||
if (expStart != null) {
|
||||
long lateSecs = Duration.between(expStart, firstCheck).getSeconds();
|
||||
if (lateSecs > LATE_EARLY_MAX_SECONDS) {
|
||||
startMissed = true;
|
||||
} else if (lateSecs > 0) {
|
||||
lateMinutes = (int) (lateSecs / 60);
|
||||
if (lateMinutes > rule.getAbsentHalfDay()) {
|
||||
status = "absent_half";
|
||||
} else if (lateMinutes > rule.getLateOne()) {
|
||||
status = "late_two";
|
||||
deduct = deduct.add(rule.getDeductTwo());
|
||||
} else if (lateMinutes > rule.getLateWarn()) {
|
||||
status = "late_one";
|
||||
deduct = deduct.add(rule.getDeductOne());
|
||||
} else {
|
||||
status = "late_warn";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下班检测:早于理论下班时间30分钟以上 → 漏打卡
|
||||
if (expEnd != null && lastCheck.isBefore(expEnd)) {
|
||||
int min = (int) Duration.between(lastCheck, expEnd).toMinutes();
|
||||
if (min > rule.getAbsentHalfDay()) {
|
||||
status = maxSeverity(status, "absent_half");
|
||||
earlyMinutes = min;
|
||||
} else if (min > rule.getLateOne()) {
|
||||
status = maxSeverity(status, "early_two");
|
||||
deduct = deduct.add(rule.getDeductTwo());
|
||||
earlyMinutes = min;
|
||||
} else if (min > rule.getLateWarn()) {
|
||||
status = maxSeverity(status, "early_one");
|
||||
deduct = deduct.add(rule.getDeductOne());
|
||||
earlyMinutes = min;
|
||||
} else {
|
||||
if ("normal".equals(status)) {
|
||||
status = "early_warn";
|
||||
long earlySecs = Duration.between(lastCheck, expEnd).getSeconds();
|
||||
if (earlySecs > LATE_EARLY_MAX_SECONDS) {
|
||||
endMissed = true;
|
||||
} else if (earlySecs > 0) {
|
||||
earlyMinutes = (int) (earlySecs / 60);
|
||||
if (earlyMinutes > rule.getAbsentHalfDay()) {
|
||||
status = maxSeverity(status, "absent_half");
|
||||
} else if (earlyMinutes > rule.getLateOne()) {
|
||||
status = maxSeverity(status, "early_two");
|
||||
deduct = deduct.add(rule.getDeductTwo());
|
||||
} else if (earlyMinutes > rule.getLateWarn()) {
|
||||
status = maxSeverity(status, "early_one");
|
||||
deduct = deduct.add(rule.getDeductOne());
|
||||
} else {
|
||||
if ("normal".equals(status)) {
|
||||
status = "early_warn";
|
||||
}
|
||||
}
|
||||
earlyMinutes = min;
|
||||
}
|
||||
}
|
||||
|
||||
// 上班漏打卡或下班漏打卡会覆盖原有状态
|
||||
if (startMissed && endMissed) {
|
||||
status = "missed";
|
||||
} else if (startMissed) {
|
||||
status = maxSeverity(status, "missed_start");
|
||||
} else if (endMissed) {
|
||||
status = maxSeverity(status, "missed_end");
|
||||
}
|
||||
|
||||
if (period == 1) {
|
||||
check.setP1FirstCheck(firstRec.getChecktime());
|
||||
check.setP1LastCheck(lastRec.getChecktime());
|
||||
check.setP1FirstCheck(startMissed ? null : firstRec.getChecktime());
|
||||
check.setP1LastCheck(endMissed ? null : lastRec.getChecktime());
|
||||
check.setP1LateMinutes(lateMinutes);
|
||||
check.setP1EarlyMinutes(earlyMinutes);
|
||||
check.setP1Status(status);
|
||||
check.setP1Deduct(deduct);
|
||||
} else {
|
||||
check.setP2FirstCheck(firstRec.getChecktime());
|
||||
check.setP2LastCheck(lastRec.getChecktime());
|
||||
check.setP2FirstCheck(startMissed ? null : firstRec.getChecktime());
|
||||
check.setP2LastCheck(endMissed ? null : lastRec.getChecktime());
|
||||
check.setP2LateMinutes(lateMinutes);
|
||||
check.setP2EarlyMinutes(earlyMinutes);
|
||||
check.setP2Status(status);
|
||||
@@ -622,19 +644,27 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
||||
if ("absent_half".equals(check.getP1Status()) || "absent_half".equals(check.getP2Status())) {
|
||||
hasAbsentHalf = true;
|
||||
}
|
||||
if ("missed".equals(check.getP1Status())) {
|
||||
if (check.getP2StartTime() != null) {
|
||||
if ("missed".equals(check.getP2Status())) {
|
||||
check.setAbsentType("full_day");
|
||||
check.setOverallStatus("absent_full");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean p1Missed = "missed".equals(check.getP1Status());
|
||||
boolean p2Missed = check.getP2Status() != null && "missed".equals(check.getP2Status());
|
||||
boolean hasP2 = check.getP2StartTime() != null;
|
||||
|
||||
if (p1Missed) {
|
||||
if (hasP2 && !p2Missed) {
|
||||
check.setAbsentType("half_day");
|
||||
check.setOverallStatus("absent_half");
|
||||
return;
|
||||
} else {
|
||||
check.setAbsentType("full_day");
|
||||
check.setOverallStatus("absent_full");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (p2Missed) {
|
||||
check.setAbsentType("half_day");
|
||||
check.setOverallStatus("absent_half");
|
||||
return;
|
||||
}
|
||||
if (check.getP1StartTime() == null && check.getP2StartTime() == null) {
|
||||
check.setAbsentType("full_day");
|
||||
check.setOverallStatus("absent_full");
|
||||
|
||||
Reference in New Issue
Block a user