> 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/fr/iot-query/schema-overview/bronze-layer/bdr.md).

# schéma BDR

BDR organise les appareils et les entités métier en actifs, avec des Champs personnalisés, des relations et des pistes d’audit. Parcourez la définition du schéma et les descriptions des tables clés

{% hint style="warning" %}
**Ce schéma est actuellement en développement.** BDR (Business Data Repository) organise les données autour des Actif : une combinaison unifiée d’un appareil et d’une entité métier, comme un véhicule et son Conducteur, ou un appareil et ses capteurs. IoT Query lit les données depuis BDR pour les exposer à des fins d’analyse. Si vous souhaitez bénéficier d’un accès anticipé ou si vous avez des questions sur cette fonctionnalité, veuillez contacter <iotquery@navixy.com>.
{% endhint %}

BDR fournit un cadre complet pour gérer les structures organisationnelles, les acteurs, les appareils, les actifs et leurs relations dans des environnements multi-locataires. Construit sur PostgreSQL 18 avec les `ltree` et `pg_trgm` extensions, le schéma prend en charge des organisations hiérarchiques, des définitions de Champs personnalisés pour tout type d’entité, un contrôle d’accès par Modèle d'accès piloté par les acteurs avec des restrictions au niveau de l’objet, le verrouillage optimiste pour les modifications simultanées et des pistes d’audit complètes avec Suivi des changements au niveau des champs. Toutes les entités peuvent être étendues sans modification du schéma, localisées pour des déploiements internationaux et reliées via des relations polymorphiques flexibles.

Le schéma répond à des scénarios complexes de gestion des données, notamment des hiérarchies d’Actif de Flotte à travers les niveaux organisationnels, des plateformes SaaS multi-locataires nécessitant l’isolation des données, des opérations soumises à la conformité avec des exigences détaillées en matière d’audit, et des systèmes nécessitant des modèles de données dynamiques adaptables via des Champs personnalisés plutôt que par des migrations de base de données.

{% hint style="info" %}
Le diagramme interactif du schéma de données BDR est disponible sur **dbdiagram.io**: <https://dbdiagram.io/d/Navixy-Repo-data-schema-68ad788c1e7a611967a0930e>
{% endhint %}

Vous trouverez ci-dessous les détails du schéma BDR.

{% code title="schéma de données BDR" expandable="true" %}

