# πŸ—οΈ 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*