Bläddra i källkod

Merge branch 'develop' of git@gitlab.2iopenservice.com:vincent/admin.git
into develop

Conflicts:
lang/breadcrumbs/fr-FR.js
package.json
services/rights/abilitiesUtils.ts
yarn.lock

Olivier Massot 4 år sedan
förälder
incheckning
1c67af0c68
43 ändrade filer med 986 tillägg och 443 borttagningar
  1. BIN
      assets/images/byDefault/men-1.png
  2. BIN
      assets/images/byDefault/women-1.png
  3. BIN
      assets/vue_js_logo.png
  4. 1 0
      components/Layout/Header.vue
  5. 64 37
      components/Layout/Header/Menu.vue
  6. 2 0
      components/Layout/SubHeader/PersonnalizedList.vue
  7. 80 0
      components/Ui/Image.vue
  8. 1 1
      components/Ui/Input/Autocomplete.vue
  9. 1 1
      components/Ui/Input/AutocompleteWithAPI.vue
  10. 1 1
      components/Ui/Input/Checkbox.vue
  11. 1 1
      components/Ui/Input/DatePicker.vue
  12. 1 1
      components/Ui/Input/Enum.vue
  13. 1 1
      components/Ui/Input/Text.vue
  14. 100 0
      config/abilities/pages/myAccount.yaml
  15. 1 1
      config/nuxtConfig/build.js
  16. 1 0
      jest.config.js
  17. 1 0
      lang/breadcrumbs/fr-FR.js
  18. 17 2
      lang/layout/fr-FR.js
  19. 1 1
      layouts/default.vue
  20. 2 2
      package.json
  21. 0 2
      pages/subscription.vue
  22. 12 8
      services/connection/connection.ts
  23. 30 1
      services/connection/urlBuilder.ts
  24. 13 14
      services/data/baseDataManager.ts
  25. 6 6
      services/data/dataDeleter.ts
  26. 9 9
      services/data/dataPersister.ts
  27. 9 8
      services/data/dataProvider.ts
  28. 3 1
      services/data/processor/_import.ts
  29. 36 0
      services/data/processor/imageProcessor.ts
  30. 70 15
      services/profile/accessProfile.ts
  31. 10 1
      services/profile/organizationProfile.ts
  32. 17 14
      services/rights/abilitiesUtils.ts
  33. 27 16
      store/profile/access.ts
  34. 6 1
      store/profile/organization.ts
  35. 28 8
      tests/unit/services/connection/connection.spec.ts
  36. 24 0
      tests/unit/services/connection/urlBuilder.spec.ts
  37. 24 0
      tests/unit/services/profile/accessProfile.spec.ts
  38. 2 1
      types/enums.ts
  39. 14 1
      types/interfaces.d.ts
  40. 65 4
      use/layout/Menus/accountMenu.ts
  41. 1 0
      use/layout/Menus/baseMenu.ts
  42. 6 1
      use/layout/Menus/configurationMenu.ts
  43. 298 283
      yarn.lock

BIN
assets/images/byDefault/men-1.png


BIN
assets/images/byDefault/women-1.png


BIN
assets/vue_js_logo.png


+ 1 - 0
components/Layout/Header.vue

@@ -38,6 +38,7 @@ et aux préférences de l'utilisateur
       <template #activator="{ on, attrs }">
         <v-btn
           icon
+          class="ml-2"
           v-bind="attrs"
           v-on="on"
         >

+ 64 - 37
components/Layout/Header/Menu.vue

@@ -4,32 +4,25 @@ header principal (configuration, paramètres du compte...)
 -->
 
 <template>
-  <v-menu offset-y left max-height="300">
+  <v-menu offset-y left>
     <template v-slot:activator="{ on: { click }, attrs }">
       <v-tooltip bottom>
         <template v-slot:activator="{ on: on_tooltips , attrs: attrs_tooltips }">
-          <v-avatar
-            v-if="avatar"
-            size="30"
-            v-bind="[attrs, attrs_tooltips]"
-            v-on="on_tooltips"
-            @click="click"
-          >
-            <img
-              src="https://cdn.vuetifyjs.com/images/john.jpg"
-              alt="John"
-            >
-          </v-avatar>
-
           <v-btn
-            v-else
+
             icon
             v-bind="[attrs, attrs_tooltips]"
             color=""
             v-on="on_tooltips"
             @click="click"
           >
-            <v-icon class="ot_white--text" small>
+            <v-avatar
+              v-if="avatar"
+              size="30"
+            >
+              <UiImage :id="avatarId" :imageByDefault="avatarByDefault" :width="30"></UiImage>
+            </v-avatar>
+            <v-icon  v-else class="ot_white--text" small>
               {{ menu.icon }}
             </v-icon>
           </v-btn>
@@ -37,31 +30,51 @@ header principal (configuration, paramètres du compte...)
         <span>{{ $t(menu.title) }}</span>
       </v-tooltip>
     </template>
-    <v-list dense :subheader="true">
-      <v-list-item dense class="ot_light_grey">
-        <v-list-item-title v-text="$t(menu.title)" />
-      </v-list-item>
-      <template v-for="(item, index) in menu.children">
-        <v-list-item
-          :key="item.title"
-          :href="item.isExternalLink ? item.to : undefined"
-          :to="!item.isExternalLink ? item.to : undefined"
-          router
-          exact
-        >
-          <v-list-item-title v-text="$t(item.title)" />
-        </v-list-item>
-        <v-divider
-          v-if="index < menu.length - 1"
-          :key="index"
-        />
-      </template>
-    </v-list>
+    <v-card scrollable>
+      <v-card-title class="ot_super_light_grey text-body-2 font-weight-bold">
+        {{$t(menu.title)}}
+      </v-card-title>
+      <v-card-text style="max-height: 300px; overflow-y: scroll" class="ma-0 pa-0">
+        <v-list dense :subheader="true">
+          <template v-for="(item, index) in menu.children">
+            <v-list-item
+
+              :id="item.title"
+              :key="item.title"
+              :href="item.isExternalLink ? item.to : undefined"
+              :to="!item.isExternalLink ? item.to : undefined"
+              router
+              exact
+            >
+              <v-list-item-title v-text="$t(item.title)"/>
+            </v-list-item>
+          </template>
+        </v-list>
+      </v-card-text>
+      <v-card-actions class="ma-0 pa-0">
+        <template v-for="(item, index) in menu.actions">
+          <v-list-item
+            class="text-body-2"
+            :id="item.title"
+            :key="item.title"
+            :href="item.isExternalLink ? item.to : undefined"
+            :to="!item.isExternalLink ? item.to : undefined"
+            router
+          >
+            <v-list-item-title v-text="$t(item.title)"/>
+          </v-list-item>
+        </template>
+      </v-card-actions>
+    </v-card>
+
+
   </v-menu>
 </template>
 
 <script lang="ts">
-import { defineComponent } from '@nuxtjs/composition-api'
+import {defineComponent, useStore} from '@nuxtjs/composition-api'
+import {accessState} from "~/types/interfaces";
+import {Store} from "vuex";
 
 export default defineComponent({
   props: {
@@ -73,6 +86,20 @@ export default defineComponent({
       type: Boolean,
       required: false
     }
+  },
+  setup () {
+    const store:Store<any> = useStore()
+    const accessStore: accessState = store.state.profile.access
+    return {
+      avatarId: accessStore.avatarId,
+      avatarByDefault: accessStore.gender == 'MISTER' ? 'men-1.png' : 'women-1.png'
+    }
   }
 })
 </script>
+<style scoped>
+  #logout{
+    background: var(--v-ot_green-base, white);
+    color: white;
+  }
+</style>

+ 2 - 0
components/Layout/SubHeader/PersonnalizedList.vue

@@ -44,6 +44,7 @@ import { PersonalizedList } from '~/models/Access/PersonalizedList'
 import { repositoryHelper } from '~/services/store/repository'
 import { AnyJson } from '~/types/interfaces'
 import { $objectProperties } from '~/services/utils/objectProperties'
+import VueI18n from "vue-i18n";
 
 export default defineComponent({
   fetchOnServer: false,
@@ -85,6 +86,7 @@ export default defineComponent({
 /**
    * On regroupe la list par clé afin de constituer les groups
    * @param lists
+   * @param i18n
    */
 function groupListByKey (lists:Collection<PersonalizedList>): Array<AnyJson> {
   const { app: { i18n } } = useContext()

+ 80 - 0
components/Ui/Image.vue

@@ -0,0 +1,80 @@
+<template>
+  <main>
+    <v-img
+      :src="imageLoaded"
+      :lazy-src="require(`/assets/images/byDefault/${imageByDefault}`)"
+      :min-height="height"
+      :min-width="width"
+      aspect-ratio="1"
+    >
+      <template v-slot:placeholder>
+        <v-row
+          class="fill-height ma-0"
+          align="center"
+          justify="center"
+        >
+          <v-progress-circular
+            indeterminate
+            color="grey lighten-1"
+          ></v-progress-circular>
+        </v-row>
+      </template>
+    </v-img>
+  </main>
+</template>
+
+
+<script lang="ts">
+import {defineComponent, ref, Ref, useContext, useFetch} from '@nuxtjs/composition-api'
+import {QUERY_TYPE} from "~/types/enums";
+
+export default defineComponent({
+  props: {
+    id: {
+      type: Number,
+      required: true
+    },
+    imageByDefault: {
+      type: String,
+      required: false,
+      default: 'default_pic.jpeg'
+    },
+    height: {
+      type: Number,
+      required: false,
+      default: 0
+    },
+    width: {
+      type: Number,
+      required: false,
+      default: 0
+    }
+  },
+  fetchOnServer: false,
+  setup(props) {
+    const {$dataProvider, $config} = useContext()
+    const imageLoaded: Ref<String> = ref('')
+
+    useFetch(async () => {
+        try{
+          imageLoaded.value = await $dataProvider.invoke({
+            type: QUERY_TYPE.IMAGE,
+            baseUrl: $config.baseURL_Legacy,
+            imgArgs: {
+              id: props.id,
+              height: props.height,
+              width: props.width
+            }
+          })
+        }catch (e){
+          imageLoaded.value = require(`/assets/images/byDefault/${props.imageByDefault}`)
+        }
+      }
+    )
+
+    return {
+      imageLoaded
+    }
+  }
+})
+</script>

+ 1 - 1
components/Ui/Input/Autocomplete.vue

@@ -28,7 +28,7 @@ export default defineComponent({
     label: {
       type: String,
       required: false,
-      default: ''
+      default: null
     },
     field: {
       type: String,

+ 1 - 1
components/Ui/Input/AutocompleteWithAPI.vue

@@ -35,7 +35,7 @@ export default defineComponent({
     label: {
       type: String,
       required: false,
-      default: ''
+      default: null
     },
     field: {
       type: String,

+ 1 - 1
components/Ui/Input/Checkbox.vue

@@ -31,7 +31,7 @@ export default defineComponent({
     label: {
       type: String,
       required: false,
-      default: ''
+      default: null
     },
     data: {
       type: Boolean,

+ 1 - 1
components/Ui/Input/DatePicker.vue

@@ -53,7 +53,7 @@ export default defineComponent({
     label: {
       type: String,
       required: false,
-      default: ''
+      default: null
     },
     data: {
       type: [String, Array],

+ 1 - 1
components/Ui/Input/Enum.vue

@@ -40,7 +40,7 @@ export default defineComponent({
     label: {
       type: String,
       required: false,
-      default: ''
+      default: null
     },
     field: {
       type: String,

+ 1 - 1
components/Ui/Input/Text.vue

@@ -26,7 +26,7 @@ export default defineComponent({
     label: {
       type: String,
       required: false,
-      default: ''
+      default: null
     },
     field: {
       type: String,

+ 100 - 0
config/abilities/pages/myAccount.yaml

@@ -0,0 +1,100 @@
+  my_schedule_page:
+    action: 'display'
+    services:
+      access:
+        - { function: isAdminAccount, result: false }
+
+  attendance_bookings_page:
+    action: 'display'
+    services:
+      access:
+        - {function: hasAbility, parameters: [{action: 'write', subject: 'attendances'}]}
+      organization:
+        - {function: hasModule, parameters: ['Attendances']}
+
+  my_attendance_page:
+    action: 'display'
+    services:
+      access:
+        - { function: isAdminAccount, result: false }
+
+  my_invitation_page:
+    action: 'display'
+    services:
+      access:
+        - { function: isAdminAccount, result: false }
+
+  my_students_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: [ 'teacher'] }
+
+  my_students_education_students_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: [ 'teacher'] }
+
+  criteria_notations_page_from_account_menu:
+    action: 'display'
+    services:
+      access:
+        - {function: hasAbility, parameters: [{action: 'read', subject: 'criterianotation'}]}
+      organization:
+        - {function: hasModule, parameters: ['PedagogicsAdministation']}
+
+  my_education_students_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: [ 'student'] }
+
+  send_an_email_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: [ 'admin', 'teacher' ] }
+
+  my_documents_page:
+    action: 'display'
+    services:
+      access:
+        - { function: isAdminAccount, result: false }
+
+  my_profile_page:
+    action: 'display'
+    services:
+      access:
+        - { function: isAdminAccount, result: false }
+
+  adherent_list_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: [ 'member'] }
+      organization:
+        - {function: isShowAdherentList}
+        - {function: hasModule, parameters: ['Users']}
+
+  subscription_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: ['admin', 'administratifManager', 'pedagogicManager', 'financialManager']}
+      organization:
+        - {function: hasModule, parameters: ['GeneralConfig']}
+
+  my_bills_page:
+    action: 'display'
+    services:
+      access:
+        - { function: hasProfile, parameters: ['guardian', 'payor']}
+
+  cmf_licence_page:
+    action: 'display'
+    services:
+      access:
+        - { function: isAdminAccount, result: false }
+      organization:
+        - {function: isCmf}

+ 1 - 1
config/nuxtConfig/build.js

@@ -2,7 +2,7 @@ import webpack from 'webpack'
 
 export default {
   // Enable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
-  ssr: true,
+  target: 'server',
 
   // Auto import components (https://go.nuxtjs.dev/config-components)
   components: true,

+ 1 - 0
jest.config.js

@@ -1,4 +1,5 @@
 module.exports = {
+  resetMocks: true,
   preset: '@nuxt/test-utils',
   moduleNameMapper: {
     '^@/(.*)$': '<rootDir>/$1',

+ 1 - 0
lang/breadcrumbs/fr-FR.js

@@ -2,6 +2,7 @@ export default (context, locale) => {
   return ({
     item: 'Détails',
     organization_breadcrumbs: 'Fiche de la structure',
+    subscription_breadcrumbs: 'Mon abonnement',
     address_breadcrumbs: 'Adresse postale',
     contact_points_breadcrumbs: 'Points de contact'
   })

+ 17 - 2
lang/layout/fr-FR.js

@@ -105,11 +105,26 @@ export default (context, locale) => {
     my_list: 'Mes listes',
     searchList: 'Rechercher parmi mes listes personnalisées',
     template_systems: 'Mails système',
-    my_subscription: 'Mon abonnement',
     informations: 'Informations',
     more_features: 'Plus de fonctionnalités',
     client_id: 'Numéro de client',
     version: 'Version',
-    paying_structure: 'Établissement payeur'
+    paying_structure: 'Établissement payeur',
+    my_account: 'Mon compte',
+    my_schedule_page: "Mon planning",
+    attendance_bookings_menu: "Gestion des absences & fiches de présence",
+    my_attendance: "Mes absences",
+    my_invitation: "Mes invitations",
+    my_students: "Mes élèves",
+    my_students_education_students: "Suivi pédagogique",
+    my_education_students: "Mes évaluations",
+    send_an_email: "Envoyer un email",
+    my_documents: "Mes documents",
+    my_profile: "Mon profil",
+    adherent_list: "Liste des adhérents avec leurs coordonnées",
+    my_subscription: "Mon abonnement",
+    my_bills: "Mes factures",
+    print_my_licence: "Imprimer ma licence CMF",
+    logout: "Se déconnecter",
   })
 }

+ 1 - 1
layouts/default.vue

@@ -1,7 +1,7 @@
 <template>
   <main>
     <!-- The client only is used to show the loading picture (@see https://nuxtjs.org/docs/2.x/features/nuxt-components#the-client-only-component) -->
-    <client-only placeholder="Loading..." />
+    <client-only placeholder=" " />
 
     <v-app dark>
       <LayoutMenu v-if="displayedMenu" :menu="menu" :mini-variant="properties.miniVariant" />

+ 2 - 2
package.json

@@ -5,7 +5,7 @@
   "scripts": {
     "dev": "nuxt --hostname '0.0.0.0' --port 3002",
     "build": "nuxt build",
-    "start": "nuxt start",
+    "start": "nuxt start --hostname '0.0.0.0' --port 3002",
     "generate": "nuxt generate",
     "lint:js": "eslint --ext .js,.vue --ignore-path .gitignore .",
     "lint": "yarn lint:js",
@@ -20,7 +20,7 @@
     "@nuxt/image": "^0.6.0",
     "@nuxt/typescript-runtime": "^2.0",
     "@nuxtjs/axios": "^5.13",
-    "@nuxtjs/composition-api": "^0.28",
+    "@nuxtjs/composition-api": "^0.29.3",
     "@nuxtjs/i18n": "^7.0",
     "@nuxtjs/vuetify": "^1.12.1",
     "@types/lodash": "^4.14",

+ 0 - 2
pages/organization/subscription.vue → pages/subscription.vue

@@ -289,8 +289,6 @@ export default defineComponent({
     const { store } = useContext()
     const organizationProfile = $organizationProfile(store)
 
-    console.log(organizationProfile)
-
     return {
       organizationProfile
     }

+ 12 - 8
services/connection/connection.ts

@@ -1,7 +1,7 @@
-import { NuxtAxiosInstance } from '@nuxtjs/axios'
-import { AxiosRequestConfig } from 'axios'
-import { AnyJson, DataPersisterArgs, DataProviderArgs, UrlArgs } from '~/types/interfaces'
-import { HTTP_METHOD } from '~/types/enums'
+import {NuxtAxiosInstance} from '@nuxtjs/axios'
+import {AxiosRequestConfig} from 'axios'
+import {AnyJson, DataPersisterArgs, DataProviderArgs, UrlArgs} from '~/types/interfaces'
+import {HTTP_METHOD, QUERY_TYPE} from '~/types/enums'
 
 /**
  * @category Services/connection
@@ -33,7 +33,7 @@ class Connection {
         if (args.id) {
           return Connection.getItem(url, args.id, args.showProgress)
         } else {
-          return Connection.getCollection(url, args.showProgress)
+          return Connection.getCollection(url, args.showProgress, args.type)
         }
 
       case HTTP_METHOD.PUT:
@@ -72,14 +72,18 @@ class Connection {
    * Get collection : préparation de la config pour la récupération d'une collection d'items
    * @param {string} url
    * @param {boolean} progress
+   * @param {QUERY_TYPE} type
    * @return {Promise<any>}
    */
-  public static getCollection (url: string, progress: boolean = true): Promise<any> {
-    const config: AxiosRequestConfig = {
+  public static getCollection (url: string, progress: boolean = true, type: QUERY_TYPE): Promise<any> {
+    let config: AxiosRequestConfig = {
       url: `${url}`,
       method: HTTP_METHOD.GET,
       progress
     }
+    if(type === QUERY_TYPE.IMAGE)
+      config = {...config, responseType: 'blob'}
+
     return Connection.request(config)
   }
 
@@ -122,7 +126,7 @@ class Connection {
    * @param {AxiosRequestConfig} config
    * @return {Promise<any>}
    */
-  private static async request (config: AxiosRequestConfig): Promise<any> {
+  public static async request (config: AxiosRequestConfig): Promise<any> {
     return await Connection.connector.$request(config)
   }
 

+ 30 - 1
services/connection/urlBuilder.ts

@@ -1,5 +1,5 @@
 import { Model } from '@vuex-orm/core'
-import { UrlArgs } from '~/types/interfaces'
+import {DataPersisterArgs, DataProviderArgs, ImageArgs, UrlArgs} from '~/types/interfaces'
 import { QUERY_TYPE } from '~/types/enums'
 import { repositoryHelper } from '~/services/store/repository'
 
@@ -25,6 +25,15 @@ class UrlBuilder {
       case QUERY_TYPE.MODEL:
         return UrlBuilder.getModelUrl(args.model, args.rootModel, args.rootId)
 
+      case QUERY_TYPE.IMAGE:
+        if (!UrlBuilder.isDataProviderArgs(args)) {
+          throw new Error('*args* is not a dataProviderArgs')
+        }
+        if (!args.imgArgs) {
+          throw new Error('*args* has no imgArgs')
+        }
+        return UrlBuilder.getImageUrl(args.imgArgs, args.baseUrl)
+
       default:
         throw new Error('url, model or enum must be defined')
     }
@@ -82,6 +91,18 @@ class UrlBuilder {
     return UrlBuilder.concat(UrlBuilder.ROOT, entity)
   }
 
+  /**
+   * Construction d'une URL "image" qui ira concaténer l'id de l'image à downloeader passé en paramètre avec la ROOT Url définie
+   * @param {number} id
+   * @param {ImageArgs} imgArgs
+   * @param {string} baseUrl
+   * @return {string}
+   */
+  private static getImageUrl (imgArgs: ImageArgs, baseUrl: string = ''): string {
+    const downloadUrl = `files/${imgArgs.id}/download/${imgArgs.height}x${imgArgs.width}?${new Date().getTime()}`
+    return UrlBuilder.concat(baseUrl, UrlBuilder.ROOT, downloadUrl)
+  }
+
   /**
    * Concatenate a base url and a tail
    * @param base
@@ -95,6 +116,14 @@ class UrlBuilder {
     })
     return url
   }
+
+  /**
+   * Test si l'argument est bien de type DataProviderArgs
+   * @param args
+   */
+  private static isDataProviderArgs (args: DataProviderArgs|DataPersisterArgs): args is DataProviderArgs {
+    return (args as DataProviderArgs).imgArgs !== undefined
+  }
 }
 
 export default UrlBuilder

+ 13 - 14
services/data/baseDataManager.ts

@@ -1,5 +1,5 @@
 import { Context } from '@nuxt/types/app'
-import { DataManager, UrlArgs } from '~/types/interfaces'
+import {DataManager, DataPersisterArgs, DataProviderArgs, UrlArgs} from '~/types/interfaces'
 import Connection from '~/services/connection/connection'
 import Hookable from '~/services/data/hookable'
 import { HTTP_METHOD, QUERY_TYPE } from '~/types/enums'
@@ -10,7 +10,6 @@ import ApiError from '~/services/exception/apiError'
  */
 abstract class BaseDataManager extends Hookable implements DataManager {
   protected ctx!: Context
-  protected arguments!: UrlArgs
   protected defaultArguments: object = {
     type: QUERY_TYPE.MODEL,
     showProgress: true
@@ -18,7 +17,7 @@ abstract class BaseDataManager extends Hookable implements DataManager {
 
   /**
    * Initialise le contexte (la connection en particulier)
-   * @param ctx
+   * @param {Context} ctx
    */
   public initCtx (ctx: Context) {
     Connection.initConnector(ctx.$axios)
@@ -27,7 +26,7 @@ abstract class BaseDataManager extends Hookable implements DataManager {
 
   /**
    * Exécute la requête et retourne la réponse désérialisée
-   * @param _args
+   * @param {UrlArgs} _args
    */
   // eslint-disable-next-line require-await
   protected async _invoke (_args: UrlArgs): Promise<any> {
@@ -36,16 +35,16 @@ abstract class BaseDataManager extends Hookable implements DataManager {
 
   /**
    * Exécute la requête
-   * @param args
+   * @param {DataProviderArgs|DataPersisterArgs} args
    */
-  public async invoke (args: UrlArgs): Promise<any> {
-    this.arguments = { ...this.defaultArguments, ...args }
-    BaseDataManager.startLoading(this.arguments)
+  public async invoke (args: DataProviderArgs|DataPersisterArgs): Promise<any> {
+    const queryArguments = { ...this.defaultArguments, ...args }
+    BaseDataManager.startLoading(queryArguments)
 
-    await this.triggerHooks(args)
+    await this.triggerHooks(queryArguments)
 
     try {
-      return this._invoke(args)
+      return this._invoke(queryArguments)
     } catch (error) {
       throw new ApiError(error.response.status, error.response.data.detail)
     }
@@ -53,7 +52,7 @@ abstract class BaseDataManager extends Hookable implements DataManager {
 
   /**
    * Signale le début du chargement à nuxt (si showProgress est true)
-   * @param args
+   * @param {UrlArgs} args
    */
   private static startLoading (args: UrlArgs) {
     if (args.showProgress) {
@@ -64,9 +63,9 @@ abstract class BaseDataManager extends Hookable implements DataManager {
 
   /**
    * Send the request trough the Connection
-   * @param url
-   * @param method
-   * @param args
+   * @param {string} url
+   * @param {HTTP_METHOD} method
+   * @param {UrlArgs} args
    */
   protected static request (url: string, method: HTTP_METHOD, args: UrlArgs): Promise<any> {
     return Connection.invoke(method, url, args)

+ 6 - 6
services/data/dataDeleter.ts

@@ -10,22 +10,22 @@ import Connection from '~/services/connection/connection'
  * Le DataDeleter a pour rôle de supprimer des enregistrements via l'API
  */
 class DataDeleter extends BaseDataManager {
-  protected arguments!: DataDeleterArgs
   protected hooks = hooksDeleter;
 
   /**
    * Exécute la requête
+   * @param {DataDeleterArgs} queryArguments
    */
-  protected async _invoke (): Promise<any> {
+  protected async _invoke (queryArguments: DataDeleterArgs): Promise<any> {
     // build the url according to the url args
-    const url = UrlBuilder.build(this.arguments)
+    const url = UrlBuilder.build(queryArguments)
 
     // send the DELETE request to the api
-    await Connection.invoke(HTTP_METHOD.DELETE, url, this.arguments)
+    await Connection.invoke(HTTP_METHOD.DELETE, url, queryArguments)
 
     // update the store
-    if (this.arguments.model) {
-      await repositoryHelper.deleteItem(this.arguments.model, this.arguments.id)
+    if (queryArguments.model) {
+      await repositoryHelper.deleteItem(queryArguments.model, queryArguments.id)
     }
   }
 }

+ 9 - 9
services/data/dataPersister.ts

@@ -14,35 +14,35 @@ import { hooksPersister } from '~/services/data/hooks/hookPersister/_import'
  * réponse de l'API et la retourner.
  */
 class DataPersister extends BaseDataManager {
-  protected arguments!: DataPersisterArgs
   protected hooks = hooksPersister;
 
   /**
    * Exécute la requête et retourne la réponse désérialisée
+   * @param {DataDeleterArgs} queryArguments
    */
-  protected async _invoke (): Promise<any> {
+  protected async _invoke (queryArguments: DataPersisterArgs): Promise<any> {
     // serialize the data to persist and attach it to the request arguments
-    this.arguments.data = Serializer.normalize(this.arguments)
+    queryArguments.data = Serializer.normalize(queryArguments)
 
     // build the url according to the url args
-    const url = UrlBuilder.build(this.arguments)
+    const url = UrlBuilder.build(queryArguments)
 
     // send the POST / PUT request to the api and retrieve the response
     const response = await DataPersister.request(
       url,
-      this.arguments.id ? HTTP_METHOD.PUT : HTTP_METHOD.POST,
-      this.arguments
+      queryArguments.id ? HTTP_METHOD.PUT : HTTP_METHOD.POST,
+      queryArguments
     )
 
     // Deserialize, post-process and return the response
-    return await this.provideResponse(response, this.arguments)
+    return await this.provideResponse(response, queryArguments)
   }
 
   /**
    * Use a data provider to deserialize, post-process and return the response
    *
-   * @param response
-   * @param args
+   * @param {AnyJson} response
+   * @param {DataPersisterArgs} args
    */
   private async provideResponse (response: AnyJson, args: DataPersisterArgs) {
 

+ 9 - 8
services/data/dataProvider.ts

@@ -17,7 +17,6 @@ import { hooksProvider } from '~/services/data/hooks/hookProvider/_import'
  */
 class DataProvider extends BaseDataManager {
   protected progress: boolean = false
-  protected arguments!: DataProviderArgs
   protected hooks = hooksProvider;
   protected defaultArguments: object = {
     type: QUERY_TYPE.MODEL,
@@ -26,35 +25,37 @@ class DataProvider extends BaseDataManager {
 
   /**
    * Exécute la requête et retourne la réponse désérialisée
+   * @param {DataDeleterArgs} queryArguments
    */
-  protected async _invoke (): Promise<any> {
+  protected async _invoke (queryArguments:DataProviderArgs): Promise<any> {
     // build the url according to the url args
-    const url = UrlBuilder.build(this.arguments)
+    const url = UrlBuilder.build(queryArguments)
 
     // send the GET request to the api and retrieve the response
-    const response = await DataProvider.request(url, HTTP_METHOD.GET, this.arguments)
+    const response = await DataProvider.request(url, HTTP_METHOD.GET, queryArguments)
 
     // deserialize the response
     const data = await Serializer.denormalize(response, DENORMALIZER_TYPE.HYDRA)
 
     // post-process the data with the first supported processor
-    return await this.process(data, this.arguments)
+    return await this.process(data, queryArguments  )
   }
 
   /**
    * Find the first supported post data processor, and process the data with it
-   * @param data
-   * @param args
+   * @param {AnyJson} data
+   * @param {DataProviderArgs} args
    */
   public async process (data: AnyJson, args: DataProviderArgs) {
     const postProcessor = this.getProcessor(args)
+    if(!postProcessor) throw new Error('A processor need to be defined')
     return await postProcessor.process(data)
   }
 
   /**
    * Iterate over the available data processors and return an instance of
    * the first one that support the given args
-   * @param args
+   * @param {DataProviderArgs} args
    */
   private getProcessor (args: DataProviderArgs): any {
     for (const processor of processors) {

+ 3 - 1
services/data/processor/_import.ts

@@ -1,9 +1,11 @@
 import ModelProcessor from '~/services/data/processor/modelProcessor'
 import EnumProcessor from '~/services/data/processor/enumProcessor'
 import DefaultProcessor from '~/services/data/processor/defaultProcessor'
+import ImageProcessor from "~/services/data/processor/imageProcessor";
 
 export const processors = [
   DefaultProcessor,
   ModelProcessor,
-  EnumProcessor
+  EnumProcessor,
+  ImageProcessor
 ]

+ 36 - 0
services/data/processor/imageProcessor.ts

@@ -0,0 +1,36 @@
+import {DataProviderArgs, Processor} from '~/types/interfaces'
+import BaseProcessor from '~/services/data/processor/baseProcessor'
+import {QUERY_TYPE} from '~/types/enums'
+
+class ImageProcessor extends BaseProcessor implements Processor {
+  /**
+   * @param {DataProviderArgs} args
+   */
+  public static support(args: DataProviderArgs): boolean {
+    return args.type === QUERY_TYPE.IMAGE
+  }
+
+  /**
+   * proccessing des data
+   * @param {BlobPart[]} data
+   */
+  // eslint-disable-next-line require-await
+  async process(data: any): Promise<any> {
+    let blob = new Blob([data], {type: 'image/jpeg'});
+    return await this.blobToBase64(blob);
+  }
+
+  /**
+   * Transforme un Blob en Base64
+   * @param {Blob} blob
+   */
+  async blobToBase64(blob: Blob) {
+    return new Promise((resolve, _) => {
+      const reader = new FileReader();
+      reader.onloadend = () => resolve(reader.result);
+      reader.readAsDataURL(blob);
+    });
+  }
+}
+
+export default ImageProcessor

+ 70 - 15
services/profile/accessProfile.ts

@@ -1,5 +1,6 @@
-import { Ability } from '@casl/ability'
-import { AbilitiesType, accessState, AccessStore, AnyJson } from '~/types/interfaces'
+import {AbilitiesType, accessState, AccessStore, AnyJson} from "~/types/interfaces";
+import {Ability} from "@casl/ability";
+import {Store} from "vuex";
 
 /**
  * L'AccessProfile permet de manipuler l'AccessState l'Access qui peuvent
@@ -8,7 +9,9 @@ import { AbilitiesType, accessState, AccessStore, AnyJson } from '~/types/interf
  */
 class AccessProfile {
   private accessProfile: accessState
-  private $ability: Ability = {} as Ability
+
+  private $ability:Ability = {} as Ability
+  private store:Store<any>
 
   /**
    * @constructor
@@ -19,6 +22,7 @@ class AccessProfile {
   constructor (store: AccessStore, ability: Ability) {
     this.accessProfile = store.state.profile.access
     this.$ability = ability
+    this.store = store
   }
 
   /**
@@ -42,21 +46,70 @@ class AccessProfile {
   }
 
   /**
-   * Est-ce que l'utilisateur possède l'habilité?
-   *
-   * @param {Array<AbilitiesType>} ability habilité à tester
+   * Est-ce que l'utilisateur possède l'abilité
+   * @param {Array<AbilitiesType>} abilities abilités à tester
    * @return {boolean}
    */
-  hasAbility (ability: Array<AbilitiesType>): boolean {
-    if (ability === null) {
-      return true
+  hasAbility(abilities:Array<AbilitiesType>): boolean{
+    if(abilities === null)
+      return true;
+
+    let hasAbility= false;
+    abilities.map((ability) => {
+      if (this.$ability.can(ability.action, ability.subject))
+        hasAbility = true;
+    });
+    return hasAbility;
+  }
+
+  /**
+   * Retourne vrai si l'utilisateur connecté possède l'un des profiles passés en paramètre
+   * @param {Array<string>} profiles : profiles à tester
+   * @return {boolean}
+   */
+  hasProfile(profiles: Array<string>): boolean{
+    if (null === profiles)
+      return true;
+
+    let hasProfile = false;
+    profiles.map(async (profile) => {
+      if (this.testProfile(profile))
+        hasProfile = true;
+    });
+    return hasProfile;
+  }
+
+  /**
+   * Factory pour tester le profil d'un utilisateur
+   * @param {string} profile : profile à tester
+   * @return {boolean}
+   */
+  testProfile(profile:string): boolean{
+    const factory: {[key: string]: boolean} = {
+      'admin': this.accessProfile.isAdmin,
+      'administratifManager': this.accessProfile.isAdministratifManager,
+      'pedagogicManager': this.accessProfile.isPedagogicManager,
+      'financialManager': this.accessProfile.isFinancialManager,
+      'caMember': this.accessProfile.isCaMember,
+      'student': this.accessProfile.isStudent,
+      'teacher': this.accessProfile.isTeacher,
+      'member': this.accessProfile.isMember,
+      'other': this.accessProfile.isOther,
+      'guardian': this.accessProfile.isGuardian,
+      'payor': this.accessProfile.isPayor,
     }
 
-    let hasAbility = false
-    ability.map((ability) => {
-      if (this.$ability.can(ability.action, ability.subject)) { hasAbility = true }
-    })
-    return hasAbility
+    if(profile in factory)
+      return factory[profile]
+    else return false
+  }
+
+  /**
+   * Est-ce que l'utilisateur possède un compte administrateur
+   * @return {boolean}
+   */
+  isAdminAccount(): boolean{
+    return this.accessProfile.isAdminAccess
   }
 
   /**
@@ -67,7 +120,9 @@ class AccessProfile {
   handler (): AnyJson {
     return {
       hasRole: this.hasRole.bind(this),
-      hasAbility: this.hasAbility.bind(this)
+      hasAbility: this.hasAbility.bind(this),
+      hasProfile: this.hasProfile.bind(this),
+      isAdminAccount: this.isAdminAccount.bind(this)
     }
   }
 }

+ 10 - 1
services/profile/organizationProfile.ts

@@ -121,13 +121,21 @@ class OrganizationProfile {
   }
 
   /**
-   * L'organization possède-t-elle des enfants
+   * L'organization possède t-elle des enfants
    * @return {boolean|null}
    */
   hasChildren (): any {
     return this.organizationProfile.hasChildren
   }
 
+  /**
+   * L'organization peut elle afficher la lister des adhérents avec leurs coordonnées
+   * @return {boolean|null}
+   */
+  isShowAdherentList():any{
+    return this.organizationProfile.showAdherentList;
+  }
+
   /**
    * Factory
    *
@@ -140,6 +148,7 @@ class OrganizationProfile {
       isArtist: this.isArtist.bind(this),
       isManagerProduct: this.isManagerProduct.bind(this),
       isOrganizationWithChildren: this.hasChildren.bind(this),
+      isShowAdherentList: this.isShowAdherentList.bind(this),
       isCmf: this.isCmf.bind(this)
     }
   }

+ 17 - 14
services/rights/abilitiesUtils.ts

@@ -58,7 +58,10 @@ class AbilitiesUtils {
 
         switch (action.type) {
           case 'profile/organization/setProfile':
-            // On les store puis on update le service ability pour le mettre à jour.
+            //On récupère les abilités
+            const abilities: Array<AbilitiesType> = this.getAbilities();
+
+            //On les store puis on update le service ability pour le mettre à jour.
             this.$store.commit('profile/access/setAbilities', abilities)
             this.$ability.update(abilities)
             // Unsubscribe pour éviter les memory leaks
@@ -74,13 +77,11 @@ class AbilitiesUtils {
    *
    * @return {Array<AbilitiesType>}
    */
-  getAbilities (): Array<AbilitiesType> {
-    const abilitiesByRoles = this.getAbilitiesByRoles(this.$store.state.profile.access.roles)
+  getAbilities():Array<AbilitiesType> {
+    const abilitiesByRoles: Array<AbilitiesType> = this.getAbilitiesByRoles(this.$store.state.profile.access.roles)
     this.$ability.update(abilitiesByRoles)
     this.initFactory()
-    return abilitiesByRoles.concat(
-      this.getAbilitiesByConfig('./config/abilities/config.yaml')
-    )
+    return abilitiesByRoles.concat(this.getAbilitiesByConfig('./config/abilities/config.yaml'))
   }
 
   /**
@@ -151,16 +152,18 @@ class AbilitiesUtils {
    * @param {AnyJson} functionByServices
    * @return {boolean}
    */
-  canHaveTheAbility (functionByServices: AnyJson) {
-    let hasAbility = true
-    _.each(functionByServices, (functions, service) => {
+  canHaveTheAbility(functionByservices: AnyJson) {
+    let hasAbility: boolean = true;
+    _.each(functionByservices, (functions, service) => {
       if (hasAbility) {
-        const nbFunctions = functions.length
-        let cmpt = 0
+        const nbFunctions: number = functions.length
+        let cmpt: number = 0
+
         while (hasAbility && nbFunctions > cmpt) {
-          const f = functions[cmpt].function
-          const parameters = functions[cmpt].parameters ?? null
-          const result = functions[cmpt].result ?? null
+          const f: string = functions[cmpt]['function'];
+          const parameters: any = functions[cmpt]['parameters'] ?? null;
+          const result: boolean = functions[cmpt]['result'] ?? true;
+
           hasAbility = result !== null ? this.factory[service].handler()[f](parameters) == result : this.factory[service].handler()[f](parameters)
           cmpt++
         }

+ 27 - 16
store/profile/access.ts

@@ -1,6 +1,6 @@
 import * as _ from 'lodash'
 import { $roleUtils } from '~/services/rights/roleUtils'
-import { AbilitiesType, accessState, baseAccessState, baseOrganizationState, Historical } from '~/types/interfaces'
+import {AbilitiesType, accessState, baseAccessState, baseOrganizationState, Historical} from '~/types/interfaces'
 import { GENDER } from '~/types/enums'
 import { MyProfile } from '~/models/Access/MyProfile'
 import { repositoryHelper } from '~/services/store/repository'
@@ -17,6 +17,7 @@ export const state = () => ({
   historical: [],
   roles: [],
   abilities: [],
+  isAdminAccess: false,
   isAdmin: false,
   isAdministratifManager: false,
   isPedagogicManager: false,
@@ -26,6 +27,8 @@ export const state = () => ({
   isTeacher: false,
   isMember: false,
   isOther: false,
+  isGuardian: false,
+  isPayor: false,
   hasLateralMenu: false,
   hasConfigurationMenu: false,
   hasAccessesMenu: false,
@@ -99,7 +102,13 @@ export const mutations = {
   setIsOther (state: accessState, isRole: boolean) {
     state.isOther = isRole
   },
-  setHasLateralMenu (state: accessState, hasLateralMenu: boolean) {
+  setIsGuardian(state:accessState, isGuardian:boolean){
+    state.isGuardian = isGuardian
+  },
+  setIsPayor(state:accessState, isPayor:boolean){
+    state.isPayor = isPayor
+  },
+  setHasLateralMenu(state:accessState, hasLateralMenu:boolean){
     state.hasLateralMenu = hasLateralMenu
   },
   setHasConfigurationMenu (state: accessState, hasConfigurationMenu: boolean) {
@@ -124,7 +133,7 @@ export const mutations = {
 
 export const actions = {
   setProfile (context: any, profile: any) {
-    const rolesArray: Array<string> = Object.values(profile.roles)
+    const roles_to_array: Array<string> = Object.values(profile.roles)
 
     context.commit('setName', profile.name)
     context.commit('setGivenName', profile.givenName)
@@ -133,16 +142,18 @@ export const actions = {
     context.commit('setActivityYear', profile.activityYear)
     context.commit('setHistorical', profile.historical)
     context.commit('setIsAdminAccess', profile.isAdminAccess)
-    context.commit('setIsAdmin', $roleUtils.isA('ADMIN', rolesArray))
-    context.commit('setIsAdministratifManager', $roleUtils.isA('ADMINISTRATIF_MANAGER', rolesArray))
-    context.commit('setIsPedagogicManager', $roleUtils.isA('PEDAGOGICS_MANAGER', rolesArray))
-    context.commit('setIsFinancialManager', $roleUtils.isA('FINANCIAL_MANAGER', rolesArray))
-    context.commit('setIsCaMember', $roleUtils.isA('CA', rolesArray))
-    context.commit('setIsStudent', $roleUtils.isA('STUDENT', rolesArray))
-    context.commit('setIsTeacher', $roleUtils.isA('TEACHER', rolesArray))
-    context.commit('setIsMember', $roleUtils.isA('MEMBER', rolesArray))
-    context.commit('setIsOther', $roleUtils.isA('OTHER', rolesArray))
-    context.commit('setRoles', $roleUtils.filterFunctionRoles(rolesArray))
+    context.commit('setIsAdmin', $roleUtils.isA('ADMIN', roles_to_array))
+    context.commit('setIsAdministratifManager', $roleUtils.isA('ADMINISTRATIF_MANAGER', roles_to_array))
+    context.commit('setIsPedagogicManager', $roleUtils.isA('PEDAGOGICS_MANAGER', roles_to_array))
+    context.commit('setIsFinancialManager', $roleUtils.isA('FINANCIAL_MANAGER', roles_to_array))
+    context.commit('setIsCaMember', $roleUtils.isA('CA', roles_to_array))
+    context.commit('setIsStudent', $roleUtils.isA('STUDENT', roles_to_array))
+    context.commit('setIsTeacher', $roleUtils.isA('TEACHER', roles_to_array))
+    context.commit('setIsMember', $roleUtils.isA('MEMBER', roles_to_array))
+    context.commit('setIsOther', $roleUtils.isA('OTHER', roles_to_array))
+    context.commit('setIsGuardian', profile.isGuardian)
+    context.commit('setIsPayor', profile.isPayor)
+    context.commit('setRoles', $roleUtils.filterFunctionRoles(roles_to_array))
     context.commit('setIsAdminAccess', profile.isAdminAccess)
 
     // Time to set Multi Accesses
@@ -159,8 +170,8 @@ export const actions = {
 
     context.dispatch('createNewMyProfileVUexOrmInstance', profile)
   },
-  setMultiAccesses (context: any, accesses: any) {
-    _.each(accesses, (organization) => {
+  setMultiAccesses (context: any, organizations: any) {
+    _.each(organizations, (organization:baseOrganizationState) => {
       const o: baseOrganizationState = {
         id: organization.id,
         name: organization.name
@@ -169,7 +180,7 @@ export const actions = {
     })
   },
   setFamilyAccesses (context: any, accesses: any) {
-    _.each(accesses, (access) => {
+    _.each(accesses, (access: baseAccessState) => {
       const a:baseAccessState = {
         id: access.id,
         name: access.name,

+ 6 - 1
store/profile/organization.ts

@@ -7,6 +7,7 @@ export const state = () => ({
   product: '',
   modules: [],
   hasChildren: false,
+  showAdherentList: false,
   networks: [],
   website: '',
   subDomain: '',
@@ -29,7 +30,10 @@ export const mutations = {
   setHasChildren (state: organizationState, hasChildren: boolean) {
     state.hasChildren = hasChildren
   },
-  setNetworks (state: organizationState, networks: Array<string>) {
+  setShowAdherentList(state:organizationState, showAdherentList:boolean) {
+    state.showAdherentList = showAdherentList
+  },
+  setNetworks(state:organizationState, networks:Array<string>) {
     state.networks = networks
   },
   setParents (state: organizationState, parents: Array<organizationState>) {
@@ -55,6 +59,7 @@ export const actions = {
     context.commit('setSubDomain', profile.subDomain)
     context.commit('setModules', profile.modules)
     context.commit('setHasChildren', profile.hasChildren)
+    context.commit('setShowAdherentList', profile.showAdherentList)
     context.commit('setNetworks', profile.networks)
 
     _.each(profile.parents, (parent) => {

+ 28 - 8
tests/unit/services/connection/connection.spec.ts

@@ -5,34 +5,54 @@ import { HTTP_METHOD, QUERY_TYPE } from '~/types/enums'
 import { DataPersisterArgs } from '~/types/interfaces'
 
 const axiosMock = axios as jest.Mocked<NuxtAxiosInstance>
+const mockFn = jest.fn();
 
 beforeAll(() => {
   Connection.initConnector(axiosMock)
 })
 
 describe('invoke()', () => {
+
   describe('getItem()', () => {
     it('should return item data', async () => {
-      Connection.connector.$request = jest.fn().mockReturnValue({ data: 'data user 1' })
+      Connection.connector.$request = mockFn.mockReturnValue({ data: 'data user 1' })
       const response = await Connection.invoke(HTTP_METHOD.GET, 'users', { type: QUERY_TYPE.MODEL })
       expect(response).toStrictEqual({ data: 'data user 1' })
     })
 
     it('should call getItem', async () => {
-      Connection.getItem = jest.fn().mockReturnValue({})
+      Connection.getItem = mockFn.mockReturnValue({})
       await Connection.invoke(HTTP_METHOD.GET, 'users', { type: QUERY_TYPE.MODEL, id: 1 })
       expect(Connection.getItem).toHaveBeenCalled()
     })
   })
 
+  describe('getCollection() for Image type', () => {
+    it('should call getCollection with a specific config', async () => {
+      Connection.request = mockFn.mockReturnValue({})
+
+      await Connection.invoke(HTTP_METHOD.GET, 'files/1/download', { showProgress: false, type: QUERY_TYPE.IMAGE })
+
+      expect(Connection.request).toHaveBeenCalled()
+
+      expect(Connection.request).toBeCalledWith({
+          'method': HTTP_METHOD.GET,
+          'progress': false,
+          'responseType': 'blob',
+          'url': 'files/1/download'
+        }
+      )
+    })
+  })
+
   describe('getCollection()', () => {
     it('should return collection data', async () => {
-      Connection.connector.$request = jest.fn().mockReturnValue([{ data: 'data user 1' }, { data: 'data user 2' }])
+      Connection.connector.$request = mockFn.mockReturnValue([{ data: 'data user 1' }, { data: 'data user 2' }])
       const response = await Connection.invoke(HTTP_METHOD.GET, 'users', { type: QUERY_TYPE.MODEL })
       expect(response).toStrictEqual([{ data: 'data user 1' }, { data: 'data user 2' }])
     })
     it('should call getCollection and return collection data', async () => {
-      Connection.getCollection = jest.fn().mockReturnValue({})
+      Connection.getCollection = mockFn.mockReturnValue({})
       await Connection.invoke(HTTP_METHOD.GET, 'users', { type: QUERY_TYPE.MODEL })
       expect(Connection.getCollection).toHaveBeenCalled()
     })
@@ -44,13 +64,13 @@ describe('invoke()', () => {
     })
 
     it('should return item data', async () => {
-      Connection.connector.$request = jest.fn().mockReturnValue({ data: 'data user 1' })
+      Connection.connector.$request = mockFn.mockReturnValue({ data: 'data user 1' })
       const response = await Connection.invoke(HTTP_METHOD.PUT, 'users', { type: QUERY_TYPE.MODEL, id: 1, data: {} } as DataPersisterArgs)
       expect(response).toStrictEqual({ data: 'data user 1' })
     })
 
     it('should call put and return item data', async () => {
-      Connection.put = jest.fn().mockReturnValue({})
+      Connection.put = mockFn.mockReturnValue({})
       await Connection.invoke(HTTP_METHOD.PUT, 'users', { type: QUERY_TYPE.MODEL, id: 1, data: {} } as DataPersisterArgs)
       expect(Connection.put).toHaveBeenCalled()
     })
@@ -58,13 +78,13 @@ describe('invoke()', () => {
 
   describe('deleteItem()', () => {
     it('should delete item', async () => {
-      Connection.connector.$request = jest.fn().mockReturnValue({})
+      Connection.connector.$request = mockFn.mockReturnValue({})
       const response = await Connection.invoke(HTTP_METHOD.DELETE, 'users', { type: QUERY_TYPE.MODEL, id: 1 })
       expect(response).toStrictEqual({})
     })
 
     it('should call deleteItem', async () => {
-      Connection.deleteItem = jest.fn().mockReturnValue({})
+      Connection.deleteItem = mockFn.mockReturnValue({})
       await Connection.invoke(HTTP_METHOD.DELETE, 'users', { type: QUERY_TYPE.MODEL, id: 1 })
       expect(Connection.deleteItem).toHaveBeenCalled()
     })

+ 24 - 0
tests/unit/services/connection/urlBuilder.spec.ts

@@ -3,6 +3,7 @@ import { QUERY_TYPE } from '~/types/enums'
 import User from '~/tests/unit/fixture/models/User'
 import Organization from '~/tests/unit/fixture/models/Organization'
 import { repositoryHelper } from '~/services/store/repository'
+import {DataProviderArgs} from "~/types/interfaces";
 
 describe('invoke()', () => {
   describe('getDefaultUrl()', () => {
@@ -90,4 +91,27 @@ describe('invoke()', () => {
       expect(UrlBuilder.concat('/api')).toEqual('/api')
     })
   })
+
+  describe('getImageUrl()', () => {
+    it('should throw an error if imgArgs is missing', () => {
+      expect(() => UrlBuilder.build({
+        type: QUERY_TYPE.IMAGE
+      })).toThrow()
+    })
+
+    it('should return the File download URL concat with Root URL', () => {
+      const args: DataProviderArgs = {
+        type: QUERY_TYPE.IMAGE,
+        imgArgs: {
+          id: 1,
+          height: 50,
+          width: 50
+        }
+      }
+      jest.useFakeTimers('modern');
+      jest.setSystemTime(new Date(2020, 3, 1));
+      expect(UrlBuilder.build(args)).toEqual('/api/files/1/download/50x50?1585692000000')
+      jest.useRealTimers();
+    })
+  })
 })

+ 24 - 0
tests/unit/services/profile/accessProfile.spec.ts

@@ -55,3 +55,27 @@ describe('hasAbility()', () => {
     expect(accessProfile.hasAbility(ability_to_have)).toBeTruthy()
   })
 })
+
+describe('hasProfile()', () => {
+  it('should return true if there is no profile', () => {
+    expect(accessProfile.hasProfile(null)).toBeTruthy()
+  })
+
+  it('should return false if user do not have the profile', () =>{
+    const profile_to_have = ['admin']
+    expect(accessProfile.hasProfile(profile_to_have)).toBeFalsy()
+  })
+
+  it('should return true if user have the profile', () =>{
+    const profile_to_have = ['admin']
+    store.commit('access/setIsAdmin', true)
+    expect(accessProfile.hasProfile(profile_to_have)).toBeTruthy()
+  })
+})
+
+describe('testProfile()', () => {
+  it('should return false if profile do not exist', () => {
+    const profile_to_test = ['none']
+    expect(accessProfile.testProfile(profile_to_test)).toBeFalsy()
+  })
+})

+ 2 - 1
types/enums.ts

@@ -13,7 +13,8 @@ export const enum DENORMALIZER_TYPE {
 export const enum QUERY_TYPE {
   DEFAULT,
   MODEL,
-  ENUM
+  ENUM,
+  IMAGE
 }
 
 export const enum ABILITIES {

+ 14 - 1
types/interfaces.d.ts

@@ -25,6 +25,7 @@ interface ItemMenu {
   to?: string,
   children?: ItemsMenu,
   isExternalLink?: boolean,
+  actions?: ItemsMenu,
 }
 interface ItemsMenu extends Array<ItemMenu> {}
 
@@ -95,6 +96,8 @@ interface accessState extends baseAccessState {
   isTeacher: boolean,
   isMember: boolean,
   isOther: boolean,
+  isGuardian: boolean,
+  isPayor: boolean,
   hasLateralMenu: boolean,
   hasConfigurationMenu: boolean,
   hasAccessesMenu: boolean,
@@ -119,6 +122,7 @@ interface organizationState extends baseOrganizationState {
   product?: string,
   modules?: Array<string>,
   hasChildren?: boolean,
+  showAdherentList?: boolean,
   networks: Array<string>,
   parents: Array<organizationState>,
 }
@@ -135,6 +139,7 @@ interface EnumChoice {
 interface UrlArgs {
   readonly type: QUERY_TYPE,
   readonly url?: string,
+  readonly baseUrl?: string,
   readonly enumType?: string,
   readonly model?: typeof Model,
   readonly rootModel?: typeof Model,
@@ -145,7 +150,15 @@ interface UrlArgs {
   readonly hook?: string
 }
 
-interface DataProviderArgs extends UrlArgs {}
+interface ImageArgs {
+  readonly id: number,
+  readonly height: number,
+  readonly width: number
+}
+
+interface DataProviderArgs extends UrlArgs {
+  imgArgs?: ImageArgs
+}
 interface DataPersisterArgs extends UrlArgs {
   data?: AnyJson
 }

+ 65 - 4
use/layout/Menus/accountMenu.ts

@@ -29,13 +29,74 @@ class AccountMenu extends BaseMenu implements Menu {
   getHeaderMenu (): ItemMenu | null {
     const children: ItemsMenu = []
 
-    if (this.$ability.can('display', 'organization_page')) {
-      children.push(this.constructMenu('organization_page', undefined, '/organization/edit'))
+    if (this.$ability.can('display', 'my_schedule_page')) {
+      children.push(this.constructMenu('my_schedule_page', undefined, '/my_calendar', true))
     }
 
-    children.push(this.constructMenu('my_subscription', undefined, '/organization/subscription'))
+    if (this.$ability.can('display', 'attendance_bookings_page')) {
+      children.push(this.constructMenu('attendance_bookings_menu', undefined, '/attendance_bookings', true))
+    }
+
+    if (this.$ability.can('display', 'my_attendance_page')) {
+      children.push(this.constructMenu('my_attendance', undefined, '/my_attendances/list', true))
+    }
+
+    if (this.$ability.can('display', 'my_invitation_page')) {
+      children.push(this.constructMenu('my_invitation', undefined, '/my_invitations/list', true))
+    }
+
+    if (this.$ability.can('display', 'my_students_page')) {
+      children.push(this.constructMenu('my_students', undefined, '/my_students/list', true))
+    }
+
+    if (this.$ability.can('display', 'my_students_education_students_page')) {
+      children.push(this.constructMenu('my_students_education_students', undefined, '/my_students_education_students/list', true))
+    }
+
+    if (this.$ability.can('display', 'criteria_notations_page') || this.$ability.can('display', 'criteria_notations_page_from_account_menu')) {
+      children.push(this.constructMenu('criteria_notations', undefined, '/criteria_notations/list', true))
+    }
+
+    if (this.$ability.can('display', 'my_education_students_page')) {
+      children.push(this.constructMenu('my_education_students', undefined, `/main/my_profile/${this.$store.state.profile.access.id}/dashboard/my_education_students/list`, true))
+    }
+
+    if (this.$ability.can('display', 'send_an_email_page')) {
+      children.push(this.constructMenu('send_an_email', undefined, `/list/create/emails`, true))
+    }
+
+    if (this.$ability.can('display', 'my_documents_page')) {
+      children.push(this.constructMenu('my_documents', undefined, `/main/my_profile/${this.$store.state.profile.access.id}/dashboard/show/my_access_file`, true))
+    }
+
+    if (this.$ability.can('display', 'my_profile_page')) {
+      children.push(this.constructMenu('my_profile', undefined, `/main/my_profile/${this.$store.state.profile.access.id}/dashboard`, true))
+    }
+
+    if (this.$ability.can('display', 'adherent_list_page')) {
+      children.push(this.constructMenu('adherent_list', undefined, `/adherent_contacts/list/`, true))
+    }
+
+    if (this.$ability.can('display', 'subscription_page')) {
+      children.push(this.constructMenu('my_subscription', undefined, `/subscription`))
+    }
+
+    if (this.$ability.can('display', 'my_bills_page')) {
+      children.push(this.constructMenu('my_bills', undefined, `/main/my_profile/${this.$store.state.profile.access.id}/dashboard/show/my_bills`, true))
+    }
+
+    if (this.$ability.can('display', 'cmf_licence_page')) {
+      children.push(this.constructMenu('print_my_licence', undefined, `/licence-cmf`, true))
+    }
+
+    const accountMenu = this.constructMenu('my_account', 'fa-user', undefined, undefined, children, false)
+
+    const actions: ItemsMenu = [];
+    actions.push(this.constructMenu('logout', undefined, `/logout`, true))
+
+    accountMenu.actions = actions
 
-    return children.length > 0 ? this.constructMenu('configuration', 'fa-user', undefined, undefined, children) : null
+    return accountMenu
   }
 }
 

+ 1 - 0
use/layout/Menus/baseMenu.ts

@@ -25,6 +25,7 @@ class BaseMenu {
    * @param {boolean} isOldLink est-ce un lien renvoyant vers l'ancien admin?
    * @param {Array<ItemMenu>} children Tableau d'ItemMenu représentant les sous menu du menu principal
    * @param {boolean} isExternalLink est-ce un lien renvoyant vers l'extérieur?
+   * @param {Array<ItemMenu>} actions Tableau d'ItemMenu représentant les actions devant apparaitre en bas du menu
    * @return {ItemMenu}
    */
   constructMenu (title: string, icon?: string, link?: string, isOldLink?: boolean, children?: Array<ItemMenu>, isExternalLink?: boolean): ItemMenu {

+ 6 - 1
use/layout/Menus/configurationMenu.ts

@@ -30,7 +30,8 @@ class ConfigurationMenu extends BaseMenu implements Menu {
     const children: ItemsMenu = []
 
     if (this.$ability.can('display', 'organization_page')) {
-      children.push(this.constructMenu('organization_page', undefined, `/main/organization/${this.$store.state.profile.organization.id}/dashboard`, true))
+      // children.push(this.constructMenu('organization_page', undefined, `/main/organization/${this.$store.state.profile.organization.id}/dashboard`, true))
+      children.push(this.constructMenu('organization_page', undefined, `/organization`))
     }
 
     if (this.$ability.can('display', 'cmf_licence_page')) {
@@ -77,6 +78,10 @@ class ConfigurationMenu extends BaseMenu implements Menu {
       children.push(this.constructMenu('course_duplication', undefined, '/duplicate_courses/', true))
     }
 
+    if (this.$ability.can('display', 'import_page')) {
+      children.push(this.constructMenu('import', undefined, '/import/all', true))
+    }
+
     if (children.length === 1) {
       return children[0]
     } else if (children.length > 0) {

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 298 - 283
yarn.lock


Vissa filer visades inte eftersom för många filer har ändrats