@blackcode_sa/metaestetics-api 1.12.8 → 1.12.10

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.12.8",
4
+ "version": "1.12.10",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -0,0 +1,13 @@
1
+ # Forms Aggregation Service
2
+
3
+ This directory contains services responsible for handling data aggregation and side effects related to form submissions within the system.
4
+
5
+ ## FilledFormsAggregationService
6
+
7
+ `FilledFormsAggregationService` is triggered when a `FilledDocument` is created, updated, or deleted. Its primary responsibility is to keep the corresponding `Appointment` document synchronized with the state of its associated forms.
8
+
9
+ ### Core Functions:
10
+
11
+ - **`handleFilledFormCreateOrUpdate`**: When a form is filled or updated, this function updates the `linkedForms` array on the parent `Appointment` document. It fetches the correct document template (including the specific version) to retrieve the form's `title` and other metadata, ensuring the appointment has up-to-date information about the form's status, path, and title.
12
+ - **`handleFilledFormDelete`**: When a filled form is deleted, this function removes the corresponding entry from the `linkedForms` and `linkedFormIds` arrays on the `Appointment` document.
13
+ - **`handleRequiredUserFormCreate`**: When a new form that is marked as `isRequired` is created for a user, this function adds the form's ID to the `pendingUserFormsIds` array on the `Appointment`, helping track incomplete required forms.
@@ -1,17 +1,13 @@
1
- import * as admin from "firebase-admin";
1
+ import * as admin from 'firebase-admin';
2
2
  import {
3
3
  FilledDocument,
4
4
  FilledDocumentStatus,
5
5
  USER_FORMS_SUBCOLLECTION,
6
6
  DOCTOR_FORMS_SUBCOLLECTION,
7
- } from "../../../types/documentation-templates";
8
- import {
9
- Appointment,
10
- LinkedFormInfo,
11
- APPOINTMENTS_COLLECTION,
12
- } from "../../../types/appointment";
13
- import { Logger } from "../../logger";
14
- import { Timestamp } from "firebase/firestore";
7
+ } from '../../../types/documentation-templates';
8
+ import { Appointment, LinkedFormInfo, APPOINTMENTS_COLLECTION } from '../../../types/appointment';
9
+ import { Logger } from '../../logger';
10
+ import { Timestamp } from 'firebase/firestore';
15
11
 
16
12
  /**
17
13
  * @class FilledFormsAggregationService
@@ -27,7 +23,7 @@ export class FilledFormsAggregationService {
27
23
  */
28
24
  constructor(firestore?: admin.firestore.Firestore) {
29
25
  this.db = firestore || admin.firestore();
30
- Logger.info("[FilledFormsAggregationService] Initialized");
26
+ Logger.info('[FilledFormsAggregationService] Initialized');
31
27
  }
32
28
 
33
29
  /**
@@ -36,11 +32,9 @@ export class FilledFormsAggregationService {
36
32
  * @param filledDocument The filled document that was created or updated.
37
33
  * @returns {Promise<void>}
38
34
  */
