@communecter/cocolight-api-client 1.0.130 → 1.0.132

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.
Files changed (48) hide show
  1. package/dist/cocolight-api-client.browser.js +3 -3
  2. package/dist/cocolight-api-client.cjs +1 -1
  3. package/dist/cocolight-api-client.mjs.js +1 -1
  4. package/dist/cocolight-api-client.vite.mjs.js +1 -1
  5. package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/Api.ts +4 -4
  8. package/src/ApiClient.ts +3 -1
  9. package/src/api/Action.ts +535 -4
  10. package/src/api/Answer.ts +4 -1
  11. package/src/api/Badge.ts +5 -0
  12. package/src/api/BaseEntity.ts +118 -7
  13. package/src/api/Classified.ts +4 -0
  14. package/src/api/Comment.ts +3 -0
  15. package/src/api/EndpointApi.ts +96 -1
  16. package/src/api/EndpointApi.types.ts +448 -44
  17. package/src/api/Event.ts +5 -0
  18. package/src/api/Form.ts +64 -2
  19. package/src/api/News.ts +3 -0
  20. package/src/api/Organization.ts +47 -3
  21. package/src/api/Poi.ts +4 -0
  22. package/src/api/Project.ts +61 -0
  23. package/src/api/User.ts +10 -0
  24. package/src/api/serverDataType/Answer.ts +25 -0
  25. package/src/api/serverDataType/Form.ts +84 -4
  26. package/src/api/serverDataType/Organization.ts +13 -0
  27. package/src/api/serverDataType/Project.ts +15 -0
  28. package/src/endpoints.module.ts +1252 -268
  29. package/types/api/Action.d.ts +276 -2
  30. package/types/api/Answer.d.ts +1 -0
  31. package/types/api/Badge.d.ts +1 -0
  32. package/types/api/BaseEntity.d.ts +62 -0
  33. package/types/api/Classified.d.ts +1 -0
  34. package/types/api/Comment.d.ts +1 -0
  35. package/types/api/EndpointApi.d.ts +60 -1
  36. package/types/api/EndpointApi.types.d.ts +397 -41
  37. package/types/api/Event.d.ts +1 -0
  38. package/types/api/Form.d.ts +34 -0
  39. package/types/api/News.d.ts +1 -0
  40. package/types/api/Organization.d.ts +20 -1
  41. package/types/api/Poi.d.ts +1 -0
  42. package/types/api/Project.d.ts +29 -0
  43. package/types/api/User.d.ts +7 -0
  44. package/types/api/serverDataType/Answer.d.ts +22 -0
  45. package/types/api/serverDataType/Form.d.ts +89 -4
  46. package/types/api/serverDataType/Organization.d.ts +13 -0
  47. package/types/api/serverDataType/Project.d.ts +15 -0
  48. package/types/endpoints.module.d.ts +2825 -1491
package/src/api/Event.ts CHANGED
@@ -256,6 +256,11 @@ export class Event extends BaseEntity<EventItemNormalized> {
256
256
  }
257
257
 
258
258
 
259
+ override async form(): Promise<never> {
260
+ throw new ApiError(`form n'existe pas dans ${this.constructor.name}`, 501);
261
+ }
262
+
263
+
259
264
  override async event(): Promise<never> {
260
265
  throw new ApiError(`les sous-events ne sont pas encore implémentés dans ${this.constructor.name}`, 501);
261
266
  }
package/src/api/Form.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { BaseEntity } from "./BaseEntity.js";
2
2
  import { ApiError } from "../error.js";
3
3
 
4
+ import type { Answer } from "./Answer.js";
5
+ import type { PaginatorPage, PaginatorState } from "./BaseEntity.js";
6
+ import type { CoformAnswersSearchData } from "./EndpointApi.types.js";
4
7
  import type { FormItemNormalized } from "./serverDataType/Form.js";
5
8
 
6
9
  export class Form extends BaseEntity<FormItemNormalized> {
@@ -42,19 +45,78 @@ export class Form extends BaseEntity<FormItemNormalized> {
42
45
  });
43
46
 
