6. Annexes

AGE devenu ELAP Finances (édité par la société ELAP) est une solution comptable pour les établissements publics nationaux s’appuyant sur les règles de la comptabilité d’engagement et la GBCP. Les typologies de montants sont spécifiques à ces établissements.

6.1 Requêtes AGE

6.1.1 Dépenses

6.1.1.1 DV

SELECT
    SUM(TL.VAL_COMPTA_MONTANT) AS "amount",
    TP.DATE_COMPTABLE AS "date",
    TV.EXERCICE AS "exercice",
    RO.CODE AS "project_code",
    RN.CODE AS "account_code",
    TP.NUMERO_PIECE AS "engagement",
    TP.OBJET AS "name",
    TP.OBJET AS "complement",
    RT.LIBELLE AS "tiers",
    OB.CODE AS "managementUnit"
FROM 
    TRESO_DV_VENTILATIONS TV
INNER JOIN REF_OPERATIONS RO ON RO.ID = TV.OPERATION_ID
INNER JOIN TRESO_DV_LIGNES TL ON TL.ID = TV.LIGNE_ID 
INNER JOIN TRESO_DV_PIECES TP ON TP.ID = TL.PIECE_ID 
LEFT JOIN REF_NATURES RN ON RN.ID = TV.NATURE_ID 
INNER JOIN REF_TIERS RT ON RT.ID = TL.TIERS_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = TV.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = TV.ETABLISSEMENT_ID
WHERE 
    TV.ETABLISSEMENT_ID = :identifier
    AND RO.TYPE_OPERATION = 1
    AND TP.ETAPE_WF_CODE = 'REGLE'
    AND TP.NATURE = 4
    AND TV.EXERCICE IN(:years)
GROUP BY TP.DATE_COMPTABLE, TV.EXERCICE, RO.CODE, RN.CODE, TP.NUMERO_PIECE, TP.OBJET, RT.LIBELLE, OB.CODE;

6.1.1.2 AE Committed

SELECT 
    SUM(CB.MONTANT_COMPTABILISATION) AS "amount", 
    CB.DATE_ECRITURE AS "date",
    CB.EXERCICE as "exercice",
    RO.CODE AS "project_code",
    RN.CODE AS "account_code",
    CASE
        WHEN EP.NATURE_DOCUMENT = 'TRANCHE' THEN CONCAT(MM.NUMERO_PIECE,'/',CB.PIECE_NUMERO_PIECE, ' (', CB.PIECE_NATURE_DOCUMENT, ') ')
        ELSE CONCAT(CB.PIECE_NUMERO_PIECE, ' (', CB.PIECE_NATURE_DOCUMENT, ') ')
        END AS "engagement",
    CASE
  WHEN CB.PIECE_NATURE_DOCUMENT = 'DP' THEN DP.OBJET
  WHEN CB.PIECE_NATURE_DOCUMENT = 'OD' THEN OD.OBJET
  ELSE EP.OBJET 
  END AS "name",
    CASE
        WHEN EP.NATURE_DOCUMENT = 'TRANCHE' THEN RT2.LIBELLE
        WHEN CB.PIECE_NATURE_DOCUMENT = 'DP' THEN RT3.LIBELLE
        WHEN CB.PIECE_NATURE_DOCUMENT = 'REJ' THEN RT4.LIBELLE
        WHEN CB.PIECE_NATURE_DOCUMENT = 'OD' THEN RT5.LIBELLE
        ELSE RT.LIBELLE
        END AS "tiers",
    OB.CODE AS "managementUnit"
FROM 
    comptabilites_budgetaires CB
INNER JOIN ref_operations RO ON RO.ID = CB.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = CB.NATURE_ID AND RN.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
LEFT JOIN ej_pieces EP ON EP.ID = CB.PIECE_ID
LEFT JOIN ref_tiers RT ON RT.ID = EP.TIERS_ID 
LEFT JOIN marches_marches MM ON MM.ID = EP.PIECE_MARCHE_ID
LEFT JOIN ref_tiers RT2 ON RT2.ID = MM.TIERS_ID
LEFT JOIN dp_pieces DP ON DP.ID = CB.PIECE_ID
LEFT JOIN ref_tiers RT3 ON RT3.ID = DP.TIERS_ID
LEFT JOIN ej_pieces EP2 ON EP2.ID = EP.PIECE_REFERENCE_ID
LEFT JOIN ref_tiers RT4 ON RT4.ID = EP2.TIERS_ID
LEFT JOIN od_pieces OD ON OD.ID = CB.PIECE_ID
LEFT JOIN od_pieces_liees ODP ON ODP.PIECE_OD_ID = OD.ID AND ODP.NATURE_PIECE_LIEE = 'EJ'
LEFT JOIN ej_pieces EP3 ON EP3.ID = ODP.PIECE_LIEE_ID
LEFT JOIN ref_tiers RT5 ON RT5.ID = EP3.TIERS_ID 
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = CB.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
 WHERE 
    CB.TYPE_COMPTA = 'AE'
    AND CB.EST_CONSOMMATION = 1
    AND CB.EST_BUDGET = 0
    AND CB.ETABLISSEMENT_ID = :identifier
    AND CB.EXERCICE IN (:years)
GROUP BY CB.DATE_ECRITURE, CB.EXERCICE, RO.CODE, RN.CODE, MM.NUMERO_PIECE, CB.PIECE_NUMERO_PIECE, CB.PIECE_NATURE_DOCUMENT, DP.OBJET, OD.OBJET, EP.OBJET, EP.NATURE_DOCUMENT, RT2.LIBELLE, RT3.LIBELLE, RT4.LIBELLE, EP3.ID, RT5.LIBELLE, RT.LIBELLE, OB.CODE;

6.1.1.3 AE

SELECT
    RO.CODE AS "project_code",
    CB.PIECE_OBJET AS "name",
    CB.EXERCICE AS "exercice",
    CB.DATE_ECRITURE AS "date",
    SUM(CB.MONTANT_COMPTABILISATION) AS "amount",
    CASE
        WHEN EP.NATURE_DOCUMENT = 'TRANCHE' THEN CONCAT(MM.NUMERO_PIECE,'/',CB.PIECE_NUMERO_PIECE, ' (', CB.PIECE_NATURE_DOCUMENT, ') ')
        ELSE CONCAT(CB.PIECE_NUMERO_PIECE, ' (', CB.PIECE_NATURE_DOCUMENT, ') ')
        END AS "engagement",
    RN.CODE AS "account_code",
    OB.CODE AS "managementUnit"
