RAP Tree Tables : Données hiérarchiques dans Fiori Elements

Catégorie
RAP
Publié
Auteur
Johannes

Les Tree Tables sont la forme idéale de représentation pour les données hiérarchiques dans Fiori Elements. Elles combinent la structure d’un tableau avec la navigation d’une vue arborescente - parfait pour les structures organisationnelles, les nomenclatures ou les hiérarchies de centres de coûts.

Concept de base

Les Tree Tables dans Fiori Elements sont basées sur les hiérarchies CDS et sont configurées via des annotations UI. La hiérarchie est définie avec DEFINE HIERARCHY et exposée via un service OData V4.

Aperçu de l’architecture

┌─────────────────────────────────────────────────────┐
│ Fiori Elements │
│ (List Report / TreeTable) │
├─────────────────────────────────────────────────────┤
│ OData V4 Service │
├─────────────────────────────────────────────────────┤
│ Projection View (ZC_*) │
│ + UI Annotations pour Tree Table │
├─────────────────────────────────────────────────────┤
│ CDS Hierarchy (ZI_*Hierarchy) │
│ + Attributs $node (Level, Rank, etc.) │
├─────────────────────────────────────────────────────┤
│ Interface View (ZI_*) │
│ + Association Parent (_Parent) │
├─────────────────────────────────────────────────────┤
│ Table de base │
└─────────────────────────────────────────────────────┘

Définition de la hiérarchie CDS

La base des Tree Tables est une hiérarchie CDS avec des attributs calculés automatiquement.

Interface View avec association Parent

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Unité organisationnelle"
define view entity ZI_OrgUnit
as select from zorgunit
association [0..1] to ZI_OrgUnit as _Parent
on $projection.ParentOrgUnit = _Parent.OrgUnitId
association [0..*] to ZI_OrgUnit as _Children
on $projection.OrgUnitId = _Children.ParentOrgUnit
{
key org_unit_id as OrgUnitId,
parent_org_unit as ParentOrgUnit,
org_unit_name as OrgUnitName,
manager as Manager,
employee_count as EmployeeCount,
budget as Budget,
currency as Currency,
valid_from as ValidFrom,
valid_to as ValidTo,
_Parent,
_Children
}

Définition de la hiérarchie

@AccessControl.authorizationCheck: #NOT_REQUIRED
define hierarchy ZI_OrgUnitHierarchy
as parent child hierarchy(
source ZI_OrgUnit
child to parent association _Parent
start where ParentOrgUnit is initial
siblings order by OrgUnitName
)
{
key OrgUnitId,
ParentOrgUnit,
OrgUnitName,
Manager,
EmployeeCount,
Budget,
Currency,
// Attributs de hiérarchie pour l'UI
$node.hierarchy_level as HierarchyLevel,
$node.hierarchy_rank as HierarchyRank,
$node.hierarchy_tree_size as TreeSize,
$node.hierarchy_parent_rank as ParentRank,
$node.hierarchy_is_cycle as IsCycle,
$node.hierarchy_is_orphan as IsOrphan,
_Parent,
_Children
}

Annotations @UI.lineItem pour Tree Tables

L’affichage Tree Table est configuré via des annotations spéciales.

Projection View avec annotations UI