44
47
  if (form?.data?.id || form?.data?._id) {
45
- // Normaliser les valeurs nulles en objets/tableaux vides pour cohérence
48
+ // Normaliser les valeurs nulles en objets/tableaux vides pour cohérence.
49
+ // Injection de `collection: "forms"` : le backend (PHDB::find) ne renvoie pas
50
+ // ce champ ; on l'ajoute côté SDK pour rester cohérent avec les autres entités.
46
51
  const normalizedData = {
47
52
  ...form.data,
53
+ collection: "forms" as const,
48
54
  inputs: form.data.inputs ?? {},
49
55
  params: form.data.params ?? {},
50
56
  subForms: form.data.subForms ?? [],
51
57
  parent: form.data.parent ?? undefined,
52
58
  config: form.data.config ?? undefined,
53
59
  } as FormItemNormalized;
54
-
60
+
55
61
  this._setData(normalizedData, { forceInitialDraftReset: true });
56
62
  return this.serverData;
57
63
  }
58
64
  throw new ApiError(`Aucun formulaire trouvé pour l'ID ${this.id}`, 404);
59
65
  }
66
+
67
+ /**
68
+ * Recherche paginée des Answers liées à CE Form, dans le costumContext de son parent.
69
+ *
70
+ * **Délégation au parent** : le Form lui-même n'a pas de costumContext (pas de
71
+ * `slug` côté serverData). Cette méthode remonte `this.parent` (qui doit être
72
+ * une `Organization` ou un `Project` ayant un slug) et délègue à
73
+ * `parent.coformAnswersSearch({ filters: { form: this.id, ... } })`.
74
+ *
75
+ * Pattern identique à `Comment._add` qui remonte `this.parent` pour récupérer
76
+ * le contexte d'ancrage.
77
+ *
78
+ * **Important** : pour utiliser cette méthode, le Form doit avoir été créé via
79
+ * `org.form({id})` ou `project.form({id})` — pas via `api.form({id})` direct
80
+ * (qui crée un Form avec `parent = ApiClient`, sans costumContext).
81
+ *
82
+ * @param data - Paramètres de recherche (`filters`, `fields`, `sortBy`, etc.).
83
+ * Le champ `filters.form` est automatiquement injecté avec `this.id`.
84
+ * @param options - Options de pagination (`restoredState` pour reprendre une navigation)
85
+ * @returns Première page paginée avec `results` (Answer[]), `count`, `hasNext`, etc.
86
+ * @throws {ApiError} si le Form n'a pas d'id ou si son parent n'a pas de costumContext.
87
+ *
88
+ * @example
89
+ * const org = await api.organization({ slug: "navigatorDesTierslieux" });
90
+ * const form = await org.form({ id: "6925e2b05dd63b02ca70d6d9" });
91
+ * const page = await form.getAnswers({ sortBy: { created: -1 } });
92
+ * page.results; // Answer[]
93
+ * page.count.total; // nombre total
94
+ */
95
+ async getAnswers(
96
+ data: Partial<CoformAnswersSearchData> = {},
97
+ options?: { restoredState?: PaginatorState }
98
+ ): Promise<PaginatorPage<Answer>> {
99
+ if (!this.id) throw new ApiError("Form sans id, impossible de chercher ses réponses.", 400);
100
+
101
+ const parent = this.parent;
102
+ if (
103
+ !(parent instanceof BaseEntity)
104
+ || typeof parent.serverData?.slug !== "string"
105
+ || typeof parent.serverData?.id !== "string"
106
+ ) {
107
+ throw new ApiError(
108
+ "form.getAnswers nécessite un Form créé via `org.form({id})` ou `project.form({id})` "
109
+ + "(parent avec costumContext). Pour un Form isolé, utilisez "
110
+ + "`organization.coformAnswersSearch({ filters: { form: form.id } })` directement.",
111
+ 400
112
+ );
113
+ }
114
+
115
+ // Injecte filters.form = this.id et délègue au parent costum
116
+ const mergedFilters = { ...(data.filters ?? {}), form: this.id };
117
+ return parent.coformAnswersSearch(
118
+ { ...data, filters: mergedFilters },
119
+ options
120
+ );
121
+ }
60
122
  }
package/src/api/News.ts CHANGED
@@ -516,4 +516,7 @@ export class News extends BaseEntity<NewsItemNormalized> {
516
516
  return Boolean(authorId && this.userId === authorId);
517
517
  }
518
518
 