FROM
    comptabilites_budgetaires CB
INNER JOIN ref_operations RO ON RO.ID = CB.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = CB.NATURE_ID AND RN.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
LEFT JOIN ej_pieces EP ON EP.ID = CB.PIECE_ID
LEFT JOIN marches_marches MM ON MM.ID = EP.PIECE_MARCHE_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = CB.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
WHERE
    CB.EST_BUDGET = 1 
    AND CB.ETABLISSEMENT_ID = :identifier
    AND CB.TYPE_COMPTA = 'AE'
    AND CB.EXERCICE IN (:years)
GROUP BY RO.CODE, CB.PIECE_OBJET, CB.EXERCICE, CB.DATE_ECRITURE, EP.NATURE_DOCUMENT, MM.NUMERO_PIECE, CB.PIECE_NUMERO_PIECE, CB.PIECE_NATURE_DOCUMENT, RN.CODE, OB.CODE;

6.1.1.4 CP

SELECT
    RO.CODE AS "project_code",
    CB.PIECE_OBJET AS "name",
    CB.EXERCICE AS "exercice",
    CB.DATE_ECRITURE AS "date",
    SUM(CB.MONTANT_COMPTABILISATION) AS "amount",
    CONCAT(CB.PIECE_NUMERO_PIECE, ' (', CB.PIECE_NATURE_DOCUMENT, ') ') AS "engagement",
    RN.CODE AS "account_code",
    OB.CODE AS "managementUnit"
FROM
    comptabilites_budgetaires CB
INNER JOIN ref_operations RO ON RO.ID = CB.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = CB.NATURE_ID AND RN.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = CB.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
WHERE
    CB.EST_BUDGET = 1 
    AND CB.ETABLISSEMENT_ID = :identifier
    AND CB.TYPE_COMPTA = 'CP'
    AND CB.EXERCICE IN (:years)
GROUP BY RO.CODE, CB.PIECE_OBJET, CB.EXERCICE, CB.DATE_ECRITURE, CB.PIECE_NUMERO_PIECE, CB.PIECE_NATURE_DOCUMENT, RN.CODE, OB.CODE;

6.1.1.5 CP Estimated

SELECT
        SUM(CB.VAL_COMPTA_CHARGE_BUD)-SUM(isnull(cp.montantCP,0)) AS "amount",
        RO.CODE AS "project_code",
        RN.CODE AS "account_code",
        DP.EXERCICE as "exercice",
        DP.DATE_RECEPTION AS "date",
        FF.DATE_FACTURE AS "date_facture",
        DP.REFERENCE_FACTURE AS "mandat",
        DP.OBJET AS "name",
        DP.OBJET AS "complement",
        RT.LIBELLE AS "tiers",
        OB.CODE AS "managementUnit"
FROM
    dp_ventilations CB
INNER JOIN ref_operations RO ON RO.ID = CB.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = CB.NATURE_ID AND CB.ETABLISSEMENT_ID = RN.ETABLISSEMENT_ID
INNER JOIN dp_pieces DP ON DP.ID = CB.PIECE_ID
INNER JOIN ref_tiers RT ON RT.ID = DP.TIERS_ID
LEFT JOIN ff_pieces FF ON FF.ID = DP.PIECE_FF_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = CB.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
LEFT JOIN (SELECT cbud.VENTILATION_ID, sum(CBUD.MONTANT_COMPTABILISATION) montantCP
          FROM comptabilites_budgetaires CBUD
          WHERE CBUD.TYPE_COMPTA = 'CP' AND CBUD.EST_CONSOMMATION = 1 AND CBUD.EST_BUDGET = 0 AND CBUD.ETABLISSEMENT_ID = :identifier
          AND CBUD.EXERCICE IN (:years)
          GROUP BY cbud.VENTILATION_ID) cp on cp.VENTILATION_ID = cb.ID
WHERE
    CB.EST_ANNULE = 0
    AND CB.ETABLISSEMENT_ID = :identifier
    AND CB.EXERCICE IN (:years)
GROUP BY RO.CODE, DP.EXERCICE, DP.DATE_RECEPTION, FF.DATE_FACTURE, DP.REFERENCE_FACTURE, DP.OBJET, RN.CODE, RT.LIBELLE, OB.CODE;

6.1.1.6 CP Committed

SELECT 
    SUM(CB.MONTANT_COMPTABILISATION) AS "amount", 
    CB.DATE_ECRITURE AS "date",
    CB.EXERCICE as "exercice",
    RO.CODE AS "project_code",
    RN.CODE AS "account_code",
    CASE CB.PIECE_NATURE_DOCUMENT
        WHEN 'OD' THEN RT2.LIBELLE
        ELSE RT.LIBELLE
  END AS "tiers",
    CASE CB.PIECE_NATURE_DOCUMENT
        WHEN 'OD' THEN OD.NUMERO_PIECE
        ELSE DP.NUMERO_PIECE
        END AS "bordereau",
    CASE CB.PIECE_NATURE_DOCUMENT
        WHEN 'OD' THEN OD.OBJET
        ELSE DP.OBJET
        END AS "complement",
    CASE CB.PIECE_NATURE_DOCUMENT
        WHEN 'OD' THEN OD.OBJET
        ELSE DP.OBJET
        END AS "name",
    OB.CODE AS "managementUnit"
FROM 
    comptabilites_budgetaires CB
INNER JOIN ref_operations RO ON RO.ID = CB.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = CB.NATURE_ID AND RN.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
LEFT JOIN dp_ventilations DV ON DV.ID = CB.VENTILATION_ID
LEFT JOIN dp_pieces DP ON DP.ID = DV.PIECE_ID
LEFT JOIN ref_tiers RT ON RT.ID = DP.TIERS_ID
LEFT JOIN od_pieces OD ON OD.ID = CB.PIECE_ID
LEFT JOIN OD_PIECES_LIEES ODL ON ODL.PIECE_OD_ID = OD.ID AND ODL.NATURE_PIECE_LIEE = 'DP'
LEFT JOIN DP_PIECES DP2 ON DP2.ID = ODL.PIECE_LIEE_ID
LEFT JOIN REF_TIERS RT2 ON RT2.ID = DP2.TIERS_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = CB.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
WHERE 
    CB.TYPE_COMPTA = 'CP'
    AND CB.EST_CONSOMMATION = 1
    AND CB.EST_BUDGET = 0
    AND CB.ETABLISSEMENT_ID = :identifier
    AND CB.EXERCICE IN (:years)
