This commit is contained in:
@@ -10,7 +10,12 @@ Webhooks are auto-registered when you add a controller from the UI.
|
||||
|
||||
**Tenant filtering:** hide building tenants (or any non-staff actor) from the
|
||||
attendance table with one click — events are still recorded so unfiltering
|
||||
restores their full history. (05/28/26)
|
||||
restores their full history.
|
||||
|
||||
**Identity merging:** the same person on two controllers gets two UniFi UUIDs.
|
||||
Merge those identities into a single "person" so the dashboard computes ONE
|
||||
first-badge time per human, not per badge UUID — no more false LATE warnings
|
||||
for staff who badge into multiple buildings before the cutoff. (05/28/26)
|
||||
|
||||
---
|
||||
|
||||
@@ -177,6 +182,7 @@ Per-controller actions in the modal:
|
||||
| **Refresh** | Reload the table |
|
||||
| **Sync Users** | Pull latest users from every enabled controller |
|
||||
| **🚫 Filtered** | Open the filtered-tenants modal to review and unhide |
|
||||
| **👥 People** | Manage merged identities and review auto-suggested merges |
|
||||
| **⚙ Controllers** | Add / manage controllers |
|
||||
| **Reset Day** | Delete all badge records for the selected date (respects the Controller filter — testing only) |
|
||||
|
||||
@@ -191,11 +197,42 @@ Per-controller actions in the modal:
|
||||
| **Latest Badge In** | Most recent entry — shows *"— same"* if only one badge event |
|
||||
| **Actor ID** | First 8 characters of the UniFi user UUID |
|
||||
| **Status** | ON TIME (green) or LATE (red) based on first badge vs cutoff |
|
||||
| **Actions** | **Hide** filters this tenant out of future views; **Unhide** restores them |
|
||||
| **Actions** | **Hide** filters this person out of future views; **Merge** joins two badge identities so they count as one human |
|
||||
|
||||
> The same physical person on two different controllers will appear as two rows
|
||||
> (different controllers issue different user UUIDs). They're distinguishable
|
||||
> by the Source column.
|
||||
> Once two identities are merged, the Source column shows a chip for every
|
||||
> controller the person badged into that day, plus a "MERGED" pill so it's
|
||||
> clear the row represents N UniFi UUIDs.
|
||||
|
||||
---
|
||||
|
||||
## Merging identities across controllers
|
||||
|
||||
UniFi issues a new UUID per controller, so the same person on Main Office and
|
||||
Warehouse shows up as two rows by default — and worst case, a badge at 8:45 on
|
||||
one and 9:15 on the other produces one ON TIME row plus one LATE row. Merging
|
||||
fixes this:
|
||||
|
||||
- Click **Merge** on any row. A picker shows other actors ordered by best
|
||||
name match — pick one, confirm a display name, done.
|
||||
- Click **👥 People** in the header to open the people manager. The top of
|
||||
that modal shows **Suggested merges** — pairs of `(controller, actor)` rows
|
||||
with matching full names across different controllers. One click confirms
|
||||
each suggestion (no auto-apply; you're always in the loop).
|
||||
- The same modal lists every merged person below the suggestions, with
|
||||
per-person actions: **Rename**, **Split off** (remove one identity from the
|
||||
group), **Dissolve** (break the whole group up — past badge events are
|
||||
preserved, the identities just become standalone rows again).
|
||||
|
||||
Once merged, the attendance table:
|
||||
|
||||
- Shows **one row** per person, with MIN/MAX badge times computed across all
|
||||
their identities — first badge wins ON TIME / LATE.
|
||||
- Renders **one source chip per controller** they badged into that day.
|
||||
- **Hide** on a merged row filters the *person*, so all their identities go
|
||||
with them. Splitting an identity off later returns it unfiltered.
|
||||
|
||||
The merge data lives in two tables (`persons`, `person_members`); user-cache
|
||||
syncs from UniFi never touch them.
|
||||
|
||||
---
|
||||
|
||||
@@ -246,6 +283,13 @@ reverse proxy with auth in front of it.
|
||||
| `GET` | `/api/first-badge-status` | `date`, `cutoff`, `controller_id?`, `include_filtered?` | Returns first + latest badge per user (filtered tenants hidden unless `include_filtered=1`) |
|
||||
| `GET` | `/api/users` | `controller_id?`, `filtered?` | List cached actors with their filtered flag |
|
||||
| `PATCH` | `/api/users/<controller_id>/<actor_id>` | `filtered` (bool) | Hide / unhide an actor from the attendance table |
|
||||
| `GET` | `/api/persons` | — | List merged people with their members |
|
||||
| `POST` | `/api/persons` | `display_name`, `members[]` | Create a merged person from 1+ `(controller_id, actor_id)` members |
|
||||
| `PATCH` | `/api/persons/<id>` | `display_name?`, `filtered?` | Rename or hide/unhide a merged person |
|
||||
| `DELETE` | `/api/persons/<id>` | — | Dissolve a merged person (members become standalone) |
|
||||
| `POST` | `/api/persons/<id>/members` | `controller_id`, `actor_id` | Add another identity to an existing person |
|
||||
| `DELETE` | `/api/persons/<id>/members/<cid>/<aid>` | — | Split one identity off; dissolves the person if it was the last member |
|
||||
| `GET` | `/api/persons/suggestions` | — | Exact full-name matches across controllers, excluding already-merged actors |
|
||||
| `GET` | `/api/controllers` | — | List configured controllers |
|
||||
| `POST` | `/api/controllers` | `name`, `host`, `port`, `api_token` | Add a controller (also registers webhook) |
|
||||
| `PATCH` | `/api/controllers/<id>` | `name?`, `enabled?` | Rename or enable/disable a controller |
|
||||
@@ -269,8 +313,10 @@ reverse proxy with auth in front of it.
|
||||
| Webhook URL stored in controller points to the wrong address | Browser's origin isn't reachable from the controller | Set `DASHBOARD_BASE_URL` in `.env`, remove + re-add the controller |
|
||||
| `Port 12445 connection refused` | Firewall blocking port | Add LAN IN firewall rule in UniFi Network (Step 1) |
|
||||
| Dashboard shows stale names after a user rename | Cache not refreshed | Click **Sync Users** or wait for the hourly auto-sync |
|
||||
| A tenant I hid is still showing up | Same person exists on a second controller | Hide them on each controller — the filter is per `(controller, actor)` |
|
||||
| A tenant I hid is still showing up | Same person exists on a second controller | Hide them on each controller, or merge their identities under the **👥 People** modal so one Hide covers both |
|
||||
| Filtered tenant doesn't appear when I tick "Show filtered" | They've never badged in on the selected date | Open the **🚫 Filtered** modal to confirm they're filtered |
|
||||
| Same person showing twice with ON TIME + LATE | They badged into two controllers and the identities aren't merged | Click **Merge** on either row (or confirm the auto-suggestion under **👥 People**) |
|
||||
| "actor already belongs to another person" when merging | The actor is already part of an existing merged person | Open **👥 People**, find that person, and add this identity to it (or split it off first) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user