浏览代码

update doc/analyse_082022.md

Olivier Massot 3 年之前
父节点
当前提交
ce5acb85ad
共有 1 个文件被更改,包括 879 次插入66 次删除
  1. 879 66
      doc/analyse_082022.md

+ 879 - 66
doc/analyse_082022.md

@@ -1,17 +1,11 @@
 # Analyse - 09/2022
 
-## Workflow du traitement de données
+## Workflow du traitement de données des adresses postales sur la fiche de la structure
 
 Etude du workflow des pages `pages/organization/index.vue` et `pages/organization/address/*`
 
-### Entités
-
 #### Get
 
-##### Via UiCollection
-
-> Exemple : l'affichage des adresses sur la fiche de la structure
-
 La page intègre le component `UiCollection`, qui prend en paramètres :
 
 * la query (récupérée depuis le repo)
@@ -25,44 +19,6 @@ Elle appelle ensuite cette méthode et récupère le `fetchState` et les items s
 
 Enfin, elle passe les items à son slot, qui les utilise pour afficher les données
 
-
-##### Via UiItemFromUri
-
-> Exemple : l'affichage du pays dans le détail des adresses de la fiche de la structure
-
-Extrait l'id de l'Uri
-
-utilise le `useFetch` natif de nuxt en lui passant en callback un appel à `$dataProvider.invoke`
-
-L'item est ensuite retourné sous forme d'une ComputedRef qui retourne elle-même `queryHelper.getItem`
-
-
-##### Via UiDataTable
-
-Prend en paramètres :
-
-* la query (récupérée depuis le repo)
-* le modèle de l'entity concernée
-* le modèle depuis lequel il est modifié
-* l'id du modèle depuis lequel il est modifié
-
-Utilise `useFetch` en lui passant en callback un appel à `$dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)`
-
-
-##### UiLayoutAlertBarCotisation
-
-Récupère l'état des cotisations avec un `useFetch` auquel on passe en callback un appel à `$dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})`
-
-##### Composable useCountryProvider
-
-Utilise `useFetch` en lui passant en callback un appel à `$dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)`
-
-##### Composable useTypeOfPracticeProvider
-
-Utilise `useFetch` en lui passant en callback un appel à `$dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)`
-
-
-
 #### Create
 
 ##### Page `entity/new.vue`
@@ -149,10 +105,8 @@ L'entry correspond à l'entité dans le store telle que récupérée via le repo
 `updateRepository` sera appellée par chaque input qui sont mis à jour
 
 
-
 #### Edit
 
-
 ##### Page `entity/_id.vue`
 
 Idem *create*, excepté que le formulaire est inclus dans une page `entity/_id.vue`
@@ -164,8 +118,81 @@ Parse l'id de l'entité à éditer directement depuis l'url
 Passe l'id et le type d'entité à la méthode `getItemToEdit` qui appelle la méthode `$dataProvider.invoke` et retourne une ref `fetchState`.
 
 
