@communecter/cocolight-api-client 1.0.137 → 1.0.139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@communecter/cocolight-api-client",
3
- "version": "1.0.137",
3
+ "version": "1.0.139",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
package/src/api/Action.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ApiError } from "../error.js";
2
2
  import { BaseEntity } from "./BaseEntity.js";
3
3
 
4
- import type { CostumProjectActionRequestNewData } from "./EndpointApi.types.js";
4
+ import type { CostumProjectActionRequestNewData, DeleteElementData } from "./EndpointApi.types.js";
5
5
  import type { ActionItemNormalized } from "./serverDataType/Action.js";
6
6
 
7
7
  /**
@@ -552,6 +552,68 @@ export class Action extends BaseEntity<ActionItemNormalized> {
552
552
  return result;
553
553
  }
554
554
 
555
+ /**
556
+ * Supprime définitivement cette Action via `DELETE_ELEMENT` (`type=actions`).
557
+ *
558
+ * **Guard** : autorisation client via `isAuthorOrAdmin({checkHierarchy: true})` :
559
+ * - **auteur** de l'Action (`serverData.creator === userId`), OU
560
+ * - **admin** du projet parent via la hiérarchie
561
+ * (`serverData.parent[projectId]` × `userContext.links.projects[projectId].isAdmin`).
562
+ *
563
+ * Le check exploite `_isAdminViaHierarchy` (étendu à `actions` côté BaseEntity)
564
+ * qui remonte vers le Project parent via `serverData.parent`.
565
+ *
566
+ * **Pré-requis** :
567
+ * - `userContext` (User connecté) doit avoir `serverData.links.projects` chargés
568
+ * (cas `api.me()`).
569
+ * - L'Action doit avoir `serverData.parent` populé (cas après `get()` ou
570
+ * `project.action({id})` qui fait un get automatique).
571
+ *
572
+ * Après succès :
573
+ * - `serverData` et `draftData` sont vidés (sans casser la réactivité)
574
+ * - `_isDeleted = true` → toute mutation ultérieure (`save`, `updateField`, etc.) throw
575
+ *
576
+ * @param reason - Raison libre transmise au backend (audit). Défaut : `"delete action"`.
577
+ * @throws {ApiError} 400 si l'Action n'a pas d'id (jamais sauvegardée).
578
+ * @throws {ApiError} 403 si ni auteur ni admin du projet parent.
579
+ *
580
+ * @example
581
+ * const action = await project.action({ id: actionId });
582
+ * await action.delete(); // OK si auteur OU admin projet
583
+ * action._isDeleted; // true
584
+ *
585
+ * @example
586
+ * // Raison custom (audit)
587
+ * await action.delete("Annulation suite à doublon (ticket #123)");
588
+ */
589
+ async delete(reason: string = "delete action"): Promise<void> {
590
+ if (!this.id) {
591
+ throw new ApiError("Vous devez fournir un id pour supprimer une Action.", 400);
592
+ }
593
+
594
+ // Guard : auteur OU admin du projet parent (via hiérarchie).
595
+ if (!this.isAuthorOrAdmin({ checkHierarchy: true })) {
596
+ throw new ApiError(
597
+ "Suppression refusée — vous devez être l'auteur de l'Action "
598
+ + "ou administrateur du projet parent.",
599
+ 403
600
+ );
601
+ }
602
+
603
+ const data: DeleteElementData = {
604
+ reason,
605
+ pathParams: { type: "actions", id: this.id },
606
+ };
607
+
608
+ await this.callIsConnected(() => this.endpointApi.deleteElement(data));
609
+
610
+ // Vider les objets réactifs sans casser la réactivité (pattern cf. Answer.delete / Comment.delete)
611
+ Object.keys(this._draftData).forEach(key => delete this._draftData[key]);
612
+ Object.keys(this._serverData).forEach(key => delete (this._serverData as any)[key]);
613
+
614
+ this._isDeleted = true;
615
+ }
616
+
555
617
  // ───────────────────────────────────────────────────────────────────────────
556
618
  // Privés
557
619
  // ───────────────────────────────────────────────────────────────────────────
