1) Purpose
Multi-tenant Django app for managing client companies, employees, licenses,
and support tickets. Access is controlled by tenant context and role-based
permissions.
2) Core Data Model (apps: icelicensing, accounts)
ClientCompany
Company master (name, contact info, is_active, timestamps).
Employee (client side), EmployeeSU (startup side), SupportAgent (support side)
Each links to auth.User (optional), to ClientCompany, and carries person
meta (email, phone, etc.).
Subscription
Product plan metadata (membership tier, price, features).
License
FK to ClientCompany and Subscription, with license_key, max_seats,
issued_at, expires_at.
EmployeeLicenseAssignment
FK to Employee and License; enforces seat capacity.
SupportTicket
Creator (User), assisted company, subject/description, status/priority,
timestamps. resolution is optional (blank=True).
Role, EmployeeRole
Role: {code, label} (e.g., startup_admin, client_admin, support_agent).
EmployeeRole: {employee_code, role} with unique pairs.
Profile (app: accounts)
OneToOne with auth.User.
Stores ID_NB (employee code: s<id>, su<id>, or b_<id>), plus
display fields. Company is computed, not stored.
3) Employee Code and Roles
generate_employee_code(obj) → s<pk> for SupportAgent, su<pk> for
EmployeeSU, b_<pk> for Employee.
Profile.ID_NB is auto-repaired by signals when Employee/EmployeeSU/SupportAgent
rows are saved (post-save hook).
EmployeeRole.employee_code ties a person to one or more roles.
Profiles expose:
profile.role_codes (list of role codes),
profile.has_role("startup_admin"),
profile.company_name (computed via linked employee’s company).
4) Multi-Tenancy Enforcement
TenantSaveMixin (model mixin):
On create: stamps the appropriate ClientCompany FK based on current tenant.
On update: blocks cross-tenant writes.
TenantAdminMixin (admin mixin):
For non-superusers and non Prod-Solution tenant: limits admin querysets and
FK dropdowns to current tenant; stamps tenant on create; blocks reassignment.
5) License Seat Enforcement
In EmployeeLicenseAssignment.clean():
Counts active assignments (revoked_at IS NULL) for the same license
(excluding the current instance).
Raises ValidationError if count >= license.max_seats.
In save():
Locks the related License row (select_for_update), re-validates, then saves.
6) Permissions and Visibility (high level)
Superuser
Sees and edits everything; bypasses tenant filters.
Startup Admin (startup_admin)
Full admin for Prod-Solution data; can manage ClientCompany, Licenses,
Subscriptions, SupportTickets, and staff records as designed.
Support Agent (support_agent)
Can create and manage SupportTickets per your admin configuration.
Client Admin (client_admin)
Read-only access to their own company’s Employees, Assignments, SupportTickets.
Cannot see other companies; cannot create/modify/delete tickets.
7) Admin UX Notes
Custom OTP admin site is used (Grappelli enabled).
Profile is shown as a read-only inline on the User change page:
Displays computed Company (not editable), employee code (ID_NB),
and basic profile fields.
Home dashboard shows sections based on profile.has_role(...):
Startup Admin: Prod-Solution section (Staff Accounts, Clients, Licenses,
Subscriptions).
Client Admin: “My Company” (Employees, Assignments, My Tickets).
Support Agent: Support (New Ticket, Tickets).
8) 2FA and Authentication
Login: /account/login/
2FA setup: /account/two_factor/setup/
Users must complete TOTP enrollment at first login.
Backup codes are available and should be stored safely.
9) Error Handling
Validation errors (e.g., over-assigning seats) are surfaced inline in the admin
with clean messages (no 500s).
Custom 403/404/500 templates are supported; ensure template DIRS only list your
intended folders and that you are not double-collecting static or overriding
admin templates unintentionally.
10) Onboarding Flow (minimal)
Create ClientCompany records (including “Prod-Solution”).
Create auth.User entries for staff and clients.
Create Employee/EmployeeSU/SupportAgent entries linked to those users.
Assign roles via EmployeeRole using the profile’s employee code (ID_NB).
For clients: issue License and create EmployeeLicenseAssignment as needed.
Have each user log in and complete 2FA.
11) Troubleshooting Checklist
“User page 500 in admin”
Remove any non-existent fields from Profile admin forms (e.g., company).
Ensure Profile inline uses read-only computed company instead.
“Client Admin sees nothing on Home”
Verify role assignment (EmployeeRole) for that user’s profile code.
Confirm tenant context is set and Profile.ID_NB is correct.
“Cannot create EmployeeSU in shell”
Set current tenant (e.g., tenant.set_current_tenant(<company_id>))
before creating tenant-stamped models.
“License assignment still overflows”
Confirm EmployeeLicenseAssignment.clean() is active and that signals
don’t duplicate older logic.
“Custom 404 not used”
Verify handler404/403/500 in core.urls and template DIRS point to the
right folder; avoid duplicate template dirs that shadow admin.
12) Glossary
Tenant: Current company context; used to stamp and filter data.
Employee code (ID_NB): Short string that encodes person type and PK
(s<id>, su<id>, b_<id>).
Role: Functional capability tag (startup_admin, support_agent, client_admin).
Seat: An assignable unit on a license (max_seats).
13) Contact
For assistance, open a ticket via “New Ticket” in the Support section, or contact
your Prod-Solution administrator.