@communecter/cocolight-api-client 1.0.133 → 1.0.134
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/dist/cocolight-api-client.browser.js +1 -1
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
- package/package.json +1 -1
- package/src/api/Answer.ts +239 -20
- package/src/api/serverDataType/Answer.ts +19 -0
- package/types/api/Answer.d.ts +121 -4
- package/types/api/serverDataType/Answer.d.ts +14 -0
package/package.json
CHANGED
package/src/api/Answer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseEntity } from "./BaseEntity.js";
|
|
2
2
|
import { ApiError } from "../error.js";
|
|
3
3
|
|
|
4
|
-
import type { CoformUploadAnswerFileData, DeleteElementData, SaveCoformAnswerData } from "./EndpointApi.types.js";
|
|
4
|
+
import type { CoformAnswersByIdData, CoformGetAnswerFilesData, CoformUploadAnswerFileData, DeleteElementData, SaveCoformAnswerData } from "./EndpointApi.types.js";
|
|
5
5
|
import type { AnswerItemNormalized } from "./serverDataType/Answer.js";
|
|
6
6
|
import type { FormItemNormalized } from "./serverDataType/Form.js";
|
|
7
7
|
|
|
@@ -45,6 +45,26 @@ export interface ProcessUploadsOptions {
|
|
|
45
45
|
batchSize?: number;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Options de `Answer.get()` — params optionnels du endpoint `COFORM_ANSWERS_BY_ID`.
|
|
50
|
+
*/
|
|
51
|
+
export interface AnswerGetOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Liste de champs à retourner côté backend (filtrage). Si absent, tous les
|
|
54
|
+
* champs sont renvoyés. Utile pour optimiser la taille de la réponse.
|
|
55
|
+
*/
|
|
56
|
+
fields?: string[];
|
|
57
|
+
/**
|
|
58
|
+
* ID du formulaire parent. Utilisé côté backend pour calculer `canEdit` et
|
|
59
|
+
* `editDeniedReason` (droits d'édition contextuels). Si absent, **auto-injecté
|
|
60
|
+
* via `this.parent.id`** quand le parent est un `Form` (cas typique
|
|
61
|
+
* `form.answer({id})`).
|
|
62
|
+
*/
|
|
63
|
+
formId?: string;
|
|
64
|
+
/** Chemin Finder (rarement utilisé). */
|
|
65
|
+
finderPath?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
/**
|
|
49
69
|
* Options pour `Answer.uploadFile()`. Tous les champs sont optionnels — les
|
|
50
70
|
* défauts reproduisent les valeurs backend (`docType: "image"`, `contentKey: "slider"`).
|
|
@@ -83,6 +103,40 @@ export interface UploadAnswerFileResult {
|
|
|
83
103
|
answerId: string;
|
|
84
104
|
}
|
|
85
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Options de `Answer.getFiles()`.
|
|
108
|
+
*/
|
|
109
|
+
export interface GetFilesOptions {
|
|
110
|
+
/** Clé d'identification de l'input (ex: `"subFormId.inputId"`). Requise. */
|
|
111
|
+
subKey: string;
|
|
112
|
+
/** Type de document attendu. Défaut backend : `"file"`. */
|
|
113
|
+
docType?: "image" | "file";
|
|
114
|
+
/** Clé de contenu utilisée lors de l'upload. Défaut backend : `"presentation"`. */
|
|
115
|
+
contentKey?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Élément renvoyé par `Answer.getFiles()` — un document attaché à l'Answer.
|
|
120
|
+
* Les champs `imagePath`/`imageThumbPath`/`imageMediumPath` ne sont présents
|
|
121
|
+
* que pour `docType: "image"`.
|
|
122
|
+
*/
|
|
123
|
+
export interface AnswerFileItem {
|
|
124
|
+
/** ID MongoDB du document (utilisable pour suppression via `deleteDocumentById`). */
|
|
125
|
+
docId: string;
|
|
126
|
+
/** Chemin relatif du document côté backend. */
|
|
127
|
+
docPath: string;
|
|
128
|
+
/** Nom original du fichier. */
|
|
129
|
+
name?: string;
|
|
130
|
+
/** Taille en octets. */
|
|
131
|
+
size?: number;
|
|
132
|
+
/** Chemin de l'image (uniquement si `docType: "image"`). */
|
|
133
|
+
imagePath?: string | null;
|
|
134
|
+
/** Chemin du thumbnail (uniquement si `docType: "image"`). */
|
|
135
|
+
imageThumbPath?: string | null;
|
|
136
|
+
/** Chemin de la version medium (uniquement si `docType: "image"`). */
|
|
137
|
+
imageMediumPath?: string | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
86
140
|
export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
87
141
|
static override entityType = "answers";
|
|
88
142
|
|
|
@@ -126,33 +180,61 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
126
180
|
data.user = this._linkNestedEntity({ type: "citoyens", collection: "citoyens", ...data.user });
|
|
127
181
|
}
|
|
128
182
|
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
183
|
+
// Normalisation legacy uploader : si parent Form connu, convertit les valeurs
|
|
184
|
+
// d'inputs `*.uploader` du format Array `[{docId, docPath}]` vers le format
|
|
185
|
+
// canonique `{updateDate, files: {docId: docPath}}`. Symétrique de ce que
|
|
186
|
+
// `processUploads()` écrit. Si parent pas Form → skip (pas de schéma pour
|
|
187
|
+
// identifier les inputs uploader).
|
|
188
|
+
const formData = this._resolveFormData();
|
|
189
|
+
if (formData && data.answers && typeof data.answers === "object") {
|
|
190
|
+
data.answers = this._normalizeLegacyUploaderInAnswers(
|
|
191
|
+
data.answers as Record<string, Record<string, unknown>>,
|
|
192
|
+
formData,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
141
195
|
|
|
142
196
|
return data;
|
|
143
197
|
}
|
|
144
198
|
|
|
145
199
|
/**
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
200
|
+
* Rafraîchit les données de l'entité depuis l'API (`COFORM_ANSWERS_BY_ID`).
|
|
201
|
+
*
|
|
202
|
+
* **Auto-injection du `formId`** : si le parent est un `Form` (cas typique
|
|
203
|
+
* `form.answer({id})`), le param `formId` est automatiquement envoyé pour
|
|
204
|
+
* que le backend calcule `canEdit` et `editDeniedReason` dans la réponse.
|
|
205
|
+
* Le caller peut override via `opts.formId` ou couper avec `opts.formId: ""`.
|
|
206
|
+
*
|
|
207
|
+
* @param opts - Params optionnels :
|
|
208
|
+
* - `fields` : filtrage des champs côté backend
|
|
209
|
+
* - `formId` : override de l'auto-injection (utile pour disable ou forcer un autre form)
|
|
210
|
+
* - `finderPath` : chemin Finder (rare)
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* // Cas commun : form.answer({id}) → get() auto avec formId
|
|
214
|
+
* const answer = await form.answer({ id: answerId });
|
|
215
|
+
* answer.serverData.canEdit; // true/false calculé backend
|
|
216
|
+
* answer.serverData.editDeniedReason; // raison si false
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* // Cas avancé : filtrage de champs pour optim perf
|
|
220
|
+
* const answer = await form.answer();
|
|
221
|
+
* answer._id(answerId);
|
|
222
|
+
* await answer.get({ fields: ["answers", "canEdit", "editDeniedReason"] });
|
|
223
|
+
*/
|
|
224
|
+
override async get(opts: AnswerGetOptions = {}): Promise<Record<string, any>> {
|
|
150
225
|
if (!this.id) throw new ApiError("Impossible de rafraîchir sans ID.", 400);
|
|
151
226
|
const answerId = this.id; // Type narrowing
|
|
152
227
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
228
|
+
// Auto-injection du formId via parent Form (sauf si caller a explicitement
|
|
229
|
+
// passé opts.formId — même chaîne vide pour disable).
|
|
230
|
+
const formId = "formId" in opts ? opts.formId : this._autoFormIdFromParent();
|
|
231
|
+
|
|
232
|
+
const payload: CoformAnswersByIdData = { answerId };
|
|
233
|
+
if (opts.fields && opts.fields.length > 0) payload.fields = opts.fields;
|
|
234
|
+
if (typeof formId === "string" && formId.length > 0) payload.formId = formId;
|
|
235
|
+
if (opts.finderPath) payload.finderPath = opts.finderPath;
|
|
236
|
+
|
|
237
|
+
const answer = await this.endpointApi.coformAnswersById(payload);
|
|
156
238
|
|
|
157
239
|
if (answer?.data?.id) {
|
|
158
240
|
this._setData(answer.data as AnswerItemNormalized, { forceInitialDraftReset: true });
|
|
@@ -161,6 +243,20 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
161
243
|
throw new ApiError(`Aucune réponse trouvée pour l'ID ${this.id}`, 404);
|
|
162
244
|
}
|
|
163
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Récupère l'ID du parent Form si applicable, pour l'auto-injection dans `get()`.
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
private _autoFormIdFromParent(): string | undefined {
|
|
251
|
+
const parent = this.parent;
|
|
252
|
+
if (parent && typeof (parent as any).getEntityType === "function"
|
|
253
|
+
&& (parent as any).getEntityType() === "forms"
|
|
254
|
+
&& typeof (parent as any).id === "string") {
|
|
255
|
+
return (parent as any).id as string;
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
164
260
|
override async form(): Promise<never> {
|
|
165
261
|
throw new ApiError(`form n'existe pas dans ${this.constructor.name}`, 501);
|
|
166
262
|
}
|
|
@@ -200,6 +296,83 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
200
296
|
this._isDeleted = true;
|
|
201
297
|
}
|
|
202
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Récupère la liste des fichiers attachés à un input précis de cette Answer
|
|
301
|
+
* via `COFORM_GET_ANSWER_FILES`.
|
|
302
|
+
*
|
|
303
|
+
* **Cas d'usage principal** : récupérer les fichiers d'inputs uploader stockés
|
|
304
|
+
* en **format legacy** `{updateDate: [...]}` (sans la clé `files`). Le backend
|
|
305
|
+
* recherche alors les documents MongoDB liés à `{answerId, subKey, docType}`.
|
|
306
|
+
*
|
|
307
|
+
* Pour les Answer en format moderne `{updateDate, files: {<docId>: <path>}}`,
|
|
308
|
+
* les fichiers sont déjà dans `answer.serverData.answers[subFormId][inputId].files`
|
|
309
|
+
* — pas besoin d'appel réseau.
|
|
310
|
+
*
|
|
311
|
+
* @param opts - `subKey` (requis, ex: `"subFormId.inputId"`), `docType`, `contentKey`
|
|
312
|
+
* @returns Liste normalisée des fichiers `{docId, docPath, name?, size?, imagePath?, imageThumbPath?, imageMediumPath?}`.
|
|
313
|
+
* Tableau vide si aucun fichier trouvé.
|
|
314
|
+
* @throws {ApiError} 400 si pas d'id Answer. Throws aussi si le backend renvoie `result: false`.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* const form = await org.form({ id: formId });
|
|
318
|
+
* const answer = await form.answer({ id: answerId });
|
|
319
|
+
* const files = await answer.getFiles({
|
|
320
|
+
* subKey: `${subFormId}.${inputId}`,
|
|
321
|
+
* docType: "image",
|
|
322
|
+
* });
|
|
323
|
+
* files[0].docId; // ID du document (pour suppression future)
|
|
324
|
+
* files[0].docPath; // Chemin relatif (à concaténer avec baseURL pour affichage)
|
|
325
|
+
*/
|
|
326
|
+
async getFiles(opts: GetFilesOptions): Promise<AnswerFileItem[]> {
|
|
327
|
+
if (!this.id) {
|
|
328
|
+
throw new ApiError("Answer sans id, impossible de lister ses fichiers.", 400);
|
|
329
|
+
}
|
|
330
|
+
if (!opts.subKey || opts.subKey.length === 0) {
|
|
331
|
+
throw new ApiError("`subKey` requis pour lister les fichiers d'un input.", 400);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const payload: CoformGetAnswerFilesData = {
|
|
335
|
+
answerId: this.id,
|
|
336
|
+
subKey: opts.subKey,
|
|
337
|
+
};
|
|
338
|
+
if (opts.docType) payload.docType = opts.docType;
|
|
339
|
+
if (opts.contentKey) payload.contentKey = opts.contentKey;
|
|
340
|
+
|
|
341
|
+
// Forme observée du backend (cf. site-json useCoFormAnswerFiles.tsx
|
|
342
|
+
// et schema COFORM_GET_ANSWER_FILES) :
|
|
343
|
+
// { result, answerId, subKey, contentKey, count, files: [{ id, name, size,
|
|
344
|
+
// docPath, imagePath, imageThumbPath, imageMediumPath }] }
|
|
345
|
+
const response = await this.callIsConnected(() =>
|
|
346
|
+
this.endpointApi.coformGetAnswerFiles(payload)
|
|
347
|
+
) as {
|
|
348
|
+
result?: boolean;
|
|
349
|
+
msg?: string;
|
|
350
|
+
count?: number;
|
|
351
|
+
files?: Array<{
|
|
352
|
+
id?: string | null;
|
|
353
|
+
name?: string | null;
|
|
354
|
+
size?: number | null;
|
|
355
|
+
docPath?: string;
|
|
356
|
+
imagePath?: string | null;
|
|
357
|
+
imageThumbPath?: string | null;
|
|
358
|
+
imageMediumPath?: string | null;
|
|
359
|
+
}>;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// Filtre les entrées invalides (id + docPath requis) et normalise.
|
|
363
|
+
return (response.files ?? [])
|
|
364
|
+
.filter(f => typeof f.id === "string" && typeof f.docPath === "string")
|
|
365
|
+
.map(f => ({
|
|
366
|
+
docId: f.id!,
|
|
367
|
+
docPath: f.docPath!,
|
|
368
|
+
name: f.name ?? undefined,
|
|
369
|
+
size: f.size ?? undefined,
|
|
370
|
+
imagePath: f.imagePath ?? undefined,
|
|
371
|
+
imageThumbPath: f.imageThumbPath ?? undefined,
|
|
372
|
+
imageMediumPath: f.imageMediumPath ?? undefined,
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
|
|
203
376
|
/**
|
|
204
377
|
* Upload un fichier rattaché à cette Answer via `COFORM_UPLOAD_ANSWER_FILE`.
|
|
205
378
|
*
|
|
@@ -738,6 +911,52 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
738
911
|
return obj;
|
|
739
912
|
}
|
|
740
913
|
|
|
914
|
+
/**
|
|
915
|
+
* Walk les `answers[subFormId][inputId]` pour normaliser les valeurs des
|
|
916
|
+
* inputs `*.uploader` vers le format canonique `{updateDate, files: {docId: docPath}}`.
|
|
917
|
+
*
|
|
918
|
+
* Utilisé par `_transformServerData()` pour harmoniser les Answer en format
|
|
919
|
+
* legacy Array `[{docId, docPath}]` avec le format moderne (ce que
|
|
920
|
+
* `processUploads()` écrit).
|
|
921
|
+
*
|
|
922
|
+
* Skip si le schéma `formData` n'a pas l'inputType pour un champ donné.
|
|
923
|
+
* @private
|
|
924
|
+
*/
|
|
925
|
+
private _normalizeLegacyUploaderInAnswers(
|
|
926
|
+
answers: Record<string, Record<string, unknown>>,
|
|
927
|
+
formData: FormItemNormalized,
|
|
928
|
+
): Record<string, Record<string, unknown>> {
|
|
929
|
+
const normalized: Record<string, Record<string, unknown>> = {};
|
|
930
|
+
|
|
931
|
+
for (const [subFormId, subFormData] of Object.entries(answers)) {
|
|
932
|
+
if (!this._isObjectRecord(subFormData)) {
|
|
933
|
+
normalized[subFormId] = subFormData as any;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const subForm = formData.inputs?.[subFormId];
|
|
938
|
+
if (!subForm) {
|
|
939
|
+
normalized[subFormId] = subFormData;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const cleanedSubForm: Record<string, unknown> = {};
|
|
944
|
+
for (const [inputId, inputValue] of Object.entries(subFormData)) {
|
|
945
|
+
const input = subForm.inputs?.[inputId];
|
|
946
|
+
const inputType = input && typeof input === "object" && "type" in input
|
|
947
|
+
? (input.type as string | undefined)
|
|
948
|
+
: undefined;
|
|
949
|
+
|
|
950
|
+
cleanedSubForm[inputId] = inputType?.endsWith(".uploader")
|
|
951
|
+
? this._normalizeUploaderValue(inputValue)
|
|
952
|
+
: inputValue;
|
|
953
|
+
}
|
|
954
|
+
normalized[subFormId] = cleanedSubForm;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return normalized;
|
|
958
|
+
}
|
|
959
|
+
|
|
741
960
|
/**
|
|
742
961
|
* Normalise une valeur d'input uploader vers le format legacy backend :
|
|
743
962
|
* `{ updateDate: ["DD/MM/YYYY"], files: { "<docId>": "<docPath>", ... } }`
|
|
@@ -64,6 +64,17 @@ export interface AnswerItemJson {
|
|
|
64
64
|
[key: string]: unknown;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Raisons de refus d'édition côté backend, calculées par `coformAnswersById`
|
|
69
|
+
* et présentes dans la réponse normalisée. Utilisé par les UI pour distinguer
|
|
70
|
+
* les modes readonly/edit et afficher des messages contextuels.
|
|
71
|
+
*/
|
|
72
|
+
export type AnswerEditDeniedReason =
|
|
73
|
+
| "not_logged_in"
|
|
74
|
+
| "not_owner"
|
|
75
|
+
| "form_inactive"
|
|
76
|
+
| "form_closed";
|
|
77
|
+
|
|
67
78
|
export interface AnswerItemNormalized {
|
|
68
79
|
id: string;
|
|
69
80
|
_id: ObjectID;
|
|
@@ -73,6 +84,7 @@ export interface AnswerItemNormalized {
|
|
|
73
84
|
user?: string | User;
|
|
74
85
|
links?: AnswerLinksBlock;
|
|
75
86
|
draft?: boolean;
|
|
87
|
+
finished?: boolean;
|
|
76
88
|
answers?: Record<string, unknown>;
|
|
77
89
|
context?: ParentsMap | Record<string, Organization | Project>;
|
|
78
90
|
form?: string;
|
|
@@ -83,5 +95,12 @@ export interface AnswerItemNormalized {
|
|
|
83
95
|
date: Date;
|
|
84
96
|
}>;
|
|
85
97
|
project?: AnswerProjectRef;
|
|
98
|
+
/**
|
|
99
|
+
* L'utilisateur courant peut-il éditer cette réponse ? Calculé côté backend.
|
|
100
|
+
* Présent par défaut, ou enrichi avec le contexte si `formId` est fourni.
|
|
101
|
+
*/
|
|
102
|
+
canEdit?: boolean;
|
|
103
|
+
/** Raison si `canEdit === false`. `null` si `canEdit === true`. */
|
|
104
|
+
editDeniedReason?: AnswerEditDeniedReason | null;
|
|
86
105
|
[key: string]: unknown;
|
|
87
106
|
}
|
package/types/api/Answer.d.ts
CHANGED
|
@@ -36,6 +36,25 @@ export interface ProcessUploadsOptions {
|
|
|
36
36
|
/** Nombre d'uploads parallèles par batch. Défaut : 4. */
|
|
37
37
|
batchSize?: number;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Options de `Answer.get()` — params optionnels du endpoint `COFORM_ANSWERS_BY_ID`.
|
|
41
|
+
*/
|
|
42
|
+
export interface AnswerGetOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Liste de champs à retourner côté backend (filtrage). Si absent, tous les
|
|
45
|
+
* champs sont renvoyés. Utile pour optimiser la taille de la réponse.
|
|
46
|
+
*/
|
|
47
|
+
fields?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* ID du formulaire parent. Utilisé côté backend pour calculer `canEdit` et
|
|
50
|
+
* `editDeniedReason` (droits d'édition contextuels). Si absent, **auto-injecté
|
|
51
|
+
* via `this.parent.id`** quand le parent est un `Form` (cas typique
|
|
52
|
+
* `form.answer({id})`).
|
|
53
|
+
*/
|
|
54
|
+
formId?: string;
|
|
55
|
+
/** Chemin Finder (rarement utilisé). */
|
|
56
|
+
finderPath?: string;
|
|
57
|
+
}
|
|
39
58
|
/**
|
|
40
59
|
* Options pour `Answer.uploadFile()`. Tous les champs sont optionnels — les
|
|
41
60
|
* défauts reproduisent les valeurs backend (`docType: "image"`, `contentKey: "slider"`).
|
|
@@ -72,6 +91,38 @@ export interface UploadAnswerFileResult {
|
|
|
72
91
|
/** ID de l'Answer (créée par cet upload si premier, sinon `= this.id`). */
|
|
73
92
|
answerId: string;
|
|
74
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Options de `Answer.getFiles()`.
|
|
96
|
+
*/
|
|
97
|
+
export interface GetFilesOptions {
|
|
98
|
+
/** Clé d'identification de l'input (ex: `"subFormId.inputId"`). Requise. */
|
|
99
|
+
subKey: string;
|
|
100
|
+
/** Type de document attendu. Défaut backend : `"file"`. */
|
|
101
|
+
docType?: "image" | "file";
|
|
102
|
+
/** Clé de contenu utilisée lors de l'upload. Défaut backend : `"presentation"`. */
|
|
103
|
+
contentKey?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Élément renvoyé par `Answer.getFiles()` — un document attaché à l'Answer.
|
|
107
|
+
* Les champs `imagePath`/`imageThumbPath`/`imageMediumPath` ne sont présents
|
|
108
|
+
* que pour `docType: "image"`.
|
|
109
|
+
*/
|
|
110
|
+
export interface AnswerFileItem {
|
|
111
|
+
/** ID MongoDB du document (utilisable pour suppression via `deleteDocumentById`). */
|
|
112
|
+
docId: string;
|
|
113
|
+
/** Chemin relatif du document côté backend. */
|
|
114
|
+
docPath: string;
|
|
115
|
+
/** Nom original du fichier. */
|
|
116
|
+
name?: string;
|
|
117
|
+
/** Taille en octets. */
|
|
118
|
+
size?: number;
|
|
119
|
+
/** Chemin de l'image (uniquement si `docType: "image"`). */
|
|
120
|
+
imagePath?: string | null;
|
|
121
|
+
/** Chemin du thumbnail (uniquement si `docType: "image"`). */
|
|
122
|
+
imageThumbPath?: string | null;
|
|
123
|
+
/** Chemin de la version medium (uniquement si `docType: "image"`). */
|
|
124
|
+
imageMediumPath?: string | null;
|
|
125
|
+
}
|
|
75
126
|
export declare class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
76
127
|
static entityType: string;
|
|
77
128
|
static entityTag: string;
|
|
@@ -93,10 +144,36 @@ export declare class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
93
144
|
*/
|
|
94
145
|
protected _transformServerData(data: AnswerItemNormalized): AnswerItemNormalized;
|
|
95
146
|
/**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
147
|
+
* Rafraîchit les données de l'entité depuis l'API (`COFORM_ANSWERS_BY_ID`).
|
|
148
|
+
*
|
|
149
|
+
* **Auto-injection du `formId`** : si le parent est un `Form` (cas typique
|
|
150
|
+
* `form.answer({id})`), le param `formId` est automatiquement envoyé pour
|
|
151
|
+
* que le backend calcule `canEdit` et `editDeniedReason` dans la réponse.
|
|
152
|
+
* Le caller peut override via `opts.formId` ou couper avec `opts.formId: ""`.
|
|
153
|
+
*
|
|
154
|
+
* @param opts - Params optionnels :
|
|
155
|
+
* - `fields` : filtrage des champs côté backend
|
|
156
|
+
* - `formId` : override de l'auto-injection (utile pour disable ou forcer un autre form)
|
|
157
|
+
* - `finderPath` : chemin Finder (rare)
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* // Cas commun : form.answer({id}) → get() auto avec formId
|
|
161
|
+
* const answer = await form.answer({ id: answerId });
|
|
162
|
+
* answer.serverData.canEdit; // true/false calculé backend
|
|
163
|
+
* answer.serverData.editDeniedReason; // raison si false
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* // Cas avancé : filtrage de champs pour optim perf
|
|
167
|
+
* const answer = await form.answer();
|
|
168
|
+
* answer._id(answerId);
|
|
169
|
+
* await answer.get({ fields: ["answers", "canEdit", "editDeniedReason"] });
|
|
170
|
+
*/
|
|
171
|
+
get(opts?: AnswerGetOptions): Promise<Record<string, any>>;
|
|
172
|
+
/**
|
|
173
|
+
* Récupère l'ID du parent Form si applicable, pour l'auto-injection dans `get()`.
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
private _autoFormIdFromParent;
|
|
100
177
|
form(): Promise<never>;
|
|
101
178
|
/**
|
|
102
179
|
* Supprime cette Answer côté serveur via `DELETE_ELEMENT`
|
|
@@ -115,6 +192,34 @@ export declare class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
115
192
|
* answer._isDeleted; // true
|
|
116
193
|
*/
|
|
117
194
|
delete(reason?: string): Promise<void>;
|
|
195
|
+
/**
|
|
196
|
+
* Récupère la liste des fichiers attachés à un input précis de cette Answer
|
|
197
|
+
* via `COFORM_GET_ANSWER_FILES`.
|
|
198
|
+
*
|
|
199
|
+
* **Cas d'usage principal** : récupérer les fichiers d'inputs uploader stockés
|
|
200
|
+
* en **format legacy** `{updateDate: [...]}` (sans la clé `files`). Le backend
|
|
201
|
+
* recherche alors les documents MongoDB liés à `{answerId, subKey, docType}`.
|
|
202
|
+
*
|
|
203
|
+
* Pour les Answer en format moderne `{updateDate, files: {<docId>: <path>}}`,
|
|
204
|
+
* les fichiers sont déjà dans `answer.serverData.answers[subFormId][inputId].files`
|
|
205
|
+
* — pas besoin d'appel réseau.
|
|
206
|
+
*
|
|
207
|
+
* @param opts - `subKey` (requis, ex: `"subFormId.inputId"`), `docType`, `contentKey`
|
|
208
|
+
* @returns Liste normalisée des fichiers `{docId, docPath, name?, size?, imagePath?, imageThumbPath?, imageMediumPath?}`.
|
|
209
|
+
* Tableau vide si aucun fichier trouvé.
|
|
210
|
+
* @throws {ApiError} 400 si pas d'id Answer. Throws aussi si le backend renvoie `result: false`.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* const form = await org.form({ id: formId });
|
|
214
|
+
* const answer = await form.answer({ id: answerId });
|
|
215
|
+
* const files = await answer.getFiles({
|
|
216
|
+
* subKey: `${subFormId}.${inputId}`,
|
|
217
|
+
* docType: "image",
|
|
218
|
+
* });
|
|
219
|
+
* files[0].docId; // ID du document (pour suppression future)
|
|
220
|
+
* files[0].docPath; // Chemin relatif (à concaténer avec baseURL pour affichage)
|
|
221
|
+
*/
|
|
222
|
+
getFiles(opts: GetFilesOptions): Promise<AnswerFileItem[]>;
|
|
118
223
|
/**
|
|
119
224
|
* Upload un fichier rattaché à cette Answer via `COFORM_UPLOAD_ANSWER_FILE`.
|
|
120
225
|
*
|
|
@@ -276,6 +381,18 @@ export declare class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
276
381
|
private _getUploadKeys;
|
|
277
382
|
private _getValueAtPath;
|
|
278
383
|
private _setValueAtPath;
|
|
384
|
+
/**
|
|
385
|
+
* Walk les `answers[subFormId][inputId]` pour normaliser les valeurs des
|
|
386
|
+
* inputs `*.uploader` vers le format canonique `{updateDate, files: {docId: docPath}}`.
|
|
387
|
+
*
|
|
388
|
+
* Utilisé par `_transformServerData()` pour harmoniser les Answer en format
|
|
389
|
+
* legacy Array `[{docId, docPath}]` avec le format moderne (ce que
|
|
390
|
+
* `processUploads()` écrit).
|
|
391
|
+
*
|
|
392
|
+
* Skip si le schéma `formData` n'a pas l'inputType pour un champ donné.
|
|
393
|
+
* @private
|
|
394
|
+
*/
|
|
395
|
+
private _normalizeLegacyUploaderInAnswers;
|
|
279
396
|
/**
|
|
280
397
|
* Normalise une valeur d'input uploader vers le format legacy backend :
|
|
281
398
|
* `{ updateDate: ["DD/MM/YYYY"], files: { "<docId>": "<docPath>", ... } }`
|
|
@@ -55,6 +55,12 @@ export interface AnswerItemJson {
|
|
|
55
55
|
project?: AnswerProjectRef;
|
|
56
56
|
[key: string]: unknown;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Raisons de refus d'édition côté backend, calculées par `coformAnswersById`
|
|
60
|
+
* et présentes dans la réponse normalisée. Utilisé par les UI pour distinguer
|
|
61
|
+
* les modes readonly/edit et afficher des messages contextuels.
|
|
62
|
+
*/
|
|
63
|
+
export type AnswerEditDeniedReason = "not_logged_in" | "not_owner" | "form_inactive" | "form_closed";
|
|
58
64
|
export interface AnswerItemNormalized {
|
|
59
65
|
id: string;
|
|
60
66
|
_id: ObjectID;
|
|
@@ -64,6 +70,7 @@ export interface AnswerItemNormalized {
|
|
|
64
70
|
user?: string | User;
|
|
65
71
|
links?: AnswerLinksBlock;
|
|
66
72
|
draft?: boolean;
|
|
73
|
+
finished?: boolean;
|
|
67
74
|
answers?: Record<string, unknown>;
|
|
68
75
|
context?: ParentsMap | Record<string, Organization | Project>;
|
|
69
76
|
form?: string;
|
|
@@ -74,6 +81,13 @@ export interface AnswerItemNormalized {
|
|
|
74
81
|
date: Date;
|
|
75
82
|
}>;
|
|
76
83
|
project?: AnswerProjectRef;
|
|
84
|
+
/**
|
|
85
|
+
* L'utilisateur courant peut-il éditer cette réponse ? Calculé côté backend.
|
|
86
|
+
* Présent par défaut, ou enrichi avec le contexte si `formId` est fourni.
|
|
87
|
+
*/
|
|
88
|
+
canEdit?: boolean;
|
|
89
|
+
/** Raison si `canEdit === false`. `null` si `canEdit === true`. */
|
|
90
|
+
editDeniedReason?: AnswerEditDeniedReason | null;
|
|
77
91
|
[key: string]: unknown;
|
|
78
92
|
}
|
|
79
93
|
export {};
|