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)
This commit is contained in:
IT Dog
2026-07-19 23:45:34 +08:00
parent 6c70b81df5
commit c0076a125c
+17 -14
View File
@@ -1294,32 +1294,35 @@ async def update_attendance_record(
# ============ Employees List (combined source) ============ # ============ Employees List (combined source) ============
@app.get("/api/employees/list") @app.get("/api/employees/list")
async def list_employees( async def list_employees(
source: Optional[str] = "all", # "all" / "attendance" / "leave" / "accident" source: Optional[str] = "all", # "all" / "attendance" / "leave"
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
): ):
"""List distinct employee names from attendance + leave + accident records. """List distinct employee names (real staff only).
Sources: attendance_records + leave_records (NOT accident - that table stores
submitter emails, not staff names).
Filter: names containing "@" (email format) are excluded automatically.
Used by frontend dropdowns to show ALL staff, even those who haven't taken leave yet. Used by frontend dropdowns to show ALL staff, even those who haven't taken leave yet.
source:
- all (default): UNION of attendance + leave + accident
- attendance: only attendance_records.employee_name
- leave: only leave_records.employee_name
- accident: only Accident.employee_name
""" """
import re
EMAIL_RE = re.compile(r"@")
names = set() names = set()
if source in ("all", "attendance"): if source in ("all", "attendance"):
for n in db.query(AttendanceRecord.employee_name).distinct().all(): for n in db.query(AttendanceRecord.employee_name).distinct().all():
if n[0]: names.add(n[0]) if n[0] and not EMAIL_RE.search(n[0]):
names.add(n[0])
if source in ("all", "leave"): if source in ("all", "leave"):
for n in db.query(LeaveRecord.employee_name).distinct().all(): for n in db.query(LeaveRecord.employee_name).distinct().all():
if n[0]: names.add(n[0]) if n[0] and not EMAIL_RE.search(n[0]):
if source in ("all", "accident"): names.add(n[0])
for n in db.query(Accident.employee_name).distinct().all(): # NOTE: Accident.employee_name contains submitter EMAIL (e.g. info@scmedical.hk),
if n[0]: names.add(n[0]) # NOT the injured staff name. We exclude accident from employee list to avoid
# polluting staff dropdown with email addresses.
sorted_names = sorted(names, key=lambda x: x.lower()) sorted_names = sorted(names, key=lambda x: x.lower())
return {"employees": sorted_names, "count": len(sorted_names), "source": source} return {"employees": sorted_names, "count": len(sorted_names), "source": source, "excluded": "accident (email column)"}
# ============ Holidays CRUD ============# ============ Holidays CRUD ============ # ============ Holidays CRUD ============# ============ Holidays CRUD ============