GROUP BY CB.DATE_ECRITURE, CB.EXERCICE, RO.CODE, RN.CODE, CASE CB.PIECE_NATURE_DOCUMENT WHEN 'OD' THEN RT2.LIBELLE ELSE RT.LIBELLE END, CASE CB.PIECE_NATURE_DOCUMENT WHEN 'OD' THEN OD.NUMERO_PIECE ELSE DP.NUMERO_PIECE END, CASE CB.PIECE_NATURE_DOCUMENT WHEN 'OD' THEN OD.OBJET ELSE DP.OBJET END, OB.CODE;

6.1.2 Recettes

6.1.2.1 DV

SELECT
    SUM(TL.VAL_COMPTA_MONTANT) AS "amount",
    TP.DATE_COMPTABLE AS "date",
    TV.EXERCICE AS "exercice",
    RO.CODE AS "project_code",
    RN.CODE AS "account_code",
    TP.NUMERO_PIECE AS "convention_title",
    TP.OBJET AS "name",
    TP.OBJET AS "engagement",
    RT.LIBELLE AS "tiers",
    OB.CODE AS "managementUnit"
FROM 
    TRESO_DV_VENTILATIONS TV
INNER JOIN REF_OPERATIONS RO ON RO.ID = TV.OPERATION_ID
INNER JOIN TRESO_DV_LIGNES TL ON TL.ID = TV.LIGNE_ID 
INNER JOIN TRESO_DV_PIECES TP ON TP.ID = TL.PIECE_ID 
LEFT JOIN REF_NATURES RN ON RN.ID = TV.NATURE_ID AND RN.ETABLISSEMENT_ID = TV.ETABLISSEMENT_ID
INNER JOIN REF_TIERS RT ON RT.ID = TL.TIERS_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = TV.ORGANISATION_BUDGETAIRE_ID AND TV.ETABLISSEMENT_ID = TV.ETABLISSEMENT_ID
WHERE 
    TV.ETABLISSEMENT_ID = :identifier
    AND RO.TYPE_OPERATION = 1
    AND TP.ETAPE_WF_CODE = 'REGLE'
    AND TP.NATURE = 3
    AND TV.EXERCICE IN (:years)
GROUP BY TP.DATE_COMPTABLE, TV.EXERCICE, RO.CODE, RN.CODE, TP.NUMERO_PIECE, TP.OBJET, RT.LIBELLE, OB.CODE;

6.1.2.2 Requested

SELECT
    SUM(TL.VAL_COMPTA_MONTANT_TTC_A_ENCAISSER) AS "amount",
    TP.DATE_COMPTABLE AS "date",
    TV.EXERCICE as "exercice",
    RO.CODE AS "project_code",
    RN.CODE AS "account_code",
    TP.NUMERO_PIECE AS "convention_title",
    TP.OBJET AS "name",
    TP.OBJET AS "engagement",
    RT.LIBELLE AS "tiers",
    OB.CODE AS "managementUnit"
FROM
    titres_ventilations TV
INNER JOIN titres_lignes TL ON TL.ID = TV.LIGNE_ID
INNER JOIN titres_pieces TP ON TP.ID = TL.PIECE_ID
INNER JOIN ref_tiers RT ON RT.ID = TP.TIERS_ID
INNER JOIN ref_operations  RO ON RO.ID = TV.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = TV.NATURE_ID AND RN.ETABLISSEMENT_ID = TV.ETABLISSEMENT_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = TV.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = TV.ETABLISSEMENT_ID
WHERE
    TL.VAL_COMPTA_MONTANT_TTC_A_ENCAISSER <> 0
    AND TV.ETABLISSEMENT_ID = :identifier
    AND RO.NIVEAU_ANALYTIQUE_ORDRE_DE_PRESENTATION = :level
    AND TV.EXERCICE IN (:years)
GROUP BY TP.DATE_COMPTABLE, TV.EXERCICE, RO.CODE, RN.CODE, TP.NUMERO_PIECE, TP.OBJET, RT.LIBELLE, OB.CODE;

6.1.2.3 Perceived

SELECT 
    SUM(CB.MONTANT_COMPTABILISATION) AS "amount", 
    CB.DATE_ECRITURE AS "date",
    CB.EXERCICE as "exercice",
    RO.CODE AS "project_code",
    RN.CODE AS "account_code",
    TP.NUMERO_PIECE AS "convention_title",
    TP.OBJET AS "name",
    TP.OBJET AS "engagement",
    RT.LIBELLE AS "tiers",
    OB.CODE AS "managementUnit"
FROM 
    comptabilites_budgetaires CB
INNER JOIN ref_operations  RO ON RO.ID = CB.OPERATION_ID
INNER JOIN ref_natures RN ON RN.ID = CB.NATURE_ID AND RN.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
INNER JOIN titres_ventilations TV ON TV.ID = CB.VENTILATION_ID
INNER JOIN titres_pieces TP ON TP.ID = TV.PIECE_ID
INNER JOIN ref_tiers RT ON RT.ID = TP.TIERS_ID
LEFT JOIN REF_ORGANISATIONS_BUDGETAIRES OB ON OB.ID = CB.ORGANISATION_BUDGETAIRE_ID AND OB.ETABLISSEMENT_ID = CB.ETABLISSEMENT_ID
WHERE 
    CB.TYPE_COMPTA = 'RECETTE'
    AND CB.EST_CONSOMMATION = 1
    AND CB.EST_BUDGET = 0
    AND CB.ETABLISSEMENT_ID = :identifier
    AND RO.NIVEAU_ANALYTIQUE_ORDRE_DE_PRESENTATION = :level
    AND CB.EXERCICE IN (:years)
GROUP BY CB.DATE_ECRITURE, CB.EXERCICE, RO.CODE, RN.CODE, TP.NUMERO_PIECE, TP.OBJET, RT.LIBELLE, OB.CODE;

6.2 Tutoriel : ajout d’un module

Pour faciliter l’appropriation de la documentation nous nous appuyons sur un cas d’usage.

Dans cette partie, nous allons créer de toutes pièces une fonctionnalité de gestion de congés qui devra s’intégrer dans l’architecture modulaire de Zend ainsi que dans le « framework » EVA. Nous commençons par la création du module technique représentant le conteneur fonctionnel puis l’API Create Read Update Delete afin d’interagir avec l’interface utilisateur.