519
+ override async form(): Promise<never> {
520
+ throw new ApiError(`form n'existe pas dans ${this.constructor.name}`, 501);
521
+ }
519
522
  }
@@ -128,6 +128,34 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
128
128
  signal: createSocialTransform("signal")
129
129
  };
130
130
 
131
+ /**
132
+ * Transforme les Answer docs imbriqués dans `data.answers` (renvoyé par /costum/navigator/gettl)
133
+ * en instances d'entité Answer. Le payload backend fait un PHDB::find(Answer::COLLECTION, ...) brut,
134
+ * donc on injecte `collection: "answers"` avant linkage pour rester cohérent avec les autres entités.
135
+ *
136
+ * Structure : `data.answers = { [formId]: AnswerDoc[] }` → `{ [formId]: Answer[] }`
137
+ * (le formId reste une string ; charger le Form se fait via `api.form({ id: formId })`).
138
+ *
139
+ * @protected
140
+ */
141
+ protected override _transformServerData(data: OrganizationItemNormalized): OrganizationItemNormalized {
142
+ const rawAnswers = (data as Record<string, unknown>).answers as Record<string, unknown[]> | undefined;
143
+ if (rawAnswers && typeof rawAnswers === "object") {
144
+ const linkedAnswers: Record<string, unknown[]> = {};
145
+ for (const [formId, docs] of Object.entries(rawAnswers)) {
146
+ if (Array.isArray(docs)) {
147
+ linkedAnswers[formId] = docs.map((doc) =>
148
+ doc && typeof doc === "object"
149
+ ? this._linkEntity("answers", { ...(doc as Record<string, unknown>), collection: "answers" })
150
+ : doc
151
+ );
152
+ }
153
+ }
154
+ (data as Record<string, unknown>).answers = linkedAnswers;
155
+ }
156
+ return data;
157
+ }
158
+
131
159
  override _add = async (payload: Record<string, any>): Promise<void> => {
132
160
  if (!this._calledFromSave) {
133
161
  throw new ApiError("utilisation invalide de _add, utilisez save", 400);
@@ -457,6 +485,17 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
457
485
  return super.news(newsData);
458
486
  }
459
487
 
488
+ /**
489
+ * {@inheritDoc BaseEntity#form}
490
+ *
491
+ * Crée une instance de Form dans le contexte de cette organisation : le Form aura
492
+ * `this.parent = Organization`, ce qui permet à `form.getAnswers()` de déléguer
493
+ * `coformAnswersSearch` avec le bon costumContext.
494
+ */
495
+ override async form(formData: Parameters<BaseEntity<OrganizationItemNormalized>["form"]>[0] = {}) {
496
+ return super.form(formData);
497
+ }
498
+
460
499
  /**
461
500
  * Met à jour les horaires d'ouverture de l'organisation.
462
501
  * Utilise UPDATE_PATH_VALUE pour modifier le champ openingHours.
@@ -478,12 +517,14 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
478
517
  throw new ApiError("L'organisation n'a pas d'ID, impossible de mettre à jour les horaires d'ouverture.", 400);
479
518
  }
480
519
 
481
- return this.endpointApi.updatePathValue({
520
+ const result = await this.endpointApi.updatePathValue({
482
521
  id: this.id,
483
522
  collection: "organizations",
484
523
  path: "openingHours",
485
524
  value: hours as unknown as { [k: string]: unknown }
486
525
  });
526
+ await this._refreshIfDirect();
527
+ return result;
487
528
  }
488
529
 
489
530
  /**
@@ -583,8 +624,11 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
583
624
  *
584
625
  * Cette surcharge précise que la recherche est faite dans le contexte de l'organisation.
585
626
  */
586
- override async searchCostum(data: Parameters<BaseEntity<OrganizationItemNormalized>["searchCostum"]>[0]) {
587
- return super.searchCostum(data);
627
+ override async searchCostum(
628
+ data: Parameters<BaseEntity<OrganizationItemNormalized>["searchCostum"]>[0],
629
+ options?: Parameters<BaseEntity<OrganizationItemNormalized>["searchCostum"]>[1]
630
+ ) {
631
+ return super.searchCostum(data, options);
588
632
  }
589
633
 
590
634
  /**
package/src/api/Poi.ts CHANGED
@@ -173,6 +173,10 @@ export class Poi extends BaseEntity<PoiItemNormalized> {
173
173
  throw new ApiError(`event n'existe pas dans ${this.constructor.name}`, 501);
174
174
  }
175
175
 
176
+ override async form(): Promise<never> {
177
+ throw new ApiError(`form n'existe pas dans ${this.constructor.name}`, 501);
178
+ }
179
+
176
180
  override async badge(): Promise<never> {
177
181
  throw new ApiError(`badge n'existe pas dans ${this.constructor.name}`, 501);
178
182
  }
@@ -63,6 +63,30 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
63
63
  parent: transformEntityRefs
64
64
  };
65
65
 
66
+ /**
67
+ * Transforme les Answer docs imbriqués dans `data.answers` (renvoyé par /costum/navigator/gettl)
68
+ * en instances d'entité Answer. Voir `Organization._transformServerData` pour le même pattern.
69
+ *
70
+ * @protected
71
+ */
72
+ protected override _transformServerData(data: ProjectItemNormalized): ProjectItemNormalized {
73
+ const rawAnswers = (data as Record<string, unknown>).answers as Record<string, unknown[]> | undefined;
74
+ if (rawAnswers && typeof rawAnswers === "object") {
75
+ const linkedAnswers: Record<string, unknown[]> = {};
76
+ for (const [formId, docs] of Object.entries(rawAnswers)) {
77
+ if (Array.isArray(docs)) {
78
+ linkedAnswers[formId] = docs.map((doc) =>
79
+ doc && typeof doc === "object"
80
+ ? this._linkEntity("answers", { ...(doc as Record<string, unknown>), collection: "answers" })
81
+ : doc
82
+ );
83
+ }
84
+ }
85
+ (data as Record<string, unknown>).answers = linkedAnswers;
86
+ }
87
+ return data;
88
+ }
89
+
66
90
  override _add = async (payload: Record<string, any>): Promise<void> => {
67
91
  if (!this._calledFromSave) {
68
92
  throw new ApiError("utilisation invalide de _add, utilisez save", 400);
@@ -302,6 +326,18 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
302
326
  return super.getGallery(data);
303
327
  }
304
328
 
329
+ /**
330
+ * {@inheritDoc BaseEntity#searchCostum}
331
+ *
332
+ * Cette surcharge précise que la recherche est faite dans le contexte du projet.
333
+ */
334
+ override async searchCostum(
335
+ data: Parameters<BaseEntity<ProjectItemNormalized>["searchCostum"]>[0],
336
+ options?: Parameters<BaseEntity<ProjectItemNormalized>["searchCostum"]>[1]
337
+ ) {
338
+ return super.searchCostum(data, options);
339
+ }
340
+
305
341
  /**
306
342
  * {@inheritDoc BaseEntity#project}
307
343
  *
@@ -355,6 +391,17 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
355
391
  *
356
392
  * Crée une instance de news et la récupère si nécessaire.
357
393
  */
394
+ /**
395
+ * {@inheritDoc BaseEntity#form}
396
+ *
397
+ * Crée une instance de Form dans le contexte de ce projet : le Form aura
398
+ * `this.parent = Project`, ce qui permet à `form.getAnswers()` de déléguer
399
+ * `coformAnswersSearch` avec le bon costumContext.
400
+ */
401
+ override async form(formData: Parameters<BaseEntity<ProjectItemNormalized>["form"]>[0] = {}) {
402
+ return super.form(formData);
403
+ }
404
+
358
405
  override async news(newsData: Parameters<BaseEntity<ProjectItemNormalized>["news"]>[0] = {}) {
359
406
  if(!newsData?.id && !this.isContributor()){
360
407
  throw new ApiError("Vous n'avez pas les droits pour créer une news dans ce projet", 403);
@@ -362,6 +409,20 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
362
409
  return super.news(newsData);
363
410
  }
364
411
 
412
+ /**
413
+ * Vérifie qu'un milestoneId existe dans `serverData.oceco.milestones[]`.
414
+ * Sert à valider une référence avant d'attacher une entité (Action) à un milestone.
415
+ *
416
+ * @param milestoneId - L'ID du milestone à chercher.
417
+ * @returns `true` si trouvé, `false` sinon (y compris si oceco/milestones absents).
418
+ */
419
+ hasMilestone(milestoneId: string): boolean {
420
+ if (typeof milestoneId !== "string" || milestoneId.length === 0) return false;
421
+ const milestones = (this.serverData.oceco as { milestones?: Array<{ milestoneId?: string }> } | undefined)?.milestones;
422
+ if (!Array.isArray(milestones)) return false;
423
+ return milestones.some(m => m?.milestoneId === milestoneId);
424
+ }
425
+
365
426
  /**
366
427
  * {@inheritDoc BaseEntity#action}
367
428
  *
package/src/api/User.ts CHANGED
@@ -112,6 +112,7 @@ export class User extends BaseEntity<UserItemNormalized> {
112
112
  News: typeof import("./News.js").News;
113
113
  Comment: typeof import("./Comment.js").Comment;
114
114
  Answer: typeof import("./Answer.js").Answer;
115
+ Form?: typeof import("./Form.js").Form;
115
116
  Classified?: typeof import("./Classified.js").Classified;
116
117
  Action?: typeof import("./Action.js").Action;
117
118
  }
@@ -602,6 +603,15 @@ export class User extends BaseEntity<UserItemNormalized> {
602
603
  return super.project(projectData);
603
604
  }
604
605
 
606
+ /**
607
+ * Bloqué sur User : un Form nécessite un costumContext (slug d'Organization/Project)
608
+ * que User n'a pas. Utilisez `user.organization({slug}).then(o => o.form({id}))`
609
+ * ou `user.project({slug}).then(p => p.form({id}))` pour la chaîne propagée.
610
+ */
611
+ override async form(): Promise<never> {
612
+ throw new ApiError(`form n'existe pas dans ${this.constructor.name}`, 501);
613
+ }
614
+
605
615
  /**
606
616
  * {@inheritDoc BaseEntity#news}
607
617
  *
@@ -1,6 +1,7 @@
1
1
  import { DateValue, IdObject, ParentsMap } from "./common.js";
2
2
 
3
3
  import type EJSONType from "../../EJSONType.js";
4
+ import type { Answer } from "../Answer.js";
4
5
  import type { Organization } from "../Organization.js";
5
6
  import type { Project } from "../Project.js";
6
7
  import type { User } from "../User.js";
@@ -8,6 +9,30 @@ import type { User } from "../User.js";
8
9
  type ObjectIDCtor = typeof EJSONType["ObjectID"];
9
10
  type ObjectID = InstanceType<ObjectIDCtor>;
10
11
 
12
+ /**
13
+ * Identifiant d'un Form (string 24 hex). Alias purement sémantique de `string` :
14
+ * sert à lever l'ambiguïté dans `Record<FormId, ...>` — sans ce nom, on pourrait croire
15
+ * qu'il s'agit d'un answerId ou autre.
16
+ *
17
+ * ⚠️ Ces formIds proviennent de la config costum (ex: `navigatorDesTierslieux`)
18
+ * et ne correspondent **pas toujours** à des docs queriables via `api.form({id}).get()` —
19
+ * il s'agit parfois de références internes au costum (templates, sous-forms, etc.).
20
+ * À traiter comme un opaque token côté caller.
21
+ */
22
+ export type FormId = string;
23
+
24
+ /**
25
+ * Forme JSON brute : Answer docs (MongoDB) groupés par formId.
26
+ * Apparaît dans `result.answers` du payload `/costum/navigator/gettl` avant linkage.
27
+ */
28
+ export type AnswerDocsByFormId = Record<FormId, AnswerItemJson[]>;
29
+
30
+ /**
31
+ * Forme normalisée : instances `Answer` groupées par formId.
32
+ * Construite par `Organization/Project._transformServerData` à partir de `AnswerDocsByFormId`.
33
+ */
34
+ export type AnswersByFormId = Record<FormId, Answer[]>;
35
+
11
36
  export interface AnswerLinksBlock {
12
37
  answered: string[];
13
38
  }
@@ -10,9 +10,16 @@ export interface FormSubFormInputField {
10
10
  placeholder?: string;
11
11
  info?: string;
12
12
  type?: string;
13
+ position?: string;
13
14
  positions?: Record<string, string>;
15
+ width?: string;
14
16
  markdown?: boolean;
15
17
  uploader?: Record<string, any>;
18
+ rules?: {
19
+ width?: { min: number; max: number };
20
+ syntaxe?: { regexp: string; errormsg: string };
21
+ [key: string]: unknown;
22
+ };
16
23
  [key: string]: unknown;
17
24
  }
18
25
 
@@ -26,31 +33,104 @@ export interface FormSubFormInputs {
26
33
  [key: string]: unknown;
27
34
  }
28
35
 
36
+ /**
37
+ * Bloc d'accès au Form — décrit qui peut répondre, quand, comment.
38
+ * Renvoyé par `GET_COFORM_BY_ID` (cf. /survey/coform/getformbyid).
39
+ */
40
+ export interface FormAccess {
41
+ /** Si l'utilisateur courant peut répondre maintenant. */
42
+ canAnswer: boolean;
43
+ /** Raison si `canAnswer` est false (ex: "not_logged_in", "form_closed"). */
44
+ reason?: string;
45
+ /** État global du form (ex: "open", "closed", "pending"). */
46
+ formStatus?: string;
47
+ /** Id de la réponse existante de l'user courant (s'il en a une). */
48
+ existingAnswerId?: string | null;
49
+ /** Doc de la réponse existante (peut être enrichi). */
50
+ existingAnswer?: Record<string, unknown> | null;
51
+ /** Si le form requiert d'être connecté. */
52
+ requiresLogin?: boolean;
53
+ /** Réponse temporaire (brouillon) autorisée. */
54
+ allowTemporary?: boolean;
55
+ /** Réponse nécessite confirmation. */
56
+ withConfirmation?: boolean;
57
+ /** Réservé aux membres uniquement. */
58
+ isOnlyMember?: boolean;
59
+ /** Une seule réponse par personne. */
60
+ isOneAnswerPerPers?: boolean;
61
+ /** Form actif (vs désactivé). */
62
+ isActive?: boolean;
63
+ /** Fenêtres de dates (ouverture/fermeture, avec/sans confirmation). */
64
+ dates?: {
65
+ start?: string | null;
66
+ end?: string | null;
67
+ startNoConfirmation?: string | null;
68
+ endNoConfirmation?: string | null;
69
+ };
70
+ /** Liste des champs restreints (non visibles selon le rôle). */
71
+ restrictedFields?: string[];
72
+ [key: string]: unknown;
73
+ }
74
+
75
+ /**
76
+ * Forme JSON brute du Form telle que renvoyée par `GET_COFORM_BY_ID`.
77
+ *
78
+ * ⚠️ Le champ `collection` n'est PAS présent dans le payload backend natif
79
+ * (PHDB::find renvoie le doc brut sans nom de table). Il doit être injecté
80
+ * côté SDK par `Form.get()` pour cohérence avec les autres entités.
81
+ */
29
82
  export interface FormItemJson {
30
83
  _id: IdObject;
31
- config?: string | null;
84
+ id?: string;
85
+ /** Injecté côté SDK (le backend ne le renvoie pas). */
86
+ collection?: "forms";
87
+ /** Nom du Form (ex: "Coworking", "Hébergement"). */
88
+ name?: string;
89
+ /** Type du Form (ex: "coformgeneric", "template"). */
90
+ type: string;
91
+ config?: Record<string, unknown> | null;
32
92
  parent?: ParentsMap | null;
33
93
  subForms?: string[] | null;
34
- type: string;
35
94
  created?: DateValue;
36
95
  creator?: string;
37
96
  params?: Record<string, unknown> | null;
38
97
  updated?: DateValue;
39
98
  inputs?: Record<string, FormSubFormInputs> | null;
99
+ /** Logique d'accès calculée côté backend pour l'user courant. */
100
+ access?: FormAccess;
101
+ /** Champs réservés aux admins du lieu/projet. */
102
+ placeAdminOnlyFields?: string[];
103
+ /** Champs réservés aux membres du lieu/projet. */
104
+ placeMemberOnlyFields?: string[];
105
+ /** Si le grand public peut éditer une réponse partagée. */
106
+ publicCanEditSharedAnswer?: boolean;
107
+ /** Path vers une question partagée (legacy / advanced usage). */
108
+ sharedQuestionPath?: Record<string, unknown> | null;
109
+ /** Utilise une image de bannière (présent surtout sur `type=template`). */
110
+ useBannerImg?: boolean;
40
111
  [key: string]: unknown;
41
112
  }
42
113
 
43
114
  export interface FormItemNormalized {
44
115
  id: string;
45
116
  _id: ObjectID;
46
- config?: string | null;
117
+ /** Injecté côté SDK (le backend ne le renvoie pas). */
118
+ collection?: "forms";
119
+ name?: string;
120
+ type: string;
121
+ config?: Record<string, unknown> | null;
47
122
  parent?: ParentsMap | null;
48
123
  subForms?: string[] | null;
49
- type: string;
50
124
  created?: Date;
51
125
  creator?: string;
52
126
  params?: Record<string, unknown> | null;
53
127
  updated?: Date;
54
128
  inputs?: Record<string, FormSubFormInputs> | null;
129
+ access?: FormAccess;
130
+ placeAdminOnlyFields?: string[];
131
+ placeMemberOnlyFields?: string[];
132
+ publicCanEditSharedAnswer?: boolean;
133
+ sharedQuestionPath?: Record<string, unknown> | null;
134
+ useBannerImg?: boolean;
55
135
  [key: string]: unknown;
56
136
  }
@@ -1,3 +1,4 @@
1
+ import type { AnswerDocsByFormId, AnswersByFormId } from "./Answer.js";
1
2
  import type {
2
3
  GeoCoordinates,
3
4
  GeoPosition,
@@ -64,6 +65,12 @@ export interface OrganizationItemJson {
64
65
  email?: string;
65
66
  url?: string;
66
67
  socialNetwork?: SocialNetwork;
68
+ /**
69
+ * Enrichissement par les Answers liées au résultat — renvoyé uniquement par
70
+ * `/costum/navigator/gettl` (variant `navigator-tl` de `searchCostum`).
71
+ * Forme JSON brute (avant linkage par `_transformServerData`).
72
+ */
73
+ answers?: AnswerDocsByFormId;
67
74
  [key: string]: unknown;
68
75
  }
69
76
 
@@ -94,5 +101,11 @@ export interface OrganizationItemNormalized {
94
101
  email?: string;
95
102
  url?: string;
96
103
  socialNetwork?: SocialNetwork;
104
+ /**
105
+ * Enrichissement par les Answers liées au résultat — renvoyé uniquement par
106
+ * `/costum/navigator/gettl` (variant `navigator-tl` de `searchCostum`).
107
+ * Forme normalisée (après linkage par `Organization._transformServerData`) : `Answer[]` par formId.
108
+ */
109
+ answers?: AnswersByFormId;
97
110
  [key: string]: unknown;
98
111
  }
@@ -1,3 +1,4 @@
1
+ import type { AnswerDocsByFormId, AnswersByFormId } from "./Answer.js";
1
2
  import type {
2
3
  GeoCoordinates,
3
4
  GeoPosition,
@@ -66,6 +67,13 @@ export interface ProjectItemJson {
66
67
  email?: string;
67
68
  url?: string;
68
69
  answer?: string;
70
+ /**
71
+ * Enrichissement par les Answers liées au résultat — renvoyé uniquement par
72
+ * `/costum/navigator/gettl` (variant `navigator-tl` de `searchCostum`).
73
+ * Forme JSON brute (avant linkage par `_transformServerData`).
74
+ * Distinct du champ `answer` (singulier) qui est l'id d'une Answer principale.
75
+ */
76
+ answers?: AnswerDocsByFormId;
69
77
  [key: string]: unknown;
70
78
  }
71
79
 
@@ -96,5 +104,12 @@ export interface ProjectItemNormalized {
96
104
  email?: string;
97
105
  url?: string;
98
106
  answer?: string;
107
+ /**
108
+ * Enrichissement par les Answers liées au résultat — renvoyé uniquement par
109
+ * `/costum/navigator/gettl` (variant `navigator-tl` de `searchCostum`).
110
+ * Forme normalisée (après linkage par `Project._transformServerData`) : `Answer[]` par formId.
111
+ * Distinct du champ `answer` (singulier) qui est l'id d'une Answer principale.
112
+ */
113
+ answers?: AnswersByFormId;
99
114
  [key: string]: unknown;
100
115
  }