Files
LynkedUpPro_CRM/docs/diagrams/lld_database.md
T
Satyam 7694788387 feat(ProCanvas): retro game overhaul with log actions, challenges & achievements modals
- Complete ProCanvas redesign with retro sports game aesthetic
- Card-game style photo frame with tilt, shimmer, corner star accents
- Daily Missions card with weekly challenge + individual quest progress bars
- Log Action card (Door Knocked, Lead Gained, Appointment Set, Client Meeting)
- Each log action opens themed modal with relevant input fields
- Challenges modal with 6 active challenges and progress tracking
- Achievements modal with all badges, unlock status, descriptions
- Nav bar tabs (Leaderboard, Challenges, Achievements) wired to modals
- Rewards & Checkpoints with named stages (Daily Grind to Legend Run)
- Smoother Hot Streak pulse animation (2.5s float + 3s pulse rings)
- Hit The Map button with smooth pulsing glow animation
- Grid pattern overlay in Leaderboard card
- Light mode support via dark: Tailwind variants throughout
- Top 3 badges displayed on profile card
- Fixed dropdown option visibility in dark mode
2026-02-26 01:36:36 +05:30

15 KiB

LLD — Database Schema (B2)

Scope: PostgreSQL schema — entity relationships, state machines, query patterns, data lifecycle Companion doc: docs/backend/02_database_schema.md


Diagram 1 — Core Entity Relationship Diagram

All 16 tables and their foreign-key relationships. Key fields shown per entity.