Dans EVA les modules représentent des fonctionnalités orientées métier, il en existe certains qui sont techniques (Bootstrap, Core et Application afin de permettre une réutilisation de certaines classes utilitaires). Pour notre nouvel ajout fonctionnel, il faut créer un module au sens Zend dont la structure est donnée ci-contre.

Attention : pour les namespaces PHP la casse est importante lorsque l’on travaille sous Unix.

image

6.2.1 Dossier config

La configuration du module contient différents fichiers :

  1. doctrine.config.php : configuration technique de l’ORM pour ce module

    1. <?php
    2. namespace Holiday;
    3.  
    4. return [
    5.     'doctrine' => [
    6.         'driver' => [
    7.             'holiday_entities' => [
    8.                 'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
    9.                 'cache' => 'array',
    10.                 'paths' => [
    11.                     __DIR__ . '/../src/Entity'
    12.                 ]
    13.             ],
    14.             'orm_environment' => [
    15.                 'drivers' => [
    16.                     'Holiday\Entity' => 'holiday_entities'
    17.                 ]
    18.             ]
    19.         ]
    20.     ]
    21. ];
    22.
    

    Cette configuration définit l’endroit où sont déposées les classes représentant le modèle de données. La propriété orm_environment doit être définie afin que tous les modules soient chargés lors de la création de l’entity manager de Doctrine. Le gestionnaire est créé dynamiquement lors du chargement de la plateforme « client » depuis la configuration initiée dans la base d’admin (cf DynamicEntityManager.php).

  2. icons.config.php : ce fichier définit les icônes spécifiques au module, les icônes génériques étant définies dans le module Application. Un ViewHelper Icon (module Core) permet d’utiliser cette configuration dans les vues. Dans EVA, les icônes sont gérées au travers de la bibliothèque fontawesome (disponible dans le dossier public/vendor/font-awesome).

      1. <?php
      2.  
      3. return [
      4.     'icons' => [
      5.         'holiday'   => [
      6.             'type'    => 'css',
      7.             'element' => 'i',
      8.             'classes' => 'fa fa-suitcase',
      9.         ],
      10.     ],
      11. ];
      12. 
    
  3. menu.config.php : comme son nom l’indique, ce fichier gère l’intégration du module dans le menu de la plateforme. Il est possible d’ajouter un élément dans le menu principal ou bien comme sous-menu de l’administration. L’affichage du menu final est géré par un ViewHelper dans le module Core (Menu.php).

    1. <?php
    2.  
    3. return [
    4.     'menu' => [
    5.         'client-menu' => [
    6.             'holiday' => [
    7.                 'icon'     => 'holiday',
    8.                 'right'    => 'holiday:e:holiday:r',    // Zend ACL
    9.                 'title'    => 'holiday_module_title',   // i18n
    10.                 'href'     => 'holiday',                // route to main view
    11.                 'priority' => 1000,                     // position in menu
    12.             ],
    13.             'administration' => [                       // to create a view in admin menu 
    14.                 'children' => [                         // merged with the other modules sub menu
    15.                     'holiday' => [
    16.                         'icon'     => 'holiday',
    17.                         'right'    => 'holiday:e:holiday:r',
    18.                         'title'    => 'holiday_module_title',
    19.                         'href'     => 'holiday/types',  // to create types of holidays (CP, RTT, ...)
    20.                         'priority' => 12,               // position in admin menu
    21.                     ],
    22.                 ]
    23.             ],
    24.  
    25.         ],
    26.     ],
    27. ];
    28.  
    
  4. module.config.php : contient les configurations Zend et spécifiques au module courant ; définition des chemins pour les vues et les traductions mais aussi pour le fonctionnement interne de la plateforme.

    1. <?php
    2.  
    3. return [
    4.     'module' => [
    5.         'holiday' => [
    6.             'environments' => [
    7.                 'client'            // 'admin' is for the super admin UI
    8.             ],
    9.             'active'   => false,    // inactive by default
    10.             'required' => false,
    11.             'activable' => true,    // activatable by client
    12.             'acl' => [
    13.                 'entities' => [
    14.                     [
    15.                         'name'     => 'holiday',
    16.                         'class'    => 'Holiday\Entity\Holiday',
    17.                         'keywords' => false,    // cannot add keywords on the entity
    18.                         'owner'    => ['own']
    19.                     ]
    20.                 ]
    21.             ],
    22.             'configuration' => []
    23.         ]
    24.     ],
    25.     'view_manager' => [
    26.         'template_path_stack' => [
    27.             __DIR__ . '/../view'
    28.         ],
    29.     ],
    30.     'translator' => [
    31.         'translation_file_patterns' => [    // i18n
    32.             [
    33.                 'type'     => 'phparray',
    34.                 'base_dir' => __DIR__ . '/../language',
    35.                 'pattern'  => '%s.php',
    36.             ],
    37.         ],
    38.     ],
    39. ];
    40.  
    
  5. router.config.php : dans ce fichier sont définies les urls qui permettent de faire le lien entre les vues et les méthodes dans les contrôleurs MVC. Deux types de contrôleurs sont gérés sur EVA, ceux qui sont liés aux vues Zend et ceux qui interagissent avec AngularJS dans le dossier API (cf section suivante pour la configuration complète).

    1. <?php
    2.  
    3. namespace Holiday;
    4.  
    5. use Core\Controller\Factory\ServiceLocatorFactory;
    6.  
    7. return [
    8.     'router'      => [
    9.         'client-routes' => [        // 'admin-routes' for the super admin UI            
    10.             
    11.         ],
    12.     ],
    13.     'controllers' => [
    14.         'factories' => [
    15.             Controller\IndexController::class    => ServiceLocatorFactory::class,
    16.             Controller\API\HolidayController::class => ServiceLocatorFactory::class,
    17.         ],
    18.         'aliases' => [
    19.             'Holiday\Controller\Index'    => Controller\IndexController::class,
    20.             'Holiday\Controller\API\Holiday' => Controller\API\HolidayController::class,
    21.         ],
    22.     ],
    23. ];
    24. 
    

Pour approfondir ces notions, référez-vous à la documentation Zend.

6.2.1.1 Dossier langages

Dans le dossier language, on ajoute un fichier fr_FR.php qui contiendra un ensemble de traductions sous forme d’un tableau clé-valeurs.

1. <?php
2.  
3. return [
4.     'holiday_entity'        => 'congé',
5.     'holiday_entities'      => '[[ holiday_entity ]]s',
6.  
7.     'holiday_module_title'  => [[ holiday_entities | ucfirst ]]'
8. ];

