Commit Graph

25 Commits

Author SHA1 Message Date
IT狗 f41dad6127 Fix: add 30-second tolerance for OT/early/late calculation to avoid floating-point noise (e.g. 20:00==20:00 showing OT 1min) 2026-07-21 17:41:14 +08:00
IT狗 6f2df815f5 Simplify Attendance export status groups to 6 categories: 正常|遲到|早退|加班|異常/缺勤|假期/請假 2026-07-21 13:38:58 +08:00
IT狗 c1b1d6f35b Add DELETE /api/admin/backup/{filename} and delete button in Settings 2026-07-21 13:14:56 +08:00
IT狗 e531cea8a4 Add leaves reset, split holidays/leave resets, support multi-table reset 2026-07-21 13:10:46 +08:00
IT狗 2d64d3f0b6 Add accident to reset sections 2026-07-21 13:08:00 +08:00
IT狗 08ba4cbca9 Fix: add missing db Session dependency to reset_section 2026-07-21 12:49:44 +08:00
IT狗 c07f40b28f Add /api/admin/reset/{section} endpoint for Danger Zone reset 2026-07-21 12:49:07 +08:00
IT狗 2d9b73df9d Bump version to 1.2.0 2026-07-21 12:45:46 +08:00
IT狗 9980f8d80a Fix: strip existing leave suffix before appending new leave to prevent duplication 2026-07-21 12:38:48 +08:00
IT狗 1e7d58486b Fix: prevent leave type duplication in status_text for attendance records 2026-07-21 12:35:16 +08:00
IT狗 cca088261b Fix: use roster public_holiday column to determine if employee works on holidays, instead of checking clock-in 2026-07-21 12:26:27 +08:00
IT狗 adc9e6451b Fix: show attendance status for employees who worked on holiday (not just holiday name) 2026-07-21 12:08:31 +08:00
IT狗 a34905661b Fix: add missing DB_PATH constant for backup/restore endpoints 2026-07-21 11:56:22 +08:00
IT Dog d5d9d644a0 feat: add backup/restore/version, fix leave duplicate, add Settings page to nav
- Add /api/admin/backup/list, /create, /download, /restore endpoints
- Add /api/version endpoint (git commit info)
- Add Settings page to sidebar nav + App.jsx routes
- Fix lateness stats: add total_late_count/total_late_minutes keys
- Fix leave aggregation: no more duplicate text in status
- Settings page now shows backup/restore UI + version info
- Fix leave upload: overwrite existing records on re-upload
2026-07-21 02:34:30 +08:00
IT Dog 95814350c9 Accident display: fix aliases + submitted_at format
Issues:
1. Frontend renders record[h] where h comes from column_order.
   column_order now uses submitter_email (not employee_name),
   so record[submitter_email] = None → shows as "-".

2. submitted_at shows as ISO format "2026-07-06T15:45:33.850000"
   (less readable than "2026-07-06 15:45:33").

Fix:
1. to_dict adds both submitter_email and employee_name alias
   (same value, r.submitter_email). Same for injured_person_id ↔ employee_id.
   Frontend column_order key matches record key (submitter_email shows email).

2. submitted_at formatted as "YYYY-MM-DD HH:MM:%S" via strftime

Verified:
- record.submitter_email = "operation@scmedical.hk"
- record.employee_name = "operation@scmedical.hk" (alias)
- record.submitted_at = "2026-07-06 15:45:33"
2026-07-20 01:46:59 +08:00
IT Dog 975791b9bd Accident: add incident_datetime computed field (date HH:MM:SS)
Backend:
- to_dict() now includes incident_datetime: "YYYY-MM-DD HH:MM:SS"
- Composed from r.date (YYYY-MM-DD) + r.time (HH:MM:SS, padded if HH:MM)
- _get_excel_column_order_accident() adds incident_datetime column after time
- Export xlsx rec dict now includes incident_datetime

Verified:
- /api/accident/records: incident_datetime = "2026-07-30 09:40:00"
- column_order includes incident_datetime column
- /api/accident/export/excel: xlsx 明細 sheet now has incident_datetime column
- Time edge case: HH:MM auto-padded to HH:MM:SS

