Commit 04f3b012 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Nouvelles pages non finalisées pour consulter les inscriptions administratives et les doctorants

parent abec3605
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -11673,7 +11673,7 @@
            "source": {
                "type": "git",
                "url": "https://git.unicaen.fr/open-source/sygal-api.git",
                "reference": "f5db09a318f093b5667e743bca932507280f77e8"
                "reference": "e3b079b9a3daf0e9d15b08bf09234668513d0fcf"
            },
            "require": {
                "guzzlehttp/guzzle": "^7.0",
@@ -11698,7 +11698,7 @@
                ]
            },
            "description": "API SyGAL",
            "time": "2023-06-07T15:48:09+00:00"
            "time": "2023-07-05T15:17:40+00:00"
        },
        {
            "name": "unicaen/zfc-user",
+1 −1
Original line number Diff line number Diff line
@@ -80,7 +80,7 @@ return [
        'Information',
        'Doctorant',
        'StepStar',
//        'InscriptionAdministrative',
        'InscriptionAdministrative',
        'Application',
        'SygalApi',
        'SygalApiImpl',

doc/subsitutions.md

0 → 100644
+216 −0
Original line number Diff line number Diff line
Substitutions
=============

Structures
----------

- Situation initiale

`structure`

| id | source_code | libelle | **code** |
|----|-------------|---------|----------|
| 12 | INSA::550   | ED550   | **550**  |
| 17 | UCN::550    | ED550   | **550**  |

Colonne pour détection de doublon : `code`.


- Leur `code` étant identique, les structures `12` et `17` peuvent faire l'objet d'une substitution :

`structure`

| id  | source_code | libelle                    | **code** |
|-----|-------------|----------------------------|----------|
| 12  | INSA::550   | Ecole Doctorale de Bonheur | **550**  |
| 17  | UCN::550    | Ecole Doctorale du Bonheur | **550**  |
| ... | ...         | ...                        | ...      |
| 32  | SYGAL::550  | Ecole Doctorale du Bonheur | **550**  |

`structure_substit`

| from_id | to_id   |
|---------|---------|
| 12      | 32      |
| 17      | 32      |

Les enregistrements substitués ne doivent plus être modifiables.

Dans toutes les tables où il y a un `structure_id`, il faut faire la jointure avec `structure_substit` 
pour tenir compte de la substitution.


- Si un nouveau doublon apparaît

`structure`

| id  | source_code | libelle                    | **code** |
|-----|-------------|----------------------------|----------|
| 12  | INSA::550   | Ecole Doctorale de Bonheur | **550**  |
| 17  | UCN::550    | Ecole Doctorale du Bonheur | **550**  |
| ... | ...         | ...                        | ...      |
| 32  | SYGAL::550  | Ecole Doctorale du Bonheur | **550**  |
| ... | ...         | ...                        | ...      |
| 61  | ULHN::550   | Ecole Doctrale du Bonheur  | **550**  |

Il faut modifier la substitution pour ajouter la structure `61`.

`structure_substit`

| from_id | to_id |
|---------|-------|
| 12      | 32    |
| 17      | 32    |
| 61      | 32    |



Individus
---------

- Situation initiale

`individu`

| id | source_code | nom    | prenom | date_naissance |
|----|-------------|--------|--------|----------------|
| 12 | INSA::12345 | Hochon | Paul   | null           |
| 17 | UCN::ABCD   | Hochon | Paul   | 2000-01-01     |

Colonnes utilisées pour la détection de doublon : `nom`,  `prenom`,  `date_naissance`.


- Leur `nom`, `prenom`, `date_naissance` étant identiques, les individus `12` et `17` peuvent faire l'objet d'une substitution :

`individu`

| id  | source_code | nom    | prenom | date_naissance |
|-----|-------------|--------|--------|----------------|
| 12  | INSA::12345 | Hochon | Paul   | null           |
| 17  | UCN::ABCD   | Hochon | Paul   | 2000-01-01     |
| ... | ...         | ...    | ...    | ...            |
| 32  | SYGAL::8889 | Hochon | Paul   | 2000-01-01     |

`individu_substit`

| from_id | to_id   |
|---------|---------|
| 12      | 32      |
| 17      | 32      |


Dans toutes les tables où il y a un `individu_id`, il faut faire la jointure avec `individu_substit`
pour tenir compte de la substitution.

Une évolution amélioration (?) ultérieure serait, plutôt que d'utiliser la solution de la jointure, de substituer 
directement dans les tables concernées les fk 'individu_id' par l'id de l'individu substituant.


### Un individu est modifié.

  - Calculer son NPD.

  - Rechercher par son `id` dans `individu_substit` (non historisés) selon le `from_id` s'il est substitué.
    - S'il existe dans `individu_substit`, comparer son NPD à `individu_substit.from_npd`.
      - Si les NPD sont identiques, c'est que la subsitution est toujours justifiée.
        - Faut-il mettre à jour automatiquement les attributs de l'individu substituant ? Cf. Problématiques.
        - STOP
      - Sinon, il faut sortir l'individu de la substitution.
      - S'il ne reste dans la substitution qu'un seul individu substitué, historiser le substituant et la substitution.

  - Rechercher son NPD dans `individu_substit` pour savoir si l'individu doit être ajouté à une substitution existante.
    - Si une substitution existe avec ce NPD
      - Y ajouter l'individu.
      - STOP

  - Rechercher dans `v_individu_doublon` pour savoir s'il fabrique un doublon.
    - S'il n'a pas de doublons : STOP.
    - S'il a des doublons :
        - Créer un nouvel individu substituant. Il doit posséder les meilleurs attributs des doublons (cf. Problématiques).
        - Créer la substitution dans `individu_substit`.


### Un individu est ajouté.

  - Calculer son NPD.

  - Rechercher ce NPD dans `individu_substit` pour savoir si l'individu doit être ajouté à une substitution existante.
    - Si une substitution existe avec ce NPD, y ajouter l'individu. 
    - Sinon, rechercher dans `v_individu_doublon` pour savoir s'il fabrique un doublon.
      - S'il n'a pas de doublons : stop.
      - S'il a des doublons :
        - Créer un nouvel individu substituant. Il doit posséder les meilleurs attributs des doublons (cf. Problématiques).
        - Créer la substitution dans `individu_substit`.


### Un individu est historisé.

- Rechercher dans `individu_substit` s'il est substitué.
    - S'il n'est pas substitué, stop.
    - S'il est substitué, le retirer de la substitution.
    - S'il ne reste dans la substitution qu'un seul individu substitué, historiser le substituant et la substitution.


### Un individu est supprimé.

Remarque : Ne pas gérer la suppression avec un "delete cascade" sur la fk 'individu_substit.individu_substitue_id'
car cela retire la possibilité d'amélioration évoquée plus haut (substitution réelle des fk).

- Idem historisation.


### Un individu est restauré (dé-historisé).

Idem ajout.




`individu_substit`

| from_id | to_id | from_npd               |
|---------|-------|------------------------|
| 12      | 32    | hochon_paul_           |
| 17      | 32    | hochon_paul_2000-01-01 |



Problématiques
--------------

### Comment choisir les meilleurs valeurs d'attributs parmi les doublons ?

Utiliser `mode()` : "the most frequent value of the aggregated argument"
`mode() WITHIN GROUP (ORDER BY i.nom_usuel) as nom_usuel`
(https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-ORDEREDSET-TABLE)


### Mettre à jour automatiquement les attributs de l'individu substituant ?

Quand un individu est ajouté automatiquement à une substitution, faut-il mettre à jour automatiquement les attributs 
de l'individu substituant ?
Si oui, ça écraserait les attributs éventuellement modifiés à la main par un utilisateur. Interdire la modif manuelle ?


### Créer une colonne `individu.npd_force`

Une colonne "NPD forcé" permet de forcer l'inclusion d'un individu à une substitution.

Exemples :
- Individu sans date de naissance (NPD `bernaudin_myriam_`) mais qui en fait est bien un doublon (NPD `bernaudin_myriam_1972-06-03`).
- Individu pour lequel on ne peut pas demander de correction dans la source mais dont on sait que c'est un doublon.


### Normalisation pour NPD

```sql
return unaccent(
    replace(
        translate(
            regexp_replace(lower(chaine), '[_ ''"\.,@\-]', ''), 
            'å', 'a' -- translate
        ),
        'æ', 'ae' -- replace
    )
);
```
+92 −25
Original line number Diff line number Diff line
@@ -12,8 +12,12 @@ use Doctorant\Form\MailConsentementFormFactory;
use Doctorant\Provider\Privilege\DoctorantPrivileges;
use Doctorant\Service\DoctorantService;
use Doctorant\Service\DoctorantServiceFactory;
use Doctorant\Service\Search\DoctorantSearchService;
use Doctorant\Service\Search\DoctorantSearchServiceFactory;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctorant\Controller\DoctorantController;
use Laminas\Router\Http\Literal;
use Laminas\Router\Http\Segment;
use UnicaenAuth\Guard\PrivilegeController;
use UnicaenAuth\Provider\Rule\PrivilegeRuleProvider;
@@ -54,7 +58,7 @@ return [
        'guards' => [
            PrivilegeController::class => [
                [
                    'controller' => 'Application\Controller\Doctorant',
                    'controller' => DoctorantController::class,
                    'action' => [
                        'modifier-email-contact',
                        'modifier-email-contact-consent',
@@ -63,9 +67,11 @@ return [
                    'assertion'  => TheseAssertion::class,
                ],
                [
                    'controller' => 'Application\Controller\Doctorant',
                    'controller' => DoctorantController::class,
                    'action' => [
                        'rechercher',
                        'lister',
                        'consulter',
                    ],
                    'roles' => 'user',
                ],
@@ -79,29 +85,96 @@ return [
                'options' => [
                    'route' => '/recherche-doctorant',
                    'defaults' => [
                        'controller' => 'Application\Controller\Doctorant',
                        'controller' => DoctorantController::class,
                        'action' => 'rechercher',
                    ],
                ],
                'may_terminate' => true,
            ],
            'doctorant' => [
                'type' => 'Segment',
                'type' => LIteral::class,
                'options' => [
                    'route' => '/doctorant/:doctorant',
                    'route' => '/doctorant',
                    'defaults' => [
                        'controller' => DoctorantController::class,
                    ],
                ],
                'may_terminate' => false,
                'child_routes' => [
                    'voir' => [
                        'type' => Segment::class,
                        'options' => [
                            'route' => '/consulter/:doctorant',
                            'constraints' => [
                                'doctorant' => '\d+',
                            ],
                            'defaults' => [
                        'controller' => 'Application\Controller\Doctorant',
                                'action' => 'consulter',
                            ],
                        ],
                    ],
                    'modifier' => [
                        'type' => Segment::class,
                        'options' => [
                            'route' => '/modifier/:doctorant',
                            'constraints' => [
                                'doctorant' => '\d+',
                            ],
                            'defaults' => [
                                'action' => 'modifier',
                            ],
                        ],
                    ],
                    'supprimer' => [
                        'type' => Segment::class,
                        'options' => [
                            'route' => '/supprimer/:doctorant',
                            'constraints' => [
                                'doctorant' => '\d+',
                            ],
                            'defaults' => [
                                'action' => 'supprimer',
                            ],
                        ],
                    ],
                    'restaurer' => [
                        'type' => Segment::class,
                        'options' => [
                            'route' => '/restaurer/:doctorant',
                            'constraints' => [
                                'doctorant' => '\d+',
                            ],
                            'defaults' => [
                                'action' => 'restaurer',
                            ],
                        ],
                    ],
                    'ajouter' => [
                        'type' => Segment::class,
                        'options' => [
                            'route' => '/ajouter',
                            'defaults' => [
                                'action' => 'ajouter',
                            ],
                        ],
                    ],
                    'rechercher' => [
                        'type' => Literal::class,
                        'options' => [
                            'route' => '/rechercher',
                            'defaults' => [
                                'controller' => DoctorantController::class,
                                'action' => 'rechercher',
                            ],
                        ],
                    ],
                'may_terminate' => false,
                'child_routes' => [
                    'modifier-email-contact' => [
                        'type' => 'Segment',
                        'options' => [
                            'route' => '/modifier-email-contact',
                            'route' => '/modifier-email-contact/:doctorant',
                            'constraints' => [
                                'doctorant' => '\d+',
                            ],
                            'defaults' => [
                                /**
                                 * @see \Doctorant\Controller\DoctorantController::modifierEmailContactAction()
@@ -113,7 +186,10 @@ return [
                    'modifier-email-contact-consent' => [
                        'type' => 'Segment',
                        'options' => [
                            'route' => '/modifier-email-contact-consent',
                            'route' => '/modifier-email-contact-consent/:doctorant',
                            'constraints' => [
                                'doctorant' => '\d+',
                            ],
                            'defaults' => [
                                /**
                                 * @see \Doctorant\Controller\DoctorantController::modifierEmailContactConsentAction()
@@ -122,18 +198,6 @@ return [
                            ],
                        ],
                    ],
                    'consentement' => [
                        'type' => 'Literal',
                        'options' => [
                            'route' => '/consentement',
                            'defaults' => [
                                /**
                                 * @see \Doctorant\Controller\DoctorantController::consentementAction()
                                 */
                                'action' => 'consentement',
                            ],
                        ],
                    ],
                ],
            ],
        ],
@@ -158,6 +222,7 @@ return [
    'service_manager' => [
        'factories' => [
            'DoctorantService' => DoctorantServiceFactory::class,
            DoctorantSearchService::class => DoctorantSearchServiceFactory::class,
            TheseAssertion::class => TheseAssertionFactory::class,
            TheseEntityAssertion::class => TheseEntityAssertionFactory::class,
        ],
@@ -166,9 +231,11 @@ return [
        ]
    ],
    'controllers' => [
        'invokables' => [],
        'factories' => [
            'Application\Controller\Doctorant' => DoctorantControllerFactory::class,
            DoctorantController::class => DoctorantControllerFactory::class,
        ],
        'aliases' => [
            'Application\Controller\Doctorant' => DoctorantController::class,
        ],
    ],
    'controller_plugins' => [
+37 −2
Original line number Diff line number Diff line
@@ -2,21 +2,31 @@

namespace Doctorant\Controller;

use Application\Constants;
use Application\Controller\AbstractController;
use Application\Entity\Db\MailConfirmation;
use Application\Form\MailConfirmationForm;
use Application\RouteMatch;
use Application\Search\Controller\SearchControllerInterface;
use Application\Search\Controller\SearchControllerTrait;
use Application\Search\SearchServiceAwareTrait;
use Application\Service\MailConfirmationService;
use Doctorant\Entity\Db\Doctorant;
use Doctorant\Form\MailConsentementForm;
use Doctorant\Service\DoctorantServiceAwareTrait;
use Laminas\Http\Response;
use Laminas\Paginator\Paginator as LaminasPaginator;
use Laminas\View\Model\JsonModel;
use Laminas\View\Model\ViewModel;
use UnicaenAuth\Authentication\Adapter\Ldap as LdapAuthAdapter;

class DoctorantController extends AbstractController
/**
 * @property \Doctorant\Service\Search\DoctorantSearchService $searchService
 */
class DoctorantController extends AbstractController implements SearchControllerInterface
{
    use SearchServiceAwareTrait;
    use SearchControllerTrait;

    use DoctorantServiceAwareTrait;

    /** @var MailConfirmationService $mailConfirmationService */
@@ -51,6 +61,31 @@ class DoctorantController extends AbstractController
        $this->mailConsentementForm = $mailConsentementForm;
    }

    public function indexAction(): Response|ViewModel
    {
        $result = $this->search();
        if ($result instanceof Response) {
            return $result;
        }
        /** @var LaminasPaginator $paginator */
        $paginator = $result;

        return new ViewModel([
            'paginator' => $paginator,
            'filters' => $this->filters(),
        ]);
    }

    public function consulterAction(): ViewModel
    {
        /** @var \Doctorant\Entity\Db\Doctorant $doctorant */
        $doctorant = $this->doctorantService->getRepository()->find($this->params('doctorant'));

        return new ViewModel([
            'doctorant' => $doctorant,
        ]);
    }

    /**
     * @return \Laminas\Http\Response|\Laminas\View\Model\ViewModel
     */
Loading