Fix attendance filter: align with badge via status_code (not late/early/OT thresholds)
The previous filter used 'late_minutes == 0 AND early_minutes == 0 AND ot_minutes == 0' for '正常', which incorrectly included records whose import-time late_minutes was 0 but post-enrich status_code was 'abnormal' (e.g. act_in=18:00 vs exp_in=11:00) or 'missing' (act_in='-'). Switch filter to status_code conditions so the result set matches the badge rendered from status_code. Also accept the Chinese basic-status labels (正常/異常/缺勤/公假/請假) as filter values, and return only those 5 from /api/attendance/status_texts so the dropdown vocabulary stays in sync. Frontend attendance list: simplify getStatusBadge to drop now-unused late/early/ot props; align row highlight with the basic-status grouping.
This commit is contained in:
+30
-31
@@ -1099,9 +1099,14 @@ async def attendance_status_texts(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return distinct status_text values for filter dropdown."""
|
||||
rows = db.query(AttendanceRecord.status_text).distinct().all()
|
||||
return [r[0] for r in rows if r[0]]
|
||||
"""Return BASIC status categories for filter dropdown.
|
||||
|
||||
The frontend renders only 5 basic statuses (正常/異常/缺勤/公假/請假),
|
||||
so the dropdown should offer those exact values rather than every
|
||||
raw status_text variant (e.g. '異常-遲到39分'). Keeping it server-driven
|
||||
so client and server agree on the filter vocabulary.
|
||||
"""
|
||||
return ['正常', '異常', '缺勤', '公假', '請假']
|
||||
|
||||
@app.get("/api/attendance/stats")
|
||||
async def attendance_stats(
|
||||
@@ -1263,41 +1268,35 @@ async def attendance_records(
|
||||
if status:
|
||||
q = q.filter(AttendanceRecord.status_code == status)
|
||||
if status_text:
|
||||
# Map status_text filter to actual conditions
|
||||
if status_text == 'abnormal':
|
||||
q = q.filter(
|
||||
(AttendanceRecord.late_minutes > 30) |
|
||||
(AttendanceRecord.early_minutes > 30) |
|
||||
(AttendanceRecord.ot_minutes > 30)
|
||||
)
|
||||
# Map status_text filter to status_code conditions so the result set
|
||||
# stays aligned with the badge rendered from (post-enrich) status_code.
|
||||
# Previously used late/early/OT thresholds which mismatched because
|
||||
# the import path can leave late_minutes=0 while status_code='abnormal'
|
||||
# (e.g. act_in=18:00 vs exp_in=11:00), and missing records also have
|
||||
# all three == 0 despite status_code='missing'.
|
||||
# Accept both English status_code values AND the Chinese basic-status
|
||||
# labels returned by /api/attendance/status_texts dropdown.
|
||||
abnormal_codes = ('abnormal', 'late', 'early', 'ot',
|
||||
'late_early', 'late_ot', 'early_ot')
|
||||
if status_text in ('abnormal', '異常'):
|
||||
q = q.filter(AttendanceRecord.status_code.in_(abnormal_codes))
|
||||
elif status_text == 'late':
|
||||
q = q.filter(
|
||||
(AttendanceRecord.late_minutes > 0) &
|
||||
(AttendanceRecord.late_minutes <= 30)
|
||||
)
|
||||
q = q.filter(AttendanceRecord.status_code.in_(('late', 'late_early', 'late_ot')))
|
||||
elif status_text == 'early':
|
||||
q = q.filter(
|
||||
(AttendanceRecord.early_minutes > 0) &
|
||||
(AttendanceRecord.early_minutes <= 30)
|
||||
)
|
||||
q = q.filter(AttendanceRecord.status_code.in_(('early', 'late_early', 'early_ot')))
|
||||
elif status_text == 'ot':
|
||||
q = q.filter(
|
||||
(AttendanceRecord.ot_minutes > 0) &
|
||||
(AttendanceRecord.ot_minutes <= 30)
|
||||
)
|
||||
elif status_text == 'missing':
|
||||
q = q.filter(AttendanceRecord.status_code.in_(('ot', 'late_ot', 'early_ot')))
|
||||
elif status_text in ('missing', '缺勤'):
|
||||
q = q.filter(AttendanceRecord.status_code == 'missing')
|
||||
elif status_text in ('holiday', 'al', 'sl', 'cl', 'mixed_leave'):
|
||||
elif status_text in ('holiday', '公假'):
|
||||
q = q.filter(AttendanceRecord.status_code == 'holiday')
|
||||
elif status_text in ('al', 'sl', 'cl', 'mixed_leave'):
|
||||
q = q.filter(AttendanceRecord.status_code == status_text)
|
||||
elif status_text == 'leave':
|
||||
elif status_text in ('leave', '請假'):
|
||||
# Any leave type — group filter
|
||||
q = q.filter(AttendanceRecord.status_code.in_(['al', 'sl', 'cl', 'mixed_leave']))
|
||||
elif status_text == 'normal':
|
||||
q = q.filter(
|
||||
(AttendanceRecord.late_minutes == 0) &
|
||||
(AttendanceRecord.early_minutes == 0) &
|
||||
(AttendanceRecord.ot_minutes == 0)
|
||||
)
|
||||
elif status_text in ('normal', '正常'):
|
||||
q = q.filter(AttendanceRecord.status_code == 'normal')
|
||||
else:
|
||||
# Fallback to text search
|
||||
q = q.filter(AttendanceRecord.status_text.like(f"%{status_text}%"))
|
||||
|
||||
@@ -87,18 +87,20 @@ export default function AttendanceList() {
|
||||
}
|
||||
|
||||
const getStatusBadge = (record) => {
|
||||
const lm = record.late_minutes || 0
|
||||
const em = record.early_minutes || 0
|
||||
const om = record.ot_minutes || 0
|
||||
return <StatusBadge code={record.status_code} late={lm} early={em} ot={om} text={record.status_text} />
|
||||
// StatusBadge now drives from status_code + status_text only (basic 5 categories).
|
||||
return <StatusBadge code={record.status_code} text={record.status_text} />
|
||||
}
|
||||
|
||||
const getRowClass = (code) => {
|
||||
if (code === 'abnormal') return 'bg-red-50 hover:bg-red-100'
|
||||
if (code === 'missing') return 'bg-gray-100 hover:bg-gray-200'
|
||||
if (code === 'late' || code === 'late_early' || code === 'late_ot') return 'bg-orange-50 hover:bg-orange-100'
|
||||
if (code === 'early' || code === 'early_ot') return 'bg-blue-50 hover:bg-blue-100'
|
||||
if (code === 'ot') return 'bg-purple-50 hover:bg-purple-100'
|
||||
// Match the basic-status grouping so highlights line up with the badge.
|
||||
const c = String(code || '').toLowerCase()
|
||||
if (c === 'abnormal' || c === 'late' || c === 'early' || c === 'ot'
|
||||
|| c === 'late_early' || c === 'late_ot' || c === 'early_ot') {
|
||||
return 'bg-orange-50 hover:bg-orange-100'
|
||||
}
|
||||
if (c === 'missing') return 'bg-gray-100 hover:bg-gray-200'
|
||||
if (c === 'holiday') return 'bg-indigo-50 hover:bg-indigo-100'
|
||||
if (['al', 'sl', 'cl', 'mixed_leave'].includes(c)) return 'bg-violet-50 hover:bg-violet-100'
|
||||
return 'hover:bg-slate-50'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user