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)
This commit is contained in:
IT Dog
2026-07-20 00:05:05 +08:00
parent c0076a125c
commit 4bcda0a907
2 changed files with 39 additions and 29 deletions
+26 -26
View File
@@ -1207,7 +1207,7 @@ async def attendance_records(
return {
"id": r.id,
"staff": r.employee_name,
"employee_name": r.employee_name,
"employee_name": r.submitter_email,
"company": r.company,
"department": r.department,
"date": r.date.isoformat() if r.date else None,
@@ -1318,7 +1318,7 @@ async def list_employees(
for n in db.query(LeaveRecord.employee_name).distinct().all():
if n[0] and not EMAIL_RE.search(n[0]):
names.add(n[0])
# NOTE: Accident.employee_name contains submitter EMAIL (e.g. info@scmedical.hk),
# NOTE: Accident.submitter_email contains submitter EMAIL (e.g. info@scmedical.hk),
# 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())
@@ -2122,7 +2122,7 @@ def _get_excel_column_order_accident() -> list:
"incident_score",
"date",
# DB-only (not in Excel)
"responsible_person",
"injured_person", # was responsible_person, renamed 2026-07-19
"medical_report",
]
@@ -2154,7 +2154,7 @@ async def accident_dashboard_summary(
by_severity = {}
by_department = {}
by_location = {}
by_responsible = {}
by_injured = {}
this_month = 0
earliest_date = None
latest_date = None
@@ -2165,8 +2165,8 @@ async def accident_dashboard_summary(
by_department[r.department] = by_department.get(r.department, 0) + 1
if r.location:
by_location[r.location] = by_location.get(r.location, 0) + 1
if r.responsible_person:
by_responsible[r.responsible_person] = by_responsible.get(r.responsible_person, 0) + 1
if r.injured_person:
by_injured[r.injured_person] = by_injured.get(r.injured_person, 0) + 1
if r.date and r.date >= month_start:
this_month += 1
if r.date:
@@ -2188,7 +2188,7 @@ async def accident_dashboard_summary(
"by_severity": by_severity,
"by_department": by_department,
"by_location": by_location,
"by_responsible": by_responsible,
"by_injured": by_injured,
"severity_count": { # frontend-expected
"1": by_severity.get("1", 0),
"2": by_severity.get("2", 0),
@@ -2198,7 +2198,7 @@ async def accident_dashboard_summary(
},
"unique_locations": len(by_location),
"unique_departments": len(by_department),
"unique_responsible": len(by_responsible),
"unique_responsible": len(by_injured),
"avg_per_day": avg_per_day,
"earliest_date": earliest_date.isoformat() if earliest_date else None,
"latest_date": latest_date.isoformat() if latest_date else None,
@@ -2231,7 +2231,7 @@ async def accident_dashboard_stats(
by_location = {}
by_employee = {}
by_department = {}
by_responsible = {}
by_injured = {}
for r in rows:
# Severity-weighted score (severity 5 = most critical)
@@ -2256,10 +2256,10 @@ async def accident_dashboard_stats(
by_department[dept]["count"] += 1
by_department[dept]["severity_sum"] += sev_weight
rp = r.responsible_person or "未分類"
by_responsible.setdefault(rp, {"name": rp, "count": 0, "severity_sum": 0, "avg_severity": 0})
by_responsible[rp]["count"] += 1
by_responsible[rp]["severity_sum"] += sev_weight
rp = r.injured_person or "未分類"
by_injured.setdefault(rp, {"name": rp, "count": 0, "severity_sum": 0, "avg_severity": 0})
by_injured[rp]["count"] += 1
by_injured[rp]["severity_sum"] += sev_weight
def finalize(d):
out = []
@@ -2272,7 +2272,7 @@ async def accident_dashboard_stats(
"by_location": sorted(finalize(by_location), key=lambda x: x["count"], reverse=True),
"by_employee": sorted(finalize(by_employee), key=lambda x: x["count"], reverse=True),
"by_department": sorted(finalize(by_department), key=lambda x: x["count"], reverse=True),
"by_responsible": sorted(finalize(by_responsible), key=lambda x: x["count"], reverse=True),
"by_injured": sorted(finalize(by_injured), key=lambda x: x["count"], reverse=True),
}
@@ -2312,7 +2312,7 @@ async def accident_records(
if search:
like = f"%{search}%"
q = q.filter(or_(
Accident.employee_name.like(like),
Accident.submitter_email.like(like),
Accident.location.like(like),
Accident.description.like(like),
Accident.department.like(like),
@@ -2325,13 +2325,13 @@ async def accident_records(
"severity": Accident.severity,
"time": Accident.time,
"location": Accident.location,
"employee_name": Accident.employee_name,
"employee_name": Accident.submitter_email,
"department": Accident.department,
"description": Accident.description,
"action_taken": Accident.action_taken,
"patient_or_staff": Accident.patient_or_staff,
"incident_score": Accident.incident_score,
"responsible_person": Accident.responsible_person,
"responsible_person": Accident.injured_person,
}
col = sortable.get(sort_by, Accident.date)
q = q.order_by(col.desc() if sort_order == "desc" else col.asc())
@@ -2346,14 +2346,14 @@ async def accident_records(
"date": r.date.isoformat() if r.date else None,
"time": r.time,
"location": r.location,
"employee_name": r.employee_name,
"employee_id": r.employee_id,
"employee_name": r.submitter_email,
"employee_id": r.injured_person_id,
"department": r.department,
"description": r.description,
"severity": r.severity,
"medical_report": r.medical_report,
"action_taken": r.action_taken,
"responsible_person": r.responsible_person,
"responsible_person": r.injured_person,
"patient_or_staff": r.patient_or_staff,
"long_description": r.long_description,
"incident_photos": r.incident_photos,
@@ -2393,7 +2393,7 @@ async def update_accident_record(
"employee_name", "department", "description", "severity",
"time", "location", "patient_or_staff", "long_description",
"incident_photos", "action_taken", "needs_escalation",
"incident_score", "responsible_person", "medical_report",
"incident_score", "injured_person", # was responsible_person, renamed 2026-07-19", "medical_report",
}
for k, v in payload.items():
if k in editable:
@@ -2424,14 +2424,14 @@ async def get_accident_record(
"date": r.date.isoformat() if r.date else None,
"time": r.time,
"location": r.location,
"employee_name": r.employee_name,
"employee_id": r.employee_id,
"employee_name": r.submitter_email,
"employee_id": r.injured_person_id,
"department": r.department,
"description": r.description,
"severity": r.severity,
"medical_report": r.medical_report,
"action_taken": r.action_taken,
"responsible_person": r.responsible_person,
"responsible_person": r.injured_person,
"patient_or_staff": r.patient_or_staff,
"long_description": r.long_description,
"incident_photos": r.incident_photos,
@@ -2762,7 +2762,7 @@ async def accident_export_excel(
("action_taken", "處理情況 / 已採取的行動"),
("needs_escalation", "需要管理層介入"),
("incident_score", "分數"),
("responsible_person", "負責人"),
("injured_person", "受傷員工"),
("medical_report", "醫療報告"),
]
@@ -2956,7 +2956,7 @@ async def accident_export_excel(
"severity": str(r.severity or ""),
"medical_report": r.medical_report or "",
"action_taken": r.action_taken or "",
"responsible_person": r.responsible_person or "",
"responsible_person": r.injured_person or "",
"patient_or_staff": r.patient_or_staff or "",
"long_description": r.long_description or "",
"incident_photos": r.incident_photos or "",
+13 -3
View File
@@ -25,19 +25,29 @@ class User(Base):
class Accident(Base):
"""意外記錄 Accident Reports
IMPORTANT Column naming convention (2026-07-19 cleanup):
- submitter_email: The Google Form submitter's email (NOT the injured person)
- injured_person: Name of the staff who got hurt (was: responsible_person)
- injured_person_id: Employee ID (was: employee_id)
This naming makes it clear these are different concepts, preventing
accidental joins with attendance_records.employee_name (which IS a real name).
"""
__tablename__ = "accidents"
id = Column(Integer, primary_key=True, index=True)
date = Column(Date, nullable=False)
time = Column(String(10), nullable=True)
location = Column(String(255), nullable=False)
employee_name = Column(String(255), nullable=False)
employee_id = Column(String(100))
submitter_email = Column(String(255), nullable=False)
injured_person_id = Column(String(100))
department = Column(String(100))
description = Column(Text, nullable=False)
severity = Column(String(50), nullable=False)
medical_report = Column(Text, nullable=True)
action_taken = Column(Text, nullable=True)
responsible_person = Column(String(255), nullable=True)
injured_person = Column(String(255), nullable=True)
# Excel-derived (Google Form) extra columns (full map) -- 2026-07-18
patient_or_staff = Column(Text, nullable=True) # Excel col 8