Frontend:
- Accident list page auto-displays new column via column_order
- No frontend changes needed (renders record[h] as string)
2026-07-20 01:38:24 +08:00
IT Dog 0d34b51918 Fix accident export xlsx: indent + submitter_email mapping
Issues fixed:
1. Indentation error after rename (rec dict outdented one level)
2. rec dict used employee_name/responsible_person keys but
   column_order used submitter_email/injured_person keys
   → rec.get(col, \"\") returned \"\" → xlsx cells showed None

Fix:
- Re-indent whole for r_idx block (ws.set_row, for c_idx)
- Use submitter_email/injured_person/injured_person_id as rec keys
  to match column_order
- Tested: xlsx 電子郵件地址 column now shows real email values
2026-07-20 01:08:24 +08:00
IT Dog aca1c55072 Fix: replace r.employee_name with r.submitter_email in accident references
After renaming Accident.employee_name -> submitter_email, four more
references to r.employee_name / r.employee_id slipped through and caused
500 Internal Server Errors:

1. /api/attendance/records to_dict - was using r.submitter_email (wrong table!)
   (regression from earlier batch sed that hit too many matches)
2. /api/accident/dashboard/stats - emp = r.employee_name
3. /api/accident/export/excel to_dict
4. /api/accident/export/excel overview + detail sheet

Fix: explicit context-aware replacements (only Accident-related sections)

Verification:
- /api/attendance/records: 200 OK (returns 110 records)
- /api/accident/records: 200 OK (returns 342 records)
- /api/accident/dashboard/summary: 200 OK
- /api/accident/dashboard/stats: 200 OK (was 500)
- /api/accident/export/excel: 200 OK + valid xlsx (78KB)
2026-07-20 00:22:32 +08:00
IT Dog 4bcda0a907 Accident schema: rename employee_name -> submitter_email (clearer semantic)
Database migration:
- ALTER TABLE accidents RENAME COLUMN employee_name TO submitter_email
- ALTER TABLE accidents RENAME COLUMN responsible_person TO injured_person
- ALTER TABLE accidents RENAME COLUMN employee_id TO injured_person_id

Reason: Accident.employee_name contained submitter EMAIL (not staff name),
causing confusion when joining with attendance_records.employee_name (real name).
New column names are self-documenting.

API backward-compat:
- All API responses still use "employee_name" / "responsible_person" field names
- Internal SQL mapping translates submitter_email/injured_person -> employee_name/responsible_person
- Frontend code unchanged

Verified:
- /api/accident/1 returns employee_name = submitter_email content
- /api/accident/records returns 2 records OK (was 500 before fix)
- /api/employees/list still returns 12 staff (accident excluded)
- Data preserved (1st record id=1 submitter_email = info@scmedical.hk)
2026-07-20 00:05:05 +08:00
IT Dog c0076a125c Fix: exclude accident source + filter email-format names from employees list
Root cause: Accident.employee_name contains submitter EMAIL addresses
(e.g. info@scmedical.hk), not the injured staff name. UNION of all three
sources polluted the attendance staff dropdown with emails.

Fix:
- Removed Accident from /api/employees/list UNION (source=all now means
  attendance + leave only)
- Added EMAIL_RE filter to skip any name containing @ (defensive)
- Updated docstring + response includes excluded field explaining why

Verified: 12 clean staff names returned (down from 16 with 4 emails mixed in)
2026-07-19 23:45:34 +08:00
IT Dog 25ebf6da66 Employee dropdown list all staff from attendance + leave + accident 2026-07-19 22:39:54 +08:00
IT Dog 0cd8717320 Multi-row LeaveModal frontend + LeaveRecord UNIQUE constraint
Frontend (Holidays.jsx LeaveModal):
- Split into Edit Mode (single record) + Create Mode (multi-row)
- Create mode: shared employee/date/approver + list of (type, hours, reason) rows
- Add/remove rows dynamically, each with own type + hours + reason
- Submit loops POST /api/leave per row, aggregates results (added/skipped/failed)
- Submit button label shows count: "加 N 條"
- Bottom shows total valid count + total hours
- Result summary shown after submit (expandable details)
- Auto-close on success after 1.2s

Backend:
- LeaveRecord: add UniqueConstraint (employee_name, date, leave_type)
- Migration: CREATE UNIQUE INDEX uix_leave_emp_date_type (cleaned any pre-existing dups first)
- POST /api/leave: catch IntegrityError → return 409 with clear message\n\nVerified:\n- 3 same-day different-type records (SL/CL/AL) created successfully\n- POST same type twice returns 409 with specific message\n- HTTP 200 for valid creation, 409 for collision
2026-07-19 22:13:13 +08:00
IT Dog 347a862cae Apply conditional upsert to attendance + accident Excel upload
Schema:
- attendance_records: last_edited_at + last_edited_by (was: is_manually_edited boolean only)
- accidents: last_edited_at + last_edited_by (was: no edit tracking)

Migration: ALTER TABLE adds columns (idempotent)

Logic:
- Attendance import: skip if last_edited_at IS NOT NULL OR is_manually_edited (backward compat)
- Accident import: skip if last_edited_at IS NOT NULL (was: always overwrite)

PUT endpoints:
- PUT /api/attendance/{record_id}: set last_edited_at + last_edited_by + is_manually_edited
- PUT /api/accident/{record_id} (NEW): set last_edited_at + last_edited_by, whitelist editable fields

Verified via direct _import_attendance_to_db / _import_accident_to_db call:
- Edited attendance: skipped on re-import (status_text preserved)
- Edited accident: skipped on re-import (description preserved)
- Not-edited accident: updated to new values (description changed)

Note: HTTP /api/upload/{section} returned 404 in test, but function logic verified working
2026-07-19 20:47:51 +08:00
IT Dog 8e0d73c37e Conditional upsert: upload skips edited records, updates not-edited
- Add last_edited_at + last_edited_by columns to holidays + leave_records
- Migration: ALTER TABLE adds columns (idempotent)
- PUT endpoints set last_edited_at on every manual edit
- POST /api/leave/upload: skip if last_edited_at NOT NULL, update otherwise
- New POST /api/holidays/upload: same logic, admin only
- LeaveOut + HolidayOut schemas include last_edited_at
- Response includes added/updated/skipped counters
- Verified: edited Jay Lam SL preserved, not-edited May Chan SL updated
2026-07-19 20:26:59 +08:00
IT Dog bbc0048c24 Initial commit: AARS backend + frontend + holiday/leave phase 1+2 2026-07-19 20:10:15 +08:00