```sql
// ============================================
// Schéma BDR (Business Data Repository) - Parcours client
// PostgreSQL 18 avec les extensions ltree, pg_trgm
// Version : 2.2
// ============================================

// ============================================
// TABLES DE RÉFÉRENCE DE BASE (hiérarchie ci_base - héritage par table unique)
// ============================================

Table ci_base {
  id uuid [primary key, note: 'code de type d'entité à 4 caractères intégré dans les octets 5-8 du UUID, extrait via uuid_type_code()']
  code text [not null, note: 'Code lisible par machine, doit commencer par une lettre']
  title_en texte [non null, note : 'Collation insensible à la casse (ICU)']
  description_en texte
  order entier [non null, défaut : 0]
  is_system booléen [non null, défaut : false]
  discriminator texte [non null, note : 'Renseigné automatiquement à partir de l’id via trg_ci_set_discriminator']
  catalog_id uuid [note : 'Catalogue parent pour les éléments de catalogue utilisateur. NULL pour les catalogues système et les définitions de catalogue elles-mêmes']
  organization_id uuid
  parent_id uuid
  path ltree [note : 'Maintenu automatiquement par le déclencheur lorsque is_hierarchical = true']
  is_hierarchical booléen [non null, défaut : false]
  extra jsonb [non nul, valeur par défaut : `{}`]
  version int [non nul, valeur par défaut : 1, note : 'Verrouillage optimiste']
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz

  indices {
    (id) [name: 'idx_ci_active', note: 'partiel : 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) [nom : 'idx_ci_org']
    (discriminator) [nom : 'idx_ci_discriminator']
    (title_en) [nom : 'idx_ci_title_en']
    (discriminator, code) [unique, nom : 'uq_ci_code_discriminator']
    (catalog_id, code) [unique, nom : 'uq_ci_code_catalog']
  }
}

Table ci_module {
  id uuid [clé primaire]
}

Table ci_catalog_category {
  id uuid [clé primaire]
}

Table ci_country {
  id uuid [clé primaire]
}

Table ci_Modèle d'accès {
  id uuid [clé primaire]
}

Table ci_entity_type {
  id uuid [clé primaire]
  uuid_discriminator char(4) [not null, note: 'code à 4 caractères intégré dans les UUID des entités']
  is_customizable boolean [not null, default: true, note: 'Indique si les entités de ce type prennent en charge des Champs personnalisés']
}

Table ci_device_status {
  id uuid [clé primaire]
}

Table ci_permission_scope {
  id uuid [clé primaire]
  module_id uuid [not null]
  entity_type_id uuid [not null]
  category text [not null]
}

Table ci_device_vendor {
  id uuid [primary key, note: 'Fabricants/fournisseurs de dispositifs']
}

Table ci_device_model {
  id uuid [clé primaire]
  vendor_id uuid [non nul]
}

Table ci_device_type {
  id uuid [clé primaire]
}

Table ci_Actif_type {
  id uuid [clé primaire, note: 'Hiérarchique via ci_base parent_id/path']
}

Table ci_geo_Traceur_type {
  id uuid [clé primaire]
}

Table ci_schedule_type {
  id uuid [clé primaire]
}

Table ci_Actif_Groupe_type {
  id uuid [clé primaire]
}

Table Actif_Groupe_type_to_Actif_type_relation {
  id uuid [clé primaire]
  Groupe_type_id uuid [non nul]
  identifiant de type d’Actif uuid [pas null]
  max_items int [note: 'Nombre maximal d’actifs de ce type dans le Groupe. NULL = illimité']

  indices {
    (group_type_id) [nom : 'idx_agt_at_rel_group']
    (asset_type_id) [name: 'idx_agt_at_rel_asset']
    (Groupe_type_id, Actif_type_id) [unique]
  }
}

Table ci_device_relation_type {
  id uuid [clé primaire]
}

Table ci_Mots clés {
  id uuid [clé primaire]
  entity_type_id uuid [note : 'Type d’entité applicable. NULL = Mots clés universels']
}

Table ci_user_catalog_item {
  id uuid [clé primaire, note: 'Éléments des catalogues définis par l’utilisateur']
}

Table ci_catalog {
  id uuid [clé primaire, note : 'Définitions du catalogue. Les catalogues sont eux-mêmes des éléments de catalogue']
  organization_id uuid [non null]
  module_id uuid [not null]
  id_catégorie uuid
  fields_schema jsonb [note: 'Schéma JSON pour la validation et l’interface utilisateur de item.extra']

  indices {
    (organization_id) [nom : 'idx_ci_catalog_org']
    (module_id) [nom : 'idx_ci_catalog_module']
  }
}

Table ci_custom_field_definition {
  id uuid [clé primaire]
  propriétaire_catalog_item_id uuid [non nul, note : 'Propriétaire : entity_type (system) ou un type spécifique tel que Actif_type']
  target_entity_type_id uuid [non null]
  type_de_champ texte [non nul, note: 'CHAÎNE, TEXTE, NOMBRE, BOOLÉEN, DATE, DATE_HEURE, GEOJSON, PLANIFICATION, OPTIONS, APPAREIL, RÉFÉRENCE, CATALOGUE, Mots clés']
  est_requis booléen [non nul, par défaut: false]
  paramètres jsonb [note: 'Paramètres spécifiques au type (min/max, options, réf., etc.)']

  indices {
    (Propriétaire_catalog_item_id) [nom: 'idx_ci_cfd_Propriétaire']
    (type_d'entité_cible_id) [nom: 'idx_ci_cfd_cible']
  }
}

// ============================================
// ENTITÉS DE BASE
// ============================================

Table organisation {
  id uuid [clé primaire]
  parent_id uuid
  chemin ltree
  code texte [non nul]
  titre_en texte [non nul]
  external_id texte [note : 'Identifiant du système externe pour les intégrations']
  is_active booléen [non null, valeur par défaut : true]
  is_dealer booléen [non null, valeur par défaut : false, note : 'Indique si l’organisation peut créer des organisations enfants']
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (parent_id) [name: 'idx_org_parent']
    (path) [type: gist, name: 'idx_org_path_gist']
    (code) [unique, name: 'uq_org_code']
    (external_id) [name: 'idx_org_external_id']
    (title_en) [name: 'idx_org_title', note: 'Le DDL source cible une colonne title inexistante, corrigée ici en title_en']
  }
}

Table actor {
  id uuid [clé primaire, note : 'Entité abstraite pour les utilisateurs et les intégrations. Base pour l'ACL']
  actor_type text [non nul, note : 'USER, INTEGRATION, ou SYSTEM']
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (actor_type) [name: 'idx_actor_type']
  }
}

Table user {
  id uuid [clé primaire]
  identity_provider text [non nul, default: 'keycloak']
  identity_provider_id uuid [non nul]
  full_name text [non nul]
  external_id text
  is_active booléen [non null, valeur par défaut : true]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (identity_provider, identity_provider_id) [unique, name: 'uq_user_idp']
    (external_id) [name: 'idx_user_external_id']
  }
}

Table membre {
  id uuid [clé primaire, note: 'Appartenance d’un utilisateur à une organisation']
  user_id uuid [non nul]
  organization_id uuid [non null]
  is_active booléen [non null, valeur par défaut : true]
  custom_fields_data jsonb [non nul, default: `{}`, note: 'Champs personnalisés du membre (poste, département, etc.)']
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  assigned_at timestamptz [non nul, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (user_id) [name: 'idx_member_user']
    (organization_id) [name: 'idx_member_org']
    (user_id, organization_id) [unique, name: 'uq_member_user_org']
  }
}

Table intégration {
  id uuid [clé primaire, note : 'acteur d’intégration de système externe']
  nom texte [non nul]
  credential_ref texte [note : 'Référence aux identifiants dans un coffre-fort sécurisé']
  is_active booléen [non null, valeur par défaut : true]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid
}

// ============================================
// CONTRÔLE D’ACCÈS (ACL)
// ============================================

Table acteur_modèle_d'accès {
  id uuid [clé primaire]
  actor_id uuid [non nul]
  role_id uuid [non nul]
  assigned_at timestamptz [non nul, default: `CURRENT_TIMESTAMP`]
  assigné par uuid
  expire_date timestamptz [note : 'NULL = permanent']

  indices {
    (actor_id) [name: 'idx_actor_Modèle d'accès_actor']
    (role_id) [nom : 'idx_actor_role_role']
    (actor_id, rôle_id) [unique]
  }
}

Schéma acl_role_permission {
  id uuid [clé primaire]
  role_id uuid [non nul]
  permission_scope_id uuid [non null]
  target_entity_id uuid [note: 'Entité spécifique. NULL = toutes les entités de ce type. Polymorphe, sans clé étrangère']
  actions int [not null, note: 'Masque binaire : READ=1, CREATE=2, UPDATE=4, DELETE=8']
  granted_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  uuid accordé par

  indices {
    (Modèle d'accès) [nom : 'idx_acl_role_perm_role']
    (permission_scope_id) [nom: 'idx_acl_role_perm_scope']
    (Modèle d'accès_id, permission_scope_id, target_entity_id) [unique]
  }
}

Table acl_user_scope {
  id uuid [clé primaire, note : 'Filtre de liste blanche. Vide = accès complet au Modèle d'accès. Rempli = intersection avec les Modèles d'accès']
  actor_id uuid [non nul]
  permission_scope_id uuid [non null]
  target_entity_id uuid [non nul]
  actions int [non nul]

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

// ============================================
// ENTITÉS COMMERCIALES
// ============================================

Périphérique de table {
  id uuid [clé primaire]
  organization_id uuid [non null]
  device_type_id uuid [non nul]
  model_id uuid [note : 'Facultatif']
  status_id uuid [not null]
  title text [not null]
  custom_fields_data jsonb [not null, default: `{}`]
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (organization_id) [name: 'idx_device_org']
    (device_type_id) [name: 'idx_device_type']
    (model_id) [name: 'idx_device_model']
    (status_id) [name: 'idx_device_status']
    (title) [type: gin, name: 'idx_device_title_trgm', note: 'gin_trgm_ops for fuzzy search']
  }
}

Table device_identifier {
  id uuid [clé primaire]
  device_id uuid [non null]
  type texte [non null, note: 'UUID, IMEI, MEID_HEX, MEID_DEC, MAC_ADDRESS, SERIAL_NUMBER, CUSTOM']
  value texte [non null]
  namespace texte [note: 'Portée d’unicité. NULL = global']
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]

  indices {
    (device_id) [name: 'idx_device_identifier_device']
    (value) [name: 'idx_device_identifier_value']
    (type, value) [unique, name: 'uq_device_identifier_global', note: 'partiel : WHERE namespace IS NULL']
    (type, value, namespace) [unique, name: 'uq_device_identifier_namespaced', note: 'partiel : WHERE namespace IS NOT NULL']
  }
}

Table device_relation {
  id uuid [clé primaire]
  first_id uuid [non null]
  second_id uuid [non nul]
  relation_type_id uuid [non nul, note: 'Contrainte de vérification : first_id <> second_id']

  indices {
    (first_id) [name: 'idx_device_relation_first']
    (second_id) [name: 'idx_device_relation_second']
    (first_id, second_id, relation_type_id) [unique]
  }
}

Table inventory {
  id uuid [clé primaire]
  organization_id uuid [non null]
  title text [not null]
  code texte [non nul]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (organization_id) [name: 'idx_inventory_org']
    (organization_id, code) [unique, name: 'uq_inventory_org_code']
  }
}

Table device_inventory_relation {
  id uuid [clé primaire]
  device_id uuid [non null]
  inventory_id uuid [non nul]
  assigned_at timestamptz [non nul, default: `CURRENT_TIMESTAMP`]
  assigné par uuid
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (device_id) [unique, name: 'uq_device_inventory_active', note: 'partiel : WHERE deleted_at IS NULL - un inventaire actif par appareil']
    (inventory_id) [name: 'idx_device_inventory_rel_inventory']
  }
}

Table Actif {
  id uuid [clé primaire]
  organization_id uuid [non null]
  identifiant de type d’Actif uuid [pas null]
  title text [not null]
  custom_fields_data jsonb [not null, default: `{}`]
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (organization_id) [name: 'idx_Actif_org']
    (Actif_type_id) [name: 'idx_Actif_type']
    (title) [type: gin, name: 'idx_Actif_title_trgm']
  }
}

Table Actif_Groupe {
  id uuid [clé primaire]
  organization_id uuid [non null]
  Groupe_type_id uuid [non nul]
  titre_en texte [non nul]
  texte de couleur
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (organization_id) [name: 'idx_Actif_Groupe_org']
    (Groupe_type_id) [name: 'idx_Actif_Groupe_type']
  }
}

Table Actif_Groupe_item {
  id uuid [clé primaire]
  Groupe_id uuid [non nul]
  Actif_id uuid [non nul]
  attaché_le timestamptz [non nul, défaut: `CURRENT_TIMESTAMP`]
  détaché_le timestamptz [note: 'NULL = actuellement attaché']

  indices {
    (Groupe_id) [name: 'idx_Actif_Groupe_item_Groupe']
    (Actif_id) [name: 'idx_Actif_Groupe_item_Actif']
    (Groupe_id, Actif_id, détaché_le) [unique]
  }
}

Table geo_Traceur {
  id uuid [clé primaire, note: 'Zones de géorepérage, points d'intérêt, itinéraires']
  organization_id uuid [non null]
  geo_Traceur_type_id uuid [non nul]
  title text [not null]
  Champs personnalisés_data jsonb [non nul, défaut: `{}`, note: 'Inclut la géométrie GeoJSON']
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (organization_id) [name: 'idx_geo_Traceur_org']
    (geo_Traceur_type_id) [name: 'idx_geo_Traceur_type']
    (title) [type: gin, name: 'idx_geo_Traceur_title_trgm']
  }
}

Table planning {
  id uuid [clé primaire]
  organization_id uuid [non null]
  type_de_planning_id uuid [non nul]
  title text [not null]
  Champs personnalisés_data jsonb [non nul, défaut: `{}`, note: 'Les données du planning se trouvent ici']
  version entier [non null, valeur par défaut : 1]
  created_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  updated_at timestamptz [non nul, valeur par défaut : `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indices {
    (organization_id) [name: 'idx_planning_org']
    (type_de_planning_id) [name: 'idx_planning_type']
    (title) [type: gin, name: 'idx_planning_title_trgm']
  }
}