39
- async handleFilledFormCreateOrUpdate(
40
- filledDocument: FilledDocument
41
- ): Promise<void> {
35
+ async handleFilledFormCreateOrUpdate(filledDocument: FilledDocument): Promise<void> {
42
36
  Logger.info(
43
- `[FilledFormsAggregationService] Handling CREATE/UPDATE for filled form: ${filledDocument.id}, appointment: ${filledDocument.appointmentId}`
37
+ `[FilledFormsAggregationService] Handling CREATE/UPDATE for filled form: ${filledDocument.id}, appointment: ${filledDocument.appointmentId}`,
44
38
  );
45
39
 
46
40
  try {
@@ -52,11 +46,48 @@ export class FilledFormsAggregationService {
52
46
  const appointmentDoc = await appointmentRef.get();
53
47
  if (!appointmentDoc.exists) {
54
48
  Logger.error(
55
- `[FilledFormsAggregationService] Appointment ${filledDocument.appointmentId} not found.`
49
+ `[FilledFormsAggregationService] Appointment ${filledDocument.appointmentId} not found.`,
56
50
  );
57
51
  return;
58
52
  }
59
53
 
54
+ // Fetch the document template to get the title, with fallbacks.
55
+ let templateData: admin.firestore.DocumentData | undefined;
56
+ const templateRef = this.db
57
+ .collection('documentation-templates')
58
+ .doc(filledDocument.templateId);
59
+ const templateDoc = await templateRef.get();
60
+
61
+ if (templateDoc.exists) {
62
+ const currentTemplateData = templateDoc.data();
63
+ if (currentTemplateData && currentTemplateData.version === filledDocument.templateVersion) {
64
+ // The requested version is the latest one.
65
+ templateData = currentTemplateData;
66
+ } else {
67
+ // The requested version is an older one, look in the 'versions' subcollection.
68
+ const versionRef = templateRef
69
+ .collection('versions')
70
+ .doc(filledDocument.templateVersion.toString());
71
+ const versionDoc = await versionRef.get();
72
+
73
+ if (versionDoc.exists) {
74
+ templateData = versionDoc.data();
75
+ } else {
76
+ // Fallback 1: If the specific version is not found, use the latest version as a fallback.
77
+ Logger.warn(
78
+ `[FilledFormsAggregationService] Template version ${filledDocument.templateVersion} not found for template ${filledDocument.templateId}. Falling back to the latest version.`,
79
+ );
80
+ templateData = currentTemplateData;
81
+ }
82
+ }
83
+ } else {
84
+ // This case should be rare if data is consistent, but we handle it.
85
+ Logger.error(
86
+ `[FilledFormsAggregationService] Document template ${filledDocument.templateId} not found. Cannot retrieve title.`,
87
+ );
88
+ // templateData remains undefined, will trigger the final fallback for the title.
89
+ }
90
+
60
91
  const appointment = appointmentDoc.data() as Appointment;
61
92
 
62
93
  // 2. Prepare the LinkedFormInfo object
@@ -68,9 +99,7 @@ export class FilledFormsAggregationService {
68
99
  formId: filledDocument.id,
69
100
  templateId: filledDocument.templateId,
70
101
  templateVersion: filledDocument.templateVersion,
71
- title: filledDocument.isUserForm
72
- ? "User Form" // Default title if not available
73
- : "Doctor Form", // Default title if not available
102
+ title: templateData?.title || (filledDocument.isUserForm ? 'User Form' : 'Doctor Form'),
74
103
  isUserForm: filledDocument.isUserForm,
75
104
  isRequired: filledDocument.isRequired,
76
105
  status: filledDocument.status,
@@ -81,7 +110,7 @@ export class FilledFormsAggregationService {
81
110
  if (filledDocument.updatedAt) {
82
111
  // Convert to admin.firestore.Timestamp to ensure compatibility
83
112
  linkedFormInfo.submittedAt = admin.firestore.Timestamp.fromMillis(
84
- filledDocument.updatedAt
113
+ filledDocument.updatedAt,
85
114
  ) as unknown as Timestamp;
86
115
  }
87
116
 
@@ -90,7 +119,7 @@ export class FilledFormsAggregationService {
90
119
  filledDocument.status === FilledDocumentStatus.SIGNED
91
120
  ) {
92
121
  linkedFormInfo.completedAt = admin.firestore.Timestamp.fromMillis(
93
- filledDocument.updatedAt
122
+ filledDocument.updatedAt,
94
123
  ) as unknown as Timestamp;
95
124
  }
96
125
 
@@ -101,7 +130,7 @@ export class FilledFormsAggregationService {
101
130
  if (appointment.linkedForms && appointment.linkedForms.length > 0) {
102
131
  // Filter out any existing entries for this form ID to prevent duplicates
103
132
  updatedLinkedForms = appointment.linkedForms.filter(
104
- (form) => form.formId !== filledDocument.id
133
+ form => form.formId !== filledDocument.id,
105
134
  );
106
135
  }
107
136
 
@@ -134,10 +163,11 @@ export class FilledFormsAggregationService {
134
163
  appointment.pendingUserFormsIds.includes(filledDocument.id)
135
164
  ) {
136
165
  // Remove the form ID from pendingUserFormsIds
137
- updateData.pendingUserFormsIds =
138
- admin.firestore.FieldValue.arrayRemove(filledDocument.id);
166
+ updateData.pendingUserFormsIds = admin.firestore.FieldValue.arrayRemove(
167
+ filledDocument.id,
168
+ );
139
169
  Logger.info(
140
- `[FilledFormsAggregationService] Removing form ${filledDocument.id} from pendingUserFormsIds`
170
+ `[FilledFormsAggregationService] Removing form ${filledDocument.id} from pendingUserFormsIds`,
141
171
  );
142
172
  }
143
173
  }
@@ -145,12 +175,12 @@ export class FilledFormsAggregationService {
145
175
  // 6. Update the appointment
146
176
  await appointmentRef.update(updateData);
147
177
  Logger.info(
148
- `[FilledFormsAggregationService] Successfully updated appointment ${filledDocument.appointmentId} with form info for ${filledDocument.id}`
178
+ `[FilledFormsAggregationService] Successfully updated appointment ${filledDocument.appointmentId} with form info for ${filledDocument.id}`,
149
179
  );
150
180
  } catch (error) {
151
181
  Logger.error(
152
182
  `[FilledFormsAggregationService] Error updating appointment for filled form ${filledDocument.id}:`,
153
- error
183
+ error,
154
184
  );
155
185
  throw error;
156
186
  }
@@ -164,7 +194,7 @@ export class FilledFormsAggregationService {
164
194
  */
165
195
  async handleFilledFormDelete(filledDocument: FilledDocument): Promise<void> {
166
196
  Logger.info(
167
- `[FilledFormsAggregationService] Handling DELETE for filled form: ${filledDocument.id}, appointment: ${filledDocument.appointmentId}`
197
+ `[FilledFormsAggregationService] Handling DELETE for filled form: ${filledDocument.id}, appointment: ${filledDocument.appointmentId}`,
168
198
  );
169
199
 
170
200
  try {
@@ -176,7 +206,7 @@ export class FilledFormsAggregationService {
176
206
  const appointmentDoc = await appointmentRef.get();
177
207
  if (!appointmentDoc.exists) {
178
208
  Logger.error(
179
- `[FilledFormsAggregationService] Appointment ${filledDocument.appointmentId} not found.`
209
+ `[FilledFormsAggregationService] Appointment ${filledDocument.appointmentId} not found.`,
180
210
  );
181
211
  return;
182
212
  }
@@ -189,23 +219,17 @@ export class FilledFormsAggregationService {
189
219
  // 3. Remove the form from linkedForms if it exists
190
220
  if (appointment.linkedForms && appointment.linkedForms.length > 0) {
191
221
  const formToRemove = appointment.linkedForms.find(
192
- (form) => form.formId === filledDocument.id
222
+ form => form.formId === filledDocument.id,
193
223
  );
194
224
 
195
225
  if (formToRemove) {
196
- updateData.linkedForms =
197
- admin.firestore.FieldValue.arrayRemove(formToRemove);
226
+ updateData.linkedForms = admin.firestore.FieldValue.arrayRemove(formToRemove);
198
227
  }
199
228
  }
200
229
 
201
230
  // 4. Remove the form ID from linkedFormIds
202
- if (
203
- appointment.linkedFormIds &&
204
- appointment.linkedFormIds.includes(filledDocument.id)
205
- ) {
206
- updateData.linkedFormIds = admin.firestore.FieldValue.arrayRemove(
207
- filledDocument.id
208
- );
231
+ if (appointment.linkedFormIds && appointment.linkedFormIds.includes(filledDocument.id)) {
232
+ updateData.linkedFormIds = admin.firestore.FieldValue.arrayRemove(filledDocument.id);
209
233
  }
210
234
 
211
235
  // 5. For required user forms, handle pendingUserFormsIds
@@ -231,17 +255,17 @@ export class FilledFormsAggregationService {
231
255
  updateData.updatedAt = admin.firestore.FieldValue.serverTimestamp();
232
256
  await appointmentRef.update(updateData);
233
257
  Logger.info(
234
- `[FilledFormsAggregationService] Successfully updated appointment ${filledDocument.appointmentId} after form deletion`
258
+ `[FilledFormsAggregationService] Successfully updated appointment ${filledDocument.appointmentId} after form deletion`,
235
259
  );
236
260
  } else {
237
261
  Logger.info(
238
- `[FilledFormsAggregationService] No updates needed for appointment ${filledDocument.appointmentId}`
262
+ `[FilledFormsAggregationService] No updates needed for appointment ${filledDocument.appointmentId}`,
239
263
  );
240
264
  }
241
265
  } catch (error) {
242
266
  Logger.error(
243
267
  `[FilledFormsAggregationService] Error updating appointment after form deletion for ${filledDocument.id}:`,
244
- error
268
+ error,
245
269
  );
246
270
  throw error;
247
271
  }
@@ -253,15 +277,13 @@ export class FilledFormsAggregationService {
253
277
  * @param filledDocument The newly created filled document.
254
278
  * @returns {Promise<void>}
255
279
  */
256
- async handleRequiredUserFormCreate(
257
- filledDocument: FilledDocument
258
- ): Promise<void> {
280
+ async handleRequiredUserFormCreate(filledDocument: FilledDocument): Promise<void> {
259
281
  if (!filledDocument.isUserForm || !filledDocument.isRequired) {
260
282
  return; // Only process required user forms
261
283
  }
262
284
 
263
285
  Logger.info(
264
- `[FilledFormsAggregationService] Handling new required user form creation: ${filledDocument.id}`
286
+ `[FilledFormsAggregationService] Handling new required user form creation: ${filledDocument.id}`,
265
287
  );
266
288
 
267
289
  try {
@@ -275,26 +297,24 @@ export class FilledFormsAggregationService {
275
297
  filledDocument.status === FilledDocumentStatus.SIGNED
276
298
  ) {
277
299
  Logger.info(
278
- `[FilledFormsAggregationService] Form ${filledDocument.id} is already completed/signed, not adding to pendingUserFormsIds`
300
+ `[FilledFormsAggregationService] Form ${filledDocument.id} is already completed/signed, not adding to pendingUserFormsIds`,
279
301
  );
280
302
  return;
281
303
  }
282
304
 
283
305
  // Add to pendingUserFormsIds
284
306
  await appointmentRef.update({
285
- pendingUserFormsIds: admin.firestore.FieldValue.arrayUnion(
286
- filledDocument.id
287
- ),
307
+ pendingUserFormsIds: admin.firestore.FieldValue.arrayUnion(filledDocument.id),
288
308
  updatedAt: admin.firestore.FieldValue.serverTimestamp(),
289
309
  });
290
310
 
291
311
  Logger.info(
292
- `[FilledFormsAggregationService] Successfully added form ${filledDocument.id} to pendingUserFormsIds for appointment ${filledDocument.appointmentId}`
312
+ `[FilledFormsAggregationService] Successfully added form ${filledDocument.id} to pendingUserFormsIds for appointment ${filledDocument.appointmentId}`,
293
313
  );
294
314
  } catch (error) {
295
315
  Logger.error(
296
316
  `[FilledFormsAggregationService] Error handling required user form creation for ${filledDocument.id}:`,
297
- error
317
+ error,
298
318
  );
299
319
  throw error;
300
320
  }
@@ -0,0 +1,17 @@
1
+ # Appointment Service
2
+
3
+ This service is responsible for managing appointments.
4
+
5
+ ## Methods
6
+
7
+ ### `countCompletedAppointments(patientId: string, clinicBranchId?: string, excludeClinic: boolean = true): Promise<number>`
8
+
9
+ Counts the number of completed appointments for a given patient.
10
+
11
+ - `patientId`: The ID of the patient.
12
+ - `clinicBranchId` (optional): The ID of a clinic branch to filter by.
13
+ - `excludeClinic` (optional, default: `true`):
14
+ - If `true`, it counts appointments _outside_ the specified `clinicBranchId`.
15
+ - If `false`, it counts appointments _only within_ the specified `clinicBranchId`.
16
+
17
+ If `clinicBranchId` is not provided, it counts all completed appointments for the patient across all clinics.