@blackcode_sa/metaestetics-api 1.7.12 → 1.7.13

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.7.12",
4
+ "version": "1.7.13",
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,316 @@
1
+ import * as admin from "firebase-admin";
2
+ import {
3
+ FilledDocument,
4
+ FilledDocumentStatus,
5
+ USER_FORMS_SUBCOLLECTION,
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";
15
+
16
+ /**
17
+ * @class FilledFormsAggregationService
18
+ * @description Handles aggregation tasks related to filled forms data updates.
19
+ * Updates appointment documents with LinkedFormInfo when forms are created, updated, or deleted.
20
+ */
21
+ export class FilledFormsAggregationService {
22
+ private db: admin.firestore.Firestore;
23
+
24
+ /**
25
+ * Constructor for FilledFormsAggregationService.
26
+ * @param firestore Optional Firestore instance. If not provided, it uses the default admin SDK instance.
27
+ */
28
+ constructor(firestore?: admin.firestore.Firestore) {
29
+ this.db = firestore || admin.firestore();
30
+ Logger.info("[FilledFormsAggregationService] Initialized");
31
+ }
32
+
33
+ /**
34
+ * Handles side effects when a filled form is created or updated.
35
+ * This function would typically be called by a Firestore onCreate or onUpdate trigger.
36
+ * @param filledDocument The filled document that was created or updated.
37
+ * @returns {Promise<void>}
38
+ */
39
+ async handleFilledFormCreateOrUpdate(
40
+ filledDocument: FilledDocument
41
+ ): Promise<void> {
42
+ Logger.info(
43
+ `[FilledFormsAggregationService] Handling CREATE/UPDATE for filled form: ${filledDocument.id}, appointment: ${filledDocument.appointmentId}`
44
+ );
45
+
46
+ try {
47
+ // 1. Get the appointment document
48
+ const appointmentRef = this.db
49
+ .collection(APPOINTMENTS_COLLECTION)
50
+ .doc(filledDocument.appointmentId);
51
+
52
+ const appointmentDoc = await appointmentRef.get();
53
+ if (!appointmentDoc.exists) {
54
+ Logger.error(
55
+ `[FilledFormsAggregationService] Appointment ${filledDocument.appointmentId} not found.`
56
+ );
57
+ return;
58
+ }
59
+
60
+ const appointment = appointmentDoc.data() as Appointment;
61
+
62
+ // 2. Prepare the LinkedFormInfo object
63
+ const formSubcollection = filledDocument.isUserForm
64
+ ? USER_FORMS_SUBCOLLECTION
65
+ : DOCTOR_FORMS_SUBCOLLECTION;
66
+
67
+ const linkedFormInfo: LinkedFormInfo = {
68
+ formId: filledDocument.id,
69
+ templateId: filledDocument.templateId,
70
+ templateVersion: filledDocument.templateVersion,
71
+ title: filledDocument.isUserForm
72
+ ? "User Form" // Default title if not available
73
+ : "Doctor Form", // Default title if not available
74
+ isUserForm: filledDocument.isUserForm,
75
+ isRequired: filledDocument.isRequired,
76
+ status: filledDocument.status,
77
+ path: `${APPOINTMENTS_COLLECTION}/${filledDocument.appointmentId}/${formSubcollection}/${filledDocument.id}`,
78
+ };
79
+
80
+ // Add timestamps if available
81
+ if (filledDocument.updatedAt) {
82
+ // Convert to admin.firestore.Timestamp to ensure compatibility
83
+ linkedFormInfo.submittedAt = admin.firestore.Timestamp.fromMillis(
84
+ filledDocument.updatedAt
85
+ ) as unknown as Timestamp;
86
+ }
87
+
88
+ if (
89
+ filledDocument.status === FilledDocumentStatus.COMPLETED ||
90
+ filledDocument.status === FilledDocumentStatus.SIGNED
91
+ ) {
92
+ linkedFormInfo.completedAt = admin.firestore.Timestamp.fromMillis(
93
+ filledDocument.updatedAt
94
+ ) as unknown as Timestamp;
95
+ }
96
+
97
+ // 3. Check if the appointment already has linkedForms array and this form is in it
98
+ let updateData: any = {};
99
+ let existingFormIndex = -1;
100
+
101
+ if (appointment.linkedForms && appointment.linkedForms.length > 0) {
102
+ existingFormIndex = appointment.linkedForms.findIndex(
103
+ (form) => form.formId === filledDocument.id
104
+ );
105
+ }
106
+
107
+ // 4. Update the appointment with the new/updated LinkedFormInfo
108
+ if (existingFormIndex >= 0 && appointment.linkedForms) {
109
+ // Form exists, update it using arrayRemove + arrayUnion
110
+ updateData = {
111
+ linkedForms: admin.firestore.FieldValue.arrayRemove(
112
+ appointment.linkedForms[existingFormIndex]
113
+ ),
114
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
115
+ };
116
+
117
+ // First remove the old form info
118
+ await appointmentRef.update(updateData);
119
+
120
+ // Then add the updated form info
121
+ updateData = {
122
+ linkedForms: admin.firestore.FieldValue.arrayUnion(linkedFormInfo),
123
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
124
+ };
125
+ } else {
126
+ // Form doesn't exist, add it to the array or create the array
127
+ updateData = {
128
+ linkedForms: appointment.linkedForms
129
+ ? admin.firestore.FieldValue.arrayUnion(linkedFormInfo)
130
+ : [linkedFormInfo], // If linkedForms doesn't exist, create a new array
131
+ linkedFormIds: appointment.linkedFormIds
132
+ ? admin.firestore.FieldValue.arrayUnion(filledDocument.id)
133
+ : [filledDocument.id], // If linkedFormIds doesn't exist, create a new array
134
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
135
+ };
136
+ }
137
+
138
+ // 5. Handle pendingUserFormsIds for required user forms
139
+ if (
140
+ filledDocument.isUserForm &&
141
+ filledDocument.isRequired &&
142
+ (filledDocument.status === FilledDocumentStatus.COMPLETED ||
143
+ filledDocument.status === FilledDocumentStatus.SIGNED)
144
+ ) {
145
+ // Check if the form ID is in pendingUserFormsIds
146
+ if (
147
+ appointment.pendingUserFormsIds &&
148
+ appointment.pendingUserFormsIds.includes(filledDocument.id)
149
+ ) {
150
+ // Remove the form ID from pendingUserFormsIds
151
+ updateData.pendingUserFormsIds =
152
+ admin.firestore.FieldValue.arrayRemove(filledDocument.id);
153
+ Logger.info(
154
+ `[FilledFormsAggregationService] Removing form ${filledDocument.id} from pendingUserFormsIds`
155
+ );
156
+ }
157
+ }
158
+
159
+ // 6. Update the appointment
160
+ await appointmentRef.update(updateData);
161
+ Logger.info(
162
+ `[FilledFormsAggregationService] Successfully updated appointment ${filledDocument.appointmentId} with form info for ${filledDocument.id}`
163
+ );
164
+ } catch (error) {
165
+ Logger.error(
166
+ `[FilledFormsAggregationService] Error updating appointment for filled form ${filledDocument.id}:`,
167
+ error
168
+ );
169
+ throw error;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Handles side effects when a filled form is deleted.
175
+ * This function would typically be called by a Firestore onDelete trigger.
176
+ * @param filledDocument The filled document that was deleted.
177
+ * @returns {Promise<void>}
178
+ */
179
+ async handleFilledFormDelete(filledDocument: FilledDocument): Promise<void> {
180
+ Logger.info(
181
+ `[FilledFormsAggregationService] Handling DELETE for filled form: ${filledDocument.id}, appointment: ${filledDocument.appointmentId}`
182
+ );
183
+
184
+ try {
185
+ // 1. Get the appointment document
186
+ const appointmentRef = this.db
187
+ .collection(APPOINTMENTS_COLLECTION)
188
+ .doc(filledDocument.appointmentId);
189
+
190
+ const appointmentDoc = await appointmentRef.get();
191
+ if (!appointmentDoc.exists) {
192
+ Logger.error(
193
+ `[FilledFormsAggregationService] Appointment ${filledDocument.appointmentId} not found.`
194
+ );
195
+ return;
196
+ }
197
+
198
+ const appointment = appointmentDoc.data() as Appointment;
199
+
200
+ // 2. Prepare the update data
201
+ let updateData: any = {};
202
+
203
+ // 3. Remove the form from linkedForms if it exists
204
+ if (appointment.linkedForms && appointment.linkedForms.length > 0) {
205
+ const formToRemove = appointment.linkedForms.find(
206
+ (form) => form.formId === filledDocument.id
207
+ );
208
+
209
+ if (formToRemove) {
210
+ updateData.linkedForms =
211
+ admin.firestore.FieldValue.arrayRemove(formToRemove);
212
+ }
213
+ }
214
+
215
+ // 4. Remove the form ID from linkedFormIds
216
+ if (
217
+ appointment.linkedFormIds &&
218
+ appointment.linkedFormIds.includes(filledDocument.id)
219
+ ) {
220
+ updateData.linkedFormIds = admin.firestore.FieldValue.arrayRemove(
221
+ filledDocument.id
222
+ );
223
+ }
224
+
225
+ // 5. For required user forms, handle pendingUserFormsIds
226
+ if (filledDocument.isUserForm && filledDocument.isRequired) {
227
+ if (
228
+ filledDocument.status !== FilledDocumentStatus.COMPLETED &&
229
+ filledDocument.status !== FilledDocumentStatus.SIGNED
230
+ ) {
231
+ // If the form was not completed or signed, ensure it's added back to pending
232
+ if (
233
+ !appointment.pendingUserFormsIds ||
234
+ !appointment.pendingUserFormsIds.includes(filledDocument.id)
235
+ ) {
236
+ updateData.pendingUserFormsIds = appointment.pendingUserFormsIds
237
+ ? admin.firestore.FieldValue.arrayUnion(filledDocument.id)
238
+ : [filledDocument.id];
239
+ }
240
+ }
241
+ }
242
+
243
+ // 6. Only update if there's something to update
244
+ if (Object.keys(updateData).length > 0) {
245
+ updateData.updatedAt = admin.firestore.FieldValue.serverTimestamp();
246
+ await appointmentRef.update(updateData);
247
+ Logger.info(
248
+ `[FilledFormsAggregationService] Successfully updated appointment ${filledDocument.appointmentId} after form deletion`
249
+ );
250
+ } else {
251
+ Logger.info(
252
+ `[FilledFormsAggregationService] No updates needed for appointment ${filledDocument.appointmentId}`
253
+ );
254
+ }
255
+ } catch (error) {
256
+ Logger.error(
257
+ `[FilledFormsAggregationService] Error updating appointment after form deletion for ${filledDocument.id}:`,
258
+ error
259
+ );
260
+ throw error;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Updates the appointment's pendingUserFormsIds for a new required user form.
266
+ * This should be called when a required user form is first created.
267
+ * @param filledDocument The newly created filled document.
268
+ * @returns {Promise<void>}
269
+ */
270
+ async handleRequiredUserFormCreate(
271
+ filledDocument: FilledDocument
272
+ ): Promise<void> {
273
+ if (!filledDocument.isUserForm || !filledDocument.isRequired) {
274
+ return; // Only process required user forms
275
+ }
276
+
277
+ Logger.info(
278
+ `[FilledFormsAggregationService] Handling new required user form creation: ${filledDocument.id}`
279
+ );
280
+
281
+ try {
282
+ const appointmentRef = this.db
283
+ .collection(APPOINTMENTS_COLLECTION)
284
+ .doc(filledDocument.appointmentId);
285
+
286
+ // Skip adding to pendingUserFormsIds if already completed/signed
287
+ if (
288
+ filledDocument.status === FilledDocumentStatus.COMPLETED ||
289
+ filledDocument.status === FilledDocumentStatus.SIGNED
290
+ ) {
291
+ Logger.info(
292
+ `[FilledFormsAggregationService] Form ${filledDocument.id} is already completed/signed, not adding to pendingUserFormsIds`
293
+ );
294
+ return;
295
+ }
296
+
297
+ // Add to pendingUserFormsIds
298
+ await appointmentRef.update({
299
+ pendingUserFormsIds: admin.firestore.FieldValue.arrayUnion(
300
+ filledDocument.id
301
+ ),
302
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
303
+ });
304
+
305
+ Logger.info(
306
+ `[FilledFormsAggregationService] Successfully added form ${filledDocument.id} to pendingUserFormsIds for appointment ${filledDocument.appointmentId}`
307
+ );
308
+ } catch (error) {
309
+ Logger.error(
310
+ `[FilledFormsAggregationService] Error handling required user form creation for ${filledDocument.id}:`,
311
+ error
312
+ );
313
+ throw error;
314
+ }
315
+ }
316
+ }
@@ -22,6 +22,8 @@ import { PractitionerAggregationService } from "./aggregation/practitioner/pract
22
22
  import { ProcedureAggregationService } from "./aggregation/procedure/procedure.aggregation.service";
23
23
  import { PatientAggregationService } from "./aggregation/patient/patient.aggregation.service";
24
24
  import { AppointmentAggregationService } from "./aggregation/appointment/appointment.aggregation.service";
25
+ import { FilledFormsAggregationService } from "./aggregation/forms/filled-forms.aggregation.service";
26
+ import { ReviewsAggregationService } from "./aggregation/reviews/reviews.aggregation.service";
25
27
 
26
28
  // Import mailing services
27
29
  import { BaseMailingService } from "./mailing/base.mailing.service";
@@ -50,6 +52,8 @@ export type { DoctorInfo } from "../types/clinic";
50
52
  export type { Procedure, ProcedureSummaryInfo } from "../types/procedure";
51
53
  export type { PatientProfile as Patient } from "../types/patient";
52
54
  export type { Appointment } from "../types/appointment";
55
+ export type { FilledDocument } from "../types/documentation-templates";
56
+ export type { Review } from "../types/reviews";
53
57
 
54
58
  // Re-export enums/consts
55
59
  export {
@@ -70,6 +74,8 @@ export {
70
74
  PatientAggregationService,
71
75
  AppointmentAggregationService,
72
76
  PatientRequirementsAdminService,
77
+ FilledFormsAggregationService,
78
+ ReviewsAggregationService,
73
79
  };
74
80
 
75
81
  // Export mailing services
@@ -128,7 +134,8 @@ export * from "./mailing/appointment/appointment.mailing.service";
128
134
  export * from "./notifications/notifications.admin";
129
135
  export * from "./requirements/patient-requirements.admin.service";
130
136
  export * from "./aggregation/appointment/appointment.aggregation.service";
131
-
137
+ export * from "./aggregation/forms/filled-forms.aggregation.service";
138
+ export * from "./aggregation/reviews/reviews.aggregation.service";
132
139
  // Re-export types that Cloud Functions might need direct access to
133
140
  export * from "../types/appointment";
134
141
  // Add other types as needed