erDiagram
    USERS {
        uuid id PK
        varchar legacy_id
        varchar email
        varchar emp_id
        varchar username
        varchar password_hash
        varchar full_name
        user_role role
        int xp
        int streak_days
        jsonb achievements
        timestamptz deleted_at
    }

    PROPERTIES {
        serial id PK
        varchar property_id
        varchar address
        decimal latitude
        decimal longitude
        jsonb polygon
        property_type property_type
        canvassing_status canvassing_status
        int estimated_market_value
        uuid assigned_agent_id FK
        bool pending_signature
        int proposal_value
        bool currently_rented
        timestamptz deleted_at
    }

    PROPERTY_PHOTOS {
        serial id PK
        int property_id FK
        varchar url
        varchar caption
        smallint sort_order
    }

    MEETINGS {
        uuid id PK
        varchar legacy_id
        uuid agent_id FK
        uuid customer_id FK
        int property_id FK
        date meeting_date
        time meeting_time
        meeting_status status
        int deal_value
        text notes
        timestamptz deleted_at
    }

    MEETING_CHANGE_REQUESTS {
        uuid id PK
        uuid meeting_id FK
        uuid requested_by FK
        uuid reviewed_by FK
        date proposed_date
        change_request_status status
    }

    PROJECTS {
        uuid id PK
        uuid owner_id FK
        int property_id FK
        varchar title
        project_status status
        smallint health_score
        int approved_budget_cents
        int actual_cost_cents
        smallint completion_pct
        timestamptz deleted_at
    }

    PROJECT_TASKS {
        uuid id PK
        uuid project_id FK
        uuid assigned_to FK
        varchar title
        task_status status
        task_priority priority
        date due_date
    }

    CHANGE_ORDERS {
        uuid id PK
        uuid project_id FK
        uuid requested_by FK
        uuid approved_by FK
        varchar title
        int cost_impact_cents
        change_order_status status
    }

    VENDORS {
        uuid id PK
        varchar company_name
        varchar trade_type
        vendor_status status
        decimal on_time_delivery_rate
        date coi_expiry_date
        bool is_compliant
        int total_spend_cents
        timestamptz deleted_at
    }

    VENDOR_COMPLIANCE_DOCS {
        uuid id PK
        uuid vendor_id FK
        compliance_doc_type doc_type
        compliance_doc_status status
        date expiry_date
        varchar file_url
        uuid uploaded_by FK
    }

    VENDOR_ORDERS {
        uuid id PK
        uuid vendor_id FK
        uuid project_id FK
        varchar order_number
        int total_cents
        order_status status
        date expected_date
        date delivered_date
        timestamptz deleted_at
    }

    INVOICES {
        uuid id PK
        varchar invoice_number
        invoice_type invoice_type
        uuid issued_by FK
        uuid billed_to_user FK
        uuid vendor_id FK
        uuid project_id FK
        int total_cents
        int amount_paid_cents
        int balance_cents
        invoice_status status
        date due_date
        uuid approved_by FK
        timestamptz deleted_at
    }

    DOCUMENTS {
        uuid id PK
        varchar title
        document_category category
        document_review_status review_status
        uuid project_id FK
        uuid vendor_id FK
        uuid uploaded_by FK
        varchar file_url
        date expiry_date
        timestamptz deleted_at
    }

    SALES_HISTORY {
        uuid id PK
        uuid agent_id FK
        int property_id FK
        uuid meeting_id FK
        date closed_date
        int amount_cents
        deal_status status
    }

    NOTIFICATIONS {
        uuid id PK
        uuid user_id FK
        notification_type type
        varchar title
        bool is_read
        varchar deep_link
        jsonb metadata
    }

    AUDIT_LOGS {
        bigserial id PK
        uuid actor_id FK
        varchar action
        varchar resource_type
        varchar resource_id
        jsonb old_value
        jsonb new_value
        inet ip_address
    }

    USERS ||--o{ PROPERTIES : "assigned_agent_id"
    USERS ||--o{ MEETINGS : "agent_id"
    USERS ||--o{ MEETINGS : "customer_id"
    USERS ||--o{ PROJECTS : "owner_id"
    USERS ||--o{ PROJECT_TASKS : "assigned_to"
    USERS ||--o{ CHANGE_ORDERS : "requested_by"
    USERS ||--o{ INVOICES : "issued_by"
    USERS ||--o{ INVOICES : "billed_to_user"
    USERS ||--o{ SALES_HISTORY : "agent_id"
    USERS ||--o{ NOTIFICATIONS : "user_id"
    USERS ||--o{ AUDIT_LOGS : "actor_id"
    USERS ||--o{ VENDOR_COMPLIANCE_DOCS : "uploaded_by"

    PROPERTIES ||--o{ PROPERTY_PHOTOS : "property_id"
    PROPERTIES ||--o{ MEETINGS : "property_id"
    PROPERTIES ||--o{ PROJECTS : "property_id"
    PROPERTIES ||--o{ SALES_HISTORY : "property_id"

    MEETINGS ||--o{ MEETING_CHANGE_REQUESTS : "meeting_id"
    MEETINGS ||--o{ SALES_HISTORY : "meeting_id"

    PROJECTS ||--o{ PROJECT_TASKS : "project_id"
    PROJECTS ||--o{ CHANGE_ORDERS : "project_id"
    PROJECTS ||--o{ VENDOR_ORDERS : "project_id"
    PROJECTS ||--o{ INVOICES : "project_id"
    PROJECTS ||--o{ DOCUMENTS : "project_id"

    VENDORS ||--o{ VENDOR_COMPLIANCE_DOCS : "vendor_id"
    VENDORS ||--o{ VENDOR_ORDERS : "vendor_id"
    VENDORS ||--o{ INVOICES : "vendor_id"
    VENDORS ||--o{ DOCUMENTS : "vendor_id"

Diagram 2 — Meeting Status State Machine

A meeting moves through these states. Only ADMIN/OWNER can force-advance to any state. FIELD_AGENTs can only request changes.

stateDiagram-v2
    [*] --> Scheduled : POST /meetings\n(ADMIN creates)

    Scheduled --> Rescheduled : PATCH status=Rescheduled\n(ADMIN approves change request)
    Rescheduled --> Scheduled : PATCH status=Scheduled\n(new date confirmed)

    Scheduled --> InProgress : PATCH status=In Progress\n(agent marks on-site)
    Rescheduled --> InProgress : PATCH status=In Progress

    InProgress --> Completed : PATCH status=Completed\n+ outcome + notes
    InProgress --> Cancelled : PATCH status=Cancelled\n+ reason

    Completed --> Converted : PATCH status=Converted\n+ deal_value (closes deal)

    Scheduled --> Cancelled : PATCH status=Cancelled
    Rescheduled --> Cancelled : PATCH status=Cancelled

    Converted --> [*] : Terminal state\n→ writes to sales_history
    Cancelled --> [*] : Terminal state

    note right of Scheduled
        FIELD_AGENT submits
        MeetingChangeRequest
        (cannot directly edit)
    end note

    note right of Converted
        Triggers:
        sales_history INSERT
        notification to ADMIN
    end note

Diagram 3 — Invoice Status State Machine

Invoices move through a lifecycle depending on payments, approvals, and time-based triggers.

stateDiagram-v2
    [*] --> Draft : POST /invoices\n(ADMIN / OWNER creates)

    Draft --> Sent : PATCH status=Sent\n(email triggered to recipient)
    Draft --> Void : PATCH status=Void\n(cancel before sending)

    Sent --> Viewed : Auto on first\nrecipient GET request

    Viewed --> Partial : PATCH\namount_paid_cents > 0\nbut < total_cents
    Viewed --> Paid : PATCH\namount_paid_cents = total_cents

    Partial --> Paid : PATCH\namount_paid_cents = total_cents
    Partial --> Overdue : Scheduler trigger\ndue_date < TODAY

    Sent --> Overdue : Scheduler trigger\ndue_date < TODAY

    Overdue --> Paid : PATCH\namount_paid_cents = total_cents

    Viewed --> Disputed : PATCH status=Disputed\n+ dispute_reason
    Partial --> Disputed : PATCH status=Disputed
    Overdue --> Disputed : PATCH status=Disputed

    Disputed --> Paid : OWNER resolves dispute
    Disputed --> Void : OWNER voids invoice

    Paid --> [*] : Terminal state
    Void --> [*] : Terminal state

    note right of Overdue
        Triggers:
        notification to OWNER + ADMIN
        SendGrid email to debtor
    end note

Diagram 4 — Project Status State Machine

Projects managed through the Owner's Box.

stateDiagram-v2
    [*] --> active : POST /projects\n(OWNER creates)

    active --> completed : PATCH status=completed\nall tasks done\nfinal invoice paid

    active --> delayed : Auto-trigger:\ntarget_end_date passed\ncompletion_pct < 100

    active --> on_hold : PATCH status=on_hold\n+ reason (OWNER decision)

    on_hold --> active : PATCH status=active\n(OWNER resumes)

    active --> disputed : PATCH status=disputed\n+ dispute details\n(OWNER or CONTRACTOR)

    disputed --> active : Dispute resolved\n(OWNER approves)
    disputed --> cancelled : Escalated dispute\n(OWNER decision)

    active --> cancelled : PATCH status=cancelled\n(OWNER decision)

    delayed --> active : PATCH status=active\n(new timeline set)
    delayed --> cancelled : PATCH status=cancelled

    completed --> [*] : Terminal state
    cancelled --> [*] : Terminal state

    note right of delayed
        Triggers:
        notification to OWNER
        health_score recalculated
    end note

    note right of disputed
        Triggers:
        notification to CONTRACTOR
        change_orders locked
    end note

Diagram 5 — Vendor Compliance Status Machine

Compliance documents transition based on expiry dates. A scheduler runs daily to check all docs.

stateDiagram-v2
    [*] --> Active : Document uploaded\n(expiry_date in future)

    Active --> ExpiringSoon : Scheduler:\nexpiry_date ≤ TODAY + 30 days
    Active --> Expired : Scheduler:\nexpiry_date < TODAY
    Active --> Missing : Manual flag\nor doc deleted

    ExpiringSoon --> Active : New document uploaded\nwith future expiry_date
    ExpiringSoon --> Expired : Scheduler:\nexpiry_date < TODAY

    Expired --> Active : Replacement doc uploaded

    Missing --> Active : Doc uploaded

    note right of ExpiringSoon
        Triggers at 30, 14, 7 days:
        notification to OWNER
        email to vendor contact
        vendor.is_compliant = false
    end note

    note right of Expired
        Triggers immediately:
        urgent notification to OWNER
        vendor.is_compliant = false
        vendor orders may be blocked
    end note

Diagram 6 — Soft Delete Pattern

How "deletion" works across all tables. No row is ever hard-deleted in production.

flowchart TD
    REQ["DELETE /properties/42\nor\nDELETE /vendors/uuid"] --> AUTH_CHECK["Auth: require_role\nADMIN or OWNER"]

    AUTH_CHECK --> SOFT_DEL["UPDATE table\nSET deleted_at = NOW()\nWHERE id = ?"]

    SOFT_DEL --> AUDIT["INSERT audit_logs\nactor_id, action=resource.deleted\nold_value = full row snapshot"]

    SOFT_DEL --> NOTIFY["Trigger dependent cleanup:\n• Unassign agent from properties\n• Cancel pending meetings\n• Flag open invoices"]

    subgraph AllQueries["All Application Queries"]
        Q1["SELECT * FROM properties\nWHERE deleted_at IS NULL"]
        Q2["SELECT * FROM meetings\nWHERE deleted_at IS NULL"]
        Q3["SELECT * FROM vendors\nWHERE deleted_at IS NULL"]
    end

    SOFT_DEL -.->|"row hidden from"| AllQueries

    subgraph AdminOnly["Admin / Recovery Queries"]
        Q_ADMIN["SELECT * FROM properties\nWHERE deleted_at IS NOT NULL\n(recovery / audit only)"]
    end

Diagram 7 — Audit Log Write Pattern

Which operations write to audit_logs and what data is captured.

flowchart LR
    subgraph Triggers["Write Operations That Trigger Audit Log"]
        P1["Property:\nassign_agent\nchange_status\npending_signature"]
        P2["Meeting:\nstatus change\nchange_request approved"]
        P3["Invoice:\ncreated\napproved\nvoided"]
        P4["User:\nrole changed\ndeactivated"]
        P5["Project:\ncreated\nstatus changed\nchange_order approved"]
        P6["Chatbot:\nquery with sensitive role context"]
    end

    subgraph AuditLog["audit_logs INSERT"]
        AL["{\n  actor_id: user.id,\n  action: 'property.agent_assigned',\n  resource_type: 'property',\n  resource_id: '42',\n  old_value: { assigned_agent_id: null },\n  new_value: { assigned_agent_id: 'uuid' },\n  ip_address: '1.2.3.4',\n  created_at: NOW()\n}"]
    end

    P1 & P2 & P3 & P4 & P5 & P6 --> AL

    AL --> QUERY["OWNER / ADMIN\nGET /audit-logs?resource_type=property\n&resource_id=42"]

Diagram 8 — Key Query Patterns by Role

How the ORM service layer scopes queries differently per role.

flowchart TD
    subgraph PropertiesQuery["GET /properties — Role-Scoped Query"]
        PQ_START["Request arrives\nwith current_user"]
        PQ_START --> PQ_ROLE{current_user.role}

        PQ_ROLE -->|"OWNER / ADMIN"| PQ_ALL["SELECT * FROM properties\nWHERE deleted_at IS NULL\n+ any filters"]

        PQ_ROLE -->|"FIELD_AGENT"| PQ_AGENT["SELECT * FROM properties\nWHERE assigned_agent_id = current_user.id\nAND deleted_at IS NULL"]

        PQ_ROLE -->|"CUSTOMER"| PQ_CUS["SELECT * FROM properties\nWHERE property_id = current_user.property_id\nAND deleted_at IS NULL\nLIMIT 1"]
    end

    subgraph MeetingsQuery["GET /meetings — Role-Scoped Query"]
        MQ_START["Request arrives"]
        MQ_START --> MQ_ROLE{current_user.role}

        MQ_ROLE -->|"OWNER / ADMIN"| MQ_ALL["SELECT * FROM meetings\nWHERE deleted_at IS NULL"]

        MQ_ROLE -->|"FIELD_AGENT"| MQ_AGENT["SELECT * FROM meetings\nWHERE agent_id = current_user.id\nAND deleted_at IS NULL"]

        MQ_ROLE -->|"CUSTOMER"| MQ_CUS["SELECT * FROM meetings\nWHERE customer_id = current_user.id\nAND deleted_at IS NULL"]
    end

    subgraph InvoiceQuery["GET /invoices — Role-Scoped Query"]
        IQ_START["Request arrives"]
        IQ_START --> IQ_ROLE{current_user.role}

        IQ_ROLE -->|"OWNER"| IQ_ALL["SELECT * FROM invoices\nWHERE deleted_at IS NULL\n(full financial view)"]

        IQ_ROLE -->|"ADMIN"| IQ_ADM["SELECT * FROM invoices\nWHERE deleted_at IS NULL\nAND invoice_type != 'internal'"]

        IQ_ROLE -->|"CONTRACTOR / SUBCONTRACTOR"| IQ_CON["SELECT * FROM invoices\nWHERE billed_to_user = current_user.id\nOR issued_by = current_user.id"]

        IQ_ROLE -->|"VENDOR"| IQ_VEN["SELECT * FROM invoices\nWHERE vendor_id IN\n  (SELECT id FROM vendors\n   WHERE contact_user_id = current_user.id)"]
    end

Diagram 9 — Legacy ID Migration Path

How the legacy_id column bridges the mock frontend IDs ('e1', 'own_001') to real UUIDs during the transition period.

sequenceDiagram
    participant FE as React Frontend
    participant API as FastAPI
    participant DB as PostgreSQL

    Note over FE,DB: ── Transition Phase (Integration Team replacing mock calls) ──

    FE->>API: GET /users/by-legacy-id/e1
    API->>DB: SELECT * FROM users WHERE legacy_id = 'e1'
    DB-->>API: { id: "3f8a...(uuid)", legacy_id: "e1", ... }
    API-->>FE: User object with real UUID

    Note over FE: Frontend stores UUID in state<br/>All subsequent calls use UUID

    FE->>API: GET /meetings?agent_id=3f8a...(uuid)
    API->>DB: SELECT * FROM meetings WHERE agent_id = '3f8a...'
    DB-->>API: Meeting rows
    API-->>FE: Meetings data

    Note over FE,DB: ── Post-Migration Phase (legacy_id no longer needed) ──
    Note over DB: legacy_id column remains<br/>for audit trail but<br/>no longer queried by FE