6.2.1.2 Dossier src

image

C’est dans src que l’on va créer les fichiers représentant le backend du module : les contrôleurs, le modèle de données et un ViewHelper pour les types de congés disponibles pour commencer.

⚠️ Attention à la casse des noms de dossier, ils vont définir les espaces de nom (namespace) des fichiers

Notre entité va porter elle-même la couche d’accès aux données (repository) car les actions dont nous allons avoir besoin sont basiques (CRUD).

Dans EVA, la couche de services est très peu utilisée car la plupart des fonctionnalités sont développées au sein des contrôleurs.

Un fichier Module.php est présent à la racine, il contient certaines informations nécessaires au bon fonctionnement du module.

1. <?php
2. namespace Holiday;
3.  
4. use Core\Module\AbstractModule;
5. use Holiday\View\Helper\HolidayType;
6. use Zend\ServiceManager\ServiceManager;
7.  
8. class Module extends AbstractModule 
9. {
10.  
11.     public function getServiceConfig()
12.     {
13.         return [
14.             'factories' => [
15.                 // no services
16.             ]
17.         ];
18.     }
19.  
20.     public function getViewHelperConfig()
21.     {
22.         return [
23.   'factories' => [
24.                 'holidayTypes' => function (ServiceManager $sm) {
25.                     return new HolidayType();
26.                 }
27.             ]
28.         ];
29.     }
30.  
31. }
32.  

6.2.1.3 Dossier view

image

Le répertoire view contient l’interface utilisateur spécifique à une url, la mise en page générale de la plateforme étant gérée dans le module Application (layout).

Le premier sous-dossier de view doit porter le nom du module, sous module les dossiers portant le nom des contrôleurs et les fichiers ont le nom des actions du contrôleur liées à une url.

Les deux vues form et list vont nous permettre de développer l’interface CRUD.

A présent lançons la plateforme pour vérifier l’intégration du nouveau module :

1. php -S client1.eva.localhost:8000 -t public/
1. Uncaught Zend\ModuleManager\Exception\RuntimeException: Module (Holiday) could not be initialized

EVA utilise composer comme autoloader afin d’initialiser les modules présents ; il faut donc ajouter cette ligne dans le fichier composer.json sous autload -> psr-4 :

1. "Holiday\\": "module/Holiday/src/"

Ensuite on lance la commande pour réinitialiser l’autoloader :

1. composer dump-autoload

On relance la plateforme et le menu Congés n’apparaît toujours pas, c’est normal puisque nous ne l’avions pas activé par défaut (cf fichier module) et qu’il faut un droit d’accès (cf fichier menu). Dans la page administration > configuration > modules, trouvez le module Congés et activez-le ; ensuite il faut aller dans administration > rôles sélectionner le rôle sur lequel donner accès au module (Administrateur) et lui donner les différents droits (CRUD).

imageimageimage

Notre module est accessible

6.2.2 Ajout d’une fonctionnalité CRUD

6.2.2.1 Modèle de données

Comme nous l’avons vu précédemment, nous allons créer une entité pour le modèle de données : Holiday (les accesseurs sont omis volontairement).

1. <?php
2. 
3. namespace Holiday\Entity;
4.  
5. use Core\Entity\BasicRestEntityAbstract;
6. use Doctrine\ORM\EntityManager;
7. use Doctrine\ORM\Mapping as ORM;
8. use User\Entity\User;
9. use Zend\InputFilter\Factory;
10.  
11. 
12. /**
13.  * Holiday
14.  * @ORM\Entity
15.  * @ORM\Table(name="holiday_holiday")
16. */
17. class Holiday extends BasicRestEntityAbstract
18. {
19. 
20.     /**
21.      * @var int
22.      * @ORM\Id
23.      * @ORM\Column(type="integer")
24.      * @ORM\GeneratedValue(strategy="AUTO")
25.      */
26.     private $id;
27.  
28.     /**
29.      * @var \DateTime
30.      * @ORM\Column(type="datetime", nullable=true)
31.      */
32.     private $startDate; // start of holiday period
33.  
34.     /**
35.      * @var \DateTime
36.      * @ORM\Column(type="datetime", nullable=true)
37.      */
38.     private $endDate;  // end of holiday period
39.  
40.     /**
41.      * @var User
42.      * @ORM\ManyToOne(targetEntity="User\Entity\User")
43.      * @ORM\JoinColumn(onDelete="SET NULL")
44.      */
45.     private $requestor;
46.  
47.     /**
48.     * @var string
49.     * @ORM\Column(type="text")
50.     */
51.    private $status;  // status of the request
52.  
53.     /**
54.      * @var User
55.      * @ORM\ManyToOne(targetEntity="User\Entity\User")
56.      * @ORM\JoinColumn(onDelete="SET NULL")
57.      */
58.     private $validator;
59.  
60. }
61.  

Afin de satisfaire les contraintes de l’héritage, nous allons ajouter un validateur de champs Zend à la classe ainsi que des valeurs possibles pour la propriété statut.

1.     /**
2.      * Zend validation layer
3.      */
4.     public function getInputFilter(EntityManager $entityManager)
5.     {
6.         $inputFilterFactory = new Factory();
7.         $inputFilter = $inputFilterFactory->createInputFilter([
8.             [
9.                 'name'     => 'requestor',
10.                 'required' => true,
11.             ],[
12.                 'name'     => 'validator',
13.                 'required' => true,
14.             ],
15.             [
16.                 'name'     => 'startDate',
17.                 'required' => true,
18.             ],
19.             [
20.                 'name'     => 'endDate',
21.                 'required' => true,
22.             ],
23.             [
24.    'name' => 'status',
25.    'required' => true,
26.    'validators' => [
27.    [
28.     'name' => 'Callback',
29.     'options' => [
30.      'callback' => function ($value, $context) use($entityManager){
31.       return in_array($value, Holiday::getStatuses());
32.                               },
33.      'message' => 'project_field_publicationStatus_error_right',
34.     ],
35.    ]],
36.   ],
37.         ]);
38.         return $inputFilter;
39.     }
40.  
41.   
42. 
43. 
44.   const STATUS_DRAFT = 'draft';
45.  const STATUS_PENDING = 'pending';
46.  const STATUS_VALIDATED = 'validated';
47.  const STATUS_REFUSED = 'refused';
48.  
49.   /**
50.   * @return array
51.   */
52.  public static function getStatuses()
53.  {
54.   return [
55.    self::STATUS_DRAFT,
56.    self::STATUS_PENDING,
57.    self::STATUS_VALIDATED,
58.    self::STATUS_REFUSED
59.   ];
60.  }
61.  