@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
@UI.headerInfo: {
typeName: 'Unité organisationnelle',
typeNamePlural: 'Unités organisationnelles',
title.value: 'OrgUnitName"
}
@UI.presentationVariant: [{
qualifier: 'TreeVariant',
sortOrder: [{
by: 'HierarchyRank',
direction: #ASC
}],
visualizations: [{
type: #AS_LINEITEM
}]
}]
define view entity ZC_OrgUnitTree
as projection on ZI_OrgUnitHierarchy
{
@UI.facet: [{
id: 'General',
type: #IDENTIFICATION_REFERENCE,
label: 'Général',
position: 10
}]
@UI.lineItem: [{
position: 10,
importance: #HIGH
}]
@UI.identification: [{ position: 10 }]
key OrgUnitId,
@UI.lineItem: [{ position: 15, importance: #HIGH }]
@UI.hidden: true
ParentOrgUnit,
@UI.lineItem: [{
position: 20,
importance: #HIGH,
type: #WITH_NAVIGATION_PATH
}]
@UI.identification: [{ position: 20 }]
OrgUnitName,
@UI.lineItem: [{ position: 30 }]
@UI.identification: [{ position: 30 }]
Manager,
@UI.lineItem: [{ position: 40 }]
EmployeeCount,
@UI.lineItem: [{ position: 50 }]
@Semantics.amount.currencyCode: 'Currency"
Budget,
@UI.hidden: true
Currency,
// Champs de hiérarchie pour l'affichage Tree
@UI.hidden: true
HierarchyLevel,
@UI.hidden: true
HierarchyRank,
@UI.hidden: true
TreeSize,
@UI.hidden: true
ParentRank,
@UI.hidden: true
IsCycle,
@UI.hidden: true
IsOrphan,
_Parent,
_Children
}

Service Definition pour OData V4

@EndUserText.label: 'Service hiérarchie organisationnelle"
define service ZUI_ORGUNIT_TREE_O4 {
expose ZC_OrgUnitTree as OrgUnitTree;
}

Exemple de code : Hiérarchie Parent-Enfant (Structure organisationnelle)

Un exemple complet pour une hiérarchie d’entreprise avec tous les niveaux, de la direction générale aux départements individuels.

Table de base de données

@EndUserText.label: 'Unité organisationnelle"
define table zorgunit {
key client : abap.clnt not null;
key org_unit_id : abap.char(10) not null;
parent_org_unit : abap.char(10);
org_unit_name : abap.char(60);
org_unit_type : abap.char(10); // COMPANY, DIVISION, DEPARTMENT, TEAM
manager : abap.char(12);
employee_count : abap.int4;
budget : abap.curr(15,2);
currency : abap.cuky;
cost_center : abap.char(10);
valid_from : abap.dats;
valid_to : abap.dats;
created_at : abap.utclong;
changed_at : abap.utclong;
}

Projection avec annotations spécifiques à la hiérarchie

@Metadata.layer: #CUSTOMER
@UI.chart: [{
qualifier: 'OrgChart',
chartType: #COLUMN,
dimensions: ['OrgUnitType'],
measures: ['EmployeeCount']
}]
annotate view ZC_OrgUnitTree with
{
@UI.selectionField: [{ position: 10 }]
@Consumption.filter.selectionType: #SINGLE
OrgUnitType;
@UI.selectionField: [{ position: 20 }]
Manager;
// DrillDown-State pour Tree
@UI.lineItem: [{
position: 10,
criticality: 'StatusCriticality',
type: #STANDARD
}]
OrgUnitName;
}

Générer des données de test

CLASS zcl_orgunit_testdata DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CLASS-METHODS generate_test_hierarchy.
ENDCLASS.
CLASS zcl_orgunit_testdata IMPLEMENTATION.
METHOD generate_test_hierarchy.
DATA lt_orgunits TYPE TABLE OF zorgunit.
" Niveau 1 : Entreprise
APPEND VALUE #(
org_unit_id = 'COMP001"
org_unit_name = 'ACME Corporation"
org_unit_type = 'COMPANY"
manager = 'MILLER"
employee_count = 1500
budget = '10000000.00"
currency = 'EUR"
) TO lt_orgunits.
" Niveau 2 : Divisions
APPEND VALUE #(
org_unit_id = 'DIV001"
parent_org_unit = 'COMP001"
org_unit_name = 'Ventes"
org_unit_type = 'DIVISION"
manager = 'SCHMIDT"
employee_count = 400
budget = '3000000.00"
currency = 'EUR"
) TO lt_orgunits.
APPEND VALUE #(
org_unit_id = 'DIV002"
parent_org_unit = 'COMP001"
org_unit_name = 'Développement"
org_unit_type = 'DIVISION"
manager = 'MEYER"
employee_count = 600
budget = '5000000.00"
currency = 'EUR"
) TO lt_orgunits.
" Niveau 3 : Départements
APPEND VALUE #(
org_unit_id = 'DEPT001"
parent_org_unit = 'DIV001"
org_unit_name = 'Service interne"
org_unit_type = 'DEPARTMENT"
manager = 'WEBER"
employee_count = 150
budget = '1000000.00"
currency = 'EUR"
) TO lt_orgunits.
APPEND VALUE #(
org_unit_id = 'DEPT002"
parent_org_unit = 'DIV001"
org_unit_name = 'Service externe"
org_unit_type = 'DEPARTMENT"
manager = 'FISCHER"
employee_count = 250
budget = '2000000.00"
currency = 'EUR"
) TO lt_orgunits.
APPEND VALUE #(
org_unit_id = 'DEPT003"
parent_org_unit = 'DIV002"
org_unit_name = 'Développement Backend"
org_unit_type = 'DEPARTMENT"
manager = 'BAUER"
employee_count = 300
budget = '2500000.00"
currency = 'EUR"
) TO lt_orgunits.
APPEND VALUE #(
org_unit_id = 'DEPT004"
parent_org_unit = 'DIV002"
org_unit_name = 'Développement Frontend"
org_unit_type = 'DEPARTMENT"
manager = 'WAGNER"
employee_count = 200
budget = '2000000.00"
currency = 'EUR"
) TO lt_orgunits.
" Niveau 4 : Équipes
APPEND VALUE #(
org_unit_id = 'TEAM001"
parent_org_unit = 'DEPT003"
org_unit_name = 'Équipe ABAP"
org_unit_type = 'TEAM"
manager = 'SCHULZ"
employee_count = 50
budget = '400000.00"
currency = 'EUR"
) TO lt_orgunits.
APPEND VALUE #(
org_unit_id = 'TEAM002"
parent_org_unit = 'DEPT003"
org_unit_name = 'Équipe Java"
org_unit_type = 'TEAM"
manager = 'HOFFMANN"
employee_count = 40
budget = '350000.00"
currency = 'EUR"
) TO lt_orgunits.
DELETE FROM zorgunit.
INSERT zorgunit FROM TABLE @lt_orgunits.
COMMIT WORK.
ENDMETHOD.
ENDCLASS.

