3PLAdmin Implementation Plan
Goals
- 3PLAdmin onboarding UX: 3PLAdmins should always receive a SetPassword onboarding link and set their password in that flow (no "forgot password" workaround)
- Scoped visibility: A 3PLAdmin should not see all clients in
/clients; they should only see their child clients (and optionally their 3PL org/home client) - Match agreed BE structure: CCL source-of-truth with client-level denormalization
Current Problems
No SetPassword onboarding: 3PLAdmin creation can route users to normal login without having set a password.
Overbroad access:
/clientsloads all clients and allows 3PLAdmin access, so 3PLAdmins can see global client data.
Key Design Decision
We will show child clients immediately in /clients so the 3PLAdmin can click into a child client and add couriers.
- UI scoping uses
Clients.ParentThreePlClientIdas the primary filter - The CCL field remains the "source of truth" for which courier logins (and therefore claims) are managed by a 3PL
Data Model
erDiagram
Clients {
Guid ClientId PK
Guid ParentThreePlClientId FK "nullable - UI scoping"
bool IsThreePlOrg "default false"
}
ClientCourierLogin {
Guid Id PK
Guid ClientId FK
Guid ManagedByThreePlClientId FK "nullable - source of truth"
}
ClientUsers {
Guid UserId FK
Guid ClientId FK
}
IdentityUser {
Guid Id PK
string Role "3PLAdmin or ClaimitAdmin"
}
Clients ||--o{ Clients : "ParentThreePlClientId"
Clients ||--o{ ClientCourierLogin : "has"
Clients ||--o{ ClientCourierLogin : "ManagedByThreePlClientId"
Clients ||--o{ ClientUsers : "has"
IdentityUser ||--o{ ClientUsers : "linked via"Entity Definitions
| Entity | Description |
|---|---|
| 3PL org | A Clients row with IsThreePlOrg = true |
| 3PLAdmin user | Identity user with role 3PLAdmin, linked to the 3PL org client via ClientUsers |
| Child client | A normal Clients row with ParentThreePlClientId = <3PLOrgClientId> |
| Courier ownership | Modeled at CCL level via ClientCourierLogin.ManagedByThreePlClientId = <3PLOrgClientId> |
Implementation Steps
Step 1: Ensure 3PLAdmin Uses Invite-Only SetPassword Flow
Update the admin/user creation paths so that 3PLAdmin is treated like an external/client user:
- Create
IdentityUser+ assign3PLAdminrole - Create
ClientUserlinking them to the 3PL org/home client - Send onboarding email using existing SDK onboarding flow to
/authenticate/user/password/set?pkey=... - Verify the 3PLAdmin can complete SetPassword and then sign in
Files involved:
frontend/Claimit.Portal/Pages/Admin/Dialogs/AddUserDialog.razor(.cs)backend/SDK/Onboarding.cs(already builds the password set URL)
Step 2: Add BE Fields (Schema)
ClientCourierLogin:
- Add
ManagedByThreePlClientId(nullable Guid FK toClients.ClientId) - Index on
ManagedByThreePlClientId
Clients:
- Add
ParentThreePlClientId(nullable Guid FK toClients.ClientId) - Add
IsThreePlOrg(bool, defaultfalse) - Index on
ParentThreePlClientIdandIsThreePlOrg
Step 3: Scope /clients for 3PLAdmin
flowchart TD
A[User loads /clients] --> B{User role?}
B -->|ClaimitAdmin| C[Load all clients globally]
B -->|3PLAdmin| D[Determine ThreePlHomeClientId from ClientUsers]
D --> E["Load clients WHERE ParentThreePlClientId = ThreePlHomeClientId"]
E --> F[Optionally include org/home client in list]
F --> G[Apply search/pagination to scoped list]Files involved:
frontend/Claimit.Portal/Pages/Admin/Clients.razor(.cs)
Step 4: Determine the 3PL "Home Client ID" in FE
For a logged-in 3PLAdmin, determine the 3PL org/home client from ClientUsers (the client the 3PLAdmin user is linked to). This is the ThreePlHomeClientId used for scoping and for stamping fields.
Files involved:
frontend/Claimit.Portal/Clients/SessionScopedClientContext.csfrontend/Claimit.Portal/Shared/Contexts/IClientContext.csbackend/SDK/Objects/ClientUsers.cs
Step 5: Self-Serve Child Client Creation for 3PLAdmin
When a 3PLAdmin creates a new child client via the onboarding wizard:
- Create the new
Clientrow - Immediately set
Clients.ParentThreePlClientId = <ThreePlHomeClientId> - Ensure
Clients.IsThreePlOrgremainsfalse - Hide/disable the "Company Type" selector for 3PLAdmins (they should only create child clients)
Files involved:
frontend/Claimit.Portal/Pages/Admin/Clients.razor(.cs)(launches the onboarding modal)frontend/Claimit.Portal/Components/ui/onboarding/OnboardingStepOneCi.razor(.cs)(company type UI)frontend/Claimit.Portal/Components/ui/onboarding/OnboardingStepThreeCi.razor.cs(creates client/user + sends onboarding)
Step 6: Stamp CCL Ownership When Couriers Are Added
When a 3PLAdmin adds a courier login for a child client:
- Create
ClientCourierLoginas usual - Set
ClientCourierLogin.ManagedByThreePlClientId = <ThreePlHomeClientId>
This ensures the courier/claims chain is correctly associated to the 3PL at the CCL level.
Files involved:
frontend/Claimit.Portal/Couriers/Settings/CourierSettingsPage.razor.cs- Courier credential flow components under
frontend/Claimit.Portal/Couriers/
Step 7: Badges/Indicators for ClaimitAdmin (Optional)
On ClaimitAdmin views (clients table, claims table):
- Show badge if
IsThreePlOrg == true(3PL org) - Show badge if
ParentThreePlClientId != null(child of 3PL) and display the parent 3PL name via join
Portal UX Flow
sequenceDiagram
participant CA as ClaimitAdmin
participant Portal as Portal
participant DB as Database
participant TPA as 3PLAdmin
participant Email as Email
CA->>Portal: Create 3PL org + initial 3PLAdmin
Portal->>DB: Create Clients row (IsThreePlOrg=true)
Portal->>DB: Create IdentityUser + assign 3PLAdmin role
Portal->>DB: Create ClientUsers row (user → 3PL org)
Portal->>Email: Send onboarding email with SetPassword link
Email->>TPA: /authenticate/user/password/set?pkey=...
TPA->>Portal: Set password + sign in
TPA->>Portal: View /clients
Portal->>DB: Load WHERE ParentThreePlClientId = 3PLOrgId
DB->>Portal: Return scoped child clients
TPA->>Portal: Create child client
Portal->>DB: Create Clients row + set ParentThreePlClientId
Note over Portal,DB: Child appears immediately in /clients
TPA->>Portal: Add courier for child client
Portal->>DB: Create CCL row
Portal->>DB: Set ManagedByThreePlClientId = 3PLOrgId
Note over DB: Claims flow naturally through child client CCLsBE Schema Summary
| Field | Table | Type | Purpose |
|---|---|---|---|
ManagedByThreePlClientId |
ClientCourierLogin |
Nullable Guid FK | Source of truth for 3PL ownership |
ParentThreePlClientId |
Clients |
Nullable Guid FK | UI scoping and performance |
IsThreePlOrg |
Clients |
Bool | Badges and filtering |
Verification Checklist
- Create a 3PL org client (ClaimitAdmin flow) sets
IsThreePlOrg=trueand creates a 3PLAdmin user - 3PLAdmin receives SetPassword email and can set password
- 3PLAdmin login:
/clientsshows only child clients for their org (and optionally their org) - 3PLAdmin can create a child client and see it immediately in
/clients - Adding a courier login for a child client stamps
ManagedByThreePlClientIdon the created CCL - ClaimitAdmin still sees all clients/claims and can see "3PL/Child of ..." badges