Il faut maintenant mettre à jour la base de données, on utilise un script dans le dossier bin :

1. ./bin/update (Unix)
2. .\bin\update.bat (Win)

6.2.2.2 Fonctionnalités Read

Deux pages vont être créées pour charger les données de congé, une vue liste et une vue formulaire pour interagir avec une demande de congés. Dans le contrôleur principal (IndexController) nous allons créer deux méthodes pour les lier aux vues Zend.

La vue liste :

1. <?php
2. namespace Holiday\Controller;
3.  
4. use Core\Controller\AbstractActionSLController;
5. use Zend\View\Model\ViewModel;
6.  
7. class IndexController extends AbstractActionSLController
8. {
9.  
10.     public function listAction()
11.     {
12.         if (!$this->acl()->isAllowed('holiday:e:holiday:r')) {
13.   return $this->notFoundAction();
14.        }
15.  
16.         // le chargement des listes est géré par le composant grille
17.         // cette fonction ne doit donc retourner que des données annexes
18.         return new ViewModel([
19.             
20.         ]);
21.     }
22. }

Le routage entre le contrôleur et la vue, à insérer dans le fichier outer.config.php à l’intérieur des client-routes :

1. 'holiday' => [          
2.       'type' => 'Literal',
3.       'options' => [
4.              'route'    => '/holiday',
5.              'defaults' => [
6.                    'controller' => 'Holiday\Controller\Index',
7.                    'action'     => 'list'             // list of holidays page
8.              ]
9.       ],
10.       'may_terminate' => true,
11.  ]

Dans le fichier list.phtml commençons à créer la vue Zend :

1. <ul class="breadcrumb">
2.     <li><a href="<?= $this->url('root') ?>"><?= $this->icon('home') ?></a></li>
3.     <li><a href="<?= $this->url('holiday') ?>"><?= $this->translate('holiday_module_title') ?></a></li>
4.     <li class="active"><?= $this->translate('holiday_nav_list') ?></li>
5. </ul>
6.  
7. <h1 class="page-title">
8.     <?= $this->icon('profile') ?> <?= $this->translate('holiday_module_title') ?>
9. </h1>
10.  
11. <div class="row row-padding">
12.  <div class="col-xs-12">
13.         <!-- ADD GRID THERE -->
14.     </div>
15. </div>
16.  
17. <?php
18.     // display the link for holiday creation
19.  $fabLinks = [];
20.  if ($this->acl()->isAllowed('holiday:e:holiday:c')) {
21.     $fabLinks[] = [
22.   'href' => $this->url('holiday/form'),
23.   'text' => $this->icon('create', 'fa-fw') . ' ' . $this->translate('holiday_nav_form'),
24.     ];
25.  }
26.  echo $this->fabMenu($fabLinks);
27.  

On voit dans cet extrait l’utilisation de plusieurs gestionnaires de vue (ViewHelper) :

  • $this->icon : afficher une icône tirée du fichier de configuration

  • $this->translate : afficher une traduction du fichier de langues

  • $this->url : créer un lien (Zend)

  • $this->acl : jouer avec la gestion des droits Zend

  • $this->fabMenu : afficher le menu contextuel (les liens sont à créer dans la vue)

La page est vide, nous allons maintenant afficher la liste des demandes de congés ; pour cela nous devons ajouter un composant grille (table-builder) qui est utilisé pour ce genre de rendu partout sur la plateforme. Ce composant fonctionne sous AngularJS, il faut donc une intégration de ce framework dans notre page.

Dans la vue layout.phtml dans le module Application contient l’essentiel du framework AngularJS afin de pouvoir lancer nos composants spécifiques ; on utilise pour cela le ViewHelper angular() (module Core) qui insère la balise script dans nos vues. Nous devons utiliser le même Helper pour insérer nos scripts.

On crée d’abord un contrôleur AngularJS dans le dossier public > js > src > holiday > controller > list.js :

1. (function(app) {
2.  
3.     app.controller('holidayListController', [
4.         '$scope', 'flashMessenger', 'translator',
5.         function profileListController($scope, flashMessenger, translator) {
6.             var self = this;
7.  
8.  
9.             self.init = function init() {
10.  
11.             };
12.  
13.         }
14.     ])
15. })(window.EVA.app);
16.  

On insère ce script à la fin de notre vue comme cela est préconisé afin d’éviter qu’une erreur Javascript vienne perturber toute la page.

1. $this->angular('js/src/holiday/controller/list.js');

Le composant grille ne fonctionne que lorsque des données sont chargées depuis l’API de la fonctionnalité : il faut donc créer le point d’entrée REST de chargement des données. Comme vu précédemment, le HolidayController dans le dossier API va contenir les endpoints qui seront appelés par AngularJS :

1. <?php
2. namespace Holiday\Controller\API;
3.  
4. use Core\Controller\BasicRestController;
5.  
6. class HolidayController extends BasicRestController
7. {
8.     protected $entityClass = 'Holiday\Entity\Holiday';
9.     protected $repository  = 'Holiday\Entity\Holiday';
10.     protected $entityChain = 'holiday:e:holiday';
11.  
12.     protected function searchList($queryBuilder, &$data)
13.     {
14.         $sort  = $data['sort']  ? $data['sort']  : 'object.startDate';
15.         $order = $data['order'] ? $data['order'] : 'ASC';
16.  
17.         if (strpos($sort, '.') === false) {
18.             $sort = 'object.' . $sort;
19.         }
20.  
21.         $queryBuilder->orderBy($sort, $order);
22.     }
23. }

Pour les fonctionnalités basiques (CRUD) il n’y a rien d’autre à ajouter que les critères spécifiques de recherche car c’est le BasicRestController qui contient la majorité de la logique.

Créons maintenant le service AngularJS (public > js > src > service > holiday.js) qui va se connecter à ce contrôleur :

1. (function(app) {
2.  
3.     app.factory('holidayService', [
4.         'restFactoryService',
5.         function holidayService(restFactoryService) {
6.             return restFactoryService.create('/api/holiday');
7.         }
8.     ])
9.  
10. })(window.EVA.app);

Encore une fois, le « framework » EVA contient la logique de base (restFactoryService). On injecte ce service dans le contrôleur AngularJS

