Skip to content

Redshift Clients Datalake — Entity Relationship Diagram

This documents the entity-relationship structure of the tables the Clients Datalake exports to each client’s Redshift database. The export pipeline serves 50 views, including the deprecated property_calendars view. This diagram excludes property_calendars (see Excluded: deprecated views) and therefore covers the remaining 49. The Client Data Dictionary is the column-level reference for every table listed here; this document is relationship-focused and only lists primary-key, foreign-key, and discriminator columns on each entity.

Table and column names below are the literal names in your Redshift database — they follow Lavanda’s internal data model, which differs from some product terminology. Notably: properties = Units, multi_unit_groups = Unit Types, and inquiries = Leads.

Every view is filtered to the client’s own workspace IDs. Most views reach that scope by joining through a parent table (e.g. bookings → properties → workspaces) rather than owning a workspace_id column themselves — those hops are recorded as scope-hop rows in the evidence table below but are not drawn as diagram edges, to avoid a WORKSPACES fan-out to nearly every entity. An edge to WORKSPACES is drawn only for a table that filters or joins on its own workspace_id (or workspace_uuid) column.

An edge between two other entities is drawn on one of two evidence tiers, both recorded in the evidence table below: hard-fk, where a JOIN predicate in the view SQL itself touches the served table’s own alias column (t.*) and that column is part of the view’s projection; or documented-fk, where the view SQL doesn’t join the target table, but the column’s own sources.yml description explicitly names a served table as the target (e.g. “ID of the associated booking”) — never inferred from a column name alone, always from the documented description. A *_id/*_uuid column whose description points at a table this export does not serve (e.g. an internal CRM company or contact-entity record) is listed as an external reference instead of drawn, since there is no entity in this diagram to draw it to. Dashed relationships mark polymorphic/soft references (a *_type discriminator paired with a *_id/array column that can resolve to more than one table, or that isn’t declared as a real foreign key in Lavanda’s internal schema).

%%{init: {"er": {"useMaxWidth": false}, "themeVariables": {"fontSize": "16px"}} }%%
erDiagram
direction TB
WORKSPACES {
bigint id PK
}
PROPERTIES {
integer id PK
bigint workspace_id FK
bigint multi_unit_group_id FK "-> MULTI_UNIT_GROUPS, optional"
integer parent_unit_id FK "-> PROPERTIES (self), optional"
bigint onboarding_ticket_id FK "-> TICKETS, optional"
}
BUILDINGS {
bigint id PK
bigint workspace_id FK
}
MULTI_UNIT_GROUPS {
bigint id PK
bigint workspace_id FK
}
MULTI_UNIT_GROUP_CALENDARS {
bigint id PK
bigint multi_unit_group_id FK
}
ADDRESSES {
bigint id PK
varchar addressable_type "soft ref discriminator"
bigint addressable_id "soft ref: PROPERTIES or BUILDINGS"
}
AVAILABILITY_UNIT_AVAILABILITY_POOL_ALLOCATIONS {
bigint id PK
bigint unit_id FK "-> PROPERTIES"
}
UNIT_CALENDARS {
bigint id PK
bigint unit_id FK "-> PROPERTIES"
bigint booking_id FK "-> BOOKINGS, optional"
}
TARIFFS {
varchar id PK "Mongo _id"
}
INTERVAL_PRICING_CALENDARS {
bigint id PK
varchar bookable_type "soft ref discriminator"
bigint bookable_id "soft ref: PROPERTIES or MULTI_UNIT_GROUPS"
varchar tariff_id FK "-> TARIFFS"
}
BOOKINGS {
integer id PK
integer property_id FK
bigint multi_unit_group_id FK "-> MULTI_UNIT_GROUPS, optional"
varchar product_id FK "-> CATALOG_PRODUCTS, optional"
}
REPORT_AR_DOCUMENTS {
varchar document_id PK
varchar booking_id FK "-> BOOKINGS"
varchar workspace_id FK "-> WORKSPACES"
}
REPORT_AR_BALANCES {
varchar id PK
varchar workspace_id FK "-> WORKSPACES"
}
BILLING_PERIODS {
bigint id PK
bigint booking_id FK
}
BOOKING_GUARANTOR_INFORMATIONS {
bigint id PK
bigint booking_id FK
varchar external_contact_id FK "-> CRM_CONTACT_INFORMATIONS (unique_reference)"
}
BOOKING_CONTEXT_PARTICIPANTS {
bigint id PK
bigint booking_context_id "-> BOOKINGS.booking_context_id (context, not a booking row)"
varchar external_contact_id FK "-> CRM_CONTACT_INFORMATIONS (unique_reference)"
}
BOOKING_GUESTS {
bigint id PK
bigint booking_id FK
varchar external_contact_id FK "-> CRM_CONTACT_INFORMATIONS (unique_reference)"
varchar role "GUEST, LEAD_GUEST, GUARANTOR, BOOKING_AGENT"
}
BOOKINGS_INSTALMENT_PLAN_TEMPLATES {
varchar id PK
varchar workspace_id FK "-> WORKSPACES (stored as text)"
varchar product_group_ids "soft ref: CATALOG_PRODUCT_GROUPS (array)"
}
BOOKING_CONTRACT_VALUES {
bigint id PK
bigint booking_id FK
}
DAILY_REVENUES {
integer id PK
integer property_id FK
integer booking_id FK "-> BOOKINGS, optional"
}
BOOKING_DAILY_RATES {
bigint id PK
bigint booking_id FK
bigint billing_period_id FK "-> BILLING_PERIODS"
varchar issued_invoice_uuid FK "-> INVOICING_INVOICES, optional"
}
BOOKING_RATES {
bigint id PK
bigint booking_id FK
}
FINANCE_BILLING_CYCLES {
bigint id PK
bigint booking_id FK
}
GUEST_PAYMENTS {
bigint id PK
bigint booking_id FK
varchar invoice_uuid FK "-> INVOICING_INVOICES"
}
INVOICING_INVOICES {
bigint id PK
json payload "embeds booking/guest-payment IDs, see note below"
}
INVOICE_LIST_ITEMS {
bigint id PK
varchar workspace_uuid FK "-> WORKSPACES (uuid, not id)"
varchar invoice_uuid FK "-> INVOICING_INVOICES (uuid)"
bigint booking_id FK "-> BOOKINGS"
bigint billing_period_id FK "-> BILLING_PERIODS"
}
CATALOG_PRODUCT_GROUPS {
varchar id PK "NanoID"
bigint workspace_id FK
}
CATALOG_PRODUCT_RATES {
varchar id PK "NanoID"
}
CATALOG_PRODUCTS {
varchar id PK "NanoID"
varchar product_group_id FK
varchar product_rate_id FK
}
CATALOG_RATE_CALENDARS {
varchar id PK "NanoID"
varchar product_rate_id FK
}
AGREEMENTS_AGREEMENTS {
varchar id PK "NanoID"
bigint workspace_id FK
bigint company_id "external ref, see note below"
}
AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATIONS {
varchar id PK "NanoID"
varchar agreement_id FK
bigint unit_type_id FK "-> MULTI_UNIT_GROUPS"
}
AGREEMENTS_AGREEMENT_VERSIONS {
bigint id PK
varchar item_type "soft ref discriminator (PaperTrail)"
varchar item_id "soft ref: AGREEMENTS_AGREEMENTS"
}
AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATION_VERSIONS {
bigint id PK
varchar item_type "soft ref discriminator (PaperTrail)"
varchar item_id "soft ref: AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATIONS"
}
CRM_NAMESPACES {
uuid uuid PK
}
CRM_CONTACT_INFORMATIONS {
bigint id PK
bigint crm_contact_entity_id "external ref, see note below"
}
CRM_COMPANIES {
bigint id PK
bigint crm_namespace_id "internal namespace id, see note below"
uuid crm_namespace_uuid FK "-> CRM_NAMESPACES"
}
CRM_COMPANIES_CONTACT_INFORMATIONS {
bigint id PK
bigint crm_company_id FK "-> CRM_COMPANIES"
bigint crm_contact_information_id FK "-> CRM_CONTACT_INFORMATIONS"
}
CRM_CONTACT_RELATIONSHIPS {
bigint id PK
bigint source_contact_information_id FK "-> CRM_CONTACT_INFORMATIONS (guest)"
bigint target_contact_information_id FK "-> CRM_CONTACT_INFORMATIONS (related contact)"
varchar relationship_type "discriminator"
varchar sub_type
}
LEAD_STATUSES {
bigint id PK
uuid crm_namespace_uuid FK "-> CRM_NAMESPACES"
}
INQUIRIES {
bigint id PK
bigint lead_status_id FK "-> LEAD_STATUSES, optional"
uuid crm_namespace_uuid FK "-> CRM_NAMESPACES"
}
INQUIRY_PARTICIPANTS {
bigint id PK
bigint inquiry_id FK
}
WORKSPACES_INQUIRIES {
bigint id PK
bigint workspace_id FK
bigint inquiry_id FK "-> INQUIRIES"
}
OFFER_MANAGEMENT_OFFERS {
bigint id PK
varchar resource_owner_type "soft ref discriminator"
varchar resource_owner_id "soft ref: INQUIRIES (currently always 'inquiry')"
bigint building_id FK "-> BUILDINGS"
}
OFFER_MANAGEMENT_OFFER_ACCOMMODATIONS {
bigint id PK
bigint offer_id FK
}
TAGS_TAGS {
bigint id PK
bigint container_id "external ref, see note below"
varchar scope "discriminator"
varchar entity "discriminator"
}
TAGS_ENTITY_TAGS {
bigint id PK
bigint tag_id FK "-> TAGS_TAGS"
varchar ref "soft ref, see Soft and polymorphic references"
}
TASK_CATEGORIES {
bigint id PK
}
TASKS {
integer id PK
bigint workspace_id FK
integer property_id FK "-> PROPERTIES, optional"
bigint parent_ticket_id FK "-> TICKETS, optional"
bigint booking_id FK "-> BOOKINGS, optional"
bigint task_category_id FK "-> TASK_CATEGORIES"
}
TICKETS {
integer id PK
bigint workspace_id FK
integer property_id FK "-> PROPERTIES"
}
WORKSPACES ||--o{ PROPERTIES : "scopes"
WORKSPACES ||--o{ BUILDINGS : "scopes"
WORKSPACES ||--o{ MULTI_UNIT_GROUPS : "scopes"
WORKSPACES ||--o{ AGREEMENTS_AGREEMENTS : "scopes"
WORKSPACES ||--o{ CATALOG_PRODUCT_GROUPS : "scopes"
WORKSPACES ||--o{ TASKS : "scopes"
WORKSPACES ||--o{ TICKETS : "scopes"
WORKSPACES ||--o{ WORKSPACES_INQUIRIES : "scopes"
WORKSPACES ||--o{ INVOICE_LIST_ITEMS : "scopes (via workspace_uuid)"
WORKSPACES ||--o{ REPORT_AR_DOCUMENTS : "scopes"
WORKSPACES ||--o{ REPORT_AR_BALANCES : "scopes"
WORKSPACES ||--o{ BOOKINGS_INSTALMENT_PLAN_TEMPLATES : "scopes"
PROPERTIES ||--o{ AVAILABILITY_UNIT_AVAILABILITY_POOL_ALLOCATIONS : "has many"
PROPERTIES ||--o{ UNIT_CALENDARS : "has many"
PROPERTIES ||--o{ BOOKINGS : "has many"
PROPERTIES ||--o{ DAILY_REVENUES : "has many"
PROPERTIES }o..o{ TARIFFS : "soft ref (tariff_ids array)"
MULTI_UNIT_GROUPS ||--o{ MULTI_UNIT_GROUP_CALENDARS : "has many"
ADDRESSES }o..o{ PROPERTIES : "soft ref"
ADDRESSES }o..o{ BUILDINGS : "soft ref"
INTERVAL_PRICING_CALENDARS }o..o{ PROPERTIES : "soft ref"
INTERVAL_PRICING_CALENDARS }o..o{ MULTI_UNIT_GROUPS : "soft ref"
BOOKINGS ||--o{ BILLING_PERIODS : "has many"
BOOKINGS ||--o{ BOOKING_CONTRACT_VALUES : "has many (versioned)"
BOOKINGS ||--o{ BOOKING_DAILY_RATES : "has many"
BOOKINGS |o..o{ DAILY_REVENUES : "has many (soft ref)"
BOOKINGS ||--o| BOOKING_RATES : "has one"
BOOKINGS ||--o{ FINANCE_BILLING_CYCLES : "has many"
BOOKINGS ||--o{ GUEST_PAYMENTS : "has many"
BOOKINGS ||--o{ REPORT_AR_DOCUMENTS : "issued documents"
BOOKINGS ||--o{ BOOKING_GUESTS : "has many"
BOOKINGS ||--o{ BOOKING_GUARANTOR_INFORMATIONS : "has many"
BOOKINGS }o..o{ BOOKING_CONTEXT_PARTICIPANTS : "soft ref (shared booking_context_id)"
INVOICING_INVOICES ||--o{ INVOICE_LIST_ITEMS : "has many"
CATALOG_PRODUCT_GROUPS ||--o{ CATALOG_PRODUCTS : "has many"
CATALOG_PRODUCT_RATES ||--o{ CATALOG_PRODUCTS : "prices"
CATALOG_PRODUCT_RATES ||--o{ CATALOG_RATE_CALENDARS : "has many"
AGREEMENTS_AGREEMENTS ||--o{ AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATIONS : "has many"
AGREEMENTS_AGREEMENTS }o..o{ AGREEMENTS_AGREEMENT_VERSIONS : "soft ref (PaperTrail)"
AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATIONS }o..o{ AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATION_VERSIONS : "soft ref"
INQUIRIES ||--o{ INQUIRY_PARTICIPANTS : "has many"
OFFER_MANAGEMENT_OFFERS }o..o{ INQUIRIES : "soft ref"
OFFER_MANAGEMENT_OFFERS ||--o{ OFFER_MANAGEMENT_OFFER_ACCOMMODATIONS : "has many"
MULTI_UNIT_GROUPS ||--o{ PROPERTIES : "unit type (optional)"
PROPERTIES ||--o{ PROPERTIES : "parent unit (optional)"
TICKETS ||--o{ PROPERTIES : "onboarding ticket (optional)"
MULTI_UNIT_GROUPS ||--o{ AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATIONS : "allocated to"
MULTI_UNIT_GROUPS ||--o{ BOOKINGS : "unit type (optional)"
CATALOG_PRODUCTS ||--o{ BOOKINGS : "product (optional)"
BILLING_PERIODS ||--o{ BOOKING_DAILY_RATES : "has many"
INVOICING_INVOICES ||--o{ BOOKING_DAILY_RATES : "issued invoice (optional)"
INVOICING_INVOICES ||--o{ GUEST_PAYMENTS : "has many"
LEAD_STATUSES ||--o{ INQUIRIES : "has many (optional)"
CRM_NAMESPACES ||--o{ INQUIRIES : "namespace scope"
CRM_NAMESPACES ||--o{ LEAD_STATUSES : "namespace scope"
CRM_NAMESPACES ||--o{ CRM_COMPANIES : "namespace scope"
CRM_COMPANIES ||--o{ CRM_COMPANIES_CONTACT_INFORMATIONS : "has many"
CRM_CONTACT_INFORMATIONS ||--o{ CRM_COMPANIES_CONTACT_INFORMATIONS : "linked to companies"
CRM_CONTACT_INFORMATIONS ||--o{ BOOKING_GUESTS : "contact (via unique_reference)"
CRM_CONTACT_INFORMATIONS ||--o{ BOOKING_GUARANTOR_INFORMATIONS : "contact (via unique_reference)"
CRM_CONTACT_INFORMATIONS ||--o{ BOOKING_CONTEXT_PARTICIPANTS : "contact (via unique_reference)"
CRM_CONTACT_INFORMATIONS ||--o{ CRM_CONTACT_RELATIONSHIPS : "has relationships (source)"
CRM_CONTACT_INFORMATIONS ||--o{ CRM_CONTACT_RELATIONSHIPS : "is related contact (target)"
TAGS_TAGS ||--o{ TAGS_ENTITY_TAGS : "attached as"
TAGS_ENTITY_TAGS }o..o| CATALOG_PRODUCT_GROUPS : "soft ref (ref, entity = product_group)"
TAGS_ENTITY_TAGS }o..o| BOOKINGS_INSTALMENT_PLAN_TEMPLATES : "soft ref (ref, entity = instalment_plan_template)"
BOOKINGS_INSTALMENT_PLAN_TEMPLATES }o..o{ CATALOG_PRODUCT_GROUPS : "soft ref (product_group_ids array)"
INQUIRIES ||--o{ WORKSPACES_INQUIRIES : "has many"
BUILDINGS ||--o{ OFFER_MANAGEMENT_OFFERS : "has many"
TARIFFS ||--o{ INTERVAL_PRICING_CALENDARS : "priced by"
BOOKINGS ||--o{ INVOICE_LIST_ITEMS : "has many"
BILLING_PERIODS ||--o{ INVOICE_LIST_ITEMS : "has many"
PROPERTIES ||--o{ TASKS : "has many (optional)"
TICKETS ||--o{ TASKS : "parent ticket (optional)"
BOOKINGS ||--o{ TASKS : "has many (optional)"
TASK_CATEGORIES ||--o{ TASKS : "has many"
PROPERTIES ||--o{ TICKETS : "has many"
BOOKINGS ||--o{ UNIT_CALENDARS : "has many (optional)"

CRM_CONTACT_INFORMATIONS’s own relational column, crm_contact_entity_id, points at crm_contact_entities, which this export does not serve (see External references below); its drawn edges are the reverse ones from the external_contact_id of BOOKING_GUESTS, BOOKING_GUARANTOR_INFORMATIONS and BOOKING_CONTEXT_PARTICIPANTS, which match its unique_reference. CRM_CONTACT_RELATIONSHIPS is scoped through its source contact’s crm_contact_entities row and that row’s CRM namespace; TAGS_TAGS and TAGS_ENTITY_TAGS are scoped through the tag’s tags_containers row, which carries a crm_namespace_uuid. Neither crm_contact_entities nor tags_containers is served. CRM_NAMESPACES, LEAD_STATUSES and TASK_CATEGORIES are all tenant-scoped through a crm_namespace_uuid/workspace_id/task_category_id chain that never touches a column the WORKSPACES view itself serves (see the evidence table) — they connect to other served tables via documented-fk edges instead, not through WORKSPACES directly.

Source table Column(s) Discriminator Possible targets Evidence view
ADDRESSES addressable_id addressable_type PROPERTIES, BUILDINGS addresses.sql (two UNION branches)
AGREEMENTS_AGREEMENT_VERSIONS item_id item_type AGREEMENTS_AGREEMENTS agreements_agreement_versions.sql (PaperTrail)
AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATION_VERSIONS item_id item_type AGREEMENTS_AGREEMENT_UNIT_TYPE_ALLOCATIONS agreements_agreement_unit_type_allocation_versions.sql (PaperTrail)
INTERVAL_PRICING_CALENDARS bookable_id bookable_type PROPERTIES, MULTI_UNIT_GROUPS interval_pricing_calendars.sql (two LEFT JOIN branches)
OFFER_MANAGEMENT_OFFERS resource_owner_id resource_owner_type INQUIRIES (documented as always 'inquiry') offer_management_offers.sql
PROPERTIES tariff_ids (array) — (array of IDs, no discriminator) TARIFFS tariffs.sql (many-to-many via UNNEST)
BOOKING_CONTEXT_PARTICIPANTS booking_context_id — (shared key, no discriminator) BOOKINGS (every booking in the context) booking_context_participants.sql (CTE over bookings)
BOOKINGS_INSTALMENT_PLAN_TEMPLATES product_group_ids (array) — (array of IDs, no discriminator) CATALOG_PRODUCT_GROUPS bookings_instalment_plan_templates.sql (projected, not joined)
TAGS_ENTITY_TAGS ref (text) TAGS_TAGS.scope + TAGS_TAGS.entity (via tag_id) CATALOG_PRODUCT_GROUPS (catalog / product_group), BOOKINGS_INSTALMENT_PLAN_TEMPLATES (accounting / instalment_plan_template) tags_entity_tags.sql (projected, not joined)

External references (not served by this export)

Section titled “External references (not served by this export)”

Some *_id/*_uuid columns are real foreign keys, but their target table is not exported, so there is no entity in this diagram to draw an edge to. Documented here instead so they aren’t mistaken for gaps in the audit:

Source table Column Target (not served)
CRM_CONTACT_INFORMATIONS crm_contact_entity_id crm_contact_entities — the real join key between CRM namespaces and contact records; not exported.
AGREEMENTS_AGREEMENTS company_id An internal CRM company record; not exported.
CRM_COMPANIES crm_namespace_id The internal crm_namespaces.id, which the crm_namespaces view does not serve; join on crm_namespace_uuid instead.
TAGS_TAGS container_id tags_containers — one tag container per CRM namespace; synced for tenant scoping but not exported.

INVOICING_INVOICES.payload is a JSON blob, not a column-level FK, so it can’t appear in the evidence table below — but it embeds identifiers for other served (and unserved) entities, commonly including a booking ID, a booking-context ID, and guest-payment IDs. These are not queryable as columns and are not drawn as edges; if you need them, they must be extracted from the JSON payload itself.

One row per JOIN predicate found in each view’s SQL (scope-hop rows are tenant-filtering hops that do not touch the served table’s own alias and are not drawn as edges), plus one row per documented-fk column — a real foreign key confirmed by its sources.yml description rather than by a JOIN in this particular view’s SQL.

View Joined table Join predicate Class Note
addresses.sql properties t.addressable_id = p.id soft-ref UNION branch 1; addressable_type confirms target
addresses.sql workspaces p.workspace_id = ws.id scope-hop via properties, not addresses’ own column
addresses.sql buildings t.addressable_id = b.id soft-ref UNION branch 2; addressable_type confirms target
addresses.sql workspaces b.workspace_id = ws.id scope-hop via buildings
agreements_agreement_unit_type_allocation_versions.sql agreements_agreement_unit_type_allocations t.item_id = aaut.id soft-ref PaperTrail item_type/item_id, single confirmed target
agreements_agreement_unit_type_allocation_versions.sql agreements_agreements aaut.agreement_id = aa.id scope-hop via allocations, not own alias
agreements_agreement_unit_type_allocation_versions.sql workspaces aa.workspace_id = ws.id scope-hop via agreements_agreements
agreements_agreement_unit_type_allocations.sql agreements_agreements t.agreement_id = aa.id hard-fk own column, projected
agreements_agreement_unit_type_allocations.sql workspaces aa.workspace_id = ws.id scope-hop via agreements_agreements
agreements_agreement_versions.sql agreements_agreements t.item_id = aa.id soft-ref PaperTrail item_type/item_id, single confirmed target
agreements_agreement_versions.sql workspaces aa.workspace_id = ws.id scope-hop via agreements_agreements
agreements_agreements.sql workspaces t.workspace_id = ws.id tenant-direct own workspace_id column
availability_unit_availability_pool_allocations.sql properties p.id = t.unit_id hard-fk own column t.unit_id, projected
availability_unit_availability_pool_allocations.sql workspaces ws.id = p.workspace_id scope-hop via properties
billing_periods.sql bookings b.id = t.booking_id hard-fk own column, projected
billing_periods.sql properties p.id = b.property_id scope-hop via bookings; tenant filter is WHERE p.workspace_id IN (...), no workspaces join
booking_context_participants.sql bookings (via CTE) e.booking_context_id = t.booking_context_id soft-ref own column, projected; a context spans several bookings, so DISTINCT CTE
booking_context_participants.sql properties (nested in CTE) p.id = b.property_id scope-hop via bookings
booking_context_participants.sql workspaces (nested in CTE) ws.id = p.workspace_id scope-hop via properties
booking_contract_values.sql bookings b.id = t.booking_id hard-fk own column, projected
booking_contract_values.sql properties p.id = b.property_id scope-hop via bookings
booking_contract_values.sql workspaces ws.id = p.workspace_id scope-hop via properties
booking_daily_rates.sql bookings b.id = t.booking_id hard-fk own column, projected
booking_daily_rates.sql properties p.id = b.property_id scope-hop via bookings
booking_daily_rates.sql workspaces ws.id = p.workspace_id scope-hop via properties
booking_guarantor_informations.sql bookings b.id = t.booking_id hard-fk own column, projected
booking_guarantor_informations.sql properties p.id = b.property_id scope-hop via bookings
booking_guarantor_informations.sql workspaces ws.id = p.workspace_id scope-hop via properties
booking_guests.sql bookings b.id = t.booking_id hard-fk own column, projected
booking_guests.sql properties p.id = b.property_id scope-hop via bookings
booking_guests.sql workspaces ws.id = p.workspace_id scope-hop via properties
booking_rates.sql bookings b.id = t.booking_id hard-fk own column, projected
booking_rates.sql properties p.id = b.property_id scope-hop via bookings; WHERE p.workspace_id IN (...), no workspaces join
bookings.sql properties p.id = t.property_id hard-fk own column, projected
bookings.sql workspaces p.workspace_id = ws.id scope-hop via properties
bookings_instalment_plan_templates.sql workspaces CAST(ws.id AS VARCHAR) = t.workspace_id tenant-direct own workspace_id column (text in Abacus)
buildings.sql workspaces t.workspace_id = ws.id tenant-direct own workspace_id column
catalog_product_groups.sql workspaces t.workspace_id IN ({{ client_workspace_ids }}) tenant-direct WHERE filter on own column, no JOIN keyword
catalog_product_rates.sql catalog_products t.id IN (SELECT cp.product_rate_id …) hard-fk (reverse) confirms CATALOG_PRODUCTS.product_rate_id → this view
catalog_product_rates.sql catalog_product_groups pg.id = cp.product_group_id scope-hop inside the IN subquery tenant-scope chain
catalog_product_rates.sql workspaces ws.id = pg.workspace_id scope-hop inside the IN subquery tenant-scope chain
catalog_products.sql catalog_product_groups pg.id = t.product_group_id hard-fk own column, projected
catalog_products.sql workspaces ws.id = pg.workspace_id scope-hop via catalog_product_groups
catalog_rate_calendars.sql catalog_products t.product_rate_id IN (SELECT cp.product_rate_id …) hard-fk (transitive) own column t.product_rate_id; combined with catalog_product_rates.sql’s row confirms → CATALOG_PRODUCT_RATES
catalog_rate_calendars.sql catalog_product_groups pg.id = cp.product_group_id scope-hop inside the IN subquery tenant-scope chain
catalog_rate_calendars.sql workspaces ws.id = pg.workspace_id scope-hop inside the IN subquery tenant-scope chain
crm_companies.sql crm_namespaces (via CTE) e.id = t.crm_namespace_id hard-fk own column, projected; the CTE also projects cn.uuid AS crm_namespace_uuid
crm_companies.sql workspaces (nested in CTE) ws.crm_namespace_uuid = cn.uuid scope-hop tenant eligibility chain
crm_companies_contact_informations.sql crm_companies (via CTE) e.id = t.crm_company_id hard-fk own column, projected
crm_companies_contact_informations.sql crm_namespaces, workspaces (nested in CTE) cn.id = c.crm_namespace_id, ws.crm_namespace_uuid = cn.uuid scope-hop tenant eligibility chain
crm_companies_contact_informations.sql crm_contact_informations (documented-fk) sources.yml crm_contact_information_id: “Identifier of the contact; matches crm_contact_informations.id” documented-fk no JOIN in view SQL
crm_contact_informations.sql inquiries (via CTE) e.external_contact_id = t.unique_reference tenant-scope filters to contacts referenced by client leads; not a modeled parent edge
crm_contact_informations.sql workspaces (nested in CTE) ws.crm_namespace_uuid = i.crm_namespace_uuid scope-hop via inquiries CTE
crm_contact_informations.sql crm_contact_relationships (via CTE) pe.external_contact_id = sci.unique_reference, tci.id = r.target_contact_information_id tenant-scope also serves the related contacts of contacts eligible through the participant paths
crm_contact_relationships.sql crm_contact_informations sci.id = t.source_contact_information_id hard-fk own column, projected
crm_contact_relationships.sql crm_contact_entities, crm_namespaces, workspaces (nested in CTE) sce.id = sci.crm_contact_entity_id, e.id = sce.crm_namespace_id, ws.crm_namespace_uuid = cn.uuid scope-hop tenant eligibility chain; crm_contact_entities is not served
crm_contact_relationships.sql crm_contact_informations (documented-fk) sources.yml target_contact_information_id: “Identifier of the related contact (e.g. the emergency contact); matches crm_contact_informations.id” documented-fk no JOIN on the target in view SQL
crm_namespaces.sql workspaces (via CTE) e.crm_namespace_uuid = cn.uuid tenant-scope workspaces view doesn’t serve crm_namespace_uuid; no edge drawn
daily_revenues.sql properties p.id = t.property_id hard-fk own column, projected
daily_revenues.sql workspaces ws.id = p.workspace_id scope-hop via properties
finance_billing_cycles.sql bookings b.id = t.booking_id hard-fk own column, projected
finance_billing_cycles.sql properties p.id = b.property_id scope-hop via bookings
finance_billing_cycles.sql workspaces ws.id = p.workspace_id scope-hop via properties
guest_payments.sql bookings b.id = t.booking_id hard-fk own column, projected
guest_payments.sql properties p.id = b.property_id scope-hop via bookings
guest_payments.sql workspaces ws.id = p.workspace_id scope-hop via properties
inquiries.sql workspaces (via CTE) e.crm_namespace_uuid = t.crm_namespace_uuid tenant-scope own column, but namespace-based, not workspace_id; workspaces view doesn’t serve crm_namespace_uuid
inquiry_participants.sql inquiries (via CTE) e.inquiry_id = t.inquiry_id hard-fk own column, projected; CTE resolves to inquiries.id
inquiry_participants.sql workspaces (nested in CTE) ws.crm_namespace_uuid = i.crm_namespace_uuid scope-hop via inquiries
interval_pricing_calendars.sql properties t.bookable_type = 'Spaces::Property' AND t.bookable_id = p.id soft-ref LEFT JOIN branch 1
interval_pricing_calendars.sql multi_unit_groups t.bookable_type = 'Spaces::MultiUnitGroup' AND t.bookable_id = mug.id soft-ref LEFT JOIN branch 2
interval_pricing_calendars.sql workspaces ws.id = COALESCE(p.workspace_id, mug.workspace_id) scope-hop via whichever branch matched
invoice_list_items.sql workspaces ws.uuid = t.workspace_uuid tenant-direct own column, UUID variant of workspace_id
invoicing_invoices.sql invoice_list_items il.invoice_uuid = t.uuid hard-fk (reverse) confirms INVOICE_LIST_ITEMS.invoice_uuid → this view
invoicing_invoices.sql workspaces ws.uuid = il.workspace_uuid scope-hop via invoice_list_items
lead_statuses.sql workspaces (via CTE) e.crm_namespace_uuid = t.crm_namespace_uuid tenant-scope not the workspace_id pattern; no edge drawn
multi_unit_group_calendars.sql multi_unit_groups mug.id = t.multi_unit_group_id hard-fk own column, projected
multi_unit_group_calendars.sql workspaces mug.workspace_id = ws.id scope-hop via multi_unit_groups
multi_unit_groups.sql workspaces t.workspace_id IN ({{ client_workspace_ids }}) tenant-direct WHERE filter on own column, no JOIN keyword
offer_management_offer_accommodations.sql offer_management_offers (via CTE) e.offer_id = t.offer_id hard-fk own column, projected
offer_management_offer_accommodations.sql inquiries (nested in CTE) i.unique_reference = omo.resource_owner_id scope-hop tenant eligibility chain, not this view’s own alias
offer_management_offer_accommodations.sql workspaces (nested in CTE) ws.crm_namespace_uuid = i.crm_namespace_uuid scope-hop tenant eligibility chain
offer_management_offers.sql inquiries (via CTE) e.unique_reference = t.resource_owner_id soft-ref resource_owner_type/resource_owner_id pair; documented as always 'inquiry'
offer_management_offers.sql workspaces (nested in CTE) ws.crm_namespace_uuid = i.crm_namespace_uuid scope-hop tenant eligibility chain
properties.sql workspaces t.workspace_id = ws.id tenant-direct own workspace_id column
property_calendars.sql properties p.id = t.property_id hard-fk (excluded) table is DEPRECATED; not drawn
property_calendars.sql workspaces p.workspace_id = ws.id scope-hop (excluded) table is DEPRECATED; not drawn
report_ar_balances.sql workspaces t.workspace_id IN ({{ client_workspace_ids }}) tenant-direct own workspace_id column; no issued filter
report_ar_documents.sql workspaces t.workspace_id IN ({{ client_workspace_ids }}) tenant-direct own workspace_id column; issued documents only
tariffs.sql properties (via CTE, UNNEST(tariff_ids)) t._id = wti.tariff_id soft-ref many-to-many; PROPERTIES.tariff_ids → TARIFFS.id
tariffs.sql workspaces wti.workspace_id = ws.id scope-hop via properties CTE
tags_entity_tags.sql tags_tags tt.id = t.tag_id hard-fk own column, projected
tags_entity_tags.sql tags_containers, workspaces (nested in CTE) e.id = tt.container_id, ws.crm_namespace_uuid = tc.crm_namespace_uuid scope-hop tenant eligibility chain; tags_containers is not served
tags_tags.sql tags_containers, workspaces (nested in CTE) e.id = t.container_id, ws.crm_namespace_uuid = tc.crm_namespace_uuid scope-hop tenant eligibility chain; tags_containers is not served
task_categories.sql — — none no JOIN and no tenant filter — global lookup table
tasks.sql workspaces t.workspace_id = ws.id tenant-direct own workspace_id column
tickets.sql workspaces t.workspace_id = ws.id tenant-direct own workspace_id column
unit_calendars.sql properties p.id = t.unit_id hard-fk own column, projected
unit_calendars.sql workspaces p.workspace_id = ws.id scope-hop via properties
workspaces.sql workspaces (self) ws.id IN ({{ client_workspace_ids }}) tenant-direct root of the scoping chain
workspaces_inquiries.sql workspaces wi.workspace_id = ws.id tenant-direct own workspace_id column; inquiry_id has no confirming JOIN in any of the 50 views
properties.sql multi_unit_groups (documented-fk) sources.yml: “The ID of the unit type; each unit can either have an ID or not” documented-fk no JOIN in view SQL; optional (nullable)
properties.sql properties (self) (documented-fk) sources.yml parent_unit_id: “The parent unit ID for this unit” documented-fk self-referential; optional
properties.sql tickets (documented-fk) sources.yml onboarding_ticket_id: “The ID of the onboarding ticket for this unit” documented-fk no JOIN in view SQL; optional
report_ar_documents.sql bookings (documented-fk) sources.yml booking_id: “Identifier of the booking the document was issued against” documented-fk no JOIN in view SQL
agreements_agreement_unit_type_allocations.sql multi_unit_groups (documented-fk) sources.yml unit_type_id: “Unit type identifier” documented-fk no JOIN in view SQL
bookings.sql multi_unit_groups (documented-fk) sources.yml multi_unit_group_id: “The unit type ID for this booking” documented-fk no JOIN in view SQL; optional
bookings.sql catalog_products (documented-fk) sources.yml product_id: “The ID of the Sales Proposition product linked to this booking” documented-fk no JOIN in view SQL; optional
daily_revenues.sql bookings (documented-fk) sources.yml booking_id: “Identifier for the booking the revenue belongs to” documented-fk no JOIN in view SQL
booking_daily_rates.sql billing_periods (documented-fk) sources.yml billing_period_id: “Identifier for the billing period associated with the rate” documented-fk no JOIN in view SQL
booking_daily_rates.sql invoicing_invoices (documented-fk) sources.yml issued_invoice_uuid: “UUID of the issued invoice (if applicable)” documented-fk no JOIN in view SQL; optional
guest_payments.sql invoicing_invoices (documented-fk) sources.yml invoice_uuid: “A unique UUID for the invoice related to this payment” documented-fk no JOIN in view SQL
inquiries.sql lead_statuses (documented-fk) sources.yml lead_status_id: “This is the ID of the lead status, not the lead” documented-fk no JOIN in view SQL; optional
inquiries.sql crm_namespaces (documented-fk) sources.yml crm_namespace_uuid: “The UUID for the CRM namespace” documented-fk no JOIN in view SQL
interval_pricing_calendars.sql tariffs (documented-fk) sources.yml tariff_id: “Identifier of the associated tariff” documented-fk no JOIN in view SQL; distinct from the properties.tariff_ids many-to-many soft ref
invoice_list_items.sql bookings (documented-fk) sources.yml booking_id: “The ID of the booking” documented-fk no JOIN in view SQL
invoice_list_items.sql billing_periods (documented-fk) sources.yml billing_period_id: “The ID of the billing period” documented-fk no JOIN in view SQL
offer_management_offers.sql buildings (documented-fk) sources.yml building_id: “The ID of the building” documented-fk no JOIN in view SQL
lead_statuses.sql crm_namespaces (documented-fk) sources.yml crm_namespace_uuid: “UUID of the CRM namespace associated with the lead status” documented-fk no JOIN in view SQL; view SQL only joins workspaces for tenant scope
tasks.sql properties (documented-fk) sources.yml property_id: “The identifier of the associated unit” documented-fk no JOIN in view SQL; optional
tasks.sql tickets (documented-fk) sources.yml parent_ticket_id: “The identifier of the parent ticket, if applicable” documented-fk no JOIN in view SQL; optional
tasks.sql bookings (documented-fk) sources.yml booking_id: “The ID of the booking” documented-fk no JOIN in view SQL; optional
tasks.sql task_categories (documented-fk) sources.yml task_category_id: “The identifier of the task category to which the task belongs” documented-fk no JOIN in view SQL
tickets.sql properties (documented-fk) sources.yml property_id: “The ID of the unit” documented-fk no JOIN in view SQL
booking_guests.sql crm_contact_informations (documented-fk) sources.yml external_contact_id: “External contact reference; matches crm_contact_informations.unique_reference” documented-fk no JOIN in view SQL; crm_contact_informations.sql joins the reverse way
booking_guarantor_informations.sql crm_contact_informations (documented-fk) sources.yml external_contact_id: “External contact reference; matches crm_contact_informations.unique_reference” documented-fk no JOIN in view SQL
booking_context_participants.sql crm_contact_informations (documented-fk) sources.yml external_contact_id: “External contact reference; matches crm_contact_informations.unique_reference” documented-fk no JOIN in view SQL
unit_calendars.sql bookings (documented-fk) sources.yml booking_id: “Identifier of the associated booking, if applicable” documented-fk no JOIN in view SQL; optional
workspaces_inquiries.sql inquiries (documented-fk) sources.yml inquiry_id: “Identifier for the associated lead” documented-fk no JOIN in view SQL; supersedes the earlier “no confirming JOIN” note

property_calendars is already marked DEPRECATED in sources.yml — superseded by unit_calendars (availability/booking spans) and interval_pricing_calendars (pricing). It is omitted from the diagram entirely, not greyed out.

This change also ran the mechanical deprecation rules (a source with no matching view file; a projected column that is a SQL literal/NULL constant; a table whose full grain is superseded by two or more other served views) against all 39 views. None of the three rules fired on any table or column beyond the pre-existing property_calendars flag, so no further sources.yml edits were made in this change.

This diagram is hand-maintained — there is no CI drift guard for it. make check-client-views-sources (scripts/client_data/validate_views_sources.py) checks that every view file has a matching sources.yml entry and vice versa, but it does not validate this document’s diagram content. When a view’s SQL or sources.yml changes in a way that adds, removes, or re-parents a relationship, update this document in the same PR.