453 lines
23 KiB
Markdown
453 lines
23 KiB
Markdown
# 🏗️ P0 DISCOVERY: Complete Organization (Garage) Data Anatomy & Mapping
|
|
|
|
**Audit Date:** 2026-06-25
|
|
**Scope:** Full-stack data anatomy of the `Organization` (Garage) entity
|
|
**Layers Covered:** Database Schema → API Endpoints → Frontend Wiring
|
|
**Report Version:** 1.0
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Executive Summary](#1-executive-summary)
|
|
2. [Database Schema & Relationships](#2-database-schema--relationships)
|
|
- 2.1 [The Address Question](#21-the-address-question-architects-hypothesis-verified)
|
|
- 2.2 [Contact Person: Dual System](#22-contact-person-dual-system)
|
|
- 2.3 [Relationships Map](#23-relationships-map)
|
|
- 2.4 [Subscription Architecture](#24-subscription-architecture)
|
|
- 2.5 [Digital Twin / Soft Delete](#25-digital-twin--soft-delete)
|
|
3. [API Coverage Mapping](#3-api-coverage-mapping)
|
|
- 3.1 [Complete Endpoint Table](#31-complete-endpoint-table)
|
|
- 3.2 [Request/Response Schema Analysis](#32-requestresponse-schema-analysis)
|
|
- 3.3 [Missing CRUD Operations](#33-missing-crud-operations)
|
|
4. [Frontend Wiring Analysis](#4-frontend-wiring-analysis)
|
|
- 4.1 [Garage List Page](#41-garage-list-page-frontend_adminpagesgaragesindexvue)
|
|
- 4.2 [Garage Detail Page](#42-garage-detail-page-frontend_adminpagesgaragesidindexvue)
|
|
- 4.3 [Edit Modal Wiring](#43-edit-modal-wiring)
|
|
- 4.4 [Dead Ends & Gaps](#44-dead-ends--gaps)
|
|
5. [Findings Summary](#5-findings-summary)
|
|
6. [Recommendations](#6-recommendations)
|
|
|
|
---
|
|
|
|
## 1. Executive Summary
|
|
|
|
This report presents a comprehensive three-layer audit of the **Organization (Garage)** entity across the Service Finder system. The audit covers the database schema ( [`backend/app/models/marketplace/organization.py:73`](backend/app/models/marketplace/organization.py:73) ), the admin API endpoints ( [`backend/app/api/v1/endpoints/admin_organizations.py:178`](backend/app/api/v1/endpoints/admin_organizations.py:178) ), and the frontend admin UI ( `/frontend_admin/pages/garages/` ).
|
|
|
|
**Key Findings:**
|
|
- ✅ **Address Architecture**: Three address types (Primary, Billing, Notification) exist as **flat denormalized columns** on `fleet.organizations`. The Architect's suspicion was **partially correct** — there is NO 1-to-Many `addresses` table with `address_type` enum. Instead, each address type has its own column prefix ( `address_*` , `billing_*` , `notification_*` ). A normalized `system.addresses` table exists but is only linked via a single `address_id` FK (not used for billing/notification distinction).
|
|
- ✅ **Contact Person**: A **dual system** exists — denormalized fields ( `contact_person_name` , `contact_email` , `contact_phone` ) on Organization for quick editing, AND a normalized `ContactPerson` model ( `fleet.contact_persons` → `identity.persons` ) with role/department/is_primary.
|
|
- ⚠️ **Missing CRUD**: `OrganizationFinancials` , `OrganizationRelationship` (B2B), `OrganizationSalesAssignment` , and `OrgRole` dynamic permissions exist in the database but have **zero admin API endpoints or frontend UI**.
|
|
- ⚠️ **Frontend Gap**: Billing and Notification `street_type` fields are missing from the frontend edit modal form, despite being present in the API schema.
|
|
|
|
---
|
|
|
|
## 2. Database Schema & Relationships
|
|
|
|
### 2.1 The Address Question (Architect's Hypothesis Verified)
|
|
|
|
**Source:** [`backend/app/models/marketplace/organization.py:73`](backend/app/models/marketplace/organization.py:73)
|
|
|
|
The database stores addresses as **three separate sets of flat denormalized columns** directly on the `fleet.organizations` table. This is NOT a normalized 1-to-Many `addresses` table with `address_type` enum.
|
|
|
|
#### Primary Address (lines 125-131)
|
|
```python
|
|
address_zip: Mapped[Optional[str]] # String(10)
|
|
address_city: Mapped[Optional[str]] # String(100)
|
|
address_street_name: Mapped[Optional[str]] # String(150)
|
|
address_street_type: Mapped[Optional[str]] # String(50)
|
|
address_house_number: Mapped[Optional[str]] # String(20)
|
|
address_hrsz: Mapped[Optional[str]] # String(50) - helyrajzi szám
|
|
plus_code: Mapped[Optional[str]] # String(20) - Plus code
|
|
```
|
|
|
|
#### Billing Address (lines 142-146)
|
|
```python
|
|
billing_zip: Mapped[Optional[str]] # String(10)
|
|
billing_city: Mapped[Optional[str]] # String(100)
|
|
billing_street_name: Mapped[Optional[str]] # String(150)
|
|
billing_street_type: Mapped[Optional[str]] # String(50)
|
|
billing_house_number: Mapped[Optional[str]] # String(20)
|
|
```
|
|
|
|
#### Notification Address (lines 149-153)
|
|
```python
|
|
notification_zip: Mapped[Optional[str]] # String(10)
|
|
notification_city: Mapped[Optional[str]] # String(100)
|
|
notification_street_name: Mapped[Optional[str]] # String(150)
|
|
notification_street_type: Mapped[Optional[str]] # String(50)
|
|
notification_house_number: Mapped[Optional[str]] # String(20)
|
|
```
|
|
|
|
#### Normalized Address System (Legacy/Secondary)
|
|
|
|
**Source:** [`backend/app/models/identity/address.py:39`](backend/app/models/identity/address.py:39)
|
|
|
|
There IS a normalized `system.addresses` table with GPS coordinates, geo-postal-code lookup, and full address text. The Organization has a single FK to it:
|
|
|
|
```python
|
|
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
|
PG_UUID(as_uuid=True), ForeignKey("system.addresses.id")
|
|
)
|
|
```
|
|
|
|
However, this `address_id` is **not used** for the billing/notification distinction. It appears to be a legacy holdover or a generalized address reference that the admin API does NOT populate or return.
|
|
|
|
**Conclusion:** The billing/notification address types are handled through column naming conventions, not through a normalized `address_type` enum. This works but is not extensible — adding a 4th address type (e.g., "postal", "legal") would require adding 5 more columns to the table.
|
|
|
|
---
|
|
|
|
### 2.2 Contact Person: Dual System
|
|
|
|
**Sources:**
|
|
- [`backend/app/models/marketplace/organization.py:136`](backend/app/models/marketplace/organization.py:136)
|
|
- [`backend/app/models/fleet/organization.py:95`](backend/app/models/fleet/organization.py:95)
|
|
|
|
#### System A: Denormalized (on Organization)
|
|
```python
|
|
contact_person_name: Mapped[Optional[str]] # String(255) - Kapcsolattartó neve
|
|
contact_email: Mapped[Optional[str]] # String(255) - Kapcsolattartó e-mail címe
|
|
contact_phone: Mapped[Optional[str]] # String(50) - Kapcsolattartó telefonszáma
|
|
```
|
|
|
|
#### System B: Normalized (ContactPerson model)
|
|
```python
|
|
class ContactPerson(Base):
|
|
__tablename__ = "contact_persons"
|
|
__table_args__ = {"schema": "fleet"}
|
|
|
|
id: int # PK
|
|
organization_id: int # FK → fleet.organizations.id
|
|
person_id: int # FK → identity.persons.id (BigInteger)
|
|
role: str # String(50) - "CEO", "Fleet Manager", etc.
|
|
is_primary: bool # default=False
|
|
department: Optional[str] # String(100)
|
|
notes: Optional[str] # Text
|
|
```
|
|
|
|
**Relationship:** `ContactPerson.person` → `identity.persons` (the actual person record with name, phone, email).
|
|
|
|
**How they interact:** The API loads both. The `GET /details` endpoint returns:
|
|
1. The denormalized `contact_person_name`, `contact_email`, `contact_phone` directly from the Organization record
|
|
2. A separate `primary_contact` object from the ContactPerson model (with role, department, person full_name, phone)
|
|
|
|
**Propagation logic** (in [`admin_organizations.py:1137`](backend/app/api/v1/endpoints/admin_organizations.py:1137)):
|
|
For `individual` orgs, the PUT endpoint propagates `contact_email` → `email` (User) and `contact_phone` → `phone` (Person).
|
|
|
|
---
|
|
|
|
### 2.3 Relationships Map
|
|
|
|
Below is the complete map of all relationships from the `Organization` entity, with their database schemas and connection types.
|
|
|
|
| Relationship | Schema.Table | FK Field | Type | Cascade | Admin API? |
|
|
|---|---|---|---|---|---|
|
|
| **assets** | `fleet.asset_assignments` | `organization_id` | `List[AssetAssignment]` | `all, delete-orphan` | ✅ GET vehicles |
|
|
| **members** | `fleet.organization_members` | `organization_id` | `List[OrganizationMember]` | `all, delete-orphan` | ✅ CRUD |
|
|
| **owner** | `identity.users` | `owner_id` | `Optional[User]` | — | ❌ Read-only via details |
|
|
| **financials** | `fleet.organization_financials` | `organization_id` | `List[OrganizationFinancials]` | `all, delete-orphan` | ❌ None |
|
|
| **service_profile** | `marketplace.service_profiles` | `organization_id` | `Optional[ServiceProfile]` | `SET NULL` | ❌ None |
|
|
| **branches** | `fleet.branches` | `organization_id` | `List[Branch]` | `all, delete-orphan` | ❌ None |
|
|
| **legal_owner** | `identity.persons` | `legal_owner_id` | `Optional[Person]` | — | ❌ Read-only via details |
|
|
| **subscription_tier** | `system.subscription_tiers` | `subscription_tier_id` | `Optional[SubscriptionTier]` | — | ✅ Read via details, PATCH subscription |
|
|
| **contact_persons** | `fleet.contact_persons` | `organization_id` | (via query) | — | ✅ Read-only via details |
|
|
| **org_relationships** | `fleet.org_relationships` | `source_org_id` / `target_org_id` | (via query) | — | ❌ None |
|
|
| **sales_assignments** | `fleet.org_sales_assignments` | `organization_id` | (via query) | — | ❌ None |
|
|
|
|
**Source:** [`backend/app/models/marketplace/organization.py:237`](backend/app/models/marketplace/organization.py:237)
|
|
|
|
---
|
|
|
|
### 2.4 Subscription Architecture
|
|
|
|
**Source:** [`backend/app/models/marketplace/organization.py:168`](backend/app/models/marketplace/organization.py:168)
|
|
|
|
The subscription system has **two parallel data sources**:
|
|
|
|
1. **Denormalized fields** on Organization:
|
|
- `subscription_plan` (String(30), default "FREE")
|
|
- `subscription_expires_at` (Optional datetime)
|
|
- `base_asset_limit` (Integer, default 1)
|
|
- `purchased_extra_slots` (Integer, default 0)
|
|
- `subscription_tier_id` (FK → `system.subscription_tiers.id`)
|
|
|
|
2. **Normalized** `OrganizationSubscription` table (queried separately in the details endpoint):
|
|
- The API queries `OrganizationSubscription` with `is_active=True` FIRST
|
|
- Falls back to `Organization.subscription_tier` if no active subscription found
|
|
- Returns `SubscriptionSummary` with `tier_name`, `tier_level`, `valid_from`, `expires_at`, `is_active`, `asset_count`, `asset_limit`
|
|
|
|
**Feature capabilities** come from two merged sources:
|
|
- `SubscriptionTier.feature_capabilities` (JSONB — tier defaults)
|
|
- `Organization.custom_features` (JSONB — org-specific overrides)
|
|
|
|
---
|
|
|
|
### 2.5 Digital Twin / Soft Delete
|
|
|
|
**Source:** [`backend/app/models/marketplace/organization.py:86`](backend/app/models/marketplace/organization.py:86)
|
|
|
|
The Organization implements a "Digital Twin" lifecycle pattern:
|
|
|
|
```python
|
|
legal_owner_id: int # FK → identity.persons (immutable owner reference)
|
|
first_registered_at: datetime # First registration (never changes)
|
|
current_lifecycle_started_at: datetime # Current lifecycle start (resets on re-register)
|
|
last_deactivated_at: datetime # Last deactivation
|
|
lifecycle_index: int # Reincarnation counter (default 1)
|
|
is_deleted: bool # Soft delete flag
|
|
is_active: bool # Active flag
|
|
```
|
|
|
|
This allows an organization to be "deleted" (re-registered under new name) while preserving vehicle/statistics history tied to the original Person.
|
|
|
|
---
|
|
|
|
## 3. API Coverage Mapping
|
|
|
|
### 3.1 Complete Endpoint Table
|
|
|
|
**Source:** [`backend/app/api/v1/endpoints/admin_organizations.py`](backend/app/api/v1/endpoints/admin_organizations.py)
|
|
|
|
| # | Method | Path | Purpose | Read/Write | Status |
|
|
|---|--------|------|---------|-----------|--------|
|
|
| 1 | `GET` | `/admin/organizations` | List orgs with pagination, search, filters | READ | ✅ Wired |
|
|
| 2 | `GET` | `/admin/organizations/{org_id}/details` | Full org details (all addresses, contacts, members, subscription) | READ | ✅ Wired |
|
|
| 3 | `PUT` | `/admin/organizations/{org_id}` | Update org fields (all 3 address types, contacts) | WRITE | ✅ Wired |
|
|
| 4 | `PUT` | `/admin/organizations/{org_id}/status` | Toggle status (active/inactive/suspended/pending_verification) | WRITE | ✅ Wired |
|
|
| 5 | `PATCH` | `/admin/organizations/{org_id}/subscription` | Update subscription plan/expiry | WRITE | ✅ Wired |
|
|
| 6 | `POST` | `/admin/organizations/{org_id}/members` | Add member by email | WRITE | ✅ Wired |
|
|
| 7 | `PUT` | `/admin/organizations/{org_id}/members/{member_id}` | Update member role/status | WRITE | ✅ Wired |
|
|
| 8 | `DELETE` | `/admin/organizations/{org_id}/members/{member_id}` | Remove member (soft delete) | WRITE | ✅ Wired |
|
|
| 9 | `GET` | `/admin/organizations/{org_id}/vehicles` | List fleet vehicles | READ | ✅ Wired |
|
|
|
|
### 3.2 Request/Response Schema Analysis
|
|
|
|
#### `OrganizationUpdate` schema (PUT) — Line 623
|
|
```python
|
|
class OrganizationUpdate(BaseModel):
|
|
# Basic fields
|
|
name, full_name, display_name # Names
|
|
tax_number, reg_number # Official registration
|
|
email, phone # Org-level contact (propagated for individual)
|
|
|
|
# Primary Address (5 fields)
|
|
address_zip, address_city, address_street_name, address_street_type, address_house_number
|
|
|
|
# Contact Person (3 fields)
|
|
contact_person_name, contact_email, contact_phone
|
|
|
|
# Billing Address (5 fields)
|
|
billing_zip, billing_city, billing_street_name, billing_street_type, billing_house_number
|
|
|
|
# Notification Address (5 fields)
|
|
notification_zip, notification_city, notification_street_name, notification_street_type, notification_house_number
|
|
```
|
|
All fields are `Optional` — only provided fields are updated.
|
|
|
|
#### `GarageDetailsResponse` schema (GET /details) — Line 127
|
|
Returns ALL the above fields **plus**:
|
|
- `subscription: SubscriptionSummary` (tier_name, tier_level, valid_from, expires_at, is_active, asset_count, asset_limit)
|
|
- `primary_contact: ContactPersonInfo` (id, full_name, role, department, phone, email, is_primary)
|
|
- `members: List[OrganizationMemberResponse]` (with nested Person data)
|
|
- `created_at`, `member_count`
|
|
|
|
---
|
|
|
|
### 3.3 Missing CRUD Operations
|
|
|
|
The following database entities have **NO admin API endpoints**:
|
|
|
|
| Entity | Table | Impact |
|
|
|--------|-------|--------|
|
|
| **OrganizationFinancials** | `fleet.organization_financials` | Cannot view/edit yearly financial records (revenue, expenses) |
|
|
| **OrganizationRelationship** | `fleet.org_relationships` | Cannot manage B2B relationships (customer/supplier/partner) |
|
|
| **OrganizationSalesAssignment** | `fleet.org_sales_assignments` | Cannot assign sales reps to organizations |
|
|
| **OrgRole** (dynamic) | `fleet.org_roles` | Cannot create/edit custom roles with JSONB permissions |
|
|
| **ContactPerson** (create/update) | `fleet.contact_persons` | Cannot add new ContactPerson records directly; only the denormalized fields are editable via PUT org |
|
|
| **ServiceProfile** | `marketplace.service_profiles` | Cannot manage service provider profile from admin |
|
|
| **Branch** | `fleet.branches` | Cannot create/edit physical locations/branches |
|
|
|
|
---
|
|
|
|
## 4. Frontend Wiring Analysis
|
|
|
|
### 4.1 Garage List Page ( `frontend_admin/pages/garages/index.vue` )
|
|
|
|
**Source:** [`/frontend_admin/pages/garages/index.vue`](frontend_admin/pages/garages/index.vue)
|
|
|
|
**Wired APIs:**
|
|
- ✅ `GET /admin/organizations` — Lists all orgs with search, filter, pagination
|
|
- ✅ `PUT /admin/organizations/{id}/status` — Status toggle per row
|
|
- ✅ `PATCH /admin/organizations/{id}/subscription` — Subscription update modal
|
|
|
|
**Displayed fields per garage card/row:**
|
|
| Field | Source | Status |
|
|
|-------|--------|--------|
|
|
| ID | `org.id` | ✅ |
|
|
| Company Name | `org.name` / `org.display_name` | ✅ |
|
|
| City | `org.address_city` | ✅ |
|
|
| Status | `org.status` | ✅ (with colored badge) |
|
|
| Member Count | `org.member_count` | ✅ |
|
|
| Subscription | `org.subscription_tier_name` | ✅ |
|
|
| Expires | `org.subscription_expires_at` | ✅ |
|
|
| Billing Address | — | ❌ Not shown |
|
|
| Notification Address | — | ❌ Not shown |
|
|
| Contact Person | — | ❌ Not shown |
|
|
| Financial info | — | ❌ Not shown |
|
|
|
|
**Stats cards at top:**
|
|
- Total garages count
|
|
- Active count
|
|
- Inactive count
|
|
- Pending verification count
|
|
|
|
---
|
|
|
|
### 4.2 Garage Detail Page ( `frontend_admin/pages/garages/[id]/index.vue` )
|
|
|
|
**Source:** [`/frontend_admin/pages/garages/[id]/index.vue`](frontend_admin/pages/garages/[id]/index.vue)
|
|
|
|
**Wired API:** `GET /admin/organizations/{id}/details`
|
|
|
|
#### General Tab (lines 131-256)
|
|
Displays:
|
|
- ✅ **Company Info**: `full_name`, `display_name`, `org_type`, `tax_number`, `reg_number`, `created_at`
|
|
- ✅ **Primary Address**: via `formattedAddress` computed (zip + city + street_name + street_type + house_number, filtered)
|
|
- ✅ **Subscription Info**: tier_name, tier_level, expires_at, asset_limit
|
|
- ✅ **Contact Person**: `primary_contact.full_name`, `primary_contact.role`, `primary_contact.department`, `primary_contact.phone`
|
|
- ❌ **Billing Address**: NOT displayed in General tab
|
|
- ❌ **Notification Address**: NOT displayed in General tab
|
|
- ❌ **Financials**: NOT displayed (Dashboard tab is "Coming Soon")
|
|
- ❌ **B2B Relationships**: NOT displayed
|
|
- ❌ **Branches**: NOT displayed
|
|
|
|
#### Employees Tab (lines 262-370)
|
|
- ✅ Full member table: name, email/phone, role badge, status, edit/remove actions
|
|
- ✅ Add employee modal
|
|
|
|
#### Fleet Tab (lines 375-457)
|
|
- ✅ Vehicle table: plate, brand, model, year, VIN, status
|
|
- ✅ RBAC-guarded with `fleet:view`
|
|
|
|
#### Analytics Tab
|
|
- ❌ "Coming soon" placeholder — not implemented
|
|
|
|
#### Missing Data Alert (lines 995-1022)
|
|
The frontend has a **missingFields** computed that flags:
|
|
- Missing tax_number (for business orgs)
|
|
- Missing contact_email / contact_phone
|
|
- Missing billing address (city + zip for business orgs)
|
|
|
|
This indicates the frontend already "knows" about the missing data problem but has no UI to collect it proactively.
|
|
|
|
---
|
|
|
|
### 4.3 Edit Modal Wiring
|
|
|
|
**Source:** [`/frontend_admin/pages/garages/[id]/index.vue:478`](frontend_admin/pages/garages/[id]/index.vue:478)
|
|
|
|
The edit modal has **3 tabs** with the following field coverage:
|
|
|
|
#### Tab 1: Basic (lines 517-598)
|
|
| Field | Form Key | API Field | Status |
|
|
|-------|----------|-----------|--------|
|
|
| Company Name | `full_name` | ✅ | ✅ |
|
|
| Display Name | `display_name` | ✅ | ✅ |
|
|
| Tax Number | `tax_number` | ✅ | ✅ |
|
|
| Reg Number | `reg_number` | ✅ | ✅ |
|
|
| ZIP | `address_zip` | ✅ | ✅ |
|
|
| City | `address_city` | ✅ | ✅ |
|
|
| Street Name | `address_street_name` | ✅ | ✅ |
|
|
| Street Type | `address_street_type` | ✅ | ✅ |
|
|
| House Number | `address_house_number` | ✅ | ✅ |
|
|
|
|
#### Tab 2: Contact (lines 601-628)
|
|
| Field | Form Key | API Field | Status |
|
|
|-------|----------|-----------|--------|
|
|
| Contact Name | `contact_person_name` | ✅ | ✅ |
|
|
| Contact Email | `contact_email` | ✅ | ✅ |
|
|
| Contact Phone | `contact_phone` | ✅ | ✅ |
|
|
|
|
#### Tab 3: Addresses (lines 631-713)
|
|
**Billing Address:**
|
|
| Field | Form Key | API Field | Status |
|
|
|-------|----------|-----------|--------|
|
|
| ZIP | `billing_zip` | ✅ | ✅ |
|
|
| City | `billing_city` | ✅ | ✅ |
|
|
| Street Name | `billing_street_name` | ✅ | ✅ |
|
|
| Street Type | `billing_street_type` | ❌ **MISSING** | ⚠️ |
|
|
| House Number | `billing_house_number` | ✅ | ✅ |
|
|
|
|
**Notification Address:**
|
|
| Field | Form Key | API Field | Status |
|
|
|-------|----------|-----------|--------|
|
|
| ZIP | `notification_zip` | ✅ | ✅ |
|
|
| City | `notification_city` | ✅ | ✅ |
|
|
| Street Name | `notification_street_name` | ✅ | ✅ |
|
|
| Street Type | `notification_street_type` | ❌ **MISSING** | ⚠️ |
|
|
| House Number | `notification_house_number` | ✅ | ✅ |
|
|
|
|
> ⚠️ **Bug**: `billing_street_type` and `notification_street_type` are present in the editForm (lines 943, 948) but have **no corresponding input fields** in the template. The API schema supports them, but the user cannot edit them through the UI.
|
|
|
|
---
|
|
|
|
### 4.4 Dead Ends & Gaps
|
|
|
|
| # | Gap | Layer | Severity |
|
|
|---|-----|-------|----------|
|
|
| 1 | **Billing/Notification street_type missing from UI** | Frontend | Low (data exists in DB & API) |
|
|
| 2 | **OrganizationFinancials - no CRUD anywhere** | API + Frontend | Medium (financial data invisible) |
|
|
| 3 | **OrganizationRelationship - no CRUD anywhere** | API + Frontend | Medium (B2B not manageable) |
|
|
| 4 | **OrganizationSalesAssignment - no CRUD anywhere** | API + Frontend | Low (sales pipeline not in admin) |
|
|
| 5 | **OrgRole dynamic permissions - no admin UI** | API + Frontend | Low (RBAC Phase 2 not deployed) |
|
|
| 6 | **ContactPerson create/update - no dedicated endpoint** | API | Low (denormalized fields cover basic needs) |
|
|
| 7 | **ServiceProfile - no admin management** | API + Frontend | Medium (service providers invisible) |
|
|
| 8 | **Branch - no admin management** | API + Frontend | Low (branches may not need admin UI) |
|
|
| 9 | **`address_id` (system.addresses FK) not populated** | API | Info (legacy field, not used) |
|
|
| 10 | **Analytics tab "Coming Soon"** | Frontend | Medium (TCO/KM analytics not implemented) |
|
|
|
|
---
|
|
|
|
## 5. Findings Summary
|
|
|
|
### ✅ What Works Well
|
|
1. **All 3 address types** (Primary, Billing, Notification) are properly modeled in DB, fully exposed via API, and wired in the frontend edit modal — with the minor exception of street_type fields.
|
|
2. **Contact Person dual system** is well-designed: fast denormalized fields for quick edits + normalized ContactPerson with full Person integration.
|
|
3. **Subscription system** has proper fallback logic (active subscription → org tier → defaults) and is fully wired.
|
|
4. **Member management** has full CRUD across all layers.
|
|
5. **Fleet/vehicle assignment** is properly wired with RBAC protection.
|
|
6. **Digital Twin lifecycle** fields are comprehensive and well-documented.
|
|
7. **Missing data alert** in the frontend proactively identifies incomplete profiles.
|
|
|
|
### ⚠️ What Needs Attention
|
|
1. **Missing street_type inputs** in the Addresses tab of the edit modal — minor template fix needed.
|
|
2. **No dedicated ContactPerson CRUD** — the normalized contact system can't be managed independently.
|
|
3. **Financials are invisible** — `OrganizationFinancials` is orphaned at the API layer.
|
|
4. **B2B relationships** — `OrganizationRelationship` model exists but has no management interface.
|
|
5. **Analytics tab** is a placeholder — no TCO/KM or financial analytics available.
|
|
6. **`address_id` (system.addresses)** — the normalized address FK is not populated by any admin endpoint, indicating either a legacy field or planned future use.
|
|
|
|
---
|
|
|
|
## 6. Recommendations
|
|
|
|
### Priority 1 (Quick Wins)
|
|
- **Fix missing street_type inputs** in the Addresses tab of the edit modal (add `billing_street_type` and `notification_street_type` template fields)
|
|
- **Add `address_id` population** to the PUT org endpoint if the normalized address system is intended for use
|
|
|
|
### Priority 2 (Medium Term)
|
|
- **Build OrganizationFinancials admin API** — at minimum a read-only endpoint and basic frontend display
|
|
- **Build OrganizationRelationship admin API** — enable B2B relationship management
|
|
- **Add ContactPerson create/update endpoint** — allow managing normalized contacts independently
|
|
- **Add ServiceProfile admin view** — see which orgs are service providers
|
|
|
|
### Priority 3 (Long Term)
|
|
- **Implement Analytics tab** — TCO/KM, financial trends, fleet utilization
|
|
- **Branch management UI** — create/edit physical locations
|
|
- **OrgRole dynamic permissions admin** — custom role builder UI
|
|
- **OrganizationSalesAssignment admin** — sales pipeline management
|
|
|
|
---
|
|
|
|
*End of Report — Full-stack Organization (Garage) Data Anatomy & Mapping*
|