+
+## Autres Usages
+
+### Entités
+
+##### Via UiCollection
+
+GET
+
+> Exemple : l'affichage des adresses sur la fiche de la structure
+
+La page intègre le component `UiCollection`, qui prend en paramètres :
+
+* la query (récupérée depuis le repo)
+* le modèle de l'entity concernée
+* le modèle d'origine (ici Organization)
+* l'id d'origine (celui de l'orga)
+
+L'`UiCollection` récupère la méthode `getCollection` de `useDataUtils` (à qui elle a passé le `dataProvider`)
+
+Elle appelle ensuite cette méthode et récupère le `fetchState` et les items sous forme de ref
+
+Enfin, elle passe les items à son slot, qui les utilise pour afficher les données
+
+
+##### Via UiItemFromUri
+
+GET
+
+> Exemple : l'affichage du pays dans le détail des adresses de la fiche de la structure
+
+Extrait l'id de l'Uri
+
+utilise le `useFetch` natif de nuxt en lui passant en callback un appel à `$dataProvider.invoke`
+
+L'item est ensuite retourné sous forme d'une ComputedRef qui retourne elle-même `queryHelper.getItem`
+
+
+##### Via UiDataTable
+
+GET
+
+Prend en paramètres :
+
+* la query (récupérée depuis le repo)
+* le modèle de l'entity concernée
+* le modèle depuis lequel il est modifié
+* l'id du modèle depuis lequel il est modifié
+
+Utilise `useFetch` en lui passant en callback un appel à `$dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)`
+
+
+##### UiLayoutAlertBarCotisation
+
+GET
+
+Récupère l'état des cotisations avec un `useFetch` auquel on passe en callback un appel à `$dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})`
+
+##### Composable useCountryProvider
+
+GET
+
+Utilise `useFetch` en lui passant en callback un appel à `$dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)`
+
+##### Composable useTypeOfPracticeProvider
+
+GET
+
+Utilise `useFetch` en lui passant en callback un appel à `$dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)`
+
+
 ##### Composable useMyProfile
 
+GET / EDIT
+
 Propose une méthode pour maj le profile via `$dataPersister.invoke({type: QUERY_TYPE.MODEL, ...})`
 
 
@@ -173,13 +200,16 @@ Propose une méthode pour maj le profile via `$dataPersister.invoke({type: QUERY
 
 ##### Via UiButtonDelete
 
-Appelle directement `$dataDeleter.invoke`
+DELETE
 
+Appelle directement `$dataDeleter.invoke`
 
 
 
 #### Cas particulier : UiLayoutHeaderNotification
 
+GET / EDIT
+
 Récupère les notifs depuis l'api via un useFetch avec un callback à `await $dataProvider.invoke({type: QUERY_TYPE.ENUM, enumType})`
 
 Récupère ensuite les notifs depuis le store sous forme de computed ref
@@ -189,26 +219,24 @@ Récupère ensuite les notifs depuis le store sous forme de computed ref
 
 ### Enums
 
-#### Get
-
 ##### Via UiInputEnum
 
+GET
+
 Récupère l'enum via un useFetch avec un callback à `await $dataProvider.invoke({type: QUERY_TYPE.ENUM, enumType})`
 
 Lorsque les notifications ont été lues, créé un nouvel objet NotificationUser en :
 
 * mettant à jour le store avec `repositoryHelper.persist`
 * l'api avec `$dataPersister.invoke(type: QUERY_TYPE.MODEL, ...)`
-
-
-
+* 
 
 ### Images
 
-#### Get
-
 ##### Via UiImage
 
+GET
+
 Récupère l'image grâce à la méthode `getOne` du composable `useImageProvider` (à qui on a passé le data provider et la config du context)
 
 `getOne` appelle un dataProvider avec le type `QUERY_TYPE.IMAGE` et lui passe les arguments de config de l'image (id, hauteur, largeur)
@@ -217,6 +245,8 @@ En cas d'erreur, retourne une image par défaut.
 
 ##### Via UiInputImage
 
+GET
+
 Si un id a été passé aux props, récupère l'objet File via un useFetch avec un
 callback à `await $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, url: 'api/files', id: props.existingImageId })`
 
@@ -225,23 +255,22 @@ On récupère ensuite l'image grâce à la méthode `getOne` du composable `useI
 `getOne` appelle un dataProvider avec le type `QUERY_TYPE.IMAGE` et lui passe les arguments de config de l'image (id, hauteur, largeur)
 En cas d'erreur, retourne une image par défaut.
 
-#### Put
-
 ##### Via UiInputImage
 
+GET / PUT
+
 Met à jour le store avec `repositoryHelper.persist`
 
 Envoie la requête de maj du File avec `$dataPersister.invoke(type: QUERY_TYPE.MODEL, ...)`
 
-#### Post
-
-##### Via UiInputImage
+POST
 
 Créé un nouvel objet File à sauvegarder
 
 L'envoie à l'API avec `$dataPersister.invoke(type: QUERY_TYPE.FILE, ...)`
 
 
+
 ### Api externes ou routes custom
 
 #### UiInputAutocompleteWithApi