// ============================================
// LOCALISATION
// ============================================

Table i18n_text {
  entity_id uuid [clé primaire]
  field_code text [clé primaire]
  locale text [clé primaire]
  value texte [non null]

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

// ============================================
// Champs personnalisés - VALEURS (tables de cache, une par type_de_champ)
// ============================================

Table valeur_champ_personnalisé_texte {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  value texte [non null]

  indices {
    (id_définition_champ, valeur) [name: 'idx_cfv_texte_valeur']
  }
}

Table valeur_champ_personnalisé_nombre {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  valeur numeric [non nul]
}

Table valeur_champ_personnalisé_booléen {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  valeur boolean [non nul]
}

Table valeur_champ_personnalisé_date {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  valeur date [non nul]
}

Table valeur_champ_personnalisé_date_heure {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  valeur timestamptz [non nul]
}

Table valeur_champ_personnalisé_geojson {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  valeur jsonb [non nul]
}

Table valeur_champ_personnalisé_planning {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  valeur jsonb [non nul]
}

Table valeur_champ_personnalisé_option {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  ref_item_id uuid [non nul, note: 'Référence à ci_user_catalog_item']
}

Table valeur_champ_personnalisé_appareil {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  ref_device_id uuid [non nul]
}

Table valeur_champ_personnalisé_entité {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  ref_entity_id uuid [non nul, note : 'Référence polymorphe, sans clé étrangère']
}

Table custom_field_value_catalog {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  ref_item_id uuid [non nul, note : 'Références ci_base']
}

Table custom_field_value_Mots clés {
  entity_id uuid [clé primaire]
  id_définition_champ uuid [clé primaire]
  index_de_valeur smallint [clé primaire, défaut: 0]
  ref_Mots clés_id uuid [non nul]
}

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

Énum source_type {
  WEB
  MOBILE
  API
  INTERNE
  INTÉGRATION
}

Table audit_event {
  id uuid [not null]
  organization_id uuid
  event_category text [not null, note: 'auth ou domaine']
  actor_id uuid
  ip_address inet
  user_agent text
  source_type source_type [not null, default: 'API']
  source_name text [note: 'Nom de l'application source']
  trace_id uuid [note : 'ID de traçage distribué pour la corrélation des journaux']
  aggregate_type text [note : 'Type d’entité : appareil, actif, utilisateur, etc.']
  aggregate_id uuid [note : 'Polymorphe, aucune clé étrangère']
  event_type text [non nul, note : 'CRÉÉ, MIS À JOUR, SUPPRIMÉ, RESTAURÉ, MODÈLE D'ACCÈS_ATTRIBUÉ, etc.']
  event_data jsonb [note : 'Charge utile avec delta changed_fields']
  occurred_at timestamptz [non nul, par défaut : `CURRENT_TIMESTAMP`]

  Remarque : 'Partitionné PAR PLAGE (occurred_at), partitions mensuelles, créées automatiquement via create_audit_partition_if_needed()'

  indices {
    (id, occurred_at) [clé primaire]
    (actor_id, occurred_at) [nom : 'idx_audit_event_actor']
    (aggregate_type, aggregate_id, occurred_at) [nom : 'idx_audit_event_aggregate']
    (event_category, occurred_at) [nom : 'idx_audit_event_category']
    (event_type, occurred_at) [nom : 'idx_audit_event_type']
    (organization_id, occurred_at) [nom : 'idx_audit_event_org']
    (trace_id) [nom : 'idx_audit_event_trace']
  }
}

// ============================================
// RELATIONS
// ============================================

Réf. : ci_base.catalog_id > ci_catalog.id
Réf. : ci_base.organization_id > organization.id
Réf. : ci_base.parent_id > ci_base.id

Réf. : ci_module.id - ci_base.id
Réf. : ci_catalog_category.id - ci_base.id
Réf. : ci_country.id - ci_base.id
Réf. : ci_Modèle d'accès.id - ci_base.id
Réf. : ci_entity_type.id - ci_base.id
Réf. : ci_device_status.id - ci_base.id
Réf. : ci_permission_scope.id - ci_base.id
Réf. : ci_device_vendor.id - ci_base.id
Réf. : ci_device_model.id - ci_base.id
Réf. : ci_device_type.id - ci_base.id
Réf. : ci_Actif_type.id - ci_base.id
Réf. : ci_geo_Traceur_type.id - ci_base.id
Réf. : ci_schedule_type.id - ci_base.id
Réf. : ci_actif_groupe_type.id - ci_base.id
Réf. : ci_device_relation_type.id - ci_base.id
Réf. : ci_Mots clés.id - ci_base.id
Réf. : ci_user_catalog_item.id - ci_base.id
Réf. : ci_catalog.id - ci_base.id
Réf. : ci_custom_field_definition.id - ci_base.id

Réf. : ci_device_model.vendor_id > ci_device_vendor.id
Réf : ci_permission_scope.module_id > ci_module.id
Réf. : ci_permission_scope.entity_type_id > ci_entity_type.id
Réf. : Actif_Groupe_type_to_Actif_type_relation.Groupe_type_id > ci_Actif_Groupe_type.id
Réf. : actif_groupe_type_to_actif_type_relation.actif_type_id > ci_actif_type.id
Réf. : ci_tag.entity_type_id > ci_entity_type.id
Réf. : ci_catalog.organization_id > organization.id
Réf. : ci_catalog.module_id > ci_module.id
Réf. : ci_catalog.category_id > ci_catalog_category.id
Réf. : ci_custom_field_definition.propriétaire_catalog_item_id > ci_base.id
Réf : ci_custom_field_definition.target_entity_type_id > ci_entity_type.id

Réf. : organization.parent_id > organization.id
Réf. : organization.deleted_by > actor.id

Réf : actor.deleted_by > actor.id

Réf. : user.id - actor.id
Réf. : user.deleted_by > actor.id

Réf. : member.user_id > user.id
Réf. : member.organization_id > organization.id
Réf. : member.deleted_by > actor.id

Réf : integration.id - actor.id
Réf. : integration.deleted_by > actor.id

Réf. : actor_Modèle d'accès.actor_id > actor.id
Réf. : Modèle d'accès.id > ci_role.id
Réf : Modèle d'accès.assigned_by > actor.id

Réf : Modèle d'accès.permission.role_id > ci_role.id
Réf : acl_role_permission.permission_scope_id > ci_permission_scope.id
Réf. : acl_role_permission.granted_by > actor.id

Réf. : acl_user_scope.actor_id > actor.id
Réf. : acl_user_scope.permission_scope_id > ci_permission_scope.id

Réf : device.organization_id > organization.id
Réf. : device.device_type_id > ci_device_type.id
Réf. : device.model_id > ci_device_model.id
Réf. : device.status_id > ci_device_status.id
Réf. : device.deleted_by > actor.id

Réf. : device_identifier.device_id > device.id

Réf. : device_relation.first_id > device.id
Réf. : device_relation.second_id > device.id
Réf. : device_relation.relation_type_id > ci_device_relation_type.id

Réf. : inventory.organization_id > organization.id
Réf. : inventory.deleted_by > actor.id

Réf. : device_inventory_relation.device_id > device.id
Réf. : device_inventory_relation.inventory_id > inventory.id
Réf. : device_inventory_relation.assigned_by > actor.id
Réf. : device_inventory_relation.deleted_by > actor.id

Réf. : actif.organization_id > organization.id
Réf. : actif.actif_type_id > ci_actif_type.id
Réf. : actif.deleted_by > actor.id

Réf. : actif_groupe.organization_id > organization.id
Réf. : actif_groupe.groupe_type_id > ci_actif_groupe_type.id
Réf. : actif_groupe.deleted_by > actor.id

Réf. : actif_groupe_item.groupe_id > actif_groupe.id
Réf. : actif_groupe_item.actif_id > actif.id

Réf. : geo_traceur.organization_id > organization.id
Réf : geo_object.geo_object_type_id > ci_geo_object_type.id
Réf. : geo_traceur.supprimé_par > acteur.id

Réf : schedule.organization_id > organization.id
Réf. : schedule.schedule_type_id > ci_schedule_type.id
Réf : schedule.deleted_by > actor.id

Réf. : custom_field_value_text.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_number.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_boolean.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_date.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_datetime.field_definition_id > ci_custom_field_definition.id
Réf : custom_field_value_geojson.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_schedule.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_option.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_option.ref_item_id > ci_user_catalog_item.id
Réf. : custom_field_value_device.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_device.ref_device_id > device.id
Réf. : custom_field_value_entity.field_definition_id > ci_custom_field_definition.id
Réf. : custom_field_value_catalog.field_definition_id > ci_custom_field_definition.id
Réf : custom_field_value_catalog.ref_item_id > ci_base.id
Réf. : custom_field_value_tag.field_definition_id > ci_custom_field_definition.id
Réf : Mots clés.custom_field_value_tag.ref_tag_id > ci_tag.id

Réf. : audit_event.organization_id > organization.id
Réf. : audit_event.actor_id > actor.id
```

{% endcode %}

## Fréquence de mise à jour

Les données dans BDR sont synchronisées en temps réel avec les systèmes sources. Les mises à jour se produisent immédiatement à mesure que les changements surviennent, avec des pistes d’audit capturant toutes les modifications à des fins de conformité et d’analyse historique.

## `ci_base`

BDR utilise un **Héritage à table unique** schéma pour toutes les données de référence via le `ci_base` table. Cette conception consolide les dictionnaires système, les classifications et les éléments de référence définis par l’utilisateur dans une structure unifiée, offrant cohérence et flexibilité à l’ensemble du schéma.

**Architecture :**

Le `ci_base` la table sert de base pour toutes les données de référence, en utilisant un `discriminateur` champ permettant d'identifier le type de référence spécifique. Chaque type de référence possède une table correspondante (comme `type_d_appareil_ci`, `ci_type_d'actif`) qui partage le même `identifiant` comme `ci_base`, créant une relation d'héritage typée de manière sûre. Le `discriminateur` n'est pas défini manuellement. Un déclencheur le dérive automatiquement du code de type à 4 caractères intégré dans l'UUID de l'entité, et il fait échouer l'insertion si le code de type de l'UUID n'est pas reconnu.

Les hiérarchies (par exemple, les types d'Actif imbriqués ou les Mots clés) sont gérées directement sur `ci_base` via sa propre `parent_id`/`path` colonnes plutôt qu'à travers des tables de catégories séparées. Un seul `is_hierarchical` indicateur l'active pour chaque élément du catalogue.

**Comment les entités métier se connectent à ci\_base :**

Toutes les entités métier de référence du BDR `ci_base` des sous-types pour définir leur classification et leur comportement :

* `organisation` et `utilisateur`/`membre` sont *pas* typés via `ci_base`, car les organisations et les utilisateurs sont des entités de base à part entière, non classées par un élément de catalogue
* `appareil` → références `type_d_appareil_ci`, en option `modèle_de_l'appareil_CI` (qui fait lui-même référence `fournisseur_appareil_ci`), et `statut de l'appareil CI`
* `actif` → références `ci_type_d'actif`
* `Actif_Groupe` → références `ci_Actif_Groupe_type`, avec les types de membres autorisés contraints via `relation_de_type_de_Groupe_Actif_vers_type_Actif`
* `geo_Traceur` → références `ci_type_de_geo_Traceur`
* `planning` → références `ci_type_de_planning`
* `inventaire` n'a pas de type d'élément de catalogue qui lui soit propre

**Catégories de types de référence :**

| Catégorie                            | Tables                                                                                                                             | Objectif                                                                                                                            |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Configuration du système**         | `ci_module`, `ci_country`, `ci_modèle d'accès`                                                                                     | Définir les modules du système, les références géographiques et les rôles des utilisateurs                                          |
| **Définitions des types d'entité**   | `ci_entity_type`, `type_d_appareil_ci`, `ci_type_d'actif`, `ci_type_de_geo_Traceur`, `ci_type_de_planning`, `ci_Actif_Groupe_type` | Classer toutes les entités commerciales par type                                                                                    |
| **Catalogue des appareils**          | `fournisseur_appareil_ci`, `modèle_de_l'appareil_CI`, `statut de l'appareil CI`                                                    | Modéliser les appareils par fabricant, modèle et statut actuel                                                                      |
| **Contrôle d’accès**                 | `ci_permission_scope`                                                                                                              | Définir quelles autorisations peuvent être accordées (connecté à `ci_module` et `ci_entity_type`)                                   |
| **Relations**                        | `ci_device_relation_type`                                                                                                          | Définir les types de relations entre les appareils (maître-esclave, sauvegarde, etc.)                                               |
| **Catégorisation**                   | `ci_Mots clés`, `ci_catalog_category`, `ci_user_catalog_item`                                                                      | Activer le balisage flexible et les catalogues définis par l’utilisateur                                                            |
| **Catalogue / Champs personnalisés** | `ci_catalog`, `ci_custom_field_definition`                                                                                         | Métadonnées de catalogue de catalogues et de champ personnalisé, toutes deux modélisées elles-mêmes comme des éléments de catalogue |

<details>

<summary><strong>Exemples de modèles de requêtes</strong></summary>

```sql
-- Obtenir tous les types d’appareils pour une organisation (système + personnalisé)
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;

-- Obtenir des modèles d'appareils avec leur fournisseur
Sélectionner
  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;

-- Obtenir la structure hiérarchique des mots clés
SELECT cb.id, cb.code, cb.title_en, cb.path, cb.parent_id
FROM bdr.ci_base cb
REJOINDRE bdr.ci_tag t ON t.id = cb.id
WHERE cb.discriminator = « mots clés »
  ET cb.deleted_at EST NULL
ORDER BY cb.path;
```

</details>

## Tables clés par catégorie

Les tables de BDR sont organisées en catégories fonctionnelles. Les descriptions ci-dessous résument les tables les plus importantes selon leur objectif métier.

<details>

<summary><code>organisation</code></summary>

**Objet :** Gestion de l’organisation hiérarchique

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</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>date de suppression</code></td></tr><tr><td><strong>Indexation</strong></td><td>Index GiST sur <code>path</code> pour les requêtes hiérarchiques, index unique sur <code>code</code>, index sur <code>parent_id</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Utilise ltree pour les hiérarchies à plusieurs niveaux. <code>is_dealer</code> accorde le droit de créer des organisations enfants. <code>version</code> prend en charge le verrouillage optimiste</td></tr></tbody></table>

</details>

<details>

<summary><code>acteur</code></summary>

**Objet :** Abstraction partagée par chaque principal ACL : utilisateurs, intégrations et futurs acteurs système

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>type d'acteur</code> (<code>UTILISATEUR</code>, <code>INTÉGRATION</code>, <code>SYSTÈME</code>), <code>version</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Toutes les tables ACL (<code>rôle d'acteur</code>, <code>permission du Modèle d'accès ACL</code>, <code>portée utilisateur ACL</code>) et chaque <code>supprimé par</code> clé de colonne basée sur <code>actor_id</code>, et non <code>user_id</code>, afin que les intégrations puissent également détenir des Modèles d'accès et supprimer des enregistrements</td></tr></tbody></table>

</details>

<details>

<summary><code>utilisateur</code> / <code>membre</code></summary>

**Objet :** Comptes utilisateur et leurs appartenances à des organisations

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>utilisateur</code>: <code>identifiant</code> (partagé avec <code>acteur</code>), <code>fournisseur d'identité</code>, <code>id_fournisseur_d'identité</code>, <code>nom complet</code>. <code>membre</code>: <code>user_id</code>, <code>id_organisation</code>, <code>données des champs personnalisés</code></td></tr><tr><td><strong>Indexation</strong></td><td>Index unique sur (<code>fournisseur d'identité</code>, <code>id_fournisseur_d'identité</code>). Index unique sur (<code>user_id</code>, <code>id_organisation</code>) dans <code>membre</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>L'appartenance d'un utilisateur à une organisation correspond à une ligne distincte dans <code>membre</code>, donc un utilisateur peut appartenir à plusieurs organisations. Les utilisateurs s'authentifient via un fournisseur d'identité externe (Keycloak par défaut)</td></tr></tbody></table>

</details>

<details>

<summary><code>intégration</code></summary>

**Objet :** Systèmes externes agissant sur la plateforme (par ex. via des Clés API)

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code> (partagé avec <code>acteur</code>), <code>nom</code>, <code>réf_credential</code>, <code>is_active</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td><code>réf_credential</code> pointe vers un coffre-fort externe sécurisé, donc aucun identifiant n'est stocké dans cette table</td></tr></tbody></table>

</details>

<details>

<summary><code>appareil</code> / <code>identifiant de l'appareil</code></summary>

**Objet :** Dispositifs de Suivi physiques et leurs identifiants matériels

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>appareil</code>: <code>identifiant</code>, <code>id_organisation</code>, <code>id_type_d'appareil</code>, <code>id_modèle</code>, <code>id_statut</code>, <code>titre</code>, <code>données des champs personnalisés</code>, <code>version</code>. <code>identifiant de l'appareil</code>: <code>device_id</code>, <code>type</code>, <code>valeur</code>, <code>espace de noms</code></td></tr><tr><td><strong>Indexation</strong></td><td>index GIN de trigrammes sur <code>titre</code> pour la recherche floue. Index unique sur (<code>type</code>, <code>valeur</code>) pour les identifiants globaux, ou (<code>type</code>, <code>valeur</code>, <code>espace de noms</code>) lorsqu'il est dans un espace de noms</td></tr><tr><td><strong>Notes spéciales</strong></td><td>Un appareil peut comporter plusieurs identifiants (IMEI, MAC, numéro de série, etc.) au lieu d'une seule colonne d'identifiant matériel. Les Champs personnalisés se trouvent directement dans <code>données des champs personnalisés</code> la ligne de l'appareil</td></tr></tbody></table>

</details>

<details>

<summary><code>actif</code></summary>

**Objet :** Actif physique ou virtuel

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>id_organisation</code>, <code>type_d'actif_id</code>, <code>titre</code>, <code>données des champs personnalisés</code>, <code>version</code></td></tr><tr><td><strong>Indexation</strong></td><td>Index sur <code>id_organisation</code> et <code>type_d'actif_id</code>. Index trigramme GIN sur <code>titre</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Les Champs personnalisés sont stockés directement sur la ligne, et non via une table d'entité personnalisable séparée</td></tr></tbody></table>

</details>

<details>

<summary><code>inventaire</code> / <code>device_inventory_relation</code></summary>

**Objet :** Enregistrements d'inventaire et d'entrepôt, et affectation des appareils à l'inventaire

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>inventaire</code>: <code>identifiant</code>, <code>id_organisation</code>, <code>titre</code>, <code>code</code>. <code>device_inventory_relation</code>: <code>device_id</code>, <code>inventory_id</code>, <code>assigned_at</code>, <code>date de suppression</code></td></tr><tr><td><strong>Indexation</strong></td><td>Index unique sur (<code>id_organisation</code>, <code>code</code>). Un index unique partiel garantit qu'un appareil ne peut avoir qu'une seule affectation d'inventaire active</td></tr><tr><td><strong>Notes spéciales</strong></td><td>L'affectation peut être supprimée logiquement, de sorte que l'historique appareil-inventaire est préservé</td></tr></tbody></table>

</details>

<details>

<summary><code>geo_Traceur</code></summary>

**Objet :** Géorepères, points d'intérêt et itinéraires

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>id_organisation</code>, <code>identifiant_du_type_d'objet_géo</code>, <code>titre</code>, <code>données des champs personnalisés</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Géométrie (autrefois une section dédiée <code>geojson</code> colonne) est maintenant stockée comme champ personnalisé dans <code>données des champs personnalisés</code></td></tr></tbody></table>

</details>

<details>

<summary><code>planning</code></summary>

**Objet :** Plannings réutilisables référencés par d'autres entités

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>id_organisation</code>, <code>id_du_type_de_planning</code>, <code>titre</code>, <code>données des champs personnalisés</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Les données de planification elles-mêmes (règles de récurrence, fenêtres horaires) sont stockées dans <code>données des champs personnalisés</code></td></tr></tbody></table>

</details>

<details>

<summary><code>Actif_Groupe</code></summary>

**Objet :** Regroupement d’Actif avec Suivi historique

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>id_organisation</code>, <code>groupe_type_id</code>, <code>title_en</code>, <code>couleur</code></td></tr><tr><td><strong>Relations</strong></td><td><code>DE bdr.asset_group AS ag JOINDRE bdr.asset_group_item AS agi SUR agi.group_id = ag.id JOINDRE bdr.asset AS a SUR a.id = agi.asset_id OÙ agi.detached_at N’EST PAS NULL</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Adhésion basée sur le temps via <code>Actif_Groupe_élément</code>, interroger les membres actuels avec <code>WHERE detached_at IS NULL</code>. Les types de membres autorisés par type de Groupe sont limités via <code>relation_de_type_de_Groupe_Actif_vers_type_Actif</code></td></tr></tbody></table>

</details>

<details>

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

**Objet :** Définitions de champs personnalisés et métadonnées, modélisées comme un élément de catalogue

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code> (partagé avec <code>ci_base</code>), <code>propriétaire_catalogue_élément_id</code>, <code>identifiant_du_type_d_entité_cible</code>, <code>type de champ</code>, <code>est_obligatoire</code>, <code>paramètres</code></td></tr><tr><td><strong>Contenu</strong></td><td>12 types de champs : <code>CHAÎNE</code>, <code>TEXTE</code>, <code>NOMBRE</code>, <code>BOOLÉEN</code>, <code>DATE</code>, <code>DATEHEURE</code>, <code>GEOJSON</code>, <code>PLANNING</code>, <code>OPTIONS</code>, <code>APPAREIL</code>, <code>RÉFÉRENCE</code>, <code>CATALOGUE</code>, <code>Mots clés</code></td></tr><tr><td><strong>Notes spéciales</strong></td><td>Les valeurs sont stockées à la fois comme source de vérité (<code>données des champs personnalisés</code> JSONB sur l'entité propriétaire) et, par type, dans une table dédiée <code>custom_field_value_*</code> table de cache pour le filtrage/le tri. <code>OPTIONS</code> est un wrapper autour de <code>CATALOGUE</code>, et chaque <code>OPTIONS</code> champ possède en interne son propre catalogue utilisateur masqué</td></tr></tbody></table>

</details>

<details>

<summary><code>permission du Modèle d'accès ACL</code></summary>

**Objet :** Gestion des autorisations basée sur le Modèle d'accès

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>id_rôle</code>, <code>id_portée_autorisation</code>, <code>id_entité_cible</code>, <code>actions</code></td></tr><tr><td><strong>Contenu</strong></td><td>Masque de bits d’action (LECTURE=1, CRÉATION=2, MISE À JOUR=4, SUPPRESSION=8), autorisations spécifiques à la cible ou étendues à l’ensemble du type d’entité</td></tr><tr><td><strong>Relations</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>Notes spéciales</strong></td><td>Autorisations effectives = autorisations du Modèle d'accès ∩ <code>portée utilisateur ACL</code>. d’un acteur <code>portée utilisateur ACL</code> est un filtre de liste blanche : vide signifie un accès au Modèle d'accès non restreint, rempli signifie que l’accès est restreint aux objets listés</td></tr></tbody></table>

</details>

<details>

<summary><code>événement_d'audit</code></summary>

**Objet :** Journal d’audit unifié pour toutes les modifications du système

<table><thead><tr><th width="139">Attribut</th><th>Détails</th></tr></thead><tbody><tr><td><strong>Champs clés</strong></td><td><code>identifiant</code>, <code>id_organisation</code>, <code>catégorie_événement</code>, <code>actor_id</code>, <code>type_source</code>, <code>identifiant de trace</code>, <code>type d’agrégation</code>, <code>aggregate_id</code>, <code>event_type</code>, <code>event_data</code>, <code>occurred_at</code></td></tr><tr><td><strong>Indexation</strong></td><td>Index sur (<code>actor_id</code>, <code>occurred_at</code>), (<code>type d’agrégation</code>, <code>aggregate_id</code>, <code>occurred_at</code>), (<code>catégorie_événement</code>, <code>occurred_at</code>), (<code>id_organisation</code>, <code>occurred_at</code>)</td></tr><tr><td><strong>Notes spéciales</strong></td><td>Partitionné physiquement par <code>occurred_at</code> (partitions de plage mensuelles, créées automatiquement). <code>catégorie_événement</code> est <code>auth</code> (LOGIN, LOGOUT, FAILED_LOGIN, PASSWORD_RESET, SESSION_EXPIRED) ou <code>domaine</code> (CRÉÉ, MIS À JOUR, SUPPRIMÉ, RESTAURÉ, RÔLE_ATTRIBUÉ, RÔLE_RÉVOQUÉ, AUTORISATION_ACCORDÉE, AUTORISATION_RÉVOQUÉE, LIÉ, DÉLIÉ, ATTACHÉ, DÉTACHÉ). <code>type_source</code> et <code>identifiant de trace</code> prennent en charge le traçage distribué, et la table stocke les différences de modification au niveau des champs dans <code>event_data</code> JSONB</td></tr></tbody></table>

</details>

## Relations de données

BDR implémente des modèles de relations sophistiqués pour une modélisation de données flexible :

**Structures hiérarchiques**

* Les organisations utilisent des chemins ltree pour des requêtes d'arborescence efficaces
* Éléments de référence (`ci_base`) prennent en charge des hiérarchies optionnelles via leur propre `parent_id`/`path`, conditionné par `is_hierarchical`
* Entretien automatique du chemin via des déclencheurs de base de données

**Schémas d'héritage**

* Héritage d'ID : `ci_base` → tables de type référence (`type_d_appareil_ci`, `ci_type_d'actif`, etc.), et `acteur` → `utilisateur` et `intégration`
* Les Champs personnalisés sont associés à chaque entité via un `données des champs personnalisés` colonne JSONB directement sur chaque table métier (`appareil`, `actif`, `geo_Traceur`, `planning`), plutôt qu’à travers une table d’entité de base partagée
* Discrimination de type via le `discriminateur` champ sur `ci_base`, dérivé automatiquement de l'UUID de l'entité

**Verrouillage optimiste**

Les entités métier et `ci_base` les lignes portent un `version` entier. Les mises à jour utilisent `SET version = version + 1 WHERE id = $id AND version = $expected_version`. Zéro ligne affectée signale un conflit de version, ce qui permet aux clients de détecter et de résoudre les modifications concurrentes sans verrous au niveau de la base de données. `événement_d'audit` et les autres tables en ajout uniquement ou opérées en masse n'utilisent pas ce modèle.

**Relations polymorphes**

Certaines tables utilisent des références polymorphes sans contraintes de clé étrangère pour une flexibilité maximale :

* `acl_role_permission.target_entity_id` et `acl_user_scope.target_entity_id` → toute entité commerciale
* `audit_event.aggregate_id` → toute entité commerciale, associée à `type d’agrégation`
* `custom_field_value_entity.ref_entity_id` → toute entité commerciale

Ces relations sont validées au niveau de l’application.

## Informations supplémentaires

### Validation des données

BDR garantit l’intégrité des données par plusieurs mécanismes :

**Contraintes de base de données**

* Contraintes UNIQUE avec prise en charge de la suppression logique (index partiels WHERE `date de suppression` IS NULL)
* Contraintes CHECK (par ex., `device_relation` garantit `first_id` ≠ `second_id`, et `ci_base.code` doit commencer par une lettre)
* contraintes NOT NULL sur les champs obligatoires
* Valeurs DEFAULT pour les horodatages, les booléens et les colonnes JSONB

**Validation au niveau de l'application**

* Validation du type d'entité pour les références polymorphes
* Validation du catalogue pour les références de champs personnalisés
* Validation du type de champ personnalisé
* Vérifications de version par verrouillage optimiste lors de la mise à jour

### Optimisation des requêtes

Les tables sont organisées avec des stratégies d'indexation spécifiques :

**Index standards :**

* Toutes les clés étrangères ont des index dédiés
* Index basés sur le temps sur `created_at`, `updated_at`, `date de suppression`
* Index composites pour les colonnes fréquemment jointes

**Index spécialisés :**

* Index GiST sur les chemins ltree pour les requêtes hiérarchiques
* Index uniques partiels prenant en charge la suppression logique
* Trigrammes GIN (`pg_trgm`) index pour la recherche floue sur `titre`/`title_en` et le texte traduit
* Index des valeurs de champs personnalisés pour le filtrage et le tri
* Index des événements d'audit sur le temps + l'entité pour des recherches efficaces

**Considérations de performance :**

* Regroupement des connexions recommandé (PgBouncer)
* Entretien VACUUM régulier pour les grandes tables
* `événement_d'audit` est physiquement partitionné par mois, de sorte que les partitions plus anciennes peuvent être détachées ou archivées indépendamment
* Vues matérialisées pour des calculs complexes de contrôle d'accès


---

# 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/fr/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.
