At a Glance
Owner: Sysadmin / BU Admin · Table:tb_user_location· Used by: inventory, store-requisition, physical-count, spot-check · Row-level location filter — restricts inventory rows visible to the user.
user-location narrows a user's effective scope from "all locations" to "this subset". A storekeeper assigned to two storerooms should only see those two in their location pickers, count documents, and adjustment screens. The table is a simple many-to-many between access-control/user and master-data/location with an active-only soft-delete pattern.
Unlike access-control/application-role (which gates actions) and access-control/business-unit-user (which gates BU entry), user-location is a row-level data filter — it restricts visible rows without changing roles or permissions. An empty set is conventionally interpreted as "no restriction".
Maintained by Sysadmin and BU admins. Read by every list/picker in inventory-bearing modules.
| Task | Where | Notes |
|---|---|---|
| Assign user to location | User-edit → Locations tab → Add | Pick location from BU catalogue |
| Reassign storekeeper | Soft-delete old row + insert new | Open documents continue to work (FKs target tb_location) |
| View user's effective scope | User-edit → Locations tab | Shows current active assignments |
| Remove all scope (full access) | Soft-delete all rows | Empty set = "no restriction" by convention |
| Audit scope changes | reporting-audit/activity log | Filter by entity_type = user_location |
| Symptom | Cause | Action |
|---|---|---|
| User cannot see expected location | Missing tb_user_location row OR empty-set convention not applied |
Add row or confirm service code behaviour |
| Duplicate assignment error | (user_id, location_id) exists |
Reactivate the existing row instead |
| "Location not in this BU" | Location belongs to a different BU | Pick a location from the user's active BU |
Orphan user_id after platform-user deletion |
Cross-schema, no FK enforcement | Run maintenance job to clean stale rows |
user_id references platform tb_user.id but is not a Prisma FK — application validates on insert.Source: tenant schema.
tb_user_location| Field | Prisma Type | Nullable | Description |
|---|---|---|---|
id |
String @db.Uuid |
No | Primary key. |
user_id |
String @db.Uuid |
No | References platform tb_user.id; not a Prisma FK (cross-schema). |
location_id |
String @db.Uuid |
No | FK to tenant tb_location. |
note |
String? @db.VarChar |
Yes | Assignment context. |
info |
Json? @db.JsonB |
Yes | Default {}. Reserved metadata. |
| Audit columns | — | Yes | created_*, updated_*, deleted_*. |
Constraints: @@unique([user_id, location_id, deleted_at]). Index on (user_id, location_id). FK to tb_location onDelete: NoAction. user_id enforced application-side.
count > 0.user_id on insert; maintenance job cleans up after user deletion.tb_location, so open work is preserved.../carmen-turborepo-backend-v2/packages/prisma-shared-schema-tenant/prisma/schema.prisma — tb_user_location (lines ~4451-4470).../carmen-turborepo-frontend/apps/web/app/(app)/configuration/user-role/ — Locations tab.