@@ -295,10 +324,12 @@ Appelle la route `/api/dolibarr/account/` via un appel à `$dataProvider.invoke(
 ##### Data managers
 
 * je pense qu'on devrait sortir le `$nuxt.$loading.start()` du service baseDataManager, c'est pas trop à sa place ici
-* le data deleter ne passe pas par un data processor pour mettre à jour le store. mais est-ce nécessaire au final?
+* le data deleter ne passe pas par un dataProcessor pour mettre à jour le store. mais est-ce nécessaire au final?
 * est-ce qu'on devrait pas choisir soit de récupérer les données dans des composables (ex: useTypeOfPracticeProvider, useCountryProvider), soit dans les components? si on maintient les composables, on pourrait
-  les mettre dans un sous-dossier? ou même factoriser la méthode?
-
+  les mettre dans un sous-dossier ? ou même factoriser la méthode ?
+* DataPersister L25 : c'est bizarre de stocker l'objet sérialisé dans une de ses propres props non ? on devrait peut-être créer une nouvelle variable et laisser là l'objet DataPersisterArgs
+* ce serait pas mal de passer juste les props nécessaire aux différentes méthodes plutôt que le paquet `queryArgs` à chaque fois, je pense qu'on y verrait plus clair sur qui fait quoi
+* Les services data managers devraient être indépendant de VuexOrm/PiniaOrm, on devrait donc remplacer le param 'query' du data persister par des données sérialisables
 
 ##### Forms
 
@@ -309,7 +340,9 @@ Appelle la route `/api/dolibarr/account/` via un appel à `$dataProvider.invoke(
 * plutôt que d'instancier une entité dans la page et de la passer au form, est-ce qu'on ne passerait pas un param 'create' au form? il faudrait lever des erreurs si `create == true & id != null` ou `create == false & id == null`
 * comment imposer des propriétés à tous les FormXxxx? Serait-il possible de mettre en place une interface pour l'array props, et de la tester depuis UiForm?
 * useError, ligne 7 : je suppose que la docstring est pas la bonne?
-
+* uiForm, L185-188 : je passerais ça dans une méthode `resetEntry` par ex
+* components/Form/Organization/ContactPoint.vue, L62 : est-ce que l'action Back serait pas à sa place avec les actions SUBMIT_TYPE ?
+* 
 ##### Actions des forms
 
 * `actions[SUBMIT_TYPE.SAVE] = { path: `/organization/address/` }` : un moyen de rendre plus explicite que l'url dans path est la page où l'on se rend après l'action?
@@ -320,10 +353,23 @@ Appelle la route `/api/dolibarr/account/` via un appel à `$dataProvider.invoke(
 
 * voir à sortir les services profiles/* et rights/* et à les passer dans les composables (il faudra maj les plugins ability et castl)
 * UiItemFromUri, ligne 52 : pqoi ne pas appeller directement `ModelsUtils.extractIdFromUri(uri)`
-* UiInputImage, ligne 120 : est-ce que ce ne serait pas à sa place dans un processor attaché au data provider ça?
+* UiInputImage, ligne 112 : est-ce que ce ne serait pas à sa place dans un processor attaché au data provider QUERY_TYPE.IMAGE ça?
+* pourquoi avoir séparé les pages parameters et organization en deux? devrais-je faire la même chose avec subscription? (ex: pages/organization.vue et pages/parameters/index.vue) 
+* component useDataUtils : 
+  * on pourrait renommer `getItemToEdit` en `getItem`?
+  * on pourrait ptêt renommer la méthode `createItem` en `getItemCreator` ou un truc du genre, pour expliciter le fait qu'on retourne une méthode, pas qu'on créé directement l'objet
+* page communication : passer getCurrentWebsite dans un service? (s'il n'existe pas déjà)
+* UiCollection : on pourrait pas passer l'entry root plutot que son model+id? est-ce que le terme de parent serait plus précis ou pas ?
+* middleware/auth.ts : on devrait mettre l'uri du front "admin" non?
+* pour gérer les cas comme sur la page subdomains/id_, on pourrait sortir la logique de soumission et validation de UiForm et mettre ça dans un component séparé ou dans un composable? de cette façon, on pourrait utiliser 
+  cette logique sans être dans un formulaire classique (boutons, dialog…)
+* détail : dans UiCollection, on pourrait ptêt éviter des confusions en renommant la prop `newLink` en qqchose comme `pageNewUri`, ou `linkToNewPage`, ou même `linkToNew`, mais bon, c'est ptêt pas nécessaire...
+* useValidator : est-ce qu'on ne passerait pas le contenu de ces fonctions dans des services de validation? même chose pour les autres composable qui font des requêtes à l'api ou des calculs testables
 
 ### Structure
 
+#### Entity Manager
+
 Mettre en place un "guichet unique" pour les entités, nommé par ex entityManager
 
 Décliner les data-managers en :
@@ -333,7 +379,7 @@ Décliner les data-managers en :
 * FileManager (`fileManager`)
 * ImageManager (`imageManager`), utile? ou fusionner avec le FileManager?
 
-Ces managers auraient des méthodes comme : `getRepository` (voir si nécessaire de garder l'étape des repos), `fetch`, `persist`, `delete`, `new` ou `init`, `flush` (?)
+Ces managers auraient des méthodes comme : `getRepository` (voir si nécessaire de garder l'étape des repos), `fetch`, `persist`, `delete`, `new` ou `init`, `flush` (?), `rollback` ou `reset`
 
 Ils seraient responsables de la validation, de l'état de loading, de l'état dirty
 
@@ -342,3 +388,770 @@ Ils seraient importables depuis un composable du type `useDataManager` ou `useEn
 > Incorporer les méthodes de useDataUtils dans ce manager
 > Voir si utile de récupérer des méthodes de useForm? Pas sûr, mais l'objectif serait que useForm soit seul à interagir avec le store formState, et aussi le seul à interagir avec.
 
+L'idée serait que ce que l'on écrit comme ça aujourd'hui :
+
+    const {getItemToEdit} = useDataUtils($dataProvider)
+    const {fetchState} = getItemToEdit(id, Parameters)
+    const repository: VuexRepository<Model> = repositoryHelper.getRepository(Parameters)
+    const query: ComputedRef<Query> = computed(() => repository.query())
+    const entry: ComputedRef<AnyJson> = computed(() => {
+      return queryHelper.getFlattenEntry(query.value, id)
+    })
+
+Puisse s'écrire plus ou moins comme ça :
+
+    const { em } = useEntityManager()
+    { fetchState, entity } = em.fetch(Parameters, id)   // où fetchState et entity serait des 'ref'?
+
+Ou même quelque chose comme ça ?
+
+    const { em } = useEntityManager()
+    let fetchState = ref(true)
+    let parameters = ref(null)
+    em.fetch(Parameters, id).onSuccess((e) => { fetchState.value = false; entity.value = e; })
+
+Si on décide finalement de conserver la mécanique actuelle, je propose alors de revoir les termes employés, afin de bien identifier les 
+différentes opérations
+
+#### Application aux différents types d'usage
+
+1. Récupération et mise à jour du profil
+2. Affichage des données d'une entité
+3. Affichage d'une collection d'entités
+4. Formulaire de création / édition d'une entité
+5. Population d'un input par un enum
+6. Population d'un input par des entités (ex: liste des pays)
+7. Population d'un input depuis une source externe (ex: adresses)
+8. Affichage / upload d'une image
+9. Téléchargement / upload de fichier
+10. Affichage et éditions des notifications
+11. Validation de données (ex: siret)
+12. Affichage de données issues d'une api externe ou de routes custom (ex: dolibarr, mobyt)
+
+##### 1- Récupération et mise à jour du profil
+
+Actuellement, cette mise à jour se fait dans store/index.js :
+
+    async nuxtServerInit ({ dispatch }, { req }) {
+      await dispatch('initCookies', { req })
+      await dispatch('getUserProfile')
+    },
+
+    async getUserProfile ({ dispatch }) {
+      const myProfile = await this.app.context.$dataProvider.invoke({
+        type: QUERY_TYPE.DEFAULT,
+        url: '/api/my_profile'
+      })
+      await dispatch('profile/access/setProfile', myProfile.data)
+    }
+
+
+Ici a priori, un simple appel à `Connection.get` pourrait suffire (il faut juste ajouter une méthode get pour les 
+url custom, parce que getItem est dédié aux entities). D'autant que le denormalizer des data provider est le denormalizer
+Hydra, ce qui n'est **pas adapté** aux routes comme api/my_profile par ex.
+
+Ça donnerait un truc du genre :
+
+    async getUserProfile ({ dispatch }) {
+      const myProfile = await this.app.context.$connection.get('/api/my_profile')
+      await dispatch('profile/access/setProfile', myProfile)
+    }
+
+
+##### 2- Affichage des données d'une entité
+
+Exemple de pages/organization.vue :
+
+    const {store, $dataProvider} = useContext()
+    const {getItemToEdit} = useDataUtils($dataProvider)
+    const id = store.state.profile.organization.id
+
+    const {fetchState} = getItemToEdit(id, Organization)
+
+    const repository = repositoryHelper.getRepository(Organization)
+    const query: ComputedRef<Query> = computed(() => repository.query())
+    const entry: ComputedRef<Item> = computed(() => {
+      return queryHelper.getItem(query.value, id)
+    })
+
+    return {
+      entry,
+      fetchState
+    }
+
+
+Est-ce qu'on peut espérer quelque chose de ce genre ?
+
+    const { em } = useEntityManager()
+    
+    const { fetchState, organization } = em.fetch(Organization, id)
+
+    return {
+      organization,
+      fetchState
+    }
+    
+
+##### 3- Affichage d'une collection d'entités
+
+Exemple de pages/organization/index.vue :
+
+    <script>
+      function getRepositories () {
+        return {
+           (...)
+           contactPointRepository: repositoryHelper.getRepository(ContactPoint),
+           (...)
+        }
+      }
+      
+      setup () {
+        (...)
+        return {
+          repositories: () => getRepositories(),
+          (...)  
+        }
+      }
+    </script>
+
+    <template>
+      <UiCollection
+        :query="repositories().contactPointRepository.query()"
+        :root-model="models().Organization"
+        :root-id="id"
+        :model="models().ContactPoint"
+        newLink="/organization/contact_points/new"
+      >
+        <template #list.item="{items}">
+          (...)
+        </template>
+      </UiCollection>
+
+    </template>
+
+
+Dans components/Ui/Collection.vue :
+
+    const { rootModel, rootId, model, query }: ToRefs = toRefs(props)
+    const { $dataProvider } = useContext()
+    const { getCollection } = useDataUtils($dataProvider)
+    const {fetchState} = getCollection(model.value, rootModel.value, rootId.value)
+    const items: ComputedRef<Collection> = computed(() => queryHelper.getCollection(query.value))
+
+    return {
+      items,
+      fetchState
+    }
+
+
+Est-ce qu'on peut espérer quelque chose de ce genre ?
+
+    const { em } = useEntityManager()
+    
+    const { fetchState, contactPoints } = em.fetchBy(ContactPoint, Organization, organization_id)
+
+    return {
+      contactPoints,
+      fetchState
+    }
+
+
+##### 4- Formulaire de création / édition d'une entité
+
+Exemple de pages/organization/contact_points/_id.vue :
+
+    <script>
+      setup () {
+        const {$dataProvider, route} = useContext()
+        const {getItemToEdit} = useDataUtils($dataProvider)
+        const id = parseInt(route.value.params.id)
+        const {fetchState} = getItemToEdit(id, ContactPoint)
+        return {
+          id,
+          fetchState
+        }
+    </script>
+
+    <template>
+        <FormOrganizationContactPoint :id="id" v-if="!fetchState.pending"></FormOrganizationContactPoint>
+    </template>
+
+Dans components/Form/Organization/ContactPoint.vue :
+
+    <script>
+      setup () {
+        const repository: VuexRepository<Model> = repositoryHelper.getRepository(ContactPoint)
+        const query: Query = repository.query()
+
+        (...)
+
+        return {
+          model: ContactPoint,
+          query: () => query,
+          (...)
+        }
+      }
+    </script>
+
+    <template>
+        <UiForm
+          :id="id"
+          :model="model"
+          :query="query()"
+          :submitActions="submitActions">
+        >
+          <template #form.input="{entry, updateRepository}">
+            (...)
+          </template>
+        </UiForm>
+    </template>
+
+Dans components/Ui/Form.vue :
+
+    <script>
+      setup () {
+        const {$dataPersister, store, app: {i18n}} = useContext()
+        const {id, query}: ToRefs = toRefs(props)
+
+        const entry: ComputedRef<AnyJson> = computed(() => {
+          return queryHelper.getFlattenEntry(query.value, id.value)
+        })
+
+        (...)
+
+        const submit = async (next: string|null = null) => {
+          if(await validate()){
+            markAsNotDirty()
+    
+            try {
+              const response = await $dataPersister.invoke({
+                type: QUERY_TYPE.MODEL,
+                model: props.model,
+                id: store.state.form.formStatus === FORM_STATUS.EDIT ? id.value : null,
+                idTemp: store.state.form.formStatus === FORM_STATUS.CREATE ? id.value : null,
+                query: props.query
+              })
+
+            (...)
+       }
+
+       const quitForm = () => {
+          // reset entity
+          const entryCopy = query.value.first()
+          if (entryCopy && entryCopy.$getAttributes().originalState) {
+            repositoryHelper.persist(props.model, entryCopy.$getAttributes().originalState)
+          }
+       }
+
+       return {
+        submit,
+        updateRepository,
+        entry,
+        quitForm,
+      }
+    </script>
+
+    <template>
+      <v-form
+        ref="form"
+        lazy-validation
+        :readonly="readonly"
+      >
+        (...)
+        <UiButtonSubmit
+          v-if="!readonly"
+          @submit="submit"
+          :actions="actions"
+        ></UiButtonSubmit>
+      </v-form>
+    </template>
+
+
+Alternative :
+
+    (...)
+
+##### 5- Population d'un input par un enum
+
+Exemple sur la page components/Form/Organization/ContactPoint.vue :
+
+    <UiInputEnum
+      field="contactType"
+      label="contactpoint_type"
+      :data="entry['contactType']"
+      enum-type="contact_point_type"
+      @update="updateRepository"
+    />
+
+Dans components/Ui/Input/Enum.vue :
+
+    const { enumType } = props
+    const { $dataProvider, store } = useContext()
+    (...)
+
+    const items: Ref<Array<EnumChoices>> = ref([])
+    useFetch(async () => {
+      items.value = await $dataProvider.invoke({
+        type: QUERY_TYPE.ENUM,
+        enumType
+      })
+    })
+
+    return {
+      items,
+      (...)
+    }
+
+
+Alternative :
+
+    (...)
+
+
+##### 6- Population d'un input par des entités (ex: liste des pays)
+
+Exemple sur la page components/Form/Organization/Address.vue :
+
+    <script>
+
+        const {countries, fetchState: countriesFetchingState} = useCountryProvider($dataProvider)
+
+        return {
+          (...)
+          countries,
+          countriesFetchingState,
+          (...)
+        }
+    </script>
+
+    <template>
+      <UiInputAutocomplete
+        field="addressPostal.addressCountry"
+        label="addressCountry"
+        :data="getIdFromUri(entry['addressPostal.addressCountry'])"
+        :items="countries"
+        :isLoading="countriesFetchingState.pending"
+        :item-text="['name']"
+        @update="updateRepository(`/api/countries/${$event}`, 'addressPostal.addressCountry')"
+      />
+    </template>
+
+Dans composables/data/useCountryProvider.ts :
+
+    export function useCountryProvider($dataprovider: DataProvider){
+      const {fetch, fetchState} = useFetch(async () => {
+        await $dataprovider.invoke({
+          type: QUERY_TYPE.MODEL,
+          model: Country
+        })
+      })
+    
+      const countries =  computed(() => {
+        return repositoryHelper.findCollectionFromModel(Country)
+      })
+    
+      return {
+        countries,
+        fetch,
+        fetchState
+      }
+    }
+
+Dans services/store/repository.ts :
+
+    public findCollectionFromModel (model: typeof Model, orderBy?: OrderByVuexOrm): Collection {
+      const repository = this.getRepository(model)
+      if(orderBy){
+        for(const orderKey in orderBy){
+          repository.orderBy(orderKey, orderBy[orderKey])
+        }
+      }
+      return repository.all()
+    }
+
+
+Alternative :
+
+    (...)
+
+
+##### 7- Population d'un input depuis une source externe (ex : adresses)
+
+Exemple sur la page components/Form/Organization/Address.vue :
+
+    <script>
+      const {searchFunction: addressSearch, updateCpAddress} = useAddressPostalUtils($dataProvider)
+
+      return {
+        (...)
+        addressSearch,
+        updateCpAddress,
+        (...) 
+      }
+    </script
+
+    <template>
+      (...)
+
+      <UiInputAutocompleteWithAPI
+        field="owner"
+        label="importAddress"
+        :item-text="['person.givenName', 'person.name']"
+        :searchFunction="accessSearch"
+        @update="updateAccessAddress"
+      />
+
+      (...)
+
+      <UiInputAutocompleteWithAPI
+        field="addressPostal.postalCode"
+        label="postalCode"
+        :data="getAutoCompleteAddressItem"
+        :item-text="['postcode']"
+        :slotText="['postcode', 'city']"
+        :searchFunction="addressSearch"
+        :returnObject="true"
+        @update="updateCpAddress($event, updateRepository)"
+      />
+
+      <UiInputAutocompleteWithAPI
+        field="addressPostal.addressCity"
+        label="addressCity"
+        :data="getAutoCompleteAddressItem"
+        :item-text="['city']"
+        :slotText="['postcode', 'city']"
+        :searchFunction="addressSearch"
+        :returnObject="true"
+        @update="updateCpAddress($event, updateRepository)"
+      />
+
+      (...)
+    </template>
+
+Dans components/Ui/Input/AutocompleteWithAPI.vue :
+
+
+    useFetch(async () => {
+      isLoading.value = true
+      const r = await $dataProvider.invoke({
+        type: QUERY_TYPE.DEFAULT,
+        url: props.remoteUrl,
+        listArgs: {
+          filters:[
+            {key: 'id', value: ids.join(',')}
+          ]
+        }
+      })
+      isLoading.value = false
+      remoteData.value = r.data
+      items.value = r.data
+    })
+
+
+Dans composables/data/useAddressPostalUtils.ts :
+
+    async function searchFunction(research: string, field: string): Promise<Array<AnyJson>> {
+      if (research) {
+        const response = await $dataProvider.invoke({
+          type: QUERY_TYPE.DEFAULT,
+          url: `https://api-adresse.data.gouv.fr/search/?q=${research}&type=municipality&autocomplete=1&limit=20`,
+          params: {
+            noXaccessId: true
+          }
+        })
+        const apiResponse = response.data.features.map((data: AnyJson) => data.properties)
+
+        (...)
+    }
+
+
+
+Alternative :
+
+    (...)
+
+
+##### 8- Affichage / upload d'une image
+
+Sur la page pages/organization/index.vue :
+
+    <UiImage
+      :id="getIdFromUri(entry['logo'])"
+      :upload="true"
+      :width="200"
+      field="logo"
+      :ownerId="id"
+      @update="updateRepository"
+    ></UiImage>
+
+Dans components/Ui/Image.vue :
+
+    <script>
+      const {getOne, provideImg} = useImageProvider($dataProvider, $config)
+  
+      const { imageLoaded, fetch } = getOne(props.id, props.imageByDefault, props.height, props.width)
+    </script>
+
+    <template>
+      <v-img
+        :src="imgSrcReload ? imgSrcReload : imageLoaded"
+        :lazy-src="require(`/assets/images/byDefault/${imageByDefault}`)"
+        :height="height"
+        :width="width"
+        aspect-ratio="1"
+      >(...)</v-img>
+
+      <UiInputImage
+        v-if="openUpload"
+        @close="openUpload=false"
+        :existingImageId="id"
+        :field="field"
+        :ownerId="ownerId"
+        @update="$emit('update', $event, field); openUpload=false"
+        @reload="fetch();openUpload=false"
+        @reset="reset"
+      ></UiInputImage>
+    </template>
+
+Dans components/Ui/Input/Image.vue :
+
+    const {$dataProvider, $config, $dataPersister} = useContext()
+    const {getOne} = useImageProvider($dataProvider, $config)
+
+    const fileToSave = new File()
+    const cropper:Ref<any> = ref(null)
+    const image: Ref<AnyJson> = ref({
+      id: null,
+      src: null,
+      file: null,
+      name: null
+    })
+
+    (...)
+
+    if(props.existingImageId){
+      useFetch(async () => {
+        const result: ApiResponse = await $dataProvider.invoke({
+          type: QUERY_TYPE.DEFAULT,
+          url: 'api/files',
+          id: props.existingImageId
+        })
+
+        const config = JSON.parse(result.data.config)
+        (...)
+      })
+    }
+
+    const { fetchState, imageLoaded } = getOne(props.existingImageId)
+    const unwatch: WatchStopHandle = watch(imageLoaded, (newValue, oldValue) => {
+      if (newValue === oldValue || typeof newValue === 'undefined') { return }
+      image.value.src = newValue
+    })
+
+    const save = async () => {
+      fileToSave.config = JSON.stringify({
+        x: coordinates.value.left,
+        y: coordinates.value.top,
+        height: coordinates.value.height,
+        width: coordinates.value.width
+      })
+
+      //Cas d'un PUT : l'image existe déjà on bouge simplement le cropper
+      if(image.value.id){
+        fileToSave.id = image.value.id
+        repositoryHelper.persist(File, fileToSave)
+        await $dataPersister.invoke({
+          type: QUERY_TYPE.MODEL,
+          model: File,
+          id: fileToSave.id
+        })
+        //On émet un évent afin de mettre à jour le formulaire de départ
+        emit('reload')
+      }
+
+      //Post : on créer une nouvelle image donc on passe par l'api legacy...
+      else{
+        if(image.value.file){
+          //On créer l'objet File à sauvegarder
+          fileToSave.name = image.value.name
+          fileToSave.imgFieldName = props.field
+          fileToSave.visibility = 'EVERYBODY'
+          fileToSave.folder = 'IMAGES'
+
+          if(props.ownerId)
+            fileToSave.ownerId = props.ownerId
+
+          //Appel au datapersister
+          const response: ApiResponse = await $dataPersister.invoke({
+            type: QUERY_TYPE.FILE,
+            baseUrl: $config.baseURL_Legacy,
+            data: fileToSave.$toJson(),
+            file: image.value.file
+          })
+          //On émet un évent afin de mettre à jour le formulaire de départ
+          emit('update', response.data['@id'])
+        }else{
+          //On reset l'image : on a appuyer sur "poubelle" puis on enregistre
+          emit('reset')
+        }
+      }
+    }
+
+Dans composables/data/useImageProvider.ts :
+
+    async function provideImg(id: number, height: number = 0, width: number = 0) {
+      return await $dataProvider.invoke({
+        type: QUERY_TYPE.IMAGE,
+        baseUrl: $config.baseURL_Legacy,
+        imgArgs: {
+          id: id,
+          height: height,
+          width: width
+        }
+      })
+    }
+
+
+Alternative :
+
+    (...)
+
+
+##### 9. Téléchargement / upload de fichier
+
+_(exemple?)_
+
+
+##### 10- Affichage et éditions des notifications
+
+Dans components/Layout/Header/Notification.vue :
+
+    const {fetch, fetchState} = useFetch(async () => {
+      data.value = await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: Notification,
+        listArgs: {
+          itemsPerPage: 10,
+          page: page.value
+        }
+      })
+      loading.value = false
+    })
+
+    const notifications: ComputedRef = computed(() => {
+      const query = repositoryHelper.getRepository(Notification).with('message').orderBy('id', 'desc')
+      return queryHelper.getCollection(query)
+    })
+
+    (...)
+
+    const markNotificationsAsRead = () => {
+      unreadNotification.value.map((notification:Notification)=>{
+        notification.notificationUsers = ['read']
+        repositoryHelper.persist(Notification, notification)
+        createNewNotificationUsers(notification)
+      })
+    }
+
+    const createNewNotificationUsers = (notification: Notification) =>{
+      const newNotificationUsers = repositoryHelper.persist(NotificationUsers, new NotificationUsers(
+        {
+          access:`/api/accesses/${currentAccessId}`,
+          notification:`/api/notifications/${notification.id}`,
+          isRead: true
+        }
+      )) as NotificationUsers
+
+      $dataPersister.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: NotificationUsers,
+        idTemp: newNotificationUsers.id,
+        showProgress: false
+      })
+    }
+
+Alternative :
+
+    (...)
+
+##### 11- Validation de données
+
+Sur la page pages/organization/index.vue :
+
+    const checkSiretHook = async (siret: string, field: string, updateRepository: any) => {
+      await checkSiret(siret)
+      if (!siretError.value) { updateRepository(siret, field) }
+    }
+
+Dans composables/form/useValidator.ts :
+
+    function useHandleSiret() {
+      const siretError: Ref<boolean> = ref(false)
+      const siretErrorMessage: Ref<string> = ref('')
+  
+      const checkSiret = async (siret: string) => {
+        const response = await $dataProvider.invoke({
+          type: QUERY_TYPE.DEFAULT,
+          url: '/api/siret-checking',
+          id: siret
+        })
+  
+        if (typeof response !== 'undefined') {
+          siretError.value = !response.isCorrect
+          siretErrorMessage.value = response.isCorrect ? '' : i18n.t('siret_error') as string
+        } else {
+          siretError.value = false
+          siretErrorMessage.value = ''
+        }
+      }
+      return {
+        siretError,
+        siretErrorMessage,
+        checkSiret
+      }
+    }
+
+Alternative :
+
+    (...)
+
+
+##### 12- Affichage de données issues d'une api externe ou de routes custom
+
+Dans components/Ui/Template/MobytStatus.vue :
+
+    const mobytStatus: Ref<MobytUserStatus | null> = ref(null)
+
+    // fetch the mobyt status
+    const { fetchState } = useFetch(async () => {
+      try {
+        const response:ApiResponse = await $dataProvider.invoke({
+          type: QUERY_TYPE.DEFAULT,
+          url: '/api/mobyt/status/' + id
+        })
+        mobytStatus.value = response.data as MobytUserStatus
+        if(!mobytStatus.value?.active) emit('disabled_sms_row')
+      } catch (Error) {
+        // eslint-disable-next-line no-console
+        console.error('Error: Mobyt status not found')
+      }
+    })
+
+Même remarque que pour le 1-, ce serait mieux d'utiliser directement le service Connection :
+
+    async getUserProfile ({ dispatch }) {
+      const myProfile = await this.app.context.$connection.get('/api/my_profile')
+      await dispatch('profile/access/setProfile', myProfile)
+    }
+
+    // fetch the mobyt status
+    const { fetchState } = useFetch(async () => {
+      try {
+        const response: ApiResponse = await $connection.get('/api/mobyt/status/' + id)
+        mobytStatus.value = response as MobytUserStatus
+        if(!mobytStatus.value?.active) emit('disabled_sms_row')
+      } catch (Error) {
+        // eslint-disable-next-line no-console
+        console.error('Error: Mobyt status not found')
+      }
+    })