Exemple de code : Configurer le comportement Expand/Collapse

Le comportement Expand/Collapse est contrôlé via des annotations et des paramètres OData.

Niveau d’expansion initial

@UI.presentationVariant: [{
qualifier: 'DefaultTree',
sortOrder: [{
by: 'HierarchyRank',
direction: #ASC
}],
visualizations: [{
type: #AS_LINEITEM
}],
// Initialement seulement 2 niveaux développés
requestAtLeast: ['HierarchyLevel']
}]
define view entity ZC_OrgUnitTreeExpand
as projection on ZI_OrgUnitHierarchy

Lazy Loading pour grandes hiérarchies

@ObjectModel.query.implementedBy: 'ABAP:ZCL_ORGUNIT_LAZY_QUERY"
define custom entity ZI_OrgUnitLazy
{
key OrgUnitId : abap.char(10);
ParentOrgUnit : abap.char(10);
OrgUnitName : abap.char(60);
HasChildren : abap_boolean;
IsExpanded : abap_boolean;
HierarchyLevel : abap.int4;
}

Implémentation de la requête pour Lazy Loading

CLASS zcl_orgunit_lazy_query DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
PRIVATE SECTION.
METHODS get_children
IMPORTING iv_parent_id TYPE char10
RETURNING VALUE(rt_children) TYPE zt_orgunit_lazy.
ENDCLASS.
CLASS zcl_orgunit_lazy_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.
IF io_request->is_data_requested( ) = abap_false.
RETURN.
ENDIF.
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
DATA lt_result TYPE STANDARD TABLE OF zi_orgunitlazy.
" Lire le filtre Parent
DATA lv_parent TYPE char10.
LOOP AT lt_filter INTO DATA(ls_filter)
WHERE name = 'PARENTORGUNIT'.
IF ls_filter-range IS NOT INITIAL.
lv_parent = ls_filter-range[ 1 ]-low.
ENDIF.
ENDLOOP.
" Charger les noeuds racine ou les enfants
IF lv_parent IS INITIAL.
" Noeuds racine (sans Parent)
SELECT org_unit_id, parent_org_unit, org_unit_name
FROM zorgunit
WHERE parent_org_unit = '"
INTO TABLE @DATA(lt_roots).
LOOP AT lt_roots INTO DATA(ls_root).
" Vérifier si des enfants existent
SELECT COUNT(*) FROM zorgunit
WHERE parent_org_unit = @ls_root-org_unit_id
INTO @DATA(lv_child_count).
APPEND VALUE #(
OrgUnitId = ls_root-org_unit_id
ParentOrgUnit = ls_root-parent_org_unit
OrgUnitName = ls_root-org_unit_name
HasChildren = xsdbool( lv_child_count > 0 )
IsExpanded = abap_false
HierarchyLevel = 1
) TO lt_result.
ENDLOOP.
ELSE.
" Enfants d'un noeud spécifique
lt_result = get_children( lv_parent ).
ENDIF.
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( lines( lt_result ) ).
ENDIF.
io_response->set_data( lt_result ).
ENDMETHOD.
METHOD get_children.
" Déterminer le niveau du Parent
SELECT SINGLE hierarchy_level
FROM zi_orgunithierarchy
WHERE orgunitid = @iv_parent_id
INTO @DATA(lv_parent_level).
" Charger les enfants directs
SELECT org_unit_id, parent_org_unit, org_unit_name
FROM zorgunit
WHERE parent_org_unit = @iv_parent_id
INTO TABLE @DATA(lt_children).
LOOP AT lt_children INTO DATA(ls_child).
SELECT COUNT(*) FROM zorgunit
WHERE parent_org_unit = @ls_child-org_unit_id
INTO @DATA(lv_grandchildren).
APPEND VALUE #(
OrgUnitId = ls_child-org_unit_id
ParentOrgUnit = ls_child-parent_org_unit
OrgUnitName = ls_child-org_unit_name
HasChildren = xsdbool( lv_grandchildren > 0 )
IsExpanded = abap_false
HierarchyLevel = lv_parent_level + 1
) TO rt_children.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Exemple de code : Hiérarchie avec valeurs agrégées