@@ -2777,7 +2777,7 @@ export class BaseEntity<TServerData = any> {
2777
2777
  let hierarchyField: Record<string, any> | undefined;
2778
2778
  if (entityType === "events") {
2779
2779
  hierarchyField = entityData?.organizer;
2780
- } else if (entityType === "projects" || entityType === "poi" || entityType === "classifieds") {
2780
+ } else if (entityType === "projects" || entityType === "poi" || entityType === "classifieds" || entityType === "actions") {
2781
2781
  hierarchyField = entityData?.parent;
2782
2782
  } else {
2783
2783
  // Organizations et autres types n'ont pas de hiérarchie parent
@@ -26,7 +26,8 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
26
26
  "UPDATE_BLOCK_LOCALITY",
27
27
  "UPDATE_BLOCK_SLUG",
28
28
  "PROFIL_IMAGE",
29
- "VIRTUAL_OPENING_HOURS"
29
+ "VIRTUAL_OPENING_HOURS",
30
+ "VIRTUAL_ORG_TIERS_LIEU_FIELDS"
30
31
  ];
31
32
 
32
33
  static override VIRTUAL_SCHEMAS = {
@@ -81,6 +82,27 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
81
82
  }
82
83
  }
83
84
  }
85
+ },
86
+ /**
87
+ * Champs custom tiers-lieu — enregistrent les champs éditables côté draft proxy
88
+ * via _composeAllowedFields. Aucun endpoint UPDATE_BLOCK_* ne couvre ces champs
89
+ * côté backend ; chaque champ est dispatché via UPDATE_PATH_VALUE (cf. updateField()).
90
+ *
91
+ * Même pattern qu'Action.ts/VIRTUAL_ACTION_EDITABLE : déclaration uniquement pour
92
+ * autoriser l'écriture sur le draft. Le persiste réel se fait via CUSTOM_FIELD_HANDLERS.
93
+ */
94
+ VIRTUAL_ORG_TIERS_LIEU_FIELDS: {
95
+ type: "object",
96
+ properties: {
97
+ holderOrganization: { type: "string" },
98
+ manageModel: { type: "string" },
99
+ typePlace: { type: "string" },
100
+ buildingSurfaceArea: { type: "number" },
101
+ siteSurfaceArea: { type: "number" },
102
+ openingDate: { type: "string" },
103
+ telephone: { type: "string" },
104
+ video: { type: "array", items: { type: "string" } }
105
+ }
84
106
  }
85
107
  };
86
108
 
