Quellcode durchsuchen

Merge branch 'hotfix/hf-id-field'

Vincent vor 10 Monaten
Ursprung
Commit
4471ae7d36

+ 9 - 0
models/ApiResource.ts

@@ -6,6 +6,7 @@ import { Model } from 'pinia-orm'
 class ApiResource extends Model {
   // eslint-disable-next-line no-use-before-define
   protected static _iriEncodedFields: Record<string, ApiResource>
+  protected static _idField: string
 
   public static addIriEncodedField(name: string, target: ApiResource) {
     if (!this._iriEncodedFields) {
@@ -18,6 +19,14 @@ class ApiResource extends Model {
     return this._iriEncodedFields
   }
 
+  public static setIdField(name: string) {
+    this._idField = name
+  }
+
+  public static getIdField() {
+    return this._idField
+  }
+
   /**
    * Fix the 'Cannot stringify arbitrary non-POJOs' warning, meaning server can not parse the store
    *

+ 5 - 0
models/Organization/Cotisation.ts

@@ -1,5 +1,6 @@
 import { Num, Str, Uid } from 'pinia-orm/dist/decorators'
 import ApiResource from '~/models/ApiResource'
+import {IdField} from "~/models/decorators";
 
 /**
  * Ap2i ApiResource : Cotisation
@@ -17,4 +18,8 @@ export default class Cotisation extends ApiResource {
 
   @Num(null)
   declare cotisationYear: number | null
+
+  @IdField()
+  @Num(0, { notNullable: true })
+  declare organizationId: number
 }

+ 3 - 1
models/Organization/DolibarrAccount.ts

@@ -1,5 +1,6 @@
-import { Attr, Num, Str, Uid } from 'pinia-orm/dist/decorators'
+import { Attr, Str, Uid, Num } from 'pinia-orm/dist/decorators'
 import ApiResource from '~/models/ApiResource'
+import {IdField} from "~/models/decorators";
 
 /**
  * The Dolibarr account of an organization
@@ -12,6 +13,7 @@ export default class DolibarrAccount extends ApiResource {
   @Uid()
   declare id: number | string | null
 
+  @IdField()
   @Num(0, { notNullable: true })
   declare organizationId: number
 

+ 18 - 0
models/decorators.ts

@@ -17,3 +17,21 @@ export function IriEncoded(apiResource: typeof ApiResource): PropertyDecorator {
     self.addIriEncodedField(propertyKey as string, apiResource)
   }
 }
+
+/**
+ * Decorates an ApiResource's property to signal it as a field that is provided
+ * as an unique identifier by the API
+ *
+ * If the property is decorated, the HydraNormalizer will get the item[idField] value to hydrate
+ * the id property of the instance
+ */
+export function IdField(): PropertyDecorator {
+  // We have to comply with the PropertyDecorator return type
+  // eslint-disable-next-line @typescript-eslint/ban-types
+  return (target: Object, propertyKey: string | symbol) => {
+    // @ts-expect-error The object is an ApiResource
+    const self = target.$self()
+
+    self.setIdField(propertyKey as string)
+  }
+}

+ 14 - 13
pages/subscription.vue

@@ -158,7 +158,7 @@ Page 'Mon abonnement'
                       <b
                         >{{
                           i18n.t('for_only_x_eur_ttc_by_month', {
-                            price: formatCurrency(7.25, 'EUR'),
+                            price: formatCurrency(7.5, 'EUR'),
                           })
                         }}&nbsp;*</b
                       >
@@ -167,7 +167,7 @@ Page 'Mon abonnement'
                       <i
                         >*&nbsp;{{
                           i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
-                            price: formatCurrency(87.0, 'EUR'),
+                            price: formatCurrency(90.0, 'EUR'),
                           })
                         }}</i
                       >
@@ -176,7 +176,7 @@ Page 'Mon abonnement'
                       <i
                         >{{ $t('only_for_cmf_members') }} ({{
                           i18n.t('public_price_x_ttc_a_year', {
-                            price: formatCurrency(168.0, 'EUR'),
+                            price: formatCurrency(216.0, 'EUR'),
                           })
                         }})</i
                       >
@@ -189,7 +189,7 @@ Page 'Mon abonnement'
                       <b
                         >{{
                           i18n.t('for_only_x_eur_ttc_by_month', {
-                            price: formatCurrency(14.0, 'EUR'),
+                            price: formatCurrency(18.0, 'EUR'),
                           })
                         }}&nbsp;*</b
                       >
@@ -198,7 +198,7 @@ Page 'Mon abonnement'
                       <i>
                         *&nbsp;{{
                           i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
-                            price: formatCurrency(168.0, 'EUR'),
+                            price: formatCurrency(216.0, 'EUR'),
                           })
                         }}
                       </i>