Calculer des agrégations comme les sommes ou moyennes sur tous les noeuds subordonnés.

Vue d’agrégation

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Unité organisationnelle avec agrégations"
define view entity ZI_OrgUnitAggregated
as select from ZI_OrgUnitHierarchy as hier
left outer join (
select parent_org_unit,
sum( budget ) as child_budget_sum,
sum( employee_count ) as child_employee_sum
from zorgunit
group by parent_org_unit
) as agg on hier.OrgUnitId = agg.parent_org_unit
{
key hier.OrgUnitId,
hier.ParentOrgUnit,
hier.OrgUnitName,
hier.Manager,
// Valeurs propres
hier.EmployeeCount as OwnEmployeeCount,
hier.Budget as OwnBudget,
hier.Currency,
// Valeurs agrégées des enfants directs
coalesce( agg.child_employee_sum, 0 ) as ChildEmployeeSum,
coalesce( agg.child_budget_sum, cast( 0 as abap.curr(15,2) ) ) as ChildBudgetSum,
// Total (propres + enfants) - simplifié
hier.EmployeeCount + coalesce( agg.child_employee_sum, 0 ) as TotalEmployees,
// Attributs de hiérarchie
hier.HierarchyLevel,
hier.HierarchyRank,
hier.TreeSize,
hier._Parent,
hier._Children
}

Agrégation récursive avec CTE

Pour une vraie agrégation récursive sur tous les niveaux :

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Agrégation récursive"
define view entity ZI_OrgUnitRecursiveAgg
as select from ZI_OrgUnitHierarchy as root
{
key root.OrgUnitId,
root.OrgUnitName,
root.HierarchyLevel,
root.HierarchyRank,
root.TreeSize,
// Calculer la somme sur tout le sous-arbre
// (tous les noeuds avec Rank entre le propre Rank et Rank + TreeSize)
@Aggregation.default: #SUM
(
select sum( sub.Budget )
from ZI_OrgUnitHierarchy as sub
where sub.HierarchyRank >= root.HierarchyRank
and sub.HierarchyRank < root.HierarchyRank + root.TreeSize
) as TotalSubtreeBudget,
@Aggregation.default: #SUM
(
select sum( sub.EmployeeCount )
from ZI_OrgUnitHierarchy as sub
where sub.HierarchyRank >= root.HierarchyRank
and sub.HierarchyRank < root.HierarchyRank + root.TreeSize
) as TotalSubtreeEmployees
}

Méthode ABAP pour agrégation de sous-arbre