@@ -88,7 +110,15 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
88
110
  ["openingHours", {
89
111
  updateMethod: "updateOpeningHours",
90
112
  schemaConstant: "VIRTUAL_OPENING_HOURS"
91
- }]
113
+ }],
114
+ ["holderOrganization", { updateMethod: "updateHolderOrganization", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
115
+ ["manageModel", { updateMethod: "updateManageModel", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
116
+ ["typePlace", { updateMethod: "updateTypePlace", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
117
+ ["buildingSurfaceArea", { updateMethod: "updateBuildingSurfaceArea", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
118
+ ["siteSurfaceArea", { updateMethod: "updateSiteSurfaceArea", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
119
+ ["openingDate", { updateMethod: "updateOpeningDate", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
120
+ ["telephone", { updateMethod: "updateTelephone", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }],
121
+ ["video", { updateMethod: "updateVideo", schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS" }]
92
122
  ] as const);
93
123
 
94
124
  static ADD_BLOCKS = new Map([
@@ -518,6 +548,88 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
518
548
  return result;
519
549
  }
520
550
 
551
+ /**
552
+ * ───────────────────────────────
553
+ * Champs custom tiers-lieu (navigatorDesTierslieux, etc.)
554
+ *
555
+ * Tous les champs ci-dessous sont dispatched via UPDATE_PATH_VALUE car aucun
556
+ * endpoint UPDATE_BLOCK_* côté backend ne les couvre. Sans ces méthodes, le
557
+ * flow `org.data.X = Y` + `save()` les drop silencieusement (les schémas de
558
+ * UPDATE_BLOCK_INFO/DESCRIPTION/SOCIAL/LOCALITY filtrent strictement).
559
+ *
560
+ * Pattern identique à Action.ts (CUSTOM_FIELD_HANDLERS → updateXxx → updateField).
561
+ * Voir tests/integration/advanced/tiers-lieux-save-fields.test.ts pour la preuve.
562
+ * ───────────────────────────────
563
+ */
564
+
565
+ /** Met à jour `holderOrganization` (nom de la structure porteuse). */
566
+ async updateHolderOrganization(value: string): Promise<unknown> {
567
+ const result = await this.updateField("holderOrganization", value);
568
+ await this._refreshIfDirect();
569
+ return result;
570
+ }
571
+
572
+ /** Met à jour `manageModel` (mode de gestion : association, SCIC, SCOP, etc.). */
573
+ async updateManageModel(value: string): Promise<unknown> {
574
+ const result = await this.updateField("manageModel", value);
575
+ await this._refreshIfDirect();
576
+ return result;
577
+ }
578
+
579
+ /**
580
+ * Met à jour `typePlace` (famille(s) de tiers-lieux).
581
+ * Format : string CSV "label1, label2" (le backend stocke tel quel).
582
+ */
583
+ async updateTypePlace(value: string): Promise<unknown> {
584
+ const result = await this.updateField("typePlace", value);
585
+ await this._refreshIfDirect();
586
+ return result;
587
+ }
588
+
589
+ /** Met à jour `buildingSurfaceArea` (surface bâtie en m²). `setType: "int"` force le cast. */
590
+ async updateBuildingSurfaceArea(value: number): Promise<unknown> {
591
+ if (typeof value !== "number" || !Number.isFinite(value)) {
592
+ throw new ApiError(`buildingSurfaceArea: attendu nombre fini. Reçu: ${JSON.stringify(value)}`, 400);
593
+ }
594
+ const result = await this.updateField("buildingSurfaceArea", value, { setType: "int" });
595
+ await this._refreshIfDirect();
596
+ return result;
597
+ }
598
+
599
+ /** Met à jour `siteSurfaceArea` (surface extérieure en m²). `setType: "int"` force le cast. */
600
+ async updateSiteSurfaceArea(value: number): Promise<unknown> {
601
+ if (typeof value !== "number" || !Number.isFinite(value)) {
602
+ throw new ApiError(`siteSurfaceArea: attendu nombre fini. Reçu: ${JSON.stringify(value)}`, 400);
603
+ }
604
+ const result = await this.updateField("siteSurfaceArea", value, { setType: "int" });
605
+ await this._refreshIfDirect();
606
+ return result;
607
+ }
608
+
609
+ /** Met à jour `openingDate` (format libre, ex: "05/05/2026" ou "01/MM/YYYY"). */
610
+ async updateOpeningDate(value: string): Promise<unknown> {
611
+ const result = await this.updateField("openingDate", value);
612
+ await this._refreshIfDirect();
613
+ return result;
614
+ }
615
+
616
+ /** Met à jour `telephone` (téléphone du tiers-lieu, distinct des champs citoyen fixe/mobile). */
617
+ async updateTelephone(value: string): Promise<unknown> {
618
+ const result = await this.updateField("telephone", value);
619
+ await this._refreshIfDirect();
620
+ return result;
621
+ }
622
+
623
+ /** Met à jour `video` (liste d'URLs de vidéos de présentation). */
624
+ async updateVideo(value: string[]): Promise<unknown> {
625
+ if (!Array.isArray(value)) {
626
+ throw new ApiError(`video: attendu tableau d'URLs. Reçu: ${JSON.stringify(value)}`, 400);
627
+ }
628
+ const result = await this.updateField("video", value);
629
+ await this._refreshIfDirect();
630
+ return result;
631
+ }
632
+
521
633
  /**
522
634
  * ───────────────────────────────
523
635
  * Lien utilisateur ↔ organisation
@@ -281,6 +281,41 @@ export declare class Action extends BaseEntity<ActionItemNormalized> {
281
281
  * Effets : status=disabled + unset tracking.
282
282
  */
283
283
  archive(): Promise<unknown>;
284
+ /**
285
+ * Supprime définitivement cette Action via `DELETE_ELEMENT` (`type=actions`).
286
+ *
287
+ * **Guard** : autorisation client via `isAuthorOrAdmin({checkHierarchy: true})` :
288
+ * - **auteur** de l'Action (`serverData.creator === userId`), OU
289
+ * - **admin** du projet parent via la hiérarchie
290
+ * (`serverData.parent[projectId]` × `userContext.links.projects[projectId].isAdmin`).
291
+ *
292
+ * Le check exploite `_isAdminViaHierarchy` (étendu à `actions` côté BaseEntity)
293
+ * qui remonte vers le Project parent via `serverData.parent`.
294
+ *
295
+ * **Pré-requis** :
296
+ * - `userContext` (User connecté) doit avoir `serverData.links.projects` chargés
297
+ * (cas `api.me()`).
298
+ * - L'Action doit avoir `serverData.parent` populé (cas après `get()` ou
299
+ * `project.action({id})` qui fait un get automatique).
300
+ *
301
+ * Après succès :
302
+ * - `serverData` et `draftData` sont vidés (sans casser la réactivité)
303
+ * - `_isDeleted = true` → toute mutation ultérieure (`save`, `updateField`, etc.) throw
304
+ *
305
+ * @param reason - Raison libre transmise au backend (audit). Défaut : `"delete action"`.
306
+ * @throws {ApiError} 400 si l'Action n'a pas d'id (jamais sauvegardée).
307
+ * @throws {ApiError} 403 si ni auteur ni admin du projet parent.
308
+ *
309
+ * @example
310
+ * const action = await project.action({ id: actionId });
311
+ * await action.delete(); // OK si auteur OU admin projet
312
+ * action._isDeleted; // true
313
+ *
314
+ * @example
315
+ * // Raison custom (audit)
316
+ * await action.delete("Annulation suite à doublon (ticket #123)");
317
+ */
318
+ delete(reason?: string): Promise<void>;
284
319
  /**
285
320
  * Helper privé : délègue à `BaseEntity.updateField()` pour un path donné.
286
321
  * Bénéficie du normalize automatique (R0 Date → isoDate, R1 collapse setType,
@@ -62,10 +62,74 @@ export declare class Organization extends BaseEntity<OrganizationItemNormalized>
62
62
  };
63
63
  };
64
64
  };
65
+ /**
66
+ * Champs custom tiers-lieu — enregistrent les champs éditables côté draft proxy
67
+ * via _composeAllowedFields. Aucun endpoint UPDATE_BLOCK_* ne couvre ces champs
68
+ * côté backend ; chaque champ est dispatché via UPDATE_PATH_VALUE (cf. updateField()).
69
+ *
70
+ * Même pattern qu'Action.ts/VIRTUAL_ACTION_EDITABLE : déclaration uniquement pour
71
+ * autoriser l'écriture sur le draft. Le persiste réel se fait via CUSTOM_FIELD_HANDLERS.
72
+ */
73
+ VIRTUAL_ORG_TIERS_LIEU_FIELDS: {
74
+ type: string;
75
+ properties: {
76
+ holderOrganization: {
77
+ type: string;
78
+ };
79
+ manageModel: {
80
+ type: string;
81
+ };
82
+ typePlace: {
83
+ type: string;
84
+ };
85
+ buildingSurfaceArea: {
86
+ type: string;
87
+ };
88
+ siteSurfaceArea: {
89
+ type: string;
90
+ };
91
+ openingDate: {
92
+ type: string;
93
+ };
94
+ telephone: {
95
+ type: string;
96
+ };
97
+ video: {
98
+ type: string;
99
+ items: {
100
+ type: string;
101
+ };
102
+ };
103
+ };
104
+ };
65
105
  };
66
- static CUSTOM_FIELD_HANDLERS: Map<"openingHours", {
106
+ static CUSTOM_FIELD_HANDLERS: Map<"holderOrganization" | "manageModel" | "typePlace" | "buildingSurfaceArea" | "siteSurfaceArea" | "openingDate" | "openingHours" | "video" | "telephone", {
67
107
  readonly updateMethod: "updateOpeningHours";
68
108
  readonly schemaConstant: "VIRTUAL_OPENING_HOURS";
109
+ } | {
110
+ readonly updateMethod: "updateHolderOrganization";
111
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
112
+ } | {
113
+ readonly updateMethod: "updateManageModel";
114
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
115
+ } | {
116
+ readonly updateMethod: "updateTypePlace";
117
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
118
+ } | {
119
+ readonly updateMethod: "updateBuildingSurfaceArea";
120
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
121
+ } | {
122
+ readonly updateMethod: "updateSiteSurfaceArea";
123
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
124
+ } | {
125
+ readonly updateMethod: "updateOpeningDate";
126
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
127
+ } | {
128
+ readonly updateMethod: "updateTelephone";
129
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
130
+ } | {
131
+ readonly updateMethod: "updateVideo";
132
+ readonly schemaConstant: "VIRTUAL_ORG_TIERS_LIEU_FIELDS";
69
133
  }>;
70
134
  static ADD_BLOCKS: Map<"UPDATE_BLOCK_SOCIAL" | "PROFIL_IMAGE" | "ADD_ORGANIZATION", "updateImageProfil" | "updateSocial" | "addOrganization">;
71
135
  static UPDATE_BLOCKS: Map<"UPDATE_BLOCK_DESCRIPTION" | "UPDATE_BLOCK_INFO" | "UPDATE_BLOCK_SOCIAL" | "UPDATE_BLOCK_LOCALITY" | "UPDATE_BLOCK_SLUG" | "PROFIL_IMAGE", "updateImageProfil" | "updateDescription" | "updateSocial" | "updateLocality" | "updateInfo" | "updateSlug">;
@@ -243,6 +307,38 @@ export declare class Organization extends BaseEntity<OrganizationItemNormalized>
243
307
  * await org.updateOpeningHours(openingHours);
244
308
  */
245
309
  updateOpeningHours(hours: OpeningHoursEntry[]): Promise<unknown>;
310
+ /**
311
+ * ───────────────────────────────
312
+ * Champs custom tiers-lieu (navigatorDesTierslieux, etc.)
313
+ *
314
+ * Tous les champs ci-dessous sont dispatched via UPDATE_PATH_VALUE car aucun
315
+ * endpoint UPDATE_BLOCK_* côté backend ne les couvre. Sans ces méthodes, le
316
+ * flow `org.data.X = Y` + `save()` les drop silencieusement (les schémas de
317
+ * UPDATE_BLOCK_INFO/DESCRIPTION/SOCIAL/LOCALITY filtrent strictement).
318
+ *
319
+ * Pattern identique à Action.ts (CUSTOM_FIELD_HANDLERS → updateXxx → updateField).
320
+ * Voir tests/integration/advanced/tiers-lieux-save-fields.test.ts pour la preuve.
321
+ * ───────────────────────────────
322
+ */
323
+ /** Met à jour `holderOrganization` (nom de la structure porteuse). */
324
+ updateHolderOrganization(value: string): Promise<unknown>;
325
+ /** Met à jour `manageModel` (mode de gestion : association, SCIC, SCOP, etc.). */
326
+ updateManageModel(value: string): Promise<unknown>;
327
+ /**
328
+ * Met à jour `typePlace` (famille(s) de tiers-lieux).
329
+ * Format : string CSV "label1, label2" (le backend stocke tel quel).
330
+ */
331
+ updateTypePlace(value: string): Promise<unknown>;
332
+ /** Met à jour `buildingSurfaceArea` (surface bâtie en m²). `setType: "int"` force le cast. */
333
+ updateBuildingSurfaceArea(value: number): Promise<unknown>;
334
+ /** Met à jour `siteSurfaceArea` (surface extérieure en m²). `setType: "int"` force le cast. */
335
+ updateSiteSurfaceArea(value: number): Promise<unknown>;
336
+ /** Met à jour `openingDate` (format libre, ex: "05/05/2026" ou "01/MM/YYYY"). */
337
+ updateOpeningDate(value: string): Promise<unknown>;
338
+ /** Met à jour `telephone` (téléphone du tiers-lieu, distinct des champs citoyen fixe/mobile). */
339
+ updateTelephone(value: string): Promise<unknown>;
340
+ /** Met à jour `video` (liste d'URLs de vidéos de présentation). */
341
+ updateVideo(value: string[]): Promise<unknown>;
246
342
  /**
247
343
  * ───────────────────────────────
248
344
  * Lien utilisateur ↔ organisation