@@ -224,6 +224,7 @@ Page 'Mon abonnement'
                         runtimeConfig.public.fileStorageBaseUrl +
                         '/Bon_de_commande/Opentalent_Artist_CMF.pdf'
                       "
+                      target="_blank"
                     >
                       <b>{{ $t('download_cmf_order_form') }}</b>
                     </a>
@@ -268,7 +269,7 @@ Page 'Mon abonnement'
                       <b
                         >{{
                           i18n.t('starting_from_x_eur_ttc_per_month', {
-                            price: formatCurrency(26.5, 'EUR'),
+                            price: formatCurrency(30.5, 'EUR'),
                           })
                         }}&nbsp;*</b
                       >
@@ -278,7 +279,7 @@ Page 'Mon abonnement'
                         >*
                         {{
                           i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
-                            price: formatCurrency(318.0, 'EUR'),
+                            price: formatCurrency(366.0, 'EUR'),
                           })
                         }}</i
                       >
@@ -298,7 +299,7 @@ Page 'Mon abonnement'
                       <i
                         >{{ $t('only_for_cmf_members') }} ({{
                           i18n.t('public_price_x_ttc_a_year', {
-                            price: formatCurrency(529.2, 'EUR'),
+                            price: formatCurrency(607.2, 'EUR'),
                           })
                         }})</i
                       >
@@ -309,7 +310,7 @@ Page 'Mon abonnement'
                     <p>
                       {{
                         i18n.t('starting_from_x_eur_ttc_per_month', {
-                          price: formatCurrency(44.1, 'EUR'),
+                          price: formatCurrency(50.6, 'EUR'),
                         })
                       }}&nbsp;*
                     </p>
@@ -317,7 +318,7 @@ Page 'Mon abonnement'
                       <i
                         >*&nbsp;{{
                           i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
-                            price: formatCurrency(529.2, 'EUR'),
+                            price: formatCurrency(607.2, 'EUR'),
                           })
                         }}</i
                       >
@@ -380,7 +381,7 @@ Page 'Mon abonnement'
                       <b
                         >{{
                           i18n.t('starting_from_x_eur_ttc_per_sms', {
-                            price: formatCurrency(0.1, 'EUR'),
+                            price: formatCurrency(0.11, 'EUR'),
                           })
                         }}&nbsp;*</b
                       >
@@ -411,7 +412,7 @@ Page 'Mon abonnement'
                       <b
                         >{{
                           i18n.t('starting_from_x_eur_ttc_per_sms', {
-                            price: formatCurrency(0.12, 'EUR'),
+                            price: formatCurrency(0.13, 'EUR'),
                           })
                         }}&nbsp;*</b
                       >
@@ -451,7 +452,7 @@ Page 'Mon abonnement'
                       <b>{{
                         i18n.t(
                           'get_your_own_domain_and_up_to_five_emails_for_only_x_eur_ttc_per_year',
-                          { price: formatCurrency(44.0, 'EUR') },
+                          { price: formatCurrency(46.8, 'EUR') },
                         )
                       }}</b>
                     </p>

+ 18 - 14
services/data/entityManager.ts