METHOD calculate_subtree_totals.
" Traiter tous les noeuds avec TreeSize > 0
SELECT orgunitid, hierarchyrank, treesize
FROM zi_orgunithierarchy
INTO TABLE @DATA(lt_nodes).
LOOP AT lt_nodes INTO DATA(ls_node).
" Sommer tous les noeuds du sous-arbre
SELECT SUM( budget ) AS total_budget,
SUM( employee_count ) AS total_employees
FROM zi_orgunithierarchy
WHERE hierarchyrank >= @ls_node-hierarchyrank
AND hierarchyrank < @( ls_node-hierarchyrank + ls_node-treesize )
INTO @DATA(ls_totals).
" Stocker le résultat dans la table d'agrégation
MODIFY zorgunit_agg FROM VALUE #(
org_unit_id = ls_node-orgunitid
total_budget = ls_totals-total_budget
total_employees = ls_totals-total_employees
calculated_at = utclong_current( )
).
ENDLOOP.
COMMIT WORK.
ENDMETHOD.

Projection avec colonnes agrégées

@UI.lineItem: [
{ position: 10, importance: #HIGH, label: 'Nom' },
{ position: 20, label: 'Employés propres' },
{ position: 30, label: 'Total employés', criticality: 'EmployeeCriticality' },
{ position: 40, label: 'Budget propre' },
{ position: 50, label: 'Budget total', type: #AS_DATAPOINT }
]
define view entity ZC_OrgUnitAggTree
as projection on ZI_OrgUnitAggregated
{
key OrgUnitId,
OrgUnitName,
OwnEmployeeCount,
TotalEmployees,
@Semantics.amount.currencyCode: 'Currency"
OwnBudget,
@Semantics.amount.currencyCode: 'Currency"
TotalSubtreeBudget,
Currency,
// Criticality basée sur l'utilisation du budget
case
when TotalSubtreeBudget > 1000000 then 3 -- Positif (vert)
when TotalSubtreeBudget > 500000 then 2 -- Neutre (jaune)
else 1 -- Négatif (rouge)
end as BudgetCriticality,
// Criticality pour le nombre d'employés
case
when TotalEmployees > 100 then 3
when TotalEmployees > 50 then 2
else 1
end as EmployeeCriticality,
HierarchyLevel,
HierarchyRank,
TreeSize,
_Parent,
_Children
}

Conseils de performance pour grandes hiérarchies

1. Optimiser les index

-- Index secondaire sur le champ Parent
CREATE INDEX zorgunit_parent_idx ON zorgunit (parent_org_unit);
-- Index combiné pour hiérarchies temporelles
CREATE INDEX zorgunit_temporal_idx ON zorgunit (parent_org_unit, valid_from, valid_to);

2. Implémenter la pagination

METHOD if_rap_query_provider~select.
" Lire les paramètres de pagination
DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
DATA(lv_top) = io_request->get_paging( )->get_page_size( ).
" Définir les valeurs par défaut
IF lv_top = 0.
lv_top = 100. " Maximum 100 noeuds par requête
ENDIF.
" Charger la hiérarchie avec pagination
SELECT * FROM zi_orgunithierarchy
ORDER BY hierarchyrank
INTO TABLE @DATA(lt_all)
UP TO @lv_top ROWS
OFFSET @lv_skip.
io_response->set_data( lt_all ).
" Nombre total pour la pagination
IF io_request->is_total_numb_of_rec_requested( ).
SELECT COUNT(*) FROM zi_orgunithierarchy INTO @DATA(lv_total).
io_response->set_total_number_of_records( lv_total ).
ENDIF.
ENDMETHOD.

3. Cache pour hiérarchies statiques

CLASS zcl_hierarchy_cache DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CLASS-METHODS get_cached_hierarchy
RETURNING VALUE(rt_hierarchy) TYPE zt_org_hierarchy.
CLASS-METHODS invalidate_cache.
PRIVATE SECTION.
CLASS-DATA gt_cache TYPE zt_org_hierarchy.
CLASS-DATA gv_cache_timestamp TYPE utclong.
CONSTANTS gc_cache_ttl_seconds TYPE i VALUE 3600. " 1 heure
ENDCLASS.
CLASS zcl_hierarchy_cache IMPLEMENTATION.
METHOD get_cached_hierarchy.
" Vérifier la validité du cache
DATA(lv_now) = utclong_current( ).
IF gv_cache_timestamp IS NOT INITIAL.
DATA(lv_age) = cl_abap_tstmp=>subtract(
tstmp1 = lv_now
tstmp2 = gv_cache_timestamp
).
IF lv_age < gc_cache_ttl_seconds.
rt_hierarchy = gt_cache.
RETURN.
ENDIF.
ENDIF.
" Recharger le cache
SELECT * FROM zi_orgunithierarchy
ORDER BY hierarchyrank
INTO TABLE @gt_cache.
gv_cache_timestamp = lv_now.
rt_hierarchy = gt_cache.
ENDMETHOD.
METHOD invalidate_cache.
CLEAR: gt_cache, gv_cache_timestamp.
ENDMETHOD.
ENDCLASS.

4. Agrégation virtuelle au lieu du calcul en temps réel

-- Table d'agrégation matérialisée
@EndUserText.label: 'Agrégations de hiérarchie précalculées"
define table zorgunit_agg {
key client : abap.clnt not null;
key org_unit_id : abap.char(10) not null;
total_employees : abap.int4;
total_budget : abap.curr(15,2);
currency : abap.cuky;
subtree_count : abap.int4;
calculated_at : abap.utclong;
}
" Job pour recalcul nocturne
CLASS zcl_hierarchy_aggregation_job DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_apj_dt_exec_object.
INTERFACES if_apj_rt_exec_object.
ENDCLASS.
CLASS zcl_hierarchy_aggregation_job IMPLEMENTATION.
METHOD if_apj_dt_exec_object~get_parameters.
" Aucun paramètre
ENDMETHOD.
METHOD if_apj_rt_exec_object~execute.
" Supprimer les anciennes agrégations
DELETE FROM zorgunit_agg.
" Calculer les nouvelles agrégations
SELECT orgunitid, hierarchyrank, treesize
FROM zi_orgunithierarchy
INTO TABLE @DATA(lt_nodes).
DATA lt_aggregations TYPE TABLE OF zorgunit_agg.
LOOP AT lt_nodes INTO DATA(ls_node).
SELECT SUM( budget ) AS budget,
SUM( employee_count ) AS employees,
COUNT(*) AS node_count
FROM zi_orgunithierarchy
WHERE hierarchyrank >= @ls_node-hierarchyrank
AND hierarchyrank < @( ls_node-hierarchyrank + ls_node-treesize )
INTO @DATA(ls_sum).
APPEND VALUE #(
org_unit_id = ls_node-orgunitid
total_employees = ls_sum-employees
total_budget = ls_sum-budget
currency = 'EUR"
subtree_count = ls_sum-node_count
calculated_at = utclong_current( )
) TO lt_aggregations.
ENDLOOP.
INSERT zorgunit_agg FROM TABLE @lt_aggregations.
COMMIT WORK.
ENDMETHOD.
ENDCLASS.

5. Filtrage avant le calcul de la hiérarchie

@AccessControl.authorizationCheck: #NOT_REQUIRED
define hierarchy ZI_OrgUnitFilteredHierarchy
as parent child hierarchy(
source ZI_OrgUnit
child to parent association _Parent
start where ParentOrgUnit is initial
siblings order by OrgUnitName
)
{
key OrgUnitId,
OrgUnitName,
OrgUnitType,
$node.hierarchy_level
}
-- Uniquement les unités actives
where ValidFrom <= $session.system_date
and ValidTo >= $session.system_date

Bonnes pratiques

AspectRecommandation
Profondeur de hiérarchieMaximum 10-15 niveaux pour une bonne UX
Expansion initiale2-3 niveaux développés, reste en lazy
AgrégationPrécalculer pour >10 000 noeuds
FiltreAppliquer avant le calcul de hiérarchie
CacheUtiliser pour hiérarchies statiques
IndexObligatoire sur le champ Parent
Qualité des donnéesIntégrer vérification Orphan et Cycle
PaginationCôté serveur pour >1 000 noeuds

Résumé

Les RAP Tree Tables offrent une solution élégante pour les données hiérarchiques dans Fiori Elements :

  • Les hiérarchies CDS définissent la structure de manière déclarative
  • Les attributs $node fournissent automatiquement Level, Rank et taille du sous-arbre
  • Les annotations UI configurent l’affichage Tree
  • Les agrégations peuvent être calculées sur les sous-arbres
  • La performance est optimisée par le cache, la pagination et les index

Avec ces techniques, même les structures organisationnelles complexes, les nomenclatures ou les hiérarchies de projets peuvent être visualisées de manière performante.

Sujets connexes