@blackcode_sa/metaestetics-api 1.12.8 → 1.12.11
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/admin/index.js +30 -9
- package/dist/admin/index.mjs +30 -9
- package/dist/index.d.mts +49 -32
- package/dist/index.d.ts +49 -32
- package/dist/index.js +376 -582
- package/dist/index.mjs +390 -597
- package/package.json +1 -1
- package/src/admin/aggregation/forms/README.md +13 -0
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +71 -51
- package/src/services/appointment/README.md +17 -0
- package/src/services/appointment/appointment.service.ts +233 -386
- package/src/services/auth/auth.service.ts +422 -466
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|