> For the complete documentation index, see [llms.txt](https://navixy.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://navixy.com/docs/analytics/id/iot-query/schema-overview/bronze-layer/bdr.md).

# Skema BDR

BDR mengorganisasi perangkat dan entitas bisnis ke dalam aset, dengan kolom isian kustom, relasi, dan jejak audit. Jelajahi definisi skema dan deskripsi tabel utama

{% hint style="warning" %}
**Skema ini saat ini sedang dikembangkan.** BDR (Repositori Data Bisnis) mengatur data berdasarkan aset: gabungan terpadu antara perangkat dan entitas bisnis, seperti kendaraan dan supirnya, atau perangkat dan sensornya. IoT Query membaca dari BDR untuk menampilkan data ini untuk analitik. Jika Anda tertarik pada akses awal atau memiliki pertanyaan tentang fungsi ini, silakan hubungi <iotquery@navixy.com>.
{% endhint %}

BDR menyediakan kerangka kerja komprehensif untuk mengelola struktur organisasi, aktor, perangkat, aset, dan relasinya di lingkungan multi-tenant. Dibangun di atas PostgreSQL 18 dengan `ltree` dan `pg_trgm` ekstensi tersebut, skema ini mendukung organisasi hierarkis, definisi kolom isian untuk setiap jenis entitas, kontrol akses peran berbasis aktor dengan pembatasan tingkat pelacak, penguncian optimistis untuk pengeditan bersamaan, dan jejak audit lengkap dengan pelacakan perubahan tingkat kolom. Semua entitas dapat diperluas tanpa modifikasi skema, dilokalkan untuk penerapan internasional, dan dihubungkan melalui relasi polimorfik yang fleksibel.

Skema ini menangani skenario pengelolaan data yang kompleks, termasuk hierarki aset armada di berbagai tingkat organisasi, platform SaaS multi-tenant yang memerlukan isolasi data, operasi berbasis kepatuhan dengan persyaratan audit yang terperinci, dan sistem yang memerlukan model data dinamis yang dapat disesuaikan melalui kolom isian, bukan migrasi basis data.

{% hint style="info" %}
Diagram interaktif skema data BDR tersedia di **dbdiagram.io**: <https://dbdiagram.io/d/Navixy-Repo-data-schema-68ad788c1e7a611967a0930e>
{% endhint %}

Temukan detail skema BDR di bawah ini.

{% code title="Skema data BDR" expandable="true" %}

```sql
// ============================================
// Skema BDR (Repositori Data Bisnis) - perjalanan pelanggan
// PostgreSQL 18 dengan ekstensi ltree, pg_trgm
// Versi: 2.2
// ============================================

// ============================================
// Tabel referensi dasar (hierarki ci_base - pewarisan tabel tunggal)
// ============================================

Table ci_base {
  id uuid [primary key, note: 'kode jenis entitas 4 karakter yang tertanam dalam byte UUID 5-8, diekstrak melalui uuid_type_code()']
  code text [not null, note: 'kode yang dapat dibaca mesin, harus diawali dengan huruf']
  title_en text [not null, note: 'kolasi tidak peka huruf besar-kecil (ICU)']
  description_en text
  order int [not null, default: 0]
  is_system boolean [not null, default: false]
  discriminator text [not null, note: 'diisi otomatis dari id melalui trg_ci_set_discriminator']
  catalog_id uuid [note: 'katalog induk untuk item katalog pengguna. NULL untuk katalog sistem dan definisi katalog itu sendiri']
  organization_id uuid
  parent_id uuid
  path ltree [note: 'dipelihara otomatis oleh trigger ketika is_hierarchical = true']
  is_hierarchical boolean [not null, default: false]
  extra jsonb [not null, default: `{}`]
  version int [not null, default: 1, note: 'penguncian optimistis']
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz

  indexes {
    (id) [name: 'idx_ci_active', note: 'parsial: WHERE deleted_at IS NULL']
    (parent_id) [name: 'idx_ci_parent']
    (path) [type: gist, name: 'idx_ci_path_gist']
    (catalog_id) [name: 'idx_ci_catalog']
    (organization_id) [name: 'idx_ci_org']
    (discriminator) [name: 'idx_ci_discriminator']
    (title_en) [name: 'idx_ci_title_en']
    (discriminator, code) [unique, name: 'uq_ci_code_discriminator']
    (catalog_id, code) [unique, name: 'uq_ci_code_catalog']
  }
}

Table ci_module {
  id uuid [primary key]
}

Table ci_catalog_category {
  id uuid [primary key]
}

Table ci_country {
  id uuid [primary key]
}

Table ci_role {
  id uuid [primary key]
}

Tabel ci_entity_type {
  id uuid [primary key]
  uuid_discriminator char(4) [tidak null, catatan: 'kode 4 karakter yang disematkan dalam UUID entitas']
  is_customizable boolean [tidak null, default: true, catatan: 'Apakah entitas jenis ini mendukung Kolom isian']
}

Tabel ci_device_status {
  id uuid [primary key]
}

Tabel ci_permission_scope {
  id uuid [primary key]
  module_id uuid [tidak null]
  entity_type_id uuid [tidak null]
  category text [tidak null]
}

Tabel ci_device_vendor {
  id uuid [kunci utama, catatan: 'Produsen/pemasok perangkat']
}

Tabel ci_device_model {
  id uuid [primary key]
  vendor_id uuid [tidak null]
}

Tabel ci_device_type {
  id uuid [primary key]
}

Tabel ci_asset_type {
  id uuid [primary key, note: 'Hierarkis melalui ci_base parent_id/path']
}

Tabel ci_geo_pelacak_type {
  id uuid [primary key]
}

Tabel ci_schedule_type {
  id uuid [primary key]
}

Tabel ci_asset_group_type {
  id uuid [primary key]
}

Tabel Aset_Kelompok_type_to_Aset_type_relation {
  id uuid [primary key]
  kelompok_type_id uuid [tidak null]
  asset_type_id uuid [tidak null]
  max_items int [catatan: 'Maks. aset jenis ini dalam Kelompok. NULL = tidak terbatas']

  indexes {
    (group_type_id) [nama: 'idx_agt_at_rel_group']
    (asset_type_id) [nama: 'idx_agt_at_rel_asset']
    (Kelompok_type_id, Aset_type_id) [unik]
  }
}

Tabel ci_device_relation_type {
  id uuid [primary key]
}

Tabel ci_tag {
  id uuid [primary key]
  entity_type_id uuid [catatan: 'Jenis entitas yang berlaku. NULL = tag universal']
}

Tabel ci_user_catalog_item {
  id uuid [kunci utama, catatan: 'Elemen katalog yang ditentukan pengguna']
}

Tabel ci_catalog {
  id uuid [kunci primer, catatan: 'Definisi katalog. Katalog itu sendiri adalah item katalog']
  organization_id uuid [tidak null]
  module_id uuid [tidak null]
  category_id uuid
  fields_schema jsonb [catatan: 'Skema JSON untuk validasi item.extra dan UI']

  indexes {
    (organization_id) [name: 'idx_ci_catalog_org']
    (module_id) [name: 'idx_ci_catalog_module']
  }
}

Tabel ci_custom_field_definition {
  id uuid [primary key]
  owner_catalog_item_id uuid [tidak null, catatan: 'Pemilik: entity_type (sistem) atau tipe tertentu seperti asset_type']
  target_entity_type_id uuid [tidak null]
  field_type teks [tidak null, catatan: 'STRING, TEXT, NUMBER, BOOLEAN, DATE, DATETIME, GEOJSON, SCHEDULE, OPTIONS, DEVICE, REFERENCE, CATALOG, TAG']
  is_required boolean [tidak null, default: false]
  params jsonb [catatan: 'Parameter khusus tipe (min/maks, opsi, ref, dll.)']

  indexes {
    (Pemilik_catalog_item_id) [nama: 'idx_ci_cfd_Pemilik']
    (target_entity_type_id) [name: 'idx_ci_cfd_target']
  }
}

// ============================================
// ENTITAS INTI
// ============================================

Organisasi tabel {
  id uuid [primary key]
  parent_id uuid
  jalur ltree
  teks kode [tidak null]
  title_en teks [tidak null]
  external_id text [catatan: 'Pengenal sistem eksternal untuk integrasi']
  is_active boolean [tidak null, nilai bawaan: true]
  is_dealer boolean [tidak null, nilai bawaan: false, catatan: 'Apakah organisasi dapat membuat organisasi turunan']
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (parent_id) [nama: 'idx_org_parent']
    (path) [tipe: gist, nama: 'idx_org_path_gist']
    (code) [unik, nama: 'uq_org_code']
    (external_id) [nama: 'idx_org_external_id']
    (title_en) [nama: 'idx_org_title', catatan: 'DDL sumber menargetkan kolom title yang tidak ada, diperbaiki di sini menjadi title_en']
  }
}

Tabel actor {
  id uuid [kunci primer, catatan: 'Entitas abstrak untuk pengguna dan integrasi. Basis untuk ACL']
  actor_type text [tidak null, catatan: 'USER, INTEGRATION, atau SYSTEM']
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (actor_type) [nama: 'idx_actor_type']
  }
}

Tabel user {
  id uuid [primary key]
  identity_provider text [tidak null, nilai bawaan: 'keycloak']
  identity_provider_id uuid [tidak null]
  full_name text [tidak null]
  external_id text
  is_active boolean [tidak null, nilai bawaan: true]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (identity_provider, identity_provider_id) [unik, nama: 'uq_user_idp']
    (external_id) [nama: 'idx_user_external_id']
  }
}

Tabel member {
  id uuid [kunci primer, catatan: 'Keanggotaan pengguna dalam organisasi']
  user_id uuid [tidak null]
  organization_id uuid [tidak null]
  is_active boolean [tidak null, nilai bawaan: true]
  custom_fields_data jsonb [tidak null, nilai bawaan: `{}`, catatan: 'Kolom isian anggota (jabatan, departemen, dll.)']
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  assigned_at timestamptz [tidak null, nilai bawaan: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (user_id) [nama: 'idx_member_user']
    (organization_id) [nama: 'idx_member_org']
    (user_id, organization_id) [unik, nama: 'uq_member_user_org']
  }
}

Tabel integration {
  id uuid [kunci primer, catatan: 'Aktor integrasi sistem eksternal']
  name text [tidak null]
  credential_ref text [catatan: 'Referensi ke kredensial di brankas aman']
  is_active boolean [tidak null, nilai bawaan: true]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid
}

// ============================================
// KONTROL AKSES (ACL)
// ============================================

Tabel actor_role {
  id uuid [primary key]
  actor_id uuid [tidak null]
  role_id uuid [tidak null]
  assigned_at timestamptz [tidak null, nilai bawaan: `CURRENT_TIMESTAMP`]
  assigned_by uuid
  expire_date timestamptz [catatan: 'NULL = permanen']

  indexes {
    (actor_id) [nama: 'idx_actor_role_actor']
    (role_id) [nama: 'idx_actor_role_role']
    (actor_id, role_id) [unik]
  }
}

Tabel acl_role_permission {
  id uuid [primary key]
  role_id uuid [tidak null]
  permission_scope_id uuid [tidak null]
  target_entity_id uuid [catatan: 'Entitas tertentu. NULL = semua entitas dari tipe tersebut. Polimorfik, tanpa FK']
  actions int [tidak null, catatan: 'Bitmask: READ=1, CREATE=2, UPDATE=4, DELETE=8']
  granted_at timestamptz [tidak null, nilai bawaan: `CURRENT_TIMESTAMP`]
  granted_by uuid

  indexes {
    (role_id) [nama: 'idx_acl_role_perm_role']
    (permission_scope_id) [nama: 'idx_acl_role_perm_scope']
    (role_id, permission_scope_id, target_entity_id) [unique]
  }
}

Tabel acl_user_scope {
  id uuid [primary key, note: 'Filter daftar putih. Kosong = akses peran penuh. Terisi = irisan dengan peran']
  actor_id uuid [tidak null]
  permission_scope_id uuid [tidak null]
  target_entity_id uuid [not null]
  actions int [not null]

  indexes {
    (actor_id, permission_scope_id) [name: 'idx_acl_user_scope_actor']
    (actor_id, permission_scope_id, target_entity_id) [unique]
  }
}

// ============================================
// ENTITAS BISNIS
// ============================================

Tabel device {
  id uuid [primary key]
  organization_id uuid [tidak null]
  device_type_id uuid [not null]
  model_id uuid [catatan: 'Opsional']
  status_id uuid [tidak null]
  title text [tidak null]
  custom_fields_data jsonb [tidak null, default: `{}`]
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [nama: 'idx_device_org']
    (device_type_id) [nama: 'idx_device_type']
    (model_id) [nama: 'idx_device_model']
    (status_id) [nama: 'idx_device_status']
    (title) [jenis: gin, nama: 'idx_device_title_trgm', catatan: 'gin_trgm_ops untuk pencarian fuzzy']
  }
}

Tabel device_identifier {
  id uuid [primary key]
  device_id uuid [tidak null]
  type text [tidak null, catatan: 'UUID, IMEI, MEID_HEX, MEID_DEC, MAC_ADDRESS, SERIAL_NUMBER, CUSTOM']
  value text [tidak null]
  namespace text [catatan: 'Cakupan keunikan. NULL = global']
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]

  indexes {
    (device_id) [nama: 'idx_device_identifier_device']
    (value) [nama: 'idx_device_identifier_value']
    (type, value) [unik, nama: 'uq_device_identifier_global', catatan: 'parsial: WHERE namespace IS NULL']
    (type, value, namespace) [unik, nama: 'uq_device_identifier_namespaced', catatan: 'parsial: WHERE namespace IS NOT NULL']
  }
}

Tabel device_relation {
  id uuid [primary key]
  first_id uuid [tidak null]
  second_id uuid [tidak null]
  relation_type_id uuid [tidak null, catatan: 'Batasan pemeriksaan: first_id <> second_id']

  indexes {
    (first_id) [name: 'idx_device_relation_first']
    (second_id) [name: 'idx_device_relation_second']
    (first_id, second_id, relation_type_id) [unik]
  }
}

Tabel inventory {
  id uuid [primary key]
  organization_id uuid [tidak null]
  title text [tidak null]
  teks kode [tidak null]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [name: 'idx_inventory_org']
    (organization_id, code) [unik, name: 'uq_inventory_org_code']
  }
}

Tabel device_inventory_relation {
  id uuid [primary key]
  device_id uuid [tidak null]
  inventory_id uuid [tidak null]
  assigned_at timestamptz [tidak null, nilai bawaan: `CURRENT_TIMESTAMP`]
  assigned_by uuid
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (device_id) [unik, nama: 'uq_device_inventory_active', catatan: 'parsial: WHERE deleted_at IS NULL - satu inventaris aktif per perangkat']
    (inventory_id) [name: 'idx_device_inventory_rel_inventory']
  }
}

Tabel Aset {
  id uuid [primary key]
  organization_id uuid [tidak null]
  asset_type_id uuid [tidak null]
  title text [tidak null]
  custom_fields_data jsonb [tidak null, default: `{}`]
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [nama: 'idx_asset_org']
    (id_tipe_aset) [nama: 'idx_tipe_aset']
    (judul) [type: gin, name: 'idx_asset_title_trgm']
  }
}

Tabel aset_kelompok {
  id uuid [primary key]
  organization_id uuid [tidak null]
  kelompok_type_id uuid [tidak null]
  title_en teks [tidak null]
  warna teks
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [nama: 'idx_aset_kelompok_org']
    (id_tipe_kelompok) [nama: 'indeks_Aset_Kelompok_tipe']
  }
}

Tabel aset_kelompok_item {
  id uuid [primary key]
  kelompok_id uuid [tidak null]
  Aset_id uuid [tidak boleh null]
  attached_at timestamptz [tidak null, default: `CURRENT_TIMESTAMP`]
  detached_at timestamptz [catatan: 'NULL = saat ini terpasang']

  indexes {
    (kelompok_id) [name: 'idx_aset_kelompok_item_kelompok']
    (Aset_id) [name: 'idx_Aset_Kelompok_item_Aset']
    (Kelompok_id, Aset_id, detached_at) [unik]
  }
}

Tabel geo_object {
  id uuid [kunci utama, catatan: 'Geofence, titik minat, rute']
  organization_id uuid [tidak null]
  geo_object_type_id uuid [tidak boleh null]
  title text [tidak null]
  custom_fields_data jsonb [tidak null, default: `{}`, catatan: 'Mencakup geometri geojson']
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [nama: 'idx_geo_pelacak_org']
    (geo_object_type_id) [nama: 'idx_geo_object_type']
    (judul) [jenis: gin, nama: 'idx_geo_object_title_trgm']
  }
}

Jadwal tabel {
  id uuid [primary key]
  organization_id uuid [tidak null]
  schedule_type_id uuid [tidak null]
  title text [tidak null]
  custom_fields_data jsonb [not null, default: `{}`, note: 'Data penjadwalan itu sendiri berada di sini']
  version int [tidak null, nilai bawaan: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [nama: 'idx_schedule_org']
    (schedule_type_id) [nama: 'idx_schedule_type']
    (title) [type: gin, name: 'idx_schedule_title_trgm']
  }
}

// ============================================
// Lokalisasi
// ============================================

Tabel i18n_text {
  entity_id uuid [pk]
  field_code text [pk]
  locale text [pk]
  value text [tidak null]

  indexes {
    (entity_id) [name: 'idx_i18n_entity']
    (locale) [name: 'idx_i18n_locale']
    (value) [type: gin, name: 'idx_i18n_text_trgm']
  }
}

// ============================================
// Kolom isian - nilai (tabel cache, satu per field_type)
// ============================================

Tabel custom_field_value_text {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value text [tidak null]

  indexes {
    (field_definition_id, value) [name: 'idx_cfv_text_value']
  }
}

Tabel custom_field_value_number {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value numeric [not null]
}

Tabel custom_field_value_boolean {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value boolean [not null]
}

Tabel custom_field_value_date {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value date [not null]
}

Tabel custom_field_value_datetime {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value timestamptz [tidak null]
}

Tabel custom_field_value_geojson {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value jsonb [tidak null]
}

Tabel custom_field_value_schedule {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value jsonb [tidak null]
}

Tabel custom_field_value_option {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_item_id uuid [tidak null, catatan: 'Merujuk ke ci_user_catalog_item']
}

Tabel custom_field_value_device {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_device_id uuid [tidak null]
}

Tabel custom_field_value_entity {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_entity_id uuid [not null, note: 'Referensi polimorfik, tanpa FK']
}

Tabel custom_field_value_catalog {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_item_id uuid [not null, note: 'Merujuk ke ci_base']
}

Tabel custom_field_value_tag {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_tag_id uuid [not null]
}

// ============================================
// AUDIT
// ============================================

Enum source_type {
  WEB
  MOBILE
  API
  INTERNAL
  INTEGRASI
}

Tabel audit_event {
  id uuid [tidak null]
  organization_id uuid
  event_category text [tidak null, catatan: 'auth atau domain']
  actor_id uuid
  ip_address inet
  user_agent text
  source_type source_type [tidak null, bawaan: 'API']
  source_name text [catatan: 'Nama aplikasi sumber']
  trace_id uuid [catatan: 'ID pelacakan terdistribusi untuk korelasi log']
  aggregate_type teks [catatan: 'Jenis entitas: perangkat, Aset, pengguna, dll.']
  aggregate_id uuid [catatan: 'Polimorfik, tanpa FK']
  event_type teks [tidak null, catatan: 'CREATED, UPDATED, DELETED, RESTORED, ROLE_ASSIGNED, dll.']
  event_data jsonb [catatan: 'Muatan dengan delta changed_fields']
  occurred_at timestamptz [tidak null, default: `CURRENT_TIMESTAMP`]

  Catatan: 'Dipartisi berdasarkan RANGE (occurred_at), partisi bulanan, dibuat otomatis melalui create_audit_partition_if_needed()'

  indexes {
    (id, occurred_at) [pk]
    (actor_id, occurred_at) [name: 'idx_audit_event_actor']
    (aggregate_type, aggregate_id, occurred_at) [name: 'idx_audit_event_aggregate']
    (event_category, occurred_at) [name: 'idx_audit_event_category']
    (event_type, occurred_at) [name: 'idx_audit_event_type']
    (organization_id, occurred_at) [name: 'idx_audit_event_org']
    (Trail_id) [nama: 'idx_audit_event_Trail']
  }
}

// ============================================
// HUBUNGAN
// ============================================

Rujukan: ci_base.catalog_id > ci_catalog.id
Referensi: ci_base.organization_id > organization.id
Referensi: ci_base.parent_id > ci_base.id

Referensi: ci_module.id - ci_base.id
Referensi: ci_catalog_category.id - ci_base.id
Rujukan: ci_country.id - ci_base.id
Referensi: ci_role.id - ci_base.id
Ruj: ci_entity_type.id - ci_base.id
Referensi: ci_device_status.id - ci_base.id
Ruj: ci_permission_scope.id - ci_base.id
Referensi: ci_device_vendor.id - ci_base.id
Referensi: ci_device_model.id - ci_base.id
Ruj: ci_device_type.id - ci_base.id
Ref: ci_Aset_type.id - ci_base.id
Ref: ci_geo_object_type.id - ci_base.id
Referensi: ci_schedule_type.id - ci_base.id
Ref: ci_aset_kelompok_type.id - ci_base.id
Referensi: ci_device_relation_type.id - ci_base.id
Rujukan: ci_tag.id - ci_base.id
Referensi: ci_user_catalog_item.id - ci_base.id
Referensi: ci_catalog.id - ci_base.id
Ruj: ci_custom_field_definition.id - ci_base.id

Rujukan: ci_device_model.vendor_id > ci_device_vendor.id
Ruj: ci_permission_scope.module_id > ci_module.id
Rujukan: ci_permission_scope.entity_type_id > ci_entity_type.id
Ref: asset_group_type_to_asset_type_relation.group_type_id > ci_asset_group_type.id
Ref: asset_group_type_to_asset_type_relation.asset_type_id > ci_asset_type.id
Ref: ci_tag.entity_type_id > ci_entity_type.id
Ruj: ci_catalog.organization_id > organization.id
Referensi: ci_catalog.module_id > ci_module.id
Referensi: ci_catalog.category_id > ci_catalog_category.id
Ref: ci_custom_field_definition.owner_catalog_item_id > ci_base.id
Rujukan: ci_custom_field_definition.target_entity_type_id > ci_entity_type.id

Rujukan: organization.parent_id > organization.id
Ruj: organization.deleted_by > actor.id

Rujukan: actor.deleted_by > actor.id

Rujukan: user.id - actor.id
Rujukan: user.deleted_by > actor.id

Rujukan: member.user_id > user.id
Rujukan: member.organization_id > organization.id
Rujukan: member.deleted_by > actor.id

Rujukan: integration.id - actor.id
Rujukan: integration.deleted_by > actor.id

Rujukan: actor_role.actor_id > actor.id
Rujukan: actor_role.role_id > ci_role.id
Ref: actor_role.assigned_by > actor.id

Ref: acl_role_permission.role_id > ci_role.id
Ref: acl_role_permission.permission_scope_id > ci_permission_scope.id
Ref: acl_role_permission.granted_by > actor.id

Ref: acl_user_scope.actor_id > actor.id
Ref: acl_user_scope.permission_scope_id > ci_permission_scope.id

Ref: device.organization_id > organization.id
Ref: device.device_type_id > ci_device_type.id
Ref: device.model_id > ci_device_model.id
Ref: device.status_id > ci_device_status.id
Ref: device.deleted_by > actor.id

Ref: device_identifier.device_id > device.id

Ref: device_relation.first_id > device.id
Ref: device_relation.second_id > device.id
Ref: device_relation.relation_type_id > ci_device_relation_type.id

Ref: inventory.organization_id > organization.id
Ref: inventory.deleted_by > actor.id

Ref: device_inventory_relation.device_id > device.id
Ref: device_inventory_relation.inventory_id > inventory.id
Ref: device_inventory_relation.assigned_by > actor.id
Referensi: device_inventory_relation.deleted_by > actor.id

Referensi: Aset.organization_id > organization.id
Ref: asset.asset_type_id > ci_asset_type.id
Referensi: Aset.deleted_by > actor.id

Ref: asset_group.organization_id > organization.id
Ruj: aset_kelompok.kelompok_type_id > ci_aset_kelompok_type.id
Ruj: aset_kelompok.deleted_by > actor.id

Ref: asset_group_item.group_id > asset_group.id
Ref: asset_group_item.asset_id > asset.id

Ref: geo_object.organization_id > organization.id
Ref: geo_object.geo_object_type_id > ci_geo_object_type.id
Ref: geo_object.deleted_by > actor.id

Rujukan: schedule.organization_id > organization.id
Ruj: schedule.schedule_type_id > ci_schedule_type.id
Referensi: schedule.deleted_by > actor.id

Rujukan: custom_field_value_text.field_definition_id > ci_custom_field_definition.id
Rujukan: custom_field_value_number.field_definition_id > ci_custom_field_definition.id
Rujukan: custom_field_value_boolean.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_date.field_definition_id > ci_custom_field_definition.id
Rujukan: custom_field_value_datetime.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_geojson.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_schedule.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_option.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_option.ref_item_id > ci_user_catalog_item.id
Referensi: custom_field_value_device.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_device.ref_device_id > device.id
Referensi: custom_field_value_entity.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_catalog.field_definition_id > ci_custom_field_definition.id
Referensi: custom_field_value_catalog.ref_item_id > ci_base.id
Referensi: custom_field_value_tag.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_tag.ref_tag_id > ci_tag.id

Ref: audit_event.organization_id > organization.id
Ref: audit_event.actor_id > actor.id
```

{% endcode %}

## Frekuensi pembaruan

Data dalam BDR disinkronkan secara waktu nyata dengan sistem sumber. Pembaruan terjadi segera saat perubahan berlangsung, dengan jejak audit yang menangkap semua modifikasi untuk kepatuhan dan analisis historis.

## `ci_base`

BDR menggunakan sebuah **Pewarisan satu tabel** pola untuk semua data referensi melalui `ci_base` tabel. Desain ini mengonsolidasikan kamus sistem, klasifikasi, dan item referensi yang ditentukan pengguna ke dalam satu struktur terpadu, sehingga memberikan konsistensi dan fleksibilitas di seluruh skema.

**Arsitektur:**

Ini `ci_base` tabel berfungsi sebagai fondasi bagi semua data referensi, menggunakan sebuah `pembeda` kolom untuk mengidentifikasi jenis referensi tertentu. Setiap jenis referensi memiliki tabel yang sesuai (seperti `ci_device_type`, `ci_aset_type`) yang berbagi id yang sama `id` sebagai `ci_base`, sehingga menciptakan hubungan pewarisan yang aman secara tipe. Itu `pembeda` tidak diatur secara manual. Sebuah pemicu menurunkannya secara otomatis dari kode tipe 4 karakter yang disematkan dalam UUID entitas, dan gagal saat penyisipan jika kode tipe UUID tidak dikenali.

Hierarki (misalnya, tipe Aset atau Tagar bertingkat) ditangani langsung pada `ci_base` melalui kolomnya sendiri `parent_id`/`path` kolom-kolom, bukan melalui tabel kategori terpisah. Satu `is_hierarchical` penanda ini mengaktifkannya untuk setiap item katalog.

**Bagaimana entitas bisnis terhubung ke ci\_base:**

Semua entitas bisnis dalam BDR mereferensikan `ci_base` subtipe untuk menentukan klasifikasi dan perilakunya:

* `organisasi` dan `pengguna`/`anggota` adalah *bukan* ditentukan melalui `ci_base`, karena organisasi dan pengguna adalah entitas inti yang berdiri sendiri, bukan diklasifikasikan oleh item katalog
* `perangkat` → referensi `ci_device_type`, secara opsional `ci_device_model` (yang pada gilirannya merujuk ke `ci_device_vendor`), dan `ci_device_status`
* `Aset` → referensi `ci_aset_type`
* `aset_kelompok` → referensi `ci_aset_kelompok_tipe`, dengan tipe anggota yang diizinkan dibatasi melalui `relasi tipe kelompok aset ke tipe aset`
* `objek geospasial` → referensi `tipe objek geospasial ci`
* `jadwal` → referensi `tipe jadwal ci`
* `inventaris` tidak memiliki tipe item katalog sendiri

**Kategori tipe referensi:**

| Kategori                   | Tabel                                                                                                                     | Tujuan                                                                                              |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Konfigurasi sistem**     | `ci_module`, `ci_country`, `ci_role`                                                                                      | Definisikan modul sistem, referensi geografis, dan peran pengguna                                   |
| **Definisi jenis entitas** | `ci_entity_type`, `ci_device_type`, `ci_aset_type`, `tipe objek geospasial ci`, `tipe jadwal ci`, `ci_aset_kelompok_tipe` | Klasifikasikan semua entitas bisnis berdasarkan jenis                                               |
| **Katalog perangkat**      | `ci_device_vendor`, `ci_device_model`, `ci_device_status`                                                                 | Model perangkat berdasarkan produsen, model, dan status saat ini                                    |
| **Kontrol akses**          | `ci_permission_scope`                                                                                                     | Menentukan izin yang dapat diberikan (terhubung ke `ci_module` dan `ci_entity_type`)                |
| **Relasi**                 | `ci_device_relation_type`                                                                                                 | Menentukan jenis relasi antara perangkat (master-slave, cadangan, dll.)                             |
| **Kategorisasi**           | `ci_tag`, `ci_catalog_category`, `ci_user_catalog_item`                                                                   | Memungkinkan penandaan fleksibel dan katalog yang ditentukan pengguna                               |
| **Katalog / Kolom isian**  | `ci_catalog`, `ci_custom_field_definition`                                                                                | Katalog dari katalog dan metadata Kolom isian, keduanya dimodelkan sebagai item katalog itu sendiri |

<details>

<summary><strong>Contoh pola kueri</strong></summary>

```sql
-- Dapatkan semua jenis perangkat untuk suatu organisasi (sistem + kustom)
SELECT cb.id, cb.code, cb.title_en, cb.is_system
FROM bdr.ci_base cb
JOIN bdr.ci_device_type dt ON dt.id = cb.id
WHERE cb.discriminator = 'device_type'
  AND (cb.is_system = true OR cb.organization_id = $org_id)
  AND cb.deleted_at IS NULL;

-- Dapatkan model perangkat beserta pemasoknya
SELECT
  cb.code as model_code,
  cb.title_en as model_name,
  vendor_cb.title_en as vendor_name
FROM bdr.ci_base cb
JOIN bdr.ci_device_model dm ON dm.id = cb.id
JOIN bdr.ci_device_vendor v ON v.id = dm.vendor_id
JOIN bdr.ci_base vendor_cb ON vendor_cb.id = v.id
WHERE cb.discriminator = 'device_model'
  AND cb.deleted_at IS NULL;

-- Dapatkan struktur tag hierarkis
SELECT cb.id, cb.code, cb.title_en, cb.path, cb.parent_id
FROM bdr.ci_base cb
JOIN bdr.ci_tag t ON t.id = cb.id
WHERE cb.discriminator = 'tag'
  AND cb.deleted_at IS NULL
ORDER BY cb.path;
```

</details>

## Tabel utama berdasarkan kategori

Tabel dalam BDR diorganisasikan ke dalam kategori fungsional. Deskripsi di bawah merangkum tabel yang paling penting berdasarkan tujuan bisnisnya.

<details>

<summary><code>organisasi</code></summary>

**Tujuan:** Manajemen organisasi hierarkis

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>parent_id</code>, <code>path</code>, <code>code</code>, <code>title_en</code>, <code>is_active</code>, <code>is_dealer</code>, <code>version</code>, <code>deleted_at</code></td></tr><tr><td><strong>Pengindeksan</strong></td><td>Indeks GiST pada <code>path</code> untuk kueri hierarkis, indeks unik pada <code>code</code>, indeks pada <code>parent_id</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Menggunakan ltree untuk hierarki multitingkat. <code>is_dealer</code> memberikan hak untuk membuat organisasi turunan. <code>version</code> mendukung penguncian optimistis</td></tr></tbody></table>

</details>

<details>

<summary><code>aktor</code></summary>

**Tujuan:** Abstraksi yang dibagikan oleh setiap prinsipal ACL: pengguna, integrasi, dan aktor sistem di masa mendatang

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>actor_type</code> (<code>USER</code>, <code>INTEGRASI</code>, <code>SYSTEM</code>), <code>version</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Semua tabel ACL (<code>actor_role</code>, <code>acl_role_permission</code>, <code>acl_user_scope</code>) dan setiap <code>deleted_by</code> bergantung pada <code>actor_id</code>, bukan <code>user_id</code>, sehingga integrasi juga dapat memiliki peran dan menghapus catatan</td></tr></tbody></table>

</details>

<details>

<summary><code>pengguna</code> / <code>anggota</code></summary>

**Tujuan:** Akun pengguna dan keanggotaan organisasinya

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>pengguna</code>: <code>id</code> (dibagikan dengan <code>aktor</code>), <code>identity_provider</code>, <code>identity_provider_id</code>, <code>full_name</code>. <code>anggota</code>: <code>user_id</code>, <code>organization_id</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Pengindeksan</strong></td><td>Indeks unik pada (<code>identity_provider</code>, <code>identity_provider_id</code>). Indeks unik pada (<code>user_id</code>, <code>organization_id</code>) dalam <code>anggota</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Keanggotaan organisasi seorang pengguna merupakan baris terpisah dalam <code>anggota</code>, sehingga satu pengguna dapat menjadi anggota beberapa organisasi. Pengguna melakukan autentikasi melalui penyedia identitas eksternal (Keycloak secara bawaan)</td></tr></tbody></table>

</details>

<details>

<summary><code>integrasi</code></summary>

**Tujuan:** Sistem eksternal yang bertindak pada platform (misalnya melalui Kunci API)

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code> (dibagikan dengan <code>aktor</code>), <code>nama</code>, <code>credential_ref</code>, <code>is_active</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td><code>credential_ref</code> mengarah ke vault eksternal yang aman, sehingga tidak ada kredensial yang disimpan dalam tabel ini</td></tr></tbody></table>

</details>

<details>

<summary><code>perangkat</code> / <code>device_identifier</code></summary>

**Tujuan:** Perangkat pelacak fisik dan pengidentifikasi perangkat kerasnya

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>perangkat</code>: <code>id</code>, <code>organization_id</code>, <code>device_type_id</code>, <code>model_id</code>, <code>status_id</code>, <code>judul</code>, <code>custom_fields_data</code>, <code>version</code>. <code>device_identifier</code>: <code>device_id</code>, <code>tipe</code>, <code>nilai</code>, <code>ruang nama</code></td></tr><tr><td><strong>Pengindeksan</strong></td><td>Indeks trigram GIN pada <code>judul</code> untuk pencarian fuzzy. Indeks unik pada (<code>tipe</code>, <code>nilai</code>) untuk pengenal global, atau (<code>tipe</code>, <code>nilai</code>, <code>ruang nama</code>) saat diberi namespace</td></tr><tr><td><strong>Catatan khusus</strong></td><td>Sebuah perangkat dapat memiliki beberapa pengenal (IMEI, MAC, nomor seri, dll.) alih-alih satu kolom ID perangkat keras. Kolom isian berada langsung di <code>custom_fields_data</code> pada baris perangkat</td></tr></tbody></table>

</details>

<details>

<summary><code>Aset</code></summary>

**Tujuan:** Aset fisik atau virtual

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>organization_id</code>, <code>Aset_type_id</code>, <code>judul</code>, <code>custom_fields_data</code>, <code>version</code></td></tr><tr><td><strong>Pengindeksan</strong></td><td>Indeks pada <code>organization_id</code> dan <code>Aset_type_id</code>. indeks trigram GIN pada <code>judul</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Kolom isian disimpan langsung pada baris, bukan melalui tabel entitas yang dapat dikustomisasi terpisah</td></tr></tbody></table>

</details>

<details>

<summary><code>inventaris</code> / <code>relasi inventaris perangkat</code></summary>

**Tujuan:** Catatan inventaris dan gudang, serta penugasan perangkat ke inventaris

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>inventaris</code>: <code>id</code>, <code>organization_id</code>, <code>judul</code>, <code>code</code>. <code>relasi inventaris perangkat</code>: <code>device_id</code>, <code>inventory_id</code>, <code>assigned_at</code>, <code>deleted_at</code></td></tr><tr><td><strong>Pengindeksan</strong></td><td>Indeks unik pada (<code>organization_id</code>, <code>code</code>). Indeks unik parsial memastikan sebuah perangkat memiliki paling banyak satu penugasan inventaris aktif</td></tr><tr><td><strong>Catatan khusus</strong></td><td>Penugasan dapat dihapus secara lunak, sehingga riwayat inventaris perangkat tetap dipertahankan</td></tr></tbody></table>

</details>

<details>

<summary><code>objek geospasial</code></summary>

**Tujuan:** Geofence, titik minat, dan rute

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>organization_id</code>, <code>geo_object_type_id</code>, <code>judul</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Geometri (sebelumnya didedikasikan <code>geojson</code> kolom) sekarang disimpan sebagai kolom khusus di dalam <code>custom_fields_data</code></td></tr></tbody></table>

</details>

<details>

<summary><code>jadwal</code></summary>

**Tujuan:** Jadwal yang dapat digunakan kembali yang dirujuk oleh entitas lain

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>organization_id</code>, <code>schedule_type_id</code>, <code>judul</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Data jadwal itu sendiri (aturan pengulangan, jendela waktu) disimpan di <code>custom_fields_data</code></td></tr></tbody></table>

</details>

<details>

<summary><code>aset_kelompok</code></summary>

**Tujuan:** Pengelompokan Aset dengan pelacakan historis

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>organization_id</code>, <code>kelompok_type_id</code>, <code>title_en</code>, <code>warna</code></td></tr><tr><td><strong>Relasi</strong></td><td><code>FROM bdr.asset_group AS ag JOIN bdr.asset_group_item AS agi ON agi.group_id = ag.id JOIN bdr.asset AS a ON a.id = agi.asset_id WHERE agi.detached_at IS NULL</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Keanggotaan berbasis waktu melalui <code>Aset_kelompok_item</code>, kueri anggota saat ini dengan <code>WHERE detached_at IS NULL</code>. Jenis anggota yang diizinkan per jenis Kelompok dibatasi melalui <code>relasi tipe kelompok aset ke tipe aset</code></td></tr></tbody></table>

</details>

<details>

<summary><code>ci_custom_field_definition</code></summary>

**Tujuan:** Definisi bidang kustom dan metadata, dimodelkan sebagai item katalog

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code> (dibagikan dengan <code>ci_base</code>), <code>Pemilik_catalog_item_id</code>, <code>id_jenis_entitas_target</code>, <code>field_type</code>, <code>wajib_diisi</code>, <code>parameter</code></td></tr><tr><td><strong>Konten</strong></td><td>12 jenis kolom: <code>STRING</code>, <code>TEXT</code>, <code>NUMBER</code>, <code>BOOLEAN</code>, <code>DATE</code>, <code>DATETIME</code>, <code>GEOJSON</code>, <code>Jadwal</code>, <code>Opsi</code>, <code>Perangkat</code>, <code>Referensi</code>, <code>Katalog</code>, <code>Tag</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Nilai disimpan baik sebagai sumber kebenaran (<code>custom_fields_data</code> JSONB pada entitas pemilik) dan, untuk setiap tipe, dalam sebuah <code>custom_field_value_*</code> tabel cache untuk penyaringan/pengurutan. <code>Opsi</code> adalah pembungkus untuk <code>Katalog</code>, dan setiap <code>Opsi</code> kolom mendapatkan katalog pengguna tersembunyi sendiri di balik layar</td></tr></tbody></table>

</details>

<details>

<summary><code>acl_role_permission</code></summary>

**Tujuan:** Manajemen izin berbasis peran

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>role_id</code>, <code>permission_scope_id</code>, <code>target_entity_id</code>, <code>actions</code></td></tr><tr><td><strong>Konten</strong></td><td>Mask bit aksi (READ=1, CREATE=2, UPDATE=4, DELETE=8), izin khusus target atau berlaku untuk seluruh tipe entitas</td></tr><tr><td><strong>Relasi</strong></td><td><code>FROM bdr.actor_role AS ar JOIN bdr.acl_role_permission AS rp ON rp.role_id = ar.role_id WHERE ar.actor_id = $actor_id</code></td></tr><tr><td><strong>Catatan khusus</strong></td><td>Izin efektif = izin peran ∩ <code>acl_user_scope</code>. Seorang aktor <code>acl_user_scope</code> adalah filter daftar putih: kosong berarti akses peran tidak dibatasi, terisi berarti akses dipersempit ke objek yang tercantum</td></tr></tbody></table>

</details>

<details>

<summary><code>audit_event</code></summary>

**Tujuan:** Log audit terpadu untuk semua perubahan sistem

<table><thead><tr><th width="139">Atribut</th><th>Rincian</th></tr></thead><tbody><tr><td><strong>Kolom kunci</strong></td><td><code>id</code>, <code>organization_id</code>, <code>event_category</code>, <code>actor_id</code>, <code>tipe_sumber</code>, <code>trail_id</code>, <code>aggregate_type</code>, <code>aggregate_id</code>, <code>jenis_peristiwa</code>, <code>data_peristiwa</code>, <code>terjadi_pada</code></td></tr><tr><td><strong>Pengindeksan</strong></td><td>Indeks pada (<code>actor_id</code>, <code>terjadi_pada</code>), (<code>aggregate_type</code>, <code>aggregate_id</code>, <code>terjadi_pada</code>), (<code>event_category</code>, <code>terjadi_pada</code>), (<code>organization_id</code>, <code>terjadi_pada</code>)</td></tr><tr><td><strong>Catatan khusus</strong></td><td>Dipartisi secara fisik berdasarkan <code>terjadi_pada</code> (partisi rentang bulanan, dibuat otomatis). <code>event_category</code> adalah <code>autentikasi</code> (LOGIN, LOGOUT, FAILED_LOGIN, PASSWORD_RESET, SESSION_EXPIRED) atau <code>domain</code> (CREATED, UPDATED, DELETED, RESTORED, ROLE_ASSIGNED, ROLE_REVOKED, PERMISSION_GRANTED, PERMISSION_REVOKED, LINKED, UNLINKED, ATTACHED, DETACHED). <code>tipe_sumber</code> dan <code>trail_id</code> mendukung pelacakan terdistribusi, dan tabel menyimpan delta perubahan di tingkat kolom dalam <code>data_peristiwa</code> JSONB</td></tr></tbody></table>

</details>

## Relasi data

BDR menerapkan pola relasi canggih untuk pemodelan data yang fleksibel:

**Struktur hierarkis**

* Organisasi menggunakan path ltree untuk kueri pohon yang efisien
* Item referensi (`ci_base`) mendukung hierarki opsional melalui miliknya sendiri `parent_id`/`path`, dibatasi oleh `is_hierarchical`
* Pemeliharaan jalur otomatis melalui pemicu basis data

**Pola pewarisan**

* Pewarisan ID: `ci_base` → tabel tipe referensi (`ci_device_type`, `ci_aset_type`, dan lainnya), dan `aktor` → `pengguna` dan `integrasi`
* Kolom isian dilampirkan per entitas melalui sebuah `custom_fields_data` kolom JSONB langsung pada setiap tabel bisnis (`perangkat`, `Aset`, `objek geospasial`, `jadwal`), alih-alih melalui tabel entitas dasar bersama
* Pembedaan tipe melalui `pembeda` kolom pada `ci_base`, diturunkan secara otomatis dari UUID entitas

**Penguncian optimistis**

Entitas bisnis dan `ci_base` baris memuat sebuah `version` integer. Pembaruan menggunakan `SET version = version + 1 WHERE id = $id AND version = $expected_version`. Nol baris yang terpengaruh menandakan konflik versi, sehingga klien dapat mendeteksi dan menyelesaikan pengeditan serentak tanpa kunci tingkat basis data. `audit_event` dan tabel lain yang hanya-tambah atau dioperasikan secara massal tidak menggunakan pola ini.

**Relasi polimorfik**

Beberapa tabel menggunakan referensi polimorfik tanpa kendala kunci asing untuk fleksibilitas maksimal:

* `acl_role_permission.target_entity_id` dan `acl_user_scope.target_entity_id` → entitas bisnis apa pun
* `audit_event.aggregate_id` → entitas bisnis apa pun, dipasangkan dengan `aggregate_type`
* `custom_field_value_entity.ref_entity_id` → entitas bisnis apa pun

Relasi ini divalidasi pada tingkat aplikasi.

## Informasi tambahan

### Validasi data

BDR menegakkan integritas data melalui beberapa mekanisme:

**Kendala basis data**

* Kendala UNIQUE dengan dukungan penghapusan lunak (indeks parsial WHERE `deleted_at` IS NULL)
* Kendala CHECK (misalnya, `device_relation` memastikan `first_id` ≠ `second_id`, dan `ci_base.code` harus diawali dengan huruf)
* Kendala NOT NULL pada kolom yang wajib diisi
* Nilai DEFAULT untuk stempel waktu, boolean, dan kolom JSONB

**Validasi pada tingkat aplikasi**

* Validasi tipe entitas untuk referensi polimorfik
* Validasi katalog untuk referensi kolom isian
* Validasi tipe kolom isian
* Pemeriksaan versi penguncian optimistis saat pembaruan

### Optimasi kueri

Tabel diatur dengan strategi pengindeksan khusus:

**Indeks standar:**

* Semua foreign key memiliki indeks khusus
* Indeks berbasis waktu pada `created_at`, `updated_at`, `deleted_at`
* Indeks gabungan untuk kolom yang sering digabungkan

**Indeks khusus:**

* Indeks GiST pada path ltree untuk kueri hierarkis
* Indeks unik parsial yang mendukung soft delete
* Indeks GIN trigram (`pg_trgm`) untuk pencarian kabur pada `judul`/`title_en` dan teks terjemahan
* Indeks nilai field kustom untuk pemfilteran dan pengurutan
* Indeks peristiwa audit pada waktu + entitas untuk pencarian yang efisien

**Pertimbangan performa:**

* Pooling koneksi disarankan (PgBouncer)
* Pemeliharaan VACUUM rutin untuk tabel besar
* `audit_event` dipartisi secara fisik per bulan, sehingga partisi yang lebih lama dapat dipisahkan atau diarsipkan secara independen
* View terwujud untuk perhitungan kontrol akses yang kompleks


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://navixy.com/docs/analytics/id/iot-query/schema-overview/bronze-layer/bdr.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
