From 404329d3fa751e43d758b8e2e95c9d692b5fda23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IT=E7=8B=97?= Date: Tue, 21 Jul 2026 23:54:57 +0800 Subject: [PATCH] =?UTF-8?q?Attendance:=20detect=20rest=20day=20in=20enrich?= =?UTF-8?q?=20=E2=86=92=20render=20=E4=BC=91=E6=81=AF=20instead=20of=20?= =?UTF-8?q?=E7=BC=BA=E5=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When roster says the shift rests on a given weekday (e.g. Sunday for S2/S3), the employee wasn't scheduled to work. Import still marks the record status_code='missing' because no shift times were available, but the right user-facing label is '休息' not '缺勤'. Enrich now: if status_code='missing' and get_shift_schedule_full returns None (shift defined no schedule for that weekday), override to status_code='rest' / status_text='休息'. Added '休息' to the basic-status vocabulary: dropdown option, filter condition, StatusBadge (slate grey + coffee icon), and row highlight. --- backend/main.py | 14 +++++++++++++- frontend/src/components/StatusBadge.jsx | 7 ++++++- frontend/src/pages/attendance/List.jsx | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/main.py b/backend/main.py index fc206d8..29646bd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -126,6 +126,16 @@ def enrich_attendance_with_leave(records, db): if not r.date: continue + # Rest day detection: roster says the shift rests on this weekday + # (e.g. Sunday for S2/S3) but raw status is 'missing'. Don't flag as 缺勤 + # when the employee wasn't scheduled to work. + if (r.status_code or '').lower() == 'missing' and r.shift_code and r.weekday: + shift_info = get_shift_schedule_full(r.shift_code, r.weekday) + if shift_info is None: + r.status_code = 'rest' + r.status_text = '休息' + continue + if r.date in holidays_by_date: h = holidays_by_date[r.date] # Check roster: if shift's public_holiday == 'Yes', employee works on holidays @@ -1106,7 +1116,7 @@ async def attendance_status_texts( raw status_text variant (e.g. '異常-遲到39分'). Keeping it server-driven so client and server agree on the filter vocabulary. """ - return ['正常', '異常', '缺勤', '公假', '請假'] + return ['正常', '異常', '缺勤', '公假', '請假', '休息'] @app.get("/api/attendance/stats") async def attendance_stats( @@ -1335,6 +1345,8 @@ async def attendance_records( for tag in ('SL', 'AL', 'CL'))] elif status_text in ('normal', '正常'): rows = [r for r in all_rows if (r.status_code or '').lower() == 'normal'] + elif status_text in ('rest', '休息'): + rows = [r for r in all_rows if (r.status_code or '').lower() == 'rest'] else: # Fallback to raw status_text substring search rows = [r for r in all_rows diff --git a/frontend/src/components/StatusBadge.jsx b/frontend/src/components/StatusBadge.jsx index 8d3a8b4..2848c1e 100644 --- a/frontend/src/components/StatusBadge.jsx +++ b/frontend/src/components/StatusBadge.jsx @@ -1,4 +1,4 @@ -import { CheckCircle, AlertCircle, CalendarX, Palmtree, Briefcase } from 'lucide-react' +import { CheckCircle, AlertCircle, CalendarX, Palmtree, Briefcase, Coffee } from 'lucide-react' /** * Reusable status badge for attendance records. @@ -74,6 +74,11 @@ export default function StatusBadge({ code = '', text = '', size = 'sm' }) { return wrap(badge(, '缺勤', 'bg-gray-200 text-gray-700')) } + // 休息 (rest day — roster says not scheduled, employee didn't clock in) + if (c === 'rest' || t === '休息') { + return wrap(badge(, '休息', 'bg-slate-100 text-slate-600')) + } + // 公假 (holiday) if (c === 'holiday' || t.includes('公假') || t.includes('🏖️')) { return wrap(badge(, '公假', 'bg-indigo-100 text-indigo-700')) diff --git a/frontend/src/pages/attendance/List.jsx b/frontend/src/pages/attendance/List.jsx index eac8988..93f5d7e 100644 --- a/frontend/src/pages/attendance/List.jsx +++ b/frontend/src/pages/attendance/List.jsx @@ -99,6 +99,7 @@ export default function AttendanceList() { return 'bg-orange-50 hover:bg-orange-100' } if (c === 'missing') return 'bg-gray-100 hover:bg-gray-200' + if (c === 'rest') return 'bg-slate-50 hover:bg-slate-100' 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'