1. app.controller('holidayListController', [
2.         'holidayService', '$scope', 'flashMessenger', 'translator',
3.         function profileListController(holidayService, $scope, flashMessenger, translator) {

et on ajoute la logique du composant table-builder (options) gérant le chargement des données de congé à notre place :

1. self.holidays = [];
2. self.options = {
3.     name: 'holidays-list',
4.     col: [
5.                     'id',
6.                     'startDate',
7.                     'endDate',
8.                     'requestor.id',
9.                     'requestor.name',
10.                     'status',
11.                     'validator.id',
12.                     'validator.name'
13.                 ],
14.         searchType: 'list',
15.             sort: 'startDate',
16.             order: 'asc',
17.             pager: true,
18.             getItems: function getData(params, callback, abort) {
19.                     if (self.query != null) {
20.                         params.search.data.filters = self.query.filters;
21.                     }
22.  
23.                     holidayService.findAll(params, abort.promise).then(function (res) {
24.                         callback(res);
25.                     });
26.              }
27. }; 

L’interface de recherche du logiciel EVA respecte les standards « classiques » :

  1. Définition des colonnes à charger (col)

  2. Le tri (sort + order)

  3. La gestion de la pagination (pager) La fonction getItems permet de donner la logique de récupération des données.

On complète maintenant la vue pour afficher le tableau de données (ne pas oublier d’insérer le service AngularJS) :

1. <div class="panel" ng-controller="holidayListController as self">
2.             <table-builder class="table-builder" options="self.options" items="self.holidays" ng-init="self.init()">
3.                 <div class="panel-body">
4.                     <table class="table table-bordered table-condensed table-hover" table-builder-table fixed-header>
5.                         <thead>
6.                             <tr>
7.                                 <th tb-column tb-name="selection" class="checkbox-column">
8.                                     <input type="checkbox" ng-model="__tb.selection" />
9.                                 </th>
10.                                 <th tb-column tb-name="id" tb-sortable="true" tb-toggle="true" class="fit-content">
11.                                     <?= $this->translate('holiday_field_id') ?>
12.                                 </th>
13.                                 <th tb-column tb-name="requestor.name" tb-sortable="true" tb-toggle="true" class="fit-content">
14.                                     <?= $this->translate('holiday_field_requestor') ?>
15.                                 </th>
16.                                 <th tb-column tb-name="startDate" tb-sortable="true" tb-toggle="true" class="fit-content">
17.                                     <?= $this->translate('holiday_field_startDate') ?>
18.                                 </th>
19.                                 <th tb-column tb-name="endDate" tb-sortable="true" tb-toggle="true" class="fit-content">
20.                                     <?= $this->translate('holiday_field_endDate') ?>
21.                                 </th>
22.                                 <th tb-column tb-name="status" tb-sortable="true" tb-toggle="true" class="fit-content">
23.                                     <?= $this->translate('holiday_field_status') ?>
24.                                 </th>
25.                                 <th tb-column tb-name="validator.name" tb-sortable="true" tb-toggle="true" class="fit-content">
26.                                     <?= $this->translate('holiday_field_validator') ?>
27.                                 </th>
28.                             </tr>
29.                         </thead>
30.                         <tbody>
31.                             <tr ng-repeat="holiday in self.holidays">
32.                                 <td><input type="checkbox" ng-model="holiday.selected" /></td>
33.                                 <td>{{holiday.id}}</td>
34.                                 <td>{{holiday.requestor.name}}</td>
35.                                 <td>{{holiday.startDate | dateFormat : 'DD/MM/YYYY'}}</td>
36.                                 <td>{{holiday.endDate | dateFormat : 'DD/MM/YYYY'}}</td>
37.                                 <td>{{holiday.status}}</td>
38.                                 <td>{{holiday.validator.name}}</td>
39.                             </tr>
40.                         </tbody>
41.                     </table>
42.                 </div>
43.             </table-builder>
44.         </div>
45. 

Le composant table-builder contient quelques options à bien comprendre :

Options : interface de recherche des données

Items : variable dans laquelle le composant envoie les données en retour de la recherche

Tb-column : définition d’une donnée dans la grille

  • Tb-sortable : possibilité pour la colonne d’être triée

  • Tb-toggle : possibilité de cacher la colonne

  • Tb-name : pour l’export Excel

  • Tb-group : pour grouper les colonnes afin de mieux les retrouver dans les requêtes

  • Tb-hidden : pour cacher la colonne par défaut

  • Tb-exportable : possibilité de ne pas exporter la colonne en Excel

Le résultat : image

Ajoutons un bouton d’action afin d’ouvrir le formulaire d’édition :

1. <th tb-column tb-name="actions" class="actions-column"></th>
2. .. ..
3. <td>
4.     <div class="btn-group btn-group-actions">
5.         <?php if ($this->acl()->isAllowed('holiday:e:holiday:r')) : ?>
6.             <a ng-if="holiday._rights.read" href="<?= $this->url('holiday/form') ?>/{{holiday.id}}" class="btn btn-xs btn-default" tooltip="<?= $this->translate('tooltip_edit') ?>">
7.                 <?= $this->icon('edit') ?>
8.             </a>
9.         <?php endif; ?>
10.     </div>
11. </td>

6.2.2.3 Fonctionnalités Write

Write : Create, Update

Une nouvelle vue Zend représentant le formulaire de l’entité congé va servir de base pour les deux fonctionnalités, dans le cas de la création le formulaire ne sera pas renseigné.

Pour cela il faut une nouvelle entrée dans le contrôleur Zend servant aux vues :

public function formAction() {
    $id = $this->params()->fromRoute('id', false);
    if (!$this->acl()->basicFormAccess('holiday:e:holiday', $id)) {
  return $this->notFoundAction();
    }
    $holiday = $this->entityManager()->getRepository('Holiday\Entity\Holiday')->find(['id' => $id]);
    return new ViewModel([
  'holiday' => $holiday
    ]);
}

et la route correspondante dans le fichier de routage Zend dans les chemins enfant de notre routage client principal holiday :

'child_routes'  => [
    'form' => [
        'type'    => 'Segment',
        'options' => [
            'route'    => '/form[/:id]',
            'defaults' => [
                'controller' => 'Holiday\Controller\Index',
                'action'     => 'form',     // one holiday loading page
            ],
        ],
    ]
]

On crée la page form.phtml pour afficher notre formulaire :

<ul class="breadcrumb">
    <li><a href="<?= $this->url('root') ?>"><?= $this->icon('home') ?></a></li>
    <li><a href="<?= $this->url('holiday') ?>"><?= $this->translate('holiday_module_title') ?></a></li>
    <li class="active"><?= $this->translate('holiday_request_title_short'); ?><?= $this->holiday ? $this->translate('holiday_request_number') . $this->holiday->getId() : ''; ?></li>
</ul>

<h1 class="page-title">
    <?= $this->icon('holiday') ?> <?= $this->translate('holiday_request_title') ?><?= $this->holiday ? $this->translate('holiday_request_number') . $this->holiday->getId() : ''; ?>
</h1>

<div class="row row-padding" ng-controller="holidayFormController as self" ng-init="self.init(<?= $this->holiday ? $this->holiday->getId() : ''; ?>)">
    <div class="col-xs-12">
        <div class="panel">
            <div class="panel-heading">
                <?= $this->translate('holiday_nav_form') ?>
            </div>
            <div class="panel-body">
                <div class="row">
                    <div class="col-sm-12">
                        <form name="holidayForm" ng-submit="self.saveHoliday()">
                            <input type="hidden" name="id" ng-model="self.holiday.id" />
                            <div class="row">
                                <div class="col-sm-6">
                                    <label><?= $this->translate('holiday_field_requestor') ?></i></label>
                                    <user-select name="requestor" ng-model="self.holiday.requestor" multiple="false" required="true" form="holidayForm"></user-select>
                                    <field-errors field="requestor" errors="self.errors.fields"></field-errors>
                                </div>
                                <div class="col-sm-6">
                                    <label><?= $this->translate('holiday_field_validator') ?></i></label>
                                    <user-select name="validator" ng-model="self.holiday.validator" multiple="false" required="true" form="holidayForm"></user-select>
                                    <field-errors field="validator" errors="self.errors.fields"></field-errors>
                                </div>
                            </div>
                            <div class="row">
                                <div class="col-sm-6">
                                    <div class="form-group">
                                        <label><?= $this->translate('holiday_field_startDate') ?></label>
                                        <input type="text" input-date input-date-mask="date" name="startDate" class="form-control" autocomplete="off" ng-model="self.holiday.startDate" />
                                        <field-errors field="startDate" errors="self.errors.fields"></field-errors>
                                    </div>
                                </div>
                                <div class="col-sm-6">
                                    <div class="form-group">
                                        <label><?= $this->translate('holiday_field_endDate') ?></label>
                                        <input type="text" input-date input-date-mask="date" name="endDate" class="form-control" autocomplete="off" ng-model="self.holiday.endDate" />
                                        <field-errors field="endDate" errors="self.errors.fields"></field-errors>
                                    </div>
                                </div>
                            </div>
                            <div class="row">
                                <div class="col-sm-6">
                                    <div class="form-group">
                                        <label><?= $this->translate('holiday_field_status') ?></label>
                                        <select form="holidayForm" name="status" class="form-control" ng-model="self.holiday.status">
                                            <?php foreach (\Holiday\Entity\Holiday::getStatuses() as $status) {
                                                echo '<option value="' .
                                                    $status .
                                                    '" ' .
                                                    '>' .
                                                    $this->translate('holiday_status_' . $status) .
                                                    '</option>';
                                            } ?>
                                        </select>
                                        <field-errors field="status" errors="self.errors.fields"></field-errors>
                                    </div>
                                </div>
                                <div class="col-sm-6"></div>
                            </div>
                            <div class="row">
                                <div class="col-sm-12">
                                    <div class="form-group">
                                        <button type="submit" class="btn btn-submit" ng-disabled="self.loading.save">
                                            <?= $this->icon('loading', '', ['ng-if' => 'self.loading.save']) ?>
                                            <?= $this->translate('form_button_save') ?>
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<?php
$this->angular('js/src/user/directive/userSelect.js');
$this->angular('js/src/user/service/user.js');
$this->angular('js/src/holiday/service/holiday.js');
$this->angular('js/src/holiday/controller/form.js');

Puis notre contrôleur AngularJS adossé au formulaire (form.js) :

(function (app) {
    app.controller('holidayFormController', [
        'holidayService', '$scope', 'flashMessenger', 'translator',
        function holidayFormController(holidayService, $scope, flashMessenger, translator) {
            var self = this;
            self.holiday = {
                id: null,
                requestor: null,
                validator: null,
                startDate: null,
                endDate: null,
                status: 'draft'
            };
            self.loading = {
                save: false
            };
            var apiParams = {
                col: [
                    'id',
                    'startDate',
                    'endDate',
                    'requestor.id',
                    'requestor.name',
                    'status',
                    'validator.id',
                    'validator.name'
                ]
            };
            self.init = function init(holidayId) {
                if (!holidayId) {
                    return;
                }
                holidayService.find(holidayId, apiParams).then(function (res) {
                    if (res.success) {
                        self.holiday = res.data;
                    } else {
                        flashMessenger.error(translator('error_occured'));
                    }
                })
            };
            self.saveHoliday = function saveHoliday() {
                holidayService.save(self.holiday).then(function (res) {
                    if (res.success) {
                        flashMessenger.success(translator('holiday_message_saved'));
                    } else {
                        flashMessenger.error(translator('error_occured'));
                    }
                });
            };
        }
    ])
})(window.EVA.app);

Le résultat :

image

6.2.2.5 Fonctionnalité Delete

Dans la vue liste des congés, on ajoute le bouton de suppression pour chaque ligne en n’oubliant pas d’associer les droits de suppression :

<div class="btn-group btn-group-actions">
    ...
    <?php if ($this->acl()->isAllowed('holiday:e:holiday:d')) : ?>
        <a ng-if="holiday._rights.delete" class="btn btn-xs btn-default" tooltip="<?= $this->translate('tooltip_delete') ?>" ng-click="self.deleteItem(holiday)">
            <?= $this->icon('delete') ?>
        </a>
    <?php endif; ?>
</div>

Puis on crée la méthode deleteItem dans le contrôleur AngularJS list.js :

self.deleteItem = function deleteItem(holiday) {
    if (confirm(translator('holiday_question_delete').replace('%1', holiday.requestor.name))) {
        holidayService.remove(holiday).then(function (res) {
            self.holidays.splice(self.holidays.indexOf(holiday), 1);
                if (res.success) {
                    flashMessenger.success(translator('holiday_message_deleted'));
                } else {
                    flashMessenger.error(translator('error_occured'));
                }
      }
 );
    }
};