Skip to content

Entity-Relationship Diagrams

The core schema is ~32 tables, split here into four digestible areas. Full column-level definitions live in tables.md.

How to read these diagrams

  • Every table has an internal id (BIGINT UNSIGNED, auto-increment) and, where API-exposed, a public_id (ULID, CHAR(26), unique) — diagrams show only distinguishing columns.
  • ||--o{ = one-to-many; }o--o{ via an explicit join table; dashed labels name the relationship.
  • Bilingual text = paired *_en / *_ar columns. Money = DECIMAL(12,3) + currency_code (KWD, 3 dp). Conventions in tables.md.
  • Cross-area FKs are repeated in each diagram where they matter (e.g., users appears in several areas as a reference node).

Area 1 — Identity, Vendor & Subscriptions

Who exists on the platform and what commercial standing they have. A vendor is the business entity; vendor_users links staff accounts to it. Subscriptions attach a plan to a vendor; each billing cycle issues an invoice, paid via payments (Area 3).

erDiagram
    users ||--o{ otp_challenges : "requests"
    users }o--o{ roles : "role_user"
    users ||--o{ vendor_users : "is member via"
    vendors ||--o{ vendor_users : "has team"
    vendors ||--o{ vendor_documents : "uploads"
    vendors }o--|| cities : "based in"
    vendors }o--|| countries : "operates in"
    plans ||--o{ subscriptions : "priced by"
    vendors ||--o{ subscriptions : "subscribes"
    subscriptions ||--o{ invoices : "bills"

    users {
        bigint id PK
        char(26) public_id UK
        varchar name
        varchar email UK "nullable"
        varchar phone UK "E.164, nullable"
        enum user_type "customer|vendor|admin"
        enum locale "ar|en"
        datetime deleted_at "soft delete"
    }
    roles {
        bigint id PK
        varchar name UK "customer|vendor_owner|vendor_member|admin"
    }
    otp_challenges {
        bigint id PK
        varchar phone
        char(64) code_hash
        enum purpose "login|verify_phone|reset"
        datetime expires_at
    }
    vendors {
        bigint id PK
        char(26) public_id UK
        varchar business_name_en
        varchar business_name_ar
        varchar license_number
        enum status "pending_review|approved|rejected|suspended"
        bigint city_id FK
        bigint country_id FK
        decimal rating_avg "3,2 denormalized"
        int reviews_count "denormalized"
    }
    vendor_users {
        bigint id PK
        bigint vendor_id FK
        bigint user_id FK
        enum role "owner|member"
    }
    vendor_documents {
        bigint id PK
        bigint vendor_id FK
        enum doc_type "commercial_license|civil_id_owner|other"
        varchar file_path "private bucket"
        enum status "pending|approved|rejected"
        date expires_at "license expiry"
    }
    plans {
        bigint id PK
        varchar code UK "free|basic|professional|enterprise"
        decimal price_monthly "12,3"
        char(3) currency_code
        int quota_quotations_month
        boolean featured_listing
    }
    subscriptions {
        bigint id PK
        char(26) public_id UK
        bigint vendor_id FK
        bigint plan_id FK
        enum status "active|past_due|cancelled|expired"
        date current_period_end
        boolean auto_renew
    }
    invoices {
        bigint id PK
        char(26) public_id UK
        bigint subscription_id FK
        varchar number UK "YAM-2026-000123"
        decimal total_amount "12,3"
        enum status "issued|paid|void"
    }

Area 2 — Marketplace, Listings & Localization

The catalog customers browse. categories are a self-referencing tree (venues → wedding halls). Vendors publish services and bundle them into packages. media is a polymorphic attachment table used platform-wide. countries / cities / currencies are Platform reference data.

erDiagram
    countries ||--o{ cities : "contains"
    countries }o--|| currencies : "default currency"
    categories ||--o{ categories : "parent of"
    categories ||--o{ services : "classifies"
    vendors ||--o{ services : "offers"
    services ||--o{ packages : "bundled as"
    vendors ||--o{ media : "gallery (polymorphic)"
    services ||--o{ media : "images (polymorphic)"
    packages ||--o{ media : "images (polymorphic)"
    users ||--o{ favorites : "saves"
    vendors ||--o{ favorites : "saved as"

    countries {
        bigint id PK
        char(2) iso2 UK "KW"
        varchar name_en
        varchar name_ar
        char(3) currency_code FK
        boolean is_active
    }
    cities {
        bigint id PK
        bigint country_id FK
        varchar name_en
        varchar name_ar
    }
    currencies {
        char(3) code PK "KWD"
        varchar name_en
        varchar name_ar
        tinyint decimal_places "KWD=3"
    }
    categories {
        bigint id PK
        bigint parent_id FK "nullable"
        varchar slug UK
        varchar name_en
        varchar name_ar
        int sort_order
        boolean is_active
    }
    services {
        bigint id PK
        char(26) public_id UK
        bigint vendor_id FK
        bigint category_id FK
        varchar title_en
        varchar title_ar
        decimal price_from "12,3 nullable"
        enum status "draft|published|archived"
    }
    packages {
        bigint id PK
        char(26) public_id UK
        bigint service_id FK
        varchar name_en
        varchar name_ar
        decimal price "12,3"
        int capacity_min
        int capacity_max
    }
    media {
        bigint id PK
        char(26) public_id UK
        varchar mediable_type "polymorphic"
        bigint mediable_id
        enum collection "gallery|license|attachment|avatar"
        varchar disk "public|private"
        enum status "pending|ready|rejected"
    }
    favorites {
        bigint id PK
        bigint user_id FK
        bigint vendor_id FK
    }

Area 3 — Event, RFQ, Booking & Financial

The money path. An event is the customer's anchor entity. An rfq belongs to an event and fans out to vendors through rfq_vendors; vendors answer with quotations (line items in quotation_items). Accepting a quotation creates a booking; payments settle bookings and invoices; refunds reverse payments.

erDiagram
    users ||--o{ events : "plans"
    events }o--|| cities : "located in"
    events ||--o{ rfqs : "requests quotes via"
    rfqs ||--o{ rfq_vendors : "targets"
    vendors ||--o{ rfq_vendors : "receives"
    rfq_vendors ||--o| quotations : "answered by"
    quotations ||--o{ quotation_items : "itemized as"
    quotations ||--o| bookings : "accepted into"
    events ||--o{ bookings : "collects"
    vendors ||--o{ bookings : "fulfills"
    bookings ||--o{ payments : "paid via"
    invoices ||--o{ payments : "settled by"
    payments ||--o{ refunds : "reversed by"

    events {
        bigint id PK
        char(26) public_id UK
        bigint user_id FK
        varchar title
        enum event_type "wedding|corporate|birthday|graduation|conference|social|other"
        date event_date
        int guests_expected
        decimal budget_total "12,3 nullable"
        bigint city_id FK
        enum status "planning|confirmed|completed|cancelled"
    }
    rfqs {
        bigint id PK
        char(26) public_id UK
        bigint event_id FK
        bigint category_id FK
        text description
        decimal budget_min "12,3"
        decimal budget_max "12,3"
        enum status "draft|pending|quoted|negotiating|accepted|rejected|expired|cancelled"
        datetime expires_at
    }
    rfq_vendors {
        bigint id PK
        bigint rfq_id FK
        bigint vendor_id FK
        enum status "sent|viewed|quoted|declined|expired"
        datetime viewed_at
    }
    quotations {
        bigint id PK
        char(26) public_id UK
        bigint rfq_vendor_id FK
        decimal total_amount "12,3"
        char(3) currency_code FK
        enum status "submitted|negotiating|accepted|rejected|expired|withdrawn"
        date valid_until
    }
    quotation_items {
        bigint id PK
        bigint quotation_id FK
        varchar description
        int quantity
        decimal unit_price "12,3"
        decimal line_total "12,3"
    }
    bookings {
        bigint id PK
        char(26) public_id UK
        bigint event_id FK
        bigint vendor_id FK
        bigint quotation_id FK
        decimal total_amount "12,3"
        decimal deposit_amount "12,3"
        enum status "pending|confirmed|completed|cancelled|refund_requested"
        date service_date
    }
    payments {
        bigint id PK
        char(26) public_id UK
        enum payable_type "booking|invoice"
        bigint payable_id
        decimal amount "12,3"
        char(3) currency_code FK
        enum method "knet|card|apple_pay|google_pay"
        enum status "pending|processing|succeeded|failed|expired|flagged"
        varchar provider_ref UK
        char(26) idempotency_key UK
    }
    refunds {
        bigint id PK
        char(26) public_id UK
        bigint payment_id FK
        decimal amount "12,3"
        enum status "requested|processing|completed|failed|rejected"
        varchar provider_refund_ref
    }

Area 4 — Communication, Experience & Platform

Conversations bind a customer and a vendor, usually in the context of an RFQ or booking. notifications is the in-app inbox (fan-out to push/email is job-side, not schema-side). reviews require a completed booking. cms_pages and audit_logs serve Operations and Platform.

erDiagram
    users ||--o{ conversations : "customer side"
    vendors ||--o{ conversations : "vendor side"
    conversations ||--o{ messages : "contains"
    users ||--o{ messages : "sends"
    messages ||--o{ media : "attachments (polymorphic)"
    users ||--o{ notifications : "receives"
    bookings ||--o| reviews : "reviewed once"
    users ||--o{ reviews : "writes"
    vendors ||--o{ reviews : "rated by"
    users ||--o{ audit_logs : "acts (actor)"

    conversations {
        bigint id PK
        char(26) public_id UK
        bigint customer_user_id FK
        bigint vendor_id FK
        varchar context_type "rfq|booking|null"
        bigint context_id "nullable"
        datetime last_message_at
    }
    messages {
        bigint id PK
        char(26) public_id UK
        bigint conversation_id FK
        bigint sender_user_id FK
        enum body_type "text|image|pdf|quotation_ref"
        text body
        datetime read_at
    }
    notifications {
        bigint id PK
        char(26) public_id UK
        bigint user_id FK
        varchar type "quotation.received, booking.confirmed, …"
        json data "entity refs, localized keys"
        datetime read_at
    }
    reviews {
        bigint id PK
        char(26) public_id UK
        bigint booking_id FK "unique"
        bigint user_id FK
        bigint vendor_id FK
        tinyint rating "1..5"
        text comment
        text vendor_reply
        enum status "published|hidden|flagged"
    }
    cms_pages {
        bigint id PK
        varchar slug UK "about, terms, privacy"
        varchar title_en
        varchar title_ar
        mediumtext body_en
        mediumtext body_ar
        enum status "draft|published"
    }
    audit_logs {
        bigint id PK
        bigint actor_user_id FK "nullable (system)"
        varchar action "vendor.approved, payment.succeeded"
        varchar entity_type
        bigint entity_id
        json old_values
        json new_values
        varchar ip_address
        datetime created_at "append-only"
    }

Cross-area relationship notes

  • Everything routes through Event on the customer side: rfqs.event_id, bookings.event_id; conversations reference RFQs/bookings via context_type/context_id, so an event's full communication trail is derivable.
  • payments is polymorphic (payable_type = booking | invoice): one payments/refunds engine serves both booking deposits (customer money) and subscription invoices (vendor money) — the "shared engines" rule.
  • media is polymorphic across vendors, services, packages, messages, reviews, and vendor_documents' files — one upload/verification pipeline platform-wide.
  • Denormalized aggregates (vendors.rating_avg, reviews_count, conversations.last_message_at) are recomputed by domain-event listeners, never trusted as source of truth.
  • Scale-phase tables (not diagrammed): event_budgets, event_checklists, event_guests, promo_codes, wallets, vendor_calendars.

Full column definitions, enums, and index choices: tables.md. API surface over these entities: endpoints.md.