@@ -178,24 +178,28 @@ class EntityManager {
     id: number,
     forceRefresh: boolean = false,
   ): Promise<ApiResource> {
-    // If the model instance is already in the store and forceRefresh is false, return the object in store
-    if (!forceRefresh) {
-      // TODO: est-ce qu'il y a vraiment des situations où on appellera cette méthode sans le forceRefresh?
-      const item = this.find(model, id)
-      if (item && typeof item !== 'undefined') {
-        return item
+    try {
+      // If the model instance is already in the store and forceRefresh is false, return the object in store
+      if (!forceRefresh) {
+        // TODO: est-ce qu'il y a vraiment des situations où on appellera cette méthode sans le forceRefresh?
+        const item = this.find(model, id)
+        if (item && typeof item !== 'undefined') {
+          return item
+        }
       }
-    }
 
-    // Else, get the object from the API
-    const url = UrlUtils.join('api', model.entity, String(id))
-    const response = await this.apiRequestService.get(url)
+      // Else, get the object from the API
+      const url = UrlUtils.join('api', model.entity, String(id))
+      const response = await this.apiRequestService.get(url)
 
-    // deserialize the response
-    const attributes = HydraNormalizer.denormalize(response, model)
-      .data as object
+      // deserialize the response
+      const attributes = HydraNormalizer.denormalize(response, model)
+        .data as object
 
-    return this.newInstance(model, attributes)
+      return this.newInstance(model, attributes)
+    } catch (e) {
+      console.error(e)
+    }
   }
 
   /**

+ 33 - 12
services/data/normalizer/hydraNormalizer.ts

@@ -1,7 +1,7 @@
 import * as _ from 'lodash-es'
-import type { AnyJson, ApiResponse, HydraMetadata } from '~/types/data'
+import type {AnyJson, ApiResponse, HydraMetadata} from '~/types/data'
 import UrlUtils from '~/services/utils/urlUtils'
-import { METADATA_TYPE } from '~/types/enum/data'
+import {METADATA_TYPE} from '~/types/enum/data'
 import models from '~/models/models'
 import ApiResource from '~/models/ApiResource'
 
@@ -76,7 +76,7 @@ class HydraNormalizer {
   protected static getMetadata(data: AnyJson): HydraMetadata {
     if (data['@type'] !== 'hydra:Collection') {
       // A single item, no metadata
-      return { type: METADATA_TYPE.ITEM }
+      return {type: METADATA_TYPE.ITEM}
     }
 
     const metadata: HydraMetadata = {
@@ -163,14 +163,17 @@ class HydraNormalizer {
 
     throw new Error(
       'De-normalization error : Could not determine the type of the entity ' +
-        item['@id'] +
-        ' (found: ' +
-        entity +
-        ')',
+      item['@id'] +
+      ' (found: ' +
+      entity +
+      ')',
     )
   }
 
   protected static denormalizeEntity(model: typeof ApiResource, item: AnyJson) {
+
+    item['id'] = this.getItemIdValue(model, item)
+
     // eslint-disable-next-line new-cap
     const instance = new model(item)
     const iriEncodedFields = model.getIriEncodedFields()
@@ -232,18 +235,36 @@ class HydraNormalizer {
     iri: string,
     expectedEntity: string,
   ): number | string {
-    const { entity, id } = HydraNormalizer.parseEntityIRI(iri)
+    const {entity, id} = HydraNormalizer.parseEntityIRI(iri)
     if (entity !== expectedEntity) {
       throw new Error(
         "IRI entity does not match the field's target entity (" +
-          entity +
-          ' != ' +
-          expectedEntity +
-          ')',
+        entity +
+        ' != ' +
+        expectedEntity +
+        ')',
       )
     }
     return id
   }
+
+  /**
+   * Get the id value of an item
+   * @param model
+   * @param item
+   * @protected
+   */
+  protected static getItemIdValue(model: typeof ApiResource, item: AnyJson) {
+    if (item.id !== undefined) {
+      return item.id
+    }
+
+    if (model.getIdField() !== undefined && item[model.getIdField()] !== undefined) {
+      return item[model.getIdField()]
+    }
+
+    throw new Error('Missing id field or @IdField decorator for ' + model)
+  }
 }
 
 export default HydraNormalizer