diff --git a/README-old.md b/README-old.md new file mode 100644 index 0000000000000000000000000000000000000000..997175db4252959315bfabca60b2d7b20cf95381 --- /dev/null +++ b/README-old.md @@ -0,0 +1,414 @@ +UnicaenDbImport +=============== + + +Ce module a pour but de réaliser l'import de données d'une table (ou d'un select) d'une base de données source vers une +table d'une base de données destination. + +Concrètement : + + - Si la source contient un enregistrement qui n'existe pas dans la destination, il est ajouté dans cette dernière. + - Si la source contient un enregistrement qui existe aussi dans la destination avec les mêmes valeurs de colonnes, + rien n'est fait. + - Si la source contient un enregistrement qui existe aussi dans la destination mais avec des valeurs de colonnes + différentes, les colonnes de la destination sont mises à jour en conséquence. + - Si la source ne contient pas un enregistrement qui existe dans la destination, l'enregistrement destination est historisé + (i.e. marqué "supprimé"). + - Si la source contient un enregistrement qui existe aussi dans la destination mais marqué "supprimé", ce dernier est + dé-historisé. + +Les enregistrements source et les enregistrements destination doivent avoir un identifiant unique en commun permettant +de les rapprocher : on l'appellera "code source" (cf. paramètre de config `source_code_column`). +**Cet identifiant doit être de type chaîne de caractères.** + + + +La différence avec le module UnicaenImport ? +-------------------------------------------- + +Comme son nom ne l'indique pas, UnicaenImport fonctionne uniquement entre +2 bases de données **Oracle** ; et la synchronisation est faite entièrement par le SGBD (ou presque). + +UnicaenDbImport fonctionne en majeure partie en PHP tout en déléguant au maximum au SGBD ce qu'il sait bien faire +(`select FULL OUTER JOIN` pour le différentiel entre données source et destination, fonction pour l'alimentation du registre +d'import, etc.) Par conséquent, il est possible et souhaitable de l'enrichir pour implémenter une synchronisation vers +différentes plateformes de base de données destination. Les plateformes de base de données destination suivantes sont +implémentées : + - PostgreSQL + +*NB: En revanche toutes les plateformes de base de données source sont supportées car on se contente d'y faire un select +à l'aide de l'ORM Doctrine 2 pour alimenter une table intermédiaire.* + + +Installation +------------ + + composer require unicaen/unicaen-db-import + + +Configuration +------------- + +- Récupérer les fichiers config de distribution : +```bash +cp -n vendor/unicaen/db-import/config/unicaen-db-import.global.php.dist config/autoload/unicaen-db-import.global.php +cp -n vendor/unicaen/db-import/config/unicaen-db-import.local.php.dist config/autoload/unicaen-db-import.local.php +``` + +- Adapter leur contenu à vos besoins. + + +Utilisation +----------- + +Le module fournit une ligne de commande pour : + + - lancer un import par son nom, exemple : + + ```bash + php public/index.php run import --name "IMPORT_PFI_SIFAC" + ``` + + - lancer tous les imports : + + ```bash + php public/index.php run import --all + ``` + +*NB: Cette commande exécute la synchronisation une fois. Pour synchroniser en permanence la base de données destination, +il faut programmer le lancement périodique de cette commande à l'aide de CRON (cf. plus bas pour un exemple).* + + +Contraintes +----------- + +### Identifiant commun + +L'identifiant unique commun des enregistrements source et destination doit être de type **chaîne de caractères**. + +### Source de type `'select'` + +Dans le cas d'une *source* de type `'select'`, il est nécessaire de convertir chaque colonne en +**chaîne de caractères** dans le `select`. + +- Pour une colonne de type "date", `TO_CHAR(nom_colonne,'YYYY-MM-DD')` permet de convertir + en chaînes de caractères dans un format quasi universel ; +- Pour une colonne de type numérique, `nom_colonne||''` permet par exemple de convertir + entiers et décimaux en chaînes de caractères. + +Il est également nécessaire de spécifier dans la config de la *destination* la fonction de conversion à appliquer +à chaque colonne pour la convertir en chaîne de caractères (cf. paramètre de config destination `columns_to_char`). + +### Source de type `'table'` + +Dans le cas d'une *source* de type `'table'`, il est nécessaire de spécifier dans la config de la +*destination* la fonction de conversion à appliquer à chaque colonne pour la convertir en chaîne de caractères +(cf. paramètre de config destination `columns_to_char`). + +### Table destination + +La *table destination* doit obligatoirement posséder les colonnes d'historique `created_on`, `updated_on` +et `deleted_on`. Exemple avec PostgreSQL : +```sql +ALTER TABLE TABLE_DESTINATION ADD COLUMN created_on TIMESTAMP(0) WITH TIME ZONE DEFAULT LOCALTIMESTAMP(0) NOT NULL; +ALTER TABLE TABLE_DESTINATION ADD COLUMN updated_on TIMESTAMP(0) WITH TIME ZONE; +ALTER TABLE TABLE_DESTINATION ADD COLUMN deleted_on TIMESTAMP(0) WITH TIME ZONE; +``` + + +Exemple de mise en oeuvre +------------------------- + +Import des programmes de financement SIFAC depuis une base Oracle vers une table PostgreSQL. Les données sources sont +"extraites" grâce à un select. + +- Configuration globale du module : + +`unicaen-db-import.global.php` +```php +return [ + /** + * Configuration du module UnicaenDbImport. + */ + 'import' => [ + /** + * Liste des imports. + */ + 'imports' => [ + [ + /** + * Petit nom (unique) de l'import. + */ + 'name' => 'Import_PFI_SIFAC', + + /** + * Configuration de la source de données à importer : + * - 'name' : petit nom (unique) de la source + * - 'table' : nom de la table source contenant les données à importer + * - 'select' : select SQL de mise en forme des données source à importer (NB: antinomique avec 'table') + * - 'connection' : identifiant de la connexion Doctrine à la base de données source + * - 'source_code_column' : nom de la colonne dans la table/vue source contenant l'identifiant unique + * - 'columns' : liste ordonnée des noms des colonnes à prendre en compte dans la table/vue source + */ + 'source' => [ + 'name' => 'PFI SIFAC', + 'connection' => 'doctrine.connection.orm_oracle', + 'source_code_column' => 'CODE', + 'columns' => ['LIBELLE', 'FLECHE', 'DEBUT_VALIDITE', 'FIN_VALIDITE'], + 'select' => <<<'EOT' +select distinct + MEASURE CODE, + SHORT_DESC LIBELLE, + ZFLECHE FLECHE, + to_char(TO_DATE(VALID_FROM,'YYYYMMDD'),'YYYY-MM-DD') DEBUT_VALIDITE, + to_char(TO_DATE(VALID_TO,'YYYYMMDD'),'YYYY-MM-DD') FIN_VALIDITE + from ( + SELECT + SAPSR3.FMMEASURE.MEASURE, + SAPSR3.FMMEASURET.SHORT_DESC, + SAPSR3.FMMEASURE.ZFLECHE, + SAPSR3.FMMEASURE.VALID_FROM, + SAPSR3.FMMEASURE.VALID_TO + FROM + SAPSR3.FMMEASURE, + SAPSR3.FMMEASURET + WHERE + ( (SAPSR3.FMMEASURE.CLIENT in (select A.MANDT from SAPSR3.FM01H A where A.MANDT = '500') OR SAPSR3.FMMEASURE.CLIENT is null) + and (SAPSR3.FMMEASURE.FMAREA in (select A.FIKRS from SAPSR3.FM01H A where A.FIKRS = '1010') or SAPSR3.FMMEASURE.FMAREA is null) ) + AND ( SAPSR3.FMMEASURE.CLIENT=SAPSR3.FMMEASURET.CLIENT(+) ) + AND ( SAPSR3.FMMEASURE.FMAREA=SAPSR3.FMMEASURET.FMAREA(+) ) + AND ( SAPSR3.FMMEASURE.MEASURE=SAPSR3.FMMEASURET.MEASURE(+) ) +) +EOT + , + ], + + /** + * Forçage éventuel du nom de la table intermédiaire utilisée lorsque source et destination + * ne partagent pas la même connexion. NB: cette table intermédiaire est créée/peuplée/supprimée + * dans la base de données destination à chaque import. + * En l'absence de ce forçage, le nom de la table intermédiaire sera celui de la table destination + * préfixé par "src_". + */ + 'intermediate_table' => 'src_progfin', + + /** + * Suppression automatique de la table intermédiaire "src_" au début de l'import. + * Si la suppression automatique est désactivée, l'existence de la table intermédiaire au démarrage + * de l'import fera échouer l'import. + */ + 'intermediate_table_auto_drop' => false, + + /** + * Configuration de la destination des données importées : + * - 'name' : petit nom (unique) de la destination + * - 'table' : nom de la table destination vers laquelle les données sont importées + * - 'connection' : identifiant de la connexion Doctrine à la base de données destination + * - 'source_code_column' : nom de la colonne dans la table destination contenant l'identifiant unique + * - 'columns' : liste ordonnée des noms des colonnes importées dans la table destination + * - 'columns_to_char' : format sprintf nécessaire pour mettre des colonnes au format requis (string) + */ + 'destination' => [ + 'name' => 'PFI Zébu', + 'table' => 'progfin', + 'connection' => 'doctrine.connection.orm_default', + 'source_code_column' => 'code', + 'columns' => ['libelle', 'fleche', 'debut_validite', 'fin_validite'], + 'columns_to_char' => [ + 'debut_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // car colonne destination de type TIMESTAMP + 'fin_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // idem + ], + ], + ], + ], + ], +]; +``` + +*On doit renseigner le paramètre `intermediate_table` car les données source proviennent d'un select.* + +- Configuration locale du module : + +`unicaen-db-import.local.php` +```php +return [ + /** + * Configuration Doctrine minimum requise. + */ + 'doctrine' => [ + 'connection' => [ + 'orm_oracle' => [ + 'driverClass' => 'Doctrine\\DBAL\\Driver\\OCI8\\Driver', + 'params' => [ + 'host' => 'host.domain.fr', + 'port' => '1525', + 'user' => 'x', + 'password' => 'y', + 'dbname' => 'z', + 'charset' => 'AL32UTF8', + ], + 'eventmanager' => 'orm_oracle', + ], + 'orm_default' => [ + 'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver', + 'params' => [ + 'host' => 'host.domain.fr', + 'port' => '5432', + 'charset' => 'utf8', + 'dbname' => 'a', + 'user' => 'b', + 'password' => 'c', + + ], + ], + ], + 'eventmanager' => [ + 'orm_sifac' => [ + 'subscribers' => [ + \Doctrine\DBAL\Event\Listeners\OracleSessionInit::class, + ], + ], + ], + ], +]; +``` + +*NB: les paramètres de config de la connexion `orm_default` existent sans doute déjà dans la configuration de votre +application, il est bien-sûr inutile de les répéter dans la config du module unicaen/db-import.* + +- Configuration CRON pour lancer périodiquement la synchro : +```cron +# Du lundi au vendredi, entre 6h00 et 19h45, toutes les 15 minutes +*/15 6-19 * * 1-5 root /usr/bin/php /var/www/zebu-back/public/index.php run import --all 1> /tmp/zebu-cron.log 2>&1 +``` + +- Exemple de logs affichés par la commande : + + ``` +######################## IMPORTS ######################## + +### Import des PFI SIFAC ### +05/09/2017 16:22:08 +# delete : +UPDATE progfin SET deleted_on = LOCALTIMESTAMP(0) WHERE CODE = 'fddsfsd' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'fddsfsd-99c4b652-9245-11e7-bddd-0242f725575b' ; +(1 instructions exécutées) +# insert : +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('971UP009', 'Of Wisco', ' ', '2014-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '971UP009-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('977CA025', 'Screening de 5', ' ', '2016-07-21', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '977CA025-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F950MEC1', 'MDE CAEN FONCTIONNEM', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F950MEC1-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F971CF03', 'UNIVERSITE DU HAVRE', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F971CF03-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('014DU009', 'DU RDCM', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '014DU009-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012RE001', 'COLLOQUE 50 ANS SC E', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012RE001-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012CH021', 'Typo-chronologie', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012CH021-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('013PL002', 'CMABIO', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '013PL002-99c4b652-9245-11e7-bddd-0242f725575b' ; +INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('999CM003', '2016-AGRI-133', ' ', '2016-03-30', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '999CM003-99c4b652-9245-11e7-bddd-0242f725575b' ; +(9 instructions exécutées) +# undelete : +UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '913TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '913TA017-99c4b652-9245-11e7-bddd-0242f725575b' ; +UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '920TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '920TA017-99c4b652-9245-11e7-bddd-0242f725575b' ; +UPDATE progfin SET LIBELLE = 'PAIE DIU ADOLESCENTS', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = 'P14DU910' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'P14DU910-99c4b652-9245-11e7-bddd-0242f725575b' ; +(3 instructions exécutées) +# update : +UPDATE progfin SET LIBELLE = 'PLATIN''', updated_on = LOCALTIMESTAMP(0) WHERE CODE = '925PL005' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '925PL005-PLATINvxcvxc-99c4b652-9245-11e7-bddd-0242f725575b' ; +(1 instructions exécutées) + +Import réalisé avec succès. +``` + + +Dans le moteur +-------------- + +### Table intermédiaire `SRC_*` + +Lorsque les données source sont issues d'un select, une table intermédiaire `SRC_*` est créée dans la base de données +destination à partir de ces données source. La synchronisation est ensuite réalisée entre cette table intermdiaire et +la table destination. À la fin du processus de synchronisation, cette table intermdiaire est supprimée. + +*NB: attention, si au lancement de la commande d'import la table intermédiaire `SRC_*` existe déjà dans la base de +données ete que le paramètre de config `intermediate_table_auto_drop` est à `false`, l'import échouera.* + +### Différentiel entre source et destination + +La requête utilisée pour comparer les données sources et destination pour une table destination `progfin` ressemble +à cela (syntaxe PostgreSQL) : + +```sql +SELECT create_import_metarequest_for_progfin( + src.code, src.libelle, TO_CHAR(src.debut_validite,'YYYY-MM-DD'), + dest.code, dest.libelle, TO_CHAR(dest.debut_validite,'YYYY-MM-DD'), dest.deleted_on, + 'eb1ab85c-916a-11e7-aba7-0242f725575b' +) AS operation +FROM src_progfin src +FULL OUTER JOIN progfin dest ON src.code = dest.code +; +``` + +### Fonction `create_import_metarequest_for_*` + +Le module crée automatiquement dans le SGBD pour chaque import configuré une fonction `create_import_metarequest_for_*` +chargée de : + - déterminer l'opération de mise à jour nécessaire à partir du résultat du différentiel entre données source et + destination ; + - inscrire dans un "registre d'import" (table `IMPORT_REG`) les instructions SQL nécessaires à la mise à jour de + la table destination. + +Vous trouverez dans [ce fichier](test/UnicaenDbImportUnitTest/CodeGenerator/PostgreSQL/Helper/expected_function_creation_sql.sql) +un exemple de fonction `create_import_metarequest_for_*` pour une table destination `ztemptable` dans une base PostgreSQL. + +*NB: Pour un enregistrement existant à la fois dans la source et la destination (opération "update"), la détection de +différence et donc sa mise à jour se fait colonne par colonne.* + +### Table `IMPORT_REG`, registre d'import + +Une table de travail `import_reg` est utilisée par le module pour y inscrire les opérations nécessaires à chaque +synchronisation. + +Ce registre comporte les colonnes suivantes : + - le type d'opération nécessaire : insert, update, delete, undelete + - le "source code" de l'enregistrement concerné + - le nom de la table concernée + - le nom de la colonne concernée (si c'est pertinent pour l'opération) + - la valeur de la colonne avant synchronisation (si c'est pertinent pour l'opération) + - la valeur de la colonne après synchronisation (si c'est pertinent pour l'opération) + - l'instruction SQL à exécuter pour mettre à jour la table destination + - la date d'exécution de l'instruction SQL de mise à jour de la table destination + - la date de création de la ligne de registre + - un hash permettant d'identifier l'ensemble de lignes auquel appartient cette ligne de registre + (utile pour mettre à jour la date d'exécution) + +Exemple de contenu de la table `import_reg` : + +| operation | source_code | table_name | field_name | from_value | to_value | sql | created_on | executed_on | import_hash | +|-----------|-------------|------------|------------|--------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------------------|--------------------------------------| +| insert | 013CC021 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$013CC021$$, $$Caract. verres trait$$, $$ $$, $$2017-05-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 920CA050 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA050$$, $$Tthèse CIFRE2015/119$$, $$ $$, $$2016-01-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 909CC189 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909CC189$$, $$THESE CIFRE$$, $$ $$, $$2016-02-01$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 909EA002 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909EA002$$, $$ED SIMEM FONCTIONNEM$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 913CA052 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$913CA052$$, $$ECO CORAIL$$, $$ $$, $$2014-09-26$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 917CA955 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$917CA955$$, $$CAPACITE$$, $$ $$, $$2013-11-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | F971UP03 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$F971UP03$$, $$DELAWARE$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 925CD271 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD271$$, $$CONV.2015PCM51$$, $$ $$, $$2016-01-01$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 925CD276 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD276$$, $$CONV.1060284$$, $$ $$, $$2015-12-10$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| insert | 920CA047 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA047$$, $$Projet THYMOTHE$$, $$ $$, $$2015-12-21$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| undelete | 013GR014 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$SUBVENTION COTENTIN$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2047-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$013GR014$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| undelete | 012CB018 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$Bilan sur les nouv.$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2016-12-15$$, FIN_VALIDITE = $$2018-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012CB018$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| undelete | 012RF001 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$COLLOQUE CONSTITUTIO$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2017-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012RF001$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | +| update | 971UP002 | progfin | LIBELLE | Etablissement api | Etablissement API | UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc | +| update | 971UP004 | progfin | LIBELLE | ETAB GIORGIA north | ETAB GIORGIA NORTH | UPDATE progfin SET LIBELLE = $$ETAB GIORGIA NORTH$$, updated_on = now() WHERE CODE = $$971UP004$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc | +| update | 950ME003 | progfin | LIBELLE | REGIE DE REC | REGIE DE RECETTES-EN | UPDATE progfin SET LIBELLE = $$REGIE DE RECETTES-EN$$, updated_on = now() WHERE CODE = $$950ME003$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc | +| ``` | | | | | | | | | | + +Remarques : + + - Le "hash" (`eb1ab85c-916a-11e7-aba7-0242f725575b` par exemple) dans la colonne `import_hash` permet d'identifier + un ensemble d'instructions SQL à exécuter au sein d'une même transaction dans la base de données destination. + + - Chaque instruction SQL de mise à jour de la table destination + (ex: `UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ;`) + est suivie d'une autre instruction SQL permettant d'inscrire dans le registre d'import la date d'exécution + de la fameuse instruction de mise à jour de la table destination + (ex: `UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ;`). + + - La colonne `executed_on` accueille la date d'exécution de l'instruction de mise à jour de la table destination ; + si cette date est NULL, l'instruction n'a pas été exécutée. diff --git a/README.md b/README.md index 00bfc097be73b6a551a700a35cd61a1d684e4ab8..78af97e3f28f1e3c05004fc9884b8eafc18a8997 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ UnicaenDbImport =============== - - Ce module a pour but de réaliser l'import de données d'une table (ou d'un select) d'une base de données source vers une table d'une base de données destination. @@ -22,28 +20,39 @@ Les enregistrements source et les enregistrements destination doivent avoir un i de les rapprocher : on l'appellera "code source" (cf. paramètre de config `source_code_column`). **Cet identifiant doit être de type chaîne de caractères.** +__**Table des matières**__ + + * [La différence avec le module UnicaenImport ?](#la-différence-avec-le-module-unicaenimport-) + * [Installation](#installation) + * [Configuration](#configuration) + * [Utilisation](#utilisation) + * [Contraintes](#contraintes) + * [Fonctionnement](#fonctionnement) + * [Exemples](#exemples) + + La différence avec le module UnicaenImport ? -------------------------------------------- + + Comme son nom ne l'indique pas, UnicaenImport fonctionne uniquement entre + 2 bases de données **Oracle** ; et la synchronisation est faite entièrement par le SGBD (ou presque). + + UnicaenDbImport fonctionne en majeure partie en PHP tout en déléguant au maximum au SGBD ce qu'il sait bien faire + (`select FULL OUTER JOIN` pour le différentiel entre données source et destination, fonction pour l'alimentation du registre + d'import, etc.) Par conséquent, il est possible et souhaitable de l'enrichir pour implémenter une synchronisation vers + différentes plateformes de base de données destination. Les plateformes de base de données destination suivantes sont + implémentées : + - PostgreSQL + + *NB: En revanche toutes les plateformes de base de données source sont supportées car on se contente d'y faire un select + à l'aide de l'ORM Doctrine 2 pour alimenter une table intermédiaire.* + -Comme son nom de l'indique pas, UnicaenImport fonctionne uniquement entre -2 bases de données Oracle ; et la synchronisation est faite entièrement par le SGBD (ou presque). - -UnicaenDbImport fonctionne en majeure partie en PHP tout en déléguant au maximum au SGBD ce qu'il sait bien faire -(`select FULL OUTER JOIN` pour le différentiel entre données source et destination, fonction pour l'alimentation du registre -d'import, etc.) Par conséquent, il est possible et souhaitable de l'enrichir pour implémenter une synchronisation vers -différentes plateformes de base de données destination. Les plateformes de base de données destination suivantes sont -implémentées : - - PostgreSQL - -*NB: En revanche toutes les plateformes de base de données source sont supportées car on se contente d'y faire un select -à l'aide de l'ORM Doctrine 2 pour alimenter une table intermédiaire.* - - Installation ------------ - - composer require unicaen/unicaen-db-import + + composer require unicaen/unicaen-db-import Configuration @@ -61,21 +70,37 @@ cp -n vendor/unicaen/db-import/config/unicaen-db-import.local.php.dist config/au Utilisation ----------- -Le module fournit une ligne de commande pour : +Le module possède deux mécanismes distincts : +1. import : réalise un import "brut" des données, autrement dit une copie, d'une source vers une destination; +2. synchro : réalise une synchronisation entre une source et une destination en gérant une historisation (created_on, updated_on, deleted_on). - - lancer un import par son nom, exemple : +Le module fournit donc une ligne de commande pour lancer : + + - un import par son nom : ```bash - php public/index.php run import --name "IMPORT_PFI_SIFAC" + php public/index.php run import --name "NOM_ETABLI_DANS_LA_CONFIG" ``` - - lancer tous les imports : + - tous les imports : ```bash php public/index.php run import --all ``` + + - une synchro par son nom : + + ```bash + php public/index.php run synchro --name "NOM_ETABLI_DANS_LA_CONFIG" + ``` + + - toutes les synchros : + + ```bash + public/index.php run synchro --all + ``` -*NB: Cette commande exécute la synchronisation une fois. Pour synchroniser en permanence la base de données destination, +*NB: L'exécution d'une de ces commandes n'est effectif qu'une fois. Pour importer/synchroniser en permanence (dans) la base de données destination, il faut programmer le lancement périodique de cette commande à l'aide de CRON (cf. plus bas pour un exemple).* @@ -84,7 +109,7 @@ Contraintes ### Identifiant commun -L'identifiant unique commun des enregistrements source et destination doit être de type **chaîne de caractères**. +L'identifiant unique commun des enregistrements (source_code_column) source et destination doit être de type **chaîne de caractères**. ### Source de type `'select'` @@ -99,6 +124,8 @@ Dans le cas d'une *source* de type `'select'`, il est nécessaire de convertir c Il est également nécessaire de spécifier dans la config de la *destination* la fonction de conversion à appliquer à chaque colonne pour la convertir en chaîne de caractères (cf. paramètre de config destination `columns_to_char`). +_Note : Peut être pas obligé de faire la convertion TO_CHAR dans le SELECT (mais quand même le faire dans le columns_to_char)._ + ### Source de type `'table'` Dans le cas d'une *source* de type `'table'`, il est nécessaire de spécifier dans la config de la @@ -116,299 +143,176 @@ ALTER TABLE TABLE_DESTINATION ADD COLUMN deleted_on TIMESTAMP(0) WITH TIME ZONE; ``` -Exemple de mise en oeuvre -------------------------- -Import des programmes de financement SIFAC depuis une base Oracle vers une table PostgreSQL. Les données sources sont -"extraites" grâce à un select. +Fonctionnement +-------------- + +### Import de données + +Afin de s'adapter au plus grand nombre de SGBD, UnicaenDbImport s'appuie sur l'_ORM Doctrine 2_. +Ainsi nous effectuons le déroulement suivant : + +<!--Voir le dossier documentation/ pour toutes modifications--> + + +*Schématisation du déroulement d'un import* + +1. Suppression des données existantes dans _Destination_ +2. Récupération des données de la _Source_ vers _Doctrine_ +3. Génération des requêtes SQL (insert, update, ...) depuis PHP +4. Exécution des requêtes / Récupération des données vers _Destination_ + + +### Synchronisation de données locales + +L'un des mécanismes d'UnicaenDbImport est d'appliquer un système de synchronisation avec gestion d'une historisation. +Avec une table _Source_ et une table _Destination_ toutes deux dans une base locale, le déroulement est le suivant : + +<!--Voir le dossier documentation/ pour toutes modifications--> + + +*Schématisation du déroulement d'une synchro (locale)* -- Configuration globale du module : +1. Création d'une vue différentielle +2. Préparation/détection des actions à appliquer (INSERT, UPDATE, ...) +3. Application des mises à jour (avec historisation : created_on, updated_on, ...) + + +### Synchronisation de données distantes + +Concernant le mécanisme de synchronisation, il est possible de spécifier une table _Source_ provenant d'une base distante. +Dans ce cas, les mécanismes d'[import](#import-de-données) et de [synchro (locale)](synchronisation-de-données-locales) seront réalisés successivement. + +<!--Voir le dossier documentation/ pour toutes modifications--> + + +*Schématisation du déroulement d'une synchro (distant)* + +1. Déclenchement du mécanisme d'import entre la _Source_ et une table _Temporaire_ +2. Déclenchement du mécanisme de synchro entre la table _Temporaire_ et la table _Destination +3. Suppression de la table _Temporaire_ + + +Exemple +------- + +Pour les exemples suivants, on suppose posséder : + * 1 Base de donnée **A** locale (celle de notre application) contenant les tables : + * **UTILISATEUR**(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE) + * **COMPOSANTE**(int ID, str CODE, str NOM) + * **PAIN_AU_CHOCOLAT**(int ID, str NOM, str BOULANGERIE) + * 1 Base de donnée **B** distante (par exemple Apogée) contenant les tables : + * **UTILISATEUR**(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE) + * **COMPOSANTE**(int ID, str CODE, str NOM, int DIRECTEUR_ID, str ADRESSE) + * **CHOCOLATINE**(int ID, str NOM, str BOULANGERIE, int POURCENT_GRAS, int NOTE) + + +En outre, on suppose avoir déclaré les configurations _Doctrine_ 'orm_A' et 'orm_B' respectivement pour les bases **A** et **B** ci-dessus (soit dans le fichier [unicaen-db-import.local.php](config/unicaen-db-import.local.php.dist) soit dans un autre fichier de config.). + +### Exemple 1 : Importation d'une table de données distantes + +Dans de nombreux cas, on peut souhaiter importer une table de données provenant d'une base distante. +Ce peut être le cas notamment lorsque l'on souhaite s'assurer de la constante disponibilité des données. `unicaen-db-import.global.php` ```php return [ - /** - * Configuration du module UnicaenDbImport. - */ 'import' => [ - /** - * Liste des imports. - */ 'imports' => [ [ - /** - * Petit nom (unique) de l'import. - */ - 'name' => 'Import_PFI_SIFAC', - - /** - * Configuration de la source de données à importer : - * - 'name' : petit nom (unique) de la source - * - 'table' : nom de la table source contenant les données à importer - * - 'select' : select SQL de mise en forme des données source à importer (NB: antinomique avec 'table') - * - 'connection' : identifiant de la connexion Doctrine à la base de données source - * - 'source_code_column' : nom de la colonne dans la table/vue source contenant l'identifiant unique - * - 'columns' : liste ordonnée des noms des colonnes à prendre en compte dans la table/vue source - */ + 'name' => "IMPORTATION DES DONNÉES B VERS A", + 'source' => [ - 'name' => 'PFI SIFAC', - 'connection' => 'doctrine.connection.orm_oracle', + 'name' => 'TABLE UTILISATEUR DE MA BASE B', + 'table' => 'UTILISATEUR', + 'connection' => 'doctrine.connection.orm_B', 'source_code_column' => 'CODE', - 'columns' => ['LIBELLE', 'FLECHE', 'DEBUT_VALIDITE', 'FIN_VALIDITE'], - 'select' => <<<'EOT' -select distinct - MEASURE CODE, - SHORT_DESC LIBELLE, - ZFLECHE FLECHE, - to_char(TO_DATE(VALID_FROM,'YYYYMMDD'),'YYYY-MM-DD') DEBUT_VALIDITE, - to_char(TO_DATE(VALID_TO,'YYYYMMDD'),'YYYY-MM-DD') FIN_VALIDITE - from ( - SELECT - SAPSR3.FMMEASURE.MEASURE, - SAPSR3.FMMEASURET.SHORT_DESC, - SAPSR3.FMMEASURE.ZFLECHE, - SAPSR3.FMMEASURE.VALID_FROM, - SAPSR3.FMMEASURE.VALID_TO - FROM - SAPSR3.FMMEASURE, - SAPSR3.FMMEASURET - WHERE - ( (SAPSR3.FMMEASURE.CLIENT in (select A.MANDT from SAPSR3.FM01H A where A.MANDT = '500') OR SAPSR3.FMMEASURE.CLIENT is null) - and (SAPSR3.FMMEASURE.FMAREA in (select A.FIKRS from SAPSR3.FM01H A where A.FIKRS = '1010') or SAPSR3.FMMEASURE.FMAREA is null) ) - AND ( SAPSR3.FMMEASURE.CLIENT=SAPSR3.FMMEASURET.CLIENT(+) ) - AND ( SAPSR3.FMMEASURE.FMAREA=SAPSR3.FMMEASURET.FMAREA(+) ) - AND ( SAPSR3.FMMEASURE.MEASURE=SAPSR3.FMMEASURET.MEASURE(+) ) -) -EOT - , + 'columns' => ['ID', 'CODE', 'NOM', 'PRENOM', 'NAISSANCE'], ], - - /** - * Forçage éventuel du nom de la table intermédiaire utilisée lorsque source et destination - * ne partagent pas la même connexion. NB: cette table intermédiaire est créée/peuplée/supprimée - * dans la base de données destination à chaque import. - * En l'absence de ce forçage, le nom de la table intermédiaire sera celui de la table destination - * préfixé par "src_". - */ - 'intermediate_table' => 'src_progfin', - - /** - * Suppression automatique de la table intermédiaire "src_" au début de l'import. - * Si la suppression automatique est désactivée, l'existence de la table intermédiaire au démarrage - * de l'import fera échouer l'import. - */ - 'intermediate_table_auto_drop' => false, - - /** - * Configuration de la destination des données importées : - * - 'name' : petit nom (unique) de la destination - * - 'table' : nom de la table destination vers laquelle les données sont importées - * - 'connection' : identifiant de la connexion Doctrine à la base de données destination - * - 'source_code_column' : nom de la colonne dans la table destination contenant l'identifiant unique - * - 'columns' : liste ordonnée des noms des colonnes importées dans la table destination - * - 'columns_to_char' : format sprintf nécessaire pour mettre des colonnes au format requis (string) - */ + 'destination' => [ - 'name' => 'PFI Zébu', - 'table' => 'progfin', - 'connection' => 'doctrine.connection.orm_default', - 'source_code_column' => 'code', - 'columns' => ['libelle', 'fleche', 'debut_validite', 'fin_validite'], + 'name' => 'TABLE UTILISATEUR DE MA BASE A', + 'table' => 'UTILISATEUR', + 'connection' => 'doctrine.connection.orm_A', + 'source_code_column' => 'CODE', + 'columns' => ['ID', 'CODE', 'NOM', 'PRENOM', 'NAISSANCE'], 'columns_to_char' => [ - 'debut_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // car colonne destination de type TIMESTAMP - 'fin_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // idem + 'ID' => "%s||''", + 'NAISSANCE' => "TO_CHAR(%s,'YYYY-MM-DD')", ], ], ], ], ], ]; -``` +``` + +`Dans le terminal du serveur` +```bash +php public/index.php run import --name "IMPORTATION DES DONNÉES B VERS A" +``` + + + + -*On doit renseigner le paramètre `intermediate_table` car les données source proviennent d'un select.* +### Exemple 2 : Importation d'un select de données distantes -- Configuration locale du module : +Les bases distantes requêtées sont généralement bien garnies. +Pourtant il est fréquent de ne vouloir importer qu'une partie de ces données. -`unicaen-db-import.local.php` +`unicaen-db-import.global.php` ```php return [ - /** - * Configuration Doctrine minimum requise. - */ - 'doctrine' => [ - 'connection' => [ - 'orm_oracle' => [ - 'driverClass' => 'Doctrine\\DBAL\\Driver\\OCI8\\Driver', - 'params' => [ - 'host' => 'host.domain.fr', - 'port' => '1525', - 'user' => 'x', - 'password' => 'y', - 'dbname' => 'z', - 'charset' => 'AL32UTF8', - ], - 'eventmanager' => 'orm_oracle', - ], - 'orm_default' => [ - 'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver', - 'params' => [ - 'host' => 'host.domain.fr', - 'port' => '5432', - 'charset' => 'utf8', - 'dbname' => 'a', - 'user' => 'b', - 'password' => 'c', - - ], - ], - ], - 'eventmanager' => [ - 'orm_sifac' => [ - 'subscribers' => [ - \Doctrine\DBAL\Event\Listeners\OracleSessionInit::class, + 'import' => [ + 'imports' => [ + [ + 'name' => "IMPORTATION PARTIELLE DES DONNÉES B VERS A", + + 'source' => [ + 'name' => 'TABLE CHOCOLATINE DE MA BASE B', + 'select' => <<<'EOT' +SELECT +ID, +NOM, +BOULANGERIE +FROM CHOCOLATINE +EOT + , + 'connection' => 'doctrine.connection.orm_B', + 'source_code_column' => 'ID', + 'columns' => ['ID', 'NOM', 'BOULANGERIE'], + ], + + 'destination' => [ + 'name' => 'TABLE PAIN_AU_CHOCOLAT DE MA BASE A', + 'table' => 'PAIN_AU_CHOCOLAT', + 'connection' => 'doctrine.connection.orm_A', + 'source_code_column' => 'ID', + 'columns' => ['ID', 'NOM', 'BOULANGERIE'], + 'columns_to_char' => [ + 'ID' => "%s||''", + ], + ], ], ], - ], - ], + ], ]; ``` - -*NB: les paramètres de config de la connexion `orm_default` existent sans doute déjà dans la configuration de votre -application, il est bien-sûr inutile de les répéter dans la config du module unicaen/db-import.* - -- Configuration CRON pour lancer périodiquement la synchro : -```cron -# Du lundi au vendredi, entre 6h00 et 19h45, toutes les 15 minutes -*/15 6-19 * * 1-5 root /usr/bin/php /var/www/zebu-back/public/index.php run import --all 1> /tmp/zebu-cron.log 2>&1 -``` -- Exemple de logs affichés par la commande : - - ``` -######################## IMPORTS ######################## - -### Import des PFI SIFAC ### -05/09/2017 16:22:08 -# delete : -UPDATE progfin SET deleted_on = LOCALTIMESTAMP(0) WHERE CODE = 'fddsfsd' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'fddsfsd-99c4b652-9245-11e7-bddd-0242f725575b' ; -(1 instructions exécutées) -# insert : -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('971UP009', 'Of Wisco', ' ', '2014-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '971UP009-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('977CA025', 'Screening de 5', ' ', '2016-07-21', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '977CA025-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F950MEC1', 'MDE CAEN FONCTIONNEM', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F950MEC1-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F971CF03', 'UNIVERSITE DU HAVRE', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F971CF03-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('014DU009', 'DU RDCM', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '014DU009-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012RE001', 'COLLOQUE 50 ANS SC E', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012RE001-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012CH021', 'Typo-chronologie', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012CH021-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('013PL002', 'CMABIO', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '013PL002-99c4b652-9245-11e7-bddd-0242f725575b' ; -INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('999CM003', '2016-AGRI-133', ' ', '2016-03-30', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '999CM003-99c4b652-9245-11e7-bddd-0242f725575b' ; -(9 instructions exécutées) -# undelete : -UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '913TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '913TA017-99c4b652-9245-11e7-bddd-0242f725575b' ; -UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '920TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '920TA017-99c4b652-9245-11e7-bddd-0242f725575b' ; -UPDATE progfin SET LIBELLE = 'PAIE DIU ADOLESCENTS', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = 'P14DU910' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'P14DU910-99c4b652-9245-11e7-bddd-0242f725575b' ; -(3 instructions exécutées) -# update : -UPDATE progfin SET LIBELLE = 'PLATIN''', updated_on = LOCALTIMESTAMP(0) WHERE CODE = '925PL005' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '925PL005-PLATINvxcvxc-99c4b652-9245-11e7-bddd-0242f725575b' ; -(1 instructions exécutées) - -Import réalisé avec succès. +`Dans le terminal du serveur` +```bash +php public/index.php run import --name "IMPORTATION PARTIELLE DES DONNÉES B VERS A" ``` +### Exemple 3 : Synchronisation de données locales -Dans le moteur --------------- - -### Table intermédiaire `SRC_*` +Ex: SRC_XXX vers XXX -Lorsque les données source sont issues d'un select, une table intermédiaire `SRC_*` est créée dans la base de données -destination à partir de ces données source. La synchronisation est ensuite réalisée entre cette table intermdiaire et -la table destination. À la fin du processus de synchronisation, cette table intermdiaire est supprimée. +### Exemple 4 : Synchronisation de données distantes -*NB: attention, si au lancement de la commande d'import la table intermédiaire `SRC_*` existe déjà dans la base de -données ete que le paramètre de config `intermediate_table_auto_drop` est à `false`, l'import échouera.* +Ex: WELCOME_CATALOGUE vers CATALOGUE (-> Utilisation du mécanisme d'import, etc, donc potentiellement des paramètres 'intermediate_table' et 'intermediate_table_auto_drop', ...) -### Différentiel entre source et destination - -La requête utilisée pour comparer les données sources et destination pour une table destination `progfin` ressemble -à cela (syntaxe PostgreSQL) : +### Exemple 5 : Synchronisation de données distantes avec récupération des clés étrangères locales -```sql -SELECT create_import_metarequest_for_progfin( - src.code, src.libelle, TO_CHAR(src.debut_validite,'YYYY-MM-DD'), - dest.code, dest.libelle, TO_CHAR(dest.debut_validite,'YYYY-MM-DD'), dest.deleted_on, - 'eb1ab85c-916a-11e7-aba7-0242f725575b' -) AS operation -FROM src_progfin src -FULL OUTER JOIN progfin dest ON src.code = dest.code -; -``` - -### Fonction `create_import_metarequest_for_*` - -Le module crée automatiquement dans le SGBD pour chaque import configuré une fonction `create_import_metarequest_for_*` -chargée de : - - déterminer l'opération de mise à jour nécessaire à partir du résultat du différentiel entre données source et - destination ; - - inscrire dans un "registre d'import" (table `IMPORT_REG`) les instructions SQL nécessaires à la mise à jour de - la table destination. - -Vous trouverez dans [ce fichier](test/UnicaenDbImportUnitTest/CodeGenerator/PostgreSQL/Helper/expected_function_creation_sql.sql) -un exemple de fonction `create_import_metarequest_for_*` pour une table destination `ztemptable` dans une base PostgreSQL. - -*NB: Pour un enregistrement existant à la fois dans la source et la destination (opération "update"), la détection de -différence et donc sa mise à jour se fait colonne par colonne.* - -### Table `IMPORT_REG`, registre d'import - -Une table de travail `import_reg` est utilisée par le module pour y inscrire les opérations nécessaires à chaque -synchronisation. - -Ce registre comporte les colonnes suivantes : - - le type d'opération nécessaire : insert, update, delete, undelete - - le "source code" de l'enregistrement concerné - - le nom de la table concernée - - le nom de la colonne concernée (si c'est pertinent pour l'opération) - - la valeur de la colonne avant synchronisation (si c'est pertinent pour l'opération) - - la valeur de la colonne après synchronisation (si c'est pertinent pour l'opération) - - l'instruction SQL à exécuter pour mettre à jour la table destination - - la date d'exécution de l'instruction SQL de mise à jour de la table destination - - la date de création de la ligne de registre - - un hash permettant d'identifier l'ensemble de lignes auquel appartient cette ligne de registre - (utile pour mettre à jour la date d'exécution) - -Exemple de contenu de la table `import_reg` : - -| operation | source_code | table_name | field_name | from_value | to_value | sql | created_on | executed_on | import_hash | -|-----------|-------------|------------|------------|--------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------------------|--------------------------------------| -| insert | 013CC021 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$013CC021$$, $$Caract. verres trait$$, $$ $$, $$2017-05-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 920CA050 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA050$$, $$Tthèse CIFRE2015/119$$, $$ $$, $$2016-01-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 909CC189 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909CC189$$, $$THESE CIFRE$$, $$ $$, $$2016-02-01$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 909EA002 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909EA002$$, $$ED SIMEM FONCTIONNEM$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 913CA052 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$913CA052$$, $$ECO CORAIL$$, $$ $$, $$2014-09-26$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 917CA955 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$917CA955$$, $$CAPACITE$$, $$ $$, $$2013-11-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | F971UP03 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$F971UP03$$, $$DELAWARE$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 925CD271 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD271$$, $$CONV.2015PCM51$$, $$ $$, $$2016-01-01$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 925CD276 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD276$$, $$CONV.1060284$$, $$ $$, $$2015-12-10$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| insert | 920CA047 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA047$$, $$Projet THYMOTHE$$, $$ $$, $$2015-12-21$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| undelete | 013GR014 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$SUBVENTION COTENTIN$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2047-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$013GR014$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| undelete | 012CB018 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$Bilan sur les nouv.$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2016-12-15$$, FIN_VALIDITE = $$2018-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012CB018$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| undelete | 012RF001 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$COLLOQUE CONSTITUTIO$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2017-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012RF001$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b | -| update | 971UP002 | progfin | LIBELLE | Etablissement api | Etablissement API | UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc | -| update | 971UP004 | progfin | LIBELLE | ETAB GIORGIA north | ETAB GIORGIA NORTH | UPDATE progfin SET LIBELLE = $$ETAB GIORGIA NORTH$$, updated_on = now() WHERE CODE = $$971UP004$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc | -| update | 950ME003 | progfin | LIBELLE | REGIE DE REC | REGIE DE RECETTES-EN | UPDATE progfin SET LIBELLE = $$REGIE DE RECETTES-EN$$, updated_on = now() WHERE CODE = $$950ME003$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc | -| ``` | | | | | | | | | | - -Remarques : - - - Le "hash" (`eb1ab85c-916a-11e7-aba7-0242f725575b` par exemple) dans la colonne `import_hash` permet d'identifier - un ensemble d'instructions SQL à exécuter au sein d'une même transaction dans la base de données destination. - - - Chaque instruction SQL de mise à jour de la table destination - (ex: `UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ;`) - est suivie d'une autre instruction SQL permettant d'inscrire dans le registre d'import la date d'exécution - de la fameuse instruction de mise à jour de la table destination - (ex: `UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ;`). - - - La colonne `executed_on` accueille la date d'exécution de l'instruction de mise à jour de la table destination ; - si cette date est NULL, l'instruction n'a pas été exécutée. +Cas d'une table "Composante" où des composantes sont en clé étrangère avec une table "Formation" via son ID. -> Passage par une vue SRC (déclaration d'un import et d'une synchro)... diff --git a/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8512c937febe2a7b9b115c75e0f0e8545f003f44 --- /dev/null +++ b/documentation/README.md @@ -0,0 +1,9 @@ +Documentation +============= + +Ce répertoire contient les diverses ressources (images...) nécessaire à la documentation générale de l'application. + + +*file* **.png** : image utilisée directement dans la documentation + +*file* **.drawio** : source d'une image permettant sa modification sur https://www.draw.io \ No newline at end of file diff --git a/documentation/fonctionnement_synchro_distant.drawio b/documentation/fonctionnement_synchro_distant.drawio new file mode 100644 index 0000000000000000000000000000000000000000..949b69f44c77b6e9cc8a68b95f5c28f6db230332 --- /dev/null +++ b/documentation/fonctionnement_synchro_distant.drawio @@ -0,0 +1 @@ +<mxfile modified="2019-05-15T14:18:26.824Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="Jr5_HcIyoOzdWaDvR-2I" version="10.6.7" type="device"><diagram id="mWMuuFM-TLnJckGO8D4I" name="Page-1">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</diagram></mxfile> \ No newline at end of file diff --git a/documentation/fonctionnement_synchro_distant.png b/documentation/fonctionnement_synchro_distant.png new file mode 100644 index 0000000000000000000000000000000000000000..85c92db2600b2a68b1943b58929e6a7cc866f666 Binary files /dev/null and b/documentation/fonctionnement_synchro_distant.png differ diff --git a/documentation/fonctionnement_synchro_locale.drawio b/documentation/fonctionnement_synchro_locale.drawio new file mode 100644 index 0000000000000000000000000000000000000000..62e86996705519beeb7d5a4fff6ba435977589fa --- /dev/null +++ b/documentation/fonctionnement_synchro_locale.drawio @@ -0,0 +1 @@ +<mxfile modified="2019-05-15T13:45:14.165Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="7NW-oTR3yv0mZXlWeKPD" version="10.6.7" type="device"><diagram id="mWMuuFM-TLnJckGO8D4I" name="Page-1">3Vhbj5s4FP41PM4IcMjlMSVNq2pXu1Kk2faRgkPcOpg1zm1//R5jGwwmk0TNNFUz0sQ+vn/nnO+z46F4e/zAk3LzJ8sw9UI/O3po4YXhCI3gvzSclGHqI2XIOcmUKWgNK/If1kZfW3ckw1Wno2CMClJ2jSkrCpyKji3hnB263daMdlctkxw7hlWaUNf6D8nERp8i8lv7R0zyjVk58HXLNjGdtaHaJBk7WCb03kMxZ0yo0vYYYyqxM7ioccszrc3GOC7ENQNCNWCf0J0+24rteIr17sTJHBk2WspieqKkyDD30LvDhgi8KpNU2g/gaLBtxJZCLYDiV7aDjtkfXxtDkn7PubT+tRMwC9b2NaE0ZpTxeiUUx8tlHIPdPYs+3h5zgY+WSZ/tA2ZbLPgJuhxNYKkROs4CA/uh9dpYmzaWw8ywRMdJ3kzcQgkFjeYwsshB1gvHFBZ4l5E9FHNRH1GZ1gyOaOM9/nfHTMNTVSfAHDoEYXlsG80sC1wJ8mTmgo2p6bpLgHlg4bvvpUgEYcWNe3lYrC2XM/jcJ9bQA4MtGgi2Hqi4yOaS+iSuNKkqknYhxEciPlvlL1D2nyNdW8hD+qZyMpUC9vnZrlijZLUdVtfMuLNoV4p8bGoSCc+x7vXyInjxiUTkQMJpddp/Yqv1U6DFBGcd0nb9ZPkhGvCDsXFMIYb3Xaofco5e4W9G6oTRYTDpRkE067lXHVEPspm5N08jESacxr2JFDDORHWoNKe+KnomTvQsvBh585kA8ayz2c/Ieq1s4LMUtLcfXpAkohtQleDsOzbJVjCZg53806aEkryQUQnBUOe6TDkCYjvXDVuSZXKZQRLgKvMvBdb1aRzMph3cZ5GTxcFoIHzCO6Tx1HHEQB5TCtecc3BY+CdVqe4+a3KUAPW5bx3JP8dR0DKuP5rxLbv63AnmyeQSzOEboRy4l57b2dIwX3Ab8xmW9W9g2bNo9xjvBq60eRY9ki0R6rJcc8W/lS5R0KPLvqzejy5fRfb1+94LtF17H5OU+/z4G1POk4zgDg80N3TTtiBcKwUsBZfRixcthycG4vssdYBgPUcPu2oNe9+V0JgrvdT3YX+/cx9Vv5Jo/qBLxj2XTFyPTNHb8PmwR1wtDRz8fx0t/TH0/fEl7N9KSwexNxPf4znSiuMXq+XSc6TV4WbUxeeIo6YX3ievyoCtxI8V2N4zIuw/I64W2MkFpT4jsODl5GR1K2WH6vyGw6h7/w6R/+q+Rj3h7/WHgtrBXdW+ue+2ET4vSwoEbN5L8onkb0lVf9dKANvyvwHYv7EKzMKuK0YuEQXBz1QBQzyWn9BvqwKToKfBQ/jfSQig2v5IrbKq/aUfvf8f</diagram></mxfile> \ No newline at end of file diff --git a/documentation/fonctionnement_synchro_locale.png b/documentation/fonctionnement_synchro_locale.png new file mode 100644 index 0000000000000000000000000000000000000000..3252272fbade08c5f9df71f931a44a35d28ec567 Binary files /dev/null and b/documentation/fonctionnement_synchro_locale.png differ