Admin Audit Trail on Practitioner Records - hmislk/hmis GitHub Wiki
Admin — Audit Trail on Practitioner Records
Every practitioner record in HMIS captures who created it and who retired it, with timestamps. This article describes the fields, where they are surfaced in the UI, and how to use them for audit.
The audit fields on a Staff record
All Staff records (and therefore Doctor and Consultant records too) carry the following audit fields:
| Field | Type | When set |
|---|---|---|
creater |
WebUser |
Set once when the record is first saved. Captures the user who created the record. |
createdAt |
Timestamp | Set once at creation. |
retirer |
WebUser |
Set when the record is soft-deleted via any of the Delete actions. |
retiredAt |
Timestamp | Set at soft-delete time. |
retireComments |
String | Free-text comment field that can hold a reason for retirement. Not exposed on every delete dialog. |
dateRetired |
Date | A second, date-only retirement field used by some HR retirements. Distinct from retiredAt. |
Note that
createris spelt with one t in the schema (legacy naming). Do not "fix" it; controller code references the same column name.
Where audit fields appear in the UI
Consultant edit screen — Edit tab
In admin_doctor_consultant.xhtml, the consultant edit panel has an Edit tab next to Basic Details. It shows:
| Label | Value source |
|---|---|
| ID | current.id |
| Creator | current.creater.webUserPerson.nameWithTitle |
| Created At | current.createdAt (Asia/Colombo, long date-time pattern) |
This is the cleanest in-app place to read the creator stamp on a consultant.
Staff Bulk Delete grid
In staff_bulk_delete.xhtml, the Created By column shows the creator name — useful for spotting batches uploaded by a particular user when you need to mass-retire them.
Staff List / Doctor lists
The standard list grids do not show creator or retirement fields by default — these screens prioritise operational data (name, code, EPF). To audit by creator, use the database directly or use the Bulk Delete screen filter on Created By.
Retired records
Retired records are excluded from active lists, so their retirement timestamp and retirer are not visible in the UI without database access. The data is intact and queryable; an administrator can produce a report on demand.
Sample audit queries
When the in-app surfaces are not enough, use these JPQL or SQL queries:
-- All retired doctors retired in the last 90 days, with who retired them and when
SELECT s.id, p.name, w.web_user_person_id AS retirer_user, s.retired_at
FROM staff s
LEFT JOIN person p ON p.id = s.person_id
LEFT JOIN web_user w ON w.id = s.retirer_id
WHERE s.retired = TRUE
AND s.retired_at >= NOW() - INTERVAL 90 DAY
AND s.dtype IN ('Doctor', 'Consultant')
ORDER BY s.retired_at DESC;
-- Staff created by a specific user
SELECT s.id, p.name, s.created_at
FROM staff s
LEFT JOIN person p ON p.id = s.person_id
WHERE s.creater_id = :userId
ORDER BY s.created_at DESC;
(Column names may differ in your installation; verify against the schema.)
What audit fields do NOT capture
The current schema captures creation and retirement. It does not capture:
| Event | Captured? |
|---|---|
| Field-level edits (who changed the name, when) | No |
| Signature upload / removal | No (only the current image is stored) |
| Linked-user change | No |
Re-instatement (clearing retired) |
No — only the most recent retirement is recorded |
If you need full change history (who edited what when), you must implement an external audit layer or rely on database transaction logs.
Practical audit workflows
| Question | How to answer |
|---|---|
| Who added this consultant? | Open the Consultant editor, switch to the Edit tab, read Creator and Created At. |
| Who retired this doctor last week? | Run the SQL above filtered to the previous week. |
| Was this batch of staff added by an upload? | Query for staff with created_at within the upload's clock window and the same creater_id. Compare to the spreadsheet you ran. |
| Who keeps retiring my test doctor by accident? | Restore (clear retired), then look at retirer_id if it happens again. |
| Has anyone been tampering with one specific record? | The schema only shows the most recent creation/retirement events. Use database transaction logs for richer history. |
Limitations and design notes
- Audit data lives on the same row as the record. There is no separate audit-log table.
- There is no editor for the audit fields in the UI; they are read-only by design.
retireCommentsis rarely populated because the Delete confirmation dialog does not collect a comment. To make use of it, scripts or a custom screen are required.dateRetired(date-only) andretiredAt(timestamp) can fall out of sync if data has been edited directly. TreatretiredAtas authoritative.
Recommended administrator hygiene
- Periodically run the retired in the last 90 days query to confirm only authorised users have retired staff.
- Match the creator IDs on records created during bulk uploads to the user who ran the upload — flag mismatches.
- After any direct database operation that bypasses the UI, document it externally; the schema does not record manual interventions.