@blackcode_sa/metaestetics-api 1.12.33 → 1.12.35

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.33",
4
+ "version": "1.12.35",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -15,7 +15,7 @@ import {
15
15
  startAfter,
16
16
  QueryConstraint,
17
17
  DocumentSnapshot,
18
- } from "firebase/firestore";
18
+ } from 'firebase/firestore';
19
19
  import {
20
20
  Appointment,
21
21
  AppointmentStatus,
@@ -24,25 +24,18 @@ import {
24
24
  APPOINTMENTS_COLLECTION,
25
25
  SearchAppointmentsParams,
26
26
  PaymentStatus,
27
- } from "../../../types/appointment";
28
- import { CalendarEvent, CALENDAR_COLLECTION } from "../../../types/calendar";
29
- import { ProcedureSummaryInfo } from "../../../types/procedure";
30
- import {
31
- ClinicInfo,
32
- PatientProfileInfo,
33
- PractitionerProfileInfo,
34
- } from "../../../types/profile";
35
- import { BlockingCondition } from "../../../backoffice/types/static/blocking-condition.types";
36
- import { Requirement } from "../../../backoffice/types/requirement.types";
37
- import { PRACTITIONERS_COLLECTION } from "../../../types/practitioner";
38
- import { CLINICS_COLLECTION } from "../../../types/clinic";
39
- import { PATIENTS_COLLECTION } from "../../../types/patient";
40
- import { PROCEDURES_COLLECTION } from "../../../types/procedure";
41
- import {
42
- Technology,
43
- TECHNOLOGIES_COLLECTION,
44
- } from "../../../backoffice/types/technology.types";
45
- import type { ContraindicationDynamic } from "../../../backoffice";
27
+ } from '../../../types/appointment';
28
+ import { CalendarEvent, CALENDAR_COLLECTION } from '../../../types/calendar';
29
+ import { ProcedureSummaryInfo } from '../../../types/procedure';
30
+ import { ClinicInfo, PatientProfileInfo, PractitionerProfileInfo } from '../../../types/profile';
31
+ import { BlockingCondition } from '../../../backoffice/types/static/blocking-condition.types';
32
+ import { Requirement } from '../../../backoffice/types/requirement.types';
33
+ import { PRACTITIONERS_COLLECTION } from '../../../types/practitioner';
34
+ import { CLINICS_COLLECTION } from '../../../types/clinic';
35
+ import { PATIENTS_COLLECTION } from '../../../types/patient';
36
+ import { PROCEDURES_COLLECTION } from '../../../types/procedure';
37
+ import { Technology, TECHNOLOGIES_COLLECTION } from '../../../backoffice/types/technology.types';
38
+ import type { ContraindicationDynamic } from '../../../backoffice';
46
39
 
47
40
  /**
48
41
  * Fetches all the necessary information for an appointment by IDs.
@@ -59,7 +52,7 @@ export async function fetchAggregatedInfoUtil(
59
52
  clinicId: string,
60
53
  practitionerId: string,
61
54
  patientId: string,
62
- procedureId: string
55
+ procedureId: string,
63
56
  ): Promise<{
64
57
  clinicInfo: ClinicInfo;
65
58
  practitionerInfo: PractitionerProfileInfo;
@@ -72,13 +65,12 @@ export async function fetchAggregatedInfoUtil(
72
65
  }> {
73
66
  try {
74
67
  // Fetch all data in parallel for efficiency
75
- const [clinicDoc, practitionerDoc, patientDoc, procedureDoc] =
76
- await Promise.all([
77
- getDoc(doc(db, CLINICS_COLLECTION, clinicId)),
78
- getDoc(doc(db, PRACTITIONERS_COLLECTION, practitionerId)),
79
- getDoc(doc(db, PATIENTS_COLLECTION, patientId)),
80
- getDoc(doc(db, PROCEDURES_COLLECTION, procedureId)),
81
- ]);
68
+ const [clinicDoc, practitionerDoc, patientDoc, procedureDoc] = await Promise.all([
69
+ getDoc(doc(db, CLINICS_COLLECTION, clinicId)),
70
+ getDoc(doc(db, PRACTITIONERS_COLLECTION, practitionerId)),
71
+ getDoc(doc(db, PATIENTS_COLLECTION, patientId)),
72
+ getDoc(doc(db, PROCEDURES_COLLECTION, procedureId)),
73
+ ]);
82
74
 
83
75
  // Check if all required entities exist
84
76
  if (!clinicDoc.exists()) {
@@ -102,7 +94,7 @@ export async function fetchAggregatedInfoUtil(
102
94
  // Extract relevant info for ClinicInfo
103
95
  const clinicInfo: ClinicInfo = {
104
96
  id: clinicId,
105
- featuredPhoto: clinicData.featuredPhotos?.[0] || "",
97
+ featuredPhoto: clinicData.featuredPhotos?.[0] || '',
106
98
  name: clinicData.name,
107
99
  description: clinicData.description || null,
108
100
  location: clinicData.location,
@@ -113,10 +105,10 @@ export async function fetchAggregatedInfoUtil(
113
105
  const practitionerInfo: PractitionerProfileInfo = {
114
106
  id: practitionerId,
115
107
  practitionerPhoto: practitionerData.basicInfo?.profileImageUrl || null,
116
- name: `${practitionerData.basicInfo?.firstName || ""} ${
117
- practitionerData.basicInfo?.lastName || ""
108
+ name: `${practitionerData.basicInfo?.firstName || ''} ${
109
+ practitionerData.basicInfo?.lastName || ''
118
110
  }`.trim(),
119
- email: practitionerData.basicInfo?.email || "",
111
+ email: practitionerData.basicInfo?.email || '',
120
112
  phone: practitionerData.basicInfo?.phoneNumber || null,
121
113
  certification: practitionerData.certification,
122
114
  };
@@ -125,11 +117,11 @@ export async function fetchAggregatedInfoUtil(
125
117
  // Note: This may need adjustment depending on how patient data is structured
126
118
  const patientInfo: PatientProfileInfo = {
127
119
  id: patientId,
128
- fullName: patientData.displayName || "",
129
- email: patientData.email || "",
120
+ fullName: patientData.displayName || '',
121
+ email: patientData.email || '',
130
122
  phone: patientData.phoneNumber || null,
131
123
  dateOfBirth: patientData.dateOfBirth || Timestamp.now(),
132
- gender: patientData.gender || "other",
124
+ gender: patientData.gender || 'other',
133
125
  };
134
126
 
135
127
  // Extract procedureInfo from the procedure document
@@ -138,13 +130,13 @@ export async function fetchAggregatedInfoUtil(
138
130
  id: procedureId,
139
131
  name: procedureData.name,
140
132
  description: procedureData.description,
141
- photo: procedureData.photo || "",
133
+ photo: procedureData.photo || '',
142
134
  family: procedureData.family,
143
- categoryName: procedureData.category?.name || "",
144
- subcategoryName: procedureData.subcategory?.name || "",
145
- technologyName: procedureData.technology?.name || "",
146
- brandName: procedureData.product?.brand || "",
147
- productName: procedureData.product?.name || "",
135
+ categoryName: procedureData.category?.name || '',
136
+ subcategoryName: procedureData.subcategory?.name || '',
137
+ technologyName: procedureData.technology?.name || '',
138
+ brandName: procedureData.product?.brand || '',
139
+ productName: procedureData.product?.name || '',
148
140
  price: procedureData.price || 0,
149
141
  pricingMeasure: procedureData.pricingMeasure,
150
142
  currency: procedureData.currency,
@@ -156,7 +148,7 @@ export async function fetchAggregatedInfoUtil(
156
148
  };
157
149
 
158
150
  // Fetch the technology document to get procedure requirements
159
- let technologyId = "";
151
+ let technologyId = '';
160
152
  if (procedureData.technology?.id) {
161
153
  technologyId = procedureData.technology.id;
162
154
  }
@@ -168,9 +160,7 @@ export async function fetchAggregatedInfoUtil(
168
160
 
169
161
  // If we have a technology ID, fetch its details
170
162
  if (technologyId) {
171
- const technologyDoc = await getDoc(
172
- doc(db, TECHNOLOGIES_COLLECTION, technologyId)
173
- );
163
+ const technologyDoc = await getDoc(doc(db, TECHNOLOGIES_COLLECTION, technologyId));
174
164
  if (technologyDoc.exists()) {
175
165
  const technologyData = technologyDoc.data() as Technology;
176
166
 
@@ -199,7 +189,7 @@ export async function fetchAggregatedInfoUtil(
199
189
  postProcedureRequirements,
200
190
  };
201
191
  } catch (error) {
202
- console.error("Error fetching aggregated info:", error);
192
+ console.error('Error fetching aggregated info:', error);
203
193
  throw error;
204
194
  }
205
195
  }
@@ -304,7 +294,7 @@ export async function fetchAggregatedInfoUtil(
304
294
  export async function updateAppointmentUtil(
305
295
  db: Firestore,
306
296
  appointmentId: string,
307
- data: UpdateAppointmentData
297
+ data: UpdateAppointmentData,
308
298
  ): Promise<Appointment> {
309
299
  try {
310
300
  const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
@@ -317,62 +307,46 @@ export async function updateAppointmentUtil(
317
307
  const currentAppointment = appointmentDoc.data() as Appointment;
318
308
 
319
309
  // Handle requirement completion tracking
320
- let completedPreRequirements =
321
- currentAppointment.completedPreRequirements || [];
322
- let completedPostRequirements =
323
- currentAppointment.completedPostRequirements || [];
310
+ let completedPreRequirements = currentAppointment.completedPreRequirements || [];
311
+ let completedPostRequirements = currentAppointment.completedPostRequirements || [];
324
312
 
325
313
  if (data.completedPreRequirements) {
326
314
  // Validate that all IDs exist in the pre-requirements
327
- const validPreReqIds = currentAppointment.preProcedureRequirements.map(
328
- (req) => req.id
329
- );
315
+ const validPreReqIds = currentAppointment.preProcedureRequirements.map(req => req.id);
330
316
 
331
317
  // Only perform validation and merging if the input is an array
332
318
  if (Array.isArray(data.completedPreRequirements)) {
333
319
  const invalidPreReqIds = data.completedPreRequirements.filter(
334
- (id) => !validPreReqIds.includes(id)
320
+ id => !validPreReqIds.includes(id),
335
321
  );
336
322
 
337
323
  if (invalidPreReqIds.length > 0) {
338
- throw new Error(
339
- `Invalid pre-requirement IDs: ${invalidPreReqIds.join(", ")}`
340
- );
324
+ throw new Error(`Invalid pre-requirement IDs: ${invalidPreReqIds.join(', ')}`);
341
325
  }
342
326
 
343
327
  // Update the completed pre-requirements
344
328
  completedPreRequirements = [
345
- ...new Set([
346
- ...completedPreRequirements,
347
- ...data.completedPreRequirements,
348
- ]),
329
+ ...new Set([...completedPreRequirements, ...data.completedPreRequirements]),
349
330
  ];
350
331
  }
351
332
  }
352
333
 
353
334
  if (data.completedPostRequirements) {
354
335
  // Validate that all IDs exist in the post-requirements
355
- const validPostReqIds = currentAppointment.postProcedureRequirements.map(
356
- (req) => req.id
357
- );
336
+ const validPostReqIds = currentAppointment.postProcedureRequirements.map(req => req.id);
358
337
 
359
338
  if (Array.isArray(data.completedPostRequirements)) {
360
339
  const invalidPostReqIds = data.completedPostRequirements.filter(
361
- (id) => !validPostReqIds.includes(id)
340
+ id => !validPostReqIds.includes(id),
362
341
  );
363
342
 
364
343
  if (invalidPostReqIds.length > 0) {
365
- throw new Error(
366
- `Invalid post-requirement IDs: ${invalidPostReqIds.join(", ")}`
367
- );
344
+ throw new Error(`Invalid post-requirement IDs: ${invalidPostReqIds.join(', ')}`);
368
345
  }
369
346
 
370
347
  // Update the completed post-requirements
371
348
  completedPostRequirements = [
372
- ...new Set([
373
- ...completedPostRequirements,
374
- ...data.completedPostRequirements,
375
- ]),
349
+ ...new Set([...completedPostRequirements, ...data.completedPostRequirements]),
376
350
  ];
377
351
  }
378
352
  }
@@ -390,7 +364,7 @@ export async function updateAppointmentUtil(
390
364
  };
391
365
 
392
366
  // Remove undefined fields
393
- Object.keys(updateData).forEach((key) => {
367
+ Object.keys(updateData).forEach(key => {
394
368
  if (updateData[key] === undefined) {
395
369
  delete updateData[key];
396
370
  }
@@ -399,20 +373,13 @@ export async function updateAppointmentUtil(
399
373
  // Handle status changes
400
374
  if (data.status && data.status !== currentAppointment.status) {
401
375
  // Handle confirmation
402
- if (
403
- data.status === AppointmentStatus.CONFIRMED &&
404
- !updateData.confirmationTime
405
- ) {
376
+ if (data.status === AppointmentStatus.CONFIRMED && !updateData.confirmationTime) {
406
377
  updateData.confirmationTime = Timestamp.now();
407
378
  }
408
379
 
409
380
  // Update the related calendar event status if needed
410
381
  if (currentAppointment.calendarEventId) {
411
- await updateCalendarEventStatus(
412
- db,
413
- currentAppointment.calendarEventId,
414
- data.status
415
- );
382
+ await updateCalendarEventStatus(db, currentAppointment.calendarEventId, data.status);
416
383
  }
417
384
  }
418
385
 
@@ -422,9 +389,7 @@ export async function updateAppointmentUtil(
422
389
  // Fetch the updated appointment
423
390
  const updatedAppointmentDoc = await getDoc(appointmentRef);
424
391
  if (!updatedAppointmentDoc.exists()) {
425
- throw new Error(
426
- `Failed to retrieve updated appointment ${appointmentId}`
427
- );
392
+ throw new Error(`Failed to retrieve updated appointment ${appointmentId}`);
428
393
  }
429
394
 
430
395
  return updatedAppointmentDoc.data() as Appointment;
@@ -444,7 +409,7 @@ export async function updateAppointmentUtil(
444
409
  async function updateCalendarEventStatus(
445
410
  db: Firestore,
446
411
  calendarEventId: string,
447
- appointmentStatus: AppointmentStatus
412
+ appointmentStatus: AppointmentStatus,
448
413
  ): Promise<void> {
449
414
  try {
450
415
  const calendarEventRef = doc(db, CALENDAR_COLLECTION, calendarEventId);
@@ -459,17 +424,17 @@ async function updateCalendarEventStatus(
459
424
  let calendarStatus;
460
425
  switch (appointmentStatus) {
461
426
  case AppointmentStatus.CONFIRMED:
462
- calendarStatus = "confirmed";
427
+ calendarStatus = 'confirmed';
463
428
  break;
464
429
  case AppointmentStatus.CANCELED_PATIENT:
465
430
  case AppointmentStatus.CANCELED_CLINIC:
466
- calendarStatus = "canceled";
431
+ calendarStatus = 'canceled';
467
432
  break;
468
433
  case AppointmentStatus.RESCHEDULED_BY_CLINIC:
469
- calendarStatus = "rescheduled";
434
+ calendarStatus = 'rescheduled';
470
435
  break;
471
436
  case AppointmentStatus.COMPLETED:
472
- calendarStatus = "completed";
437
+ calendarStatus = 'completed';
473
438
  break;
474
439
  default:
475
440
  // For other states, don't update the calendar status
@@ -495,12 +460,10 @@ async function updateCalendarEventStatus(
495
460
  */
496
461
  export async function getAppointmentByIdUtil(
497
462
  db: Firestore,
498
- appointmentId: string
463
+ appointmentId: string,
499
464
  ): Promise<Appointment | null> {
500
465
  try {
501
- const appointmentDoc = await getDoc(
502
- doc(db, APPOINTMENTS_COLLECTION, appointmentId)
503
- );
466
+ const appointmentDoc = await getDoc(doc(db, APPOINTMENTS_COLLECTION, appointmentId));
504
467
 
505
468
  if (!appointmentDoc.exists()) {
506
469
  return null;
@@ -522,52 +485,44 @@ export async function getAppointmentByIdUtil(
522
485
  */
523
486
  export async function searchAppointmentsUtil(
524
487
  db: Firestore,
525
- params: SearchAppointmentsParams
488
+ params: SearchAppointmentsParams,
526
489
  ): Promise<{ appointments: Appointment[]; lastDoc: DocumentSnapshot | null }> {
527
490
  try {
528
491
  const constraints: QueryConstraint[] = [];
529
492
 
530
493
  // Add filters based on provided params
531
494
  if (params.patientId) {
532
- constraints.push(where("patientId", "==", params.patientId));
495
+ constraints.push(where('patientId', '==', params.patientId));
533
496
  }
534
497
 
535
498
  if (params.practitionerId) {
536
- constraints.push(where("practitionerId", "==", params.practitionerId));
499
+ constraints.push(where('practitionerId', '==', params.practitionerId));
537
500
  }
538
501
 
539
502
  if (params.clinicBranchId) {
540
- constraints.push(where("clinicBranchId", "==", params.clinicBranchId));
503
+ constraints.push(where('clinicBranchId', '==', params.clinicBranchId));
541
504
  }
542
505
 
543
506
  if (params.startDate) {
544
- constraints.push(
545
- where(
546
- "appointmentStartTime",
547
- ">=",
548
- Timestamp.fromDate(params.startDate)
549
- )
550
- );
507
+ constraints.push(where('appointmentStartTime', '>=', Timestamp.fromDate(params.startDate)));
551
508
  }
552
509
 
553
510
  if (params.endDate) {
554
- constraints.push(
555
- where("appointmentStartTime", "<=", Timestamp.fromDate(params.endDate))
556
- );
511
+ constraints.push(where('appointmentStartTime', '<=', Timestamp.fromDate(params.endDate)));
557
512
  }
558
513
 
559
514
  if (params.status) {
560
515
  if (Array.isArray(params.status)) {
561
516
  // If multiple statuses, use in operator
562
- constraints.push(where("status", "in", params.status));
517
+ constraints.push(where('status', 'in', params.status));
563
518
  } else {
564
519
  // Single status
565
- constraints.push(where("status", "==", params.status));
520
+ constraints.push(where('status', '==', params.status));
566
521
  }
567
522
  }
568
523
 
569
524
  // Add ordering
570
- constraints.push(orderBy("appointmentStartTime", "asc"));
525
+ constraints.push(orderBy('appointmentStartTime', 'asc'));
571
526
 
572
527
  // Add pagination if specified
573
528
  if (params.limit) {
@@ -583,19 +538,15 @@ export async function searchAppointmentsUtil(
583
538
  const querySnapshot = await getDocs(q);
584
539
 
585
540
  // Extract results
586
- const appointments = querySnapshot.docs.map(
587
- (doc) => doc.data() as Appointment
588
- );
541
+ const appointments = querySnapshot.docs.map(doc => doc.data() as Appointment);
589
542
 
590
543
  // Get last document for pagination
591
544
  const lastDoc =
592
- querySnapshot.docs.length > 0
593
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
594
- : null;
545
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
595
546
 
596
547
  return { appointments, lastDoc };
597
548
  } catch (error) {
598
- console.error("Error searching appointments:", error);
549
+ console.error('Error searching appointments:', error);
599
550
  throw error;
600
551
  }
601
552
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from '../../../types/appointment';
8
8
  import { getAppointmentOrThrow, initializeMetadata } from './zone-management.utils';
9
9
  import { PROCEDURES_COLLECTION } from '../../../types/procedure';
10
+ import { initializeFormsForExtendedProcedure, removeFormsForExtendedProcedure } from './form-initialization.utils';
10
11
 
11
12
  /**
12
13
  * Aggregates products from a procedure into appointmentProducts
@@ -134,6 +135,16 @@ export async function addExtendedProcedureUtil(
134
135
  // Create extended procedure info
135
136
  const extendedProcedureInfo = await createExtendedProcedureInfo(db, procedureId);
136
137
 
138
+ // Get procedure data for forms and products
139
+ const procedureRef = doc(db, PROCEDURES_COLLECTION, procedureId);
140
+ const procedureSnap = await getDoc(procedureRef);
141
+
142
+ if (!procedureSnap.exists()) {
143
+ throw new Error(`Procedure with ID ${procedureId} not found`);
144
+ }
145
+
146
+ const procedureData = procedureSnap.data();
147
+
137
148
  // Aggregate products
138
149
  const updatedProducts = await aggregateProductsFromProcedure(
139
150
  db,
@@ -141,6 +152,28 @@ export async function addExtendedProcedureUtil(
141
152
  metadata.appointmentProducts || []
142
153
  );
143
154
 
155
+ // Initialize forms for extended procedure
156
+ let updatedLinkedFormIds = appointment.linkedFormIds || [];
157
+ let updatedLinkedForms = appointment.linkedForms || [];
158
+ let updatedPendingUserFormsIds = appointment.pendingUserFormsIds || [];
159
+
160
+ if (procedureData.documentationTemplates && procedureData.documentationTemplates.length > 0) {
161
+ const formInitResult = await initializeFormsForExtendedProcedure(
162
+ db,
163
+ appointmentId,
164
+ procedureId,
165
+ procedureData.documentationTemplates,
166
+ appointment.patientId,
167
+ appointment.practitionerId,
168
+ appointment.clinicBranchId
169
+ );
170
+
171
+ // Merge form IDs and info
172
+ updatedLinkedFormIds = [...updatedLinkedFormIds, ...formInitResult.allLinkedFormIds];
173
+ updatedLinkedForms = [...updatedLinkedForms, ...formInitResult.initializedFormsInfo];
174
+ updatedPendingUserFormsIds = [...updatedPendingUserFormsIds, ...formInitResult.pendingUserFormsIds];
175
+ }
176
+
144
177
  // Add extended procedure
145
178
  const extendedProcedures = [...(metadata.extendedProcedures || []), extendedProcedureInfo];
146
179
 
@@ -149,6 +182,9 @@ export async function addExtendedProcedureUtil(
149
182
  await updateDoc(appointmentRef, {
150
183
  'metadata.extendedProcedures': extendedProcedures,
151
184
  'metadata.appointmentProducts': updatedProducts,
185
+ linkedFormIds: updatedLinkedFormIds,
186
+ linkedForms: updatedLinkedForms,
187
+ pendingUserFormsIds: updatedPendingUserFormsIds,
152
188
  updatedAt: serverTimestamp(),
153
189
  });
154
190
 
@@ -191,11 +227,28 @@ export async function removeExtendedProcedureUtil(
191
227
  p => p.procedureId !== procedureId
192
228
  );
193
229
 
230
+ // Remove forms associated with this procedure
231
+ const removedFormIds = await removeFormsForExtendedProcedure(db, appointmentId, procedureId);
232
+
233
+ // Update appointment form arrays
234
+ const updatedLinkedFormIds = (appointment.linkedFormIds || []).filter(
235
+ formId => !removedFormIds.includes(formId)
236
+ );
237
+ const updatedLinkedForms = (appointment.linkedForms || []).filter(
238
+ form => !removedFormIds.includes(form.formId)
239
+ );
240
+ const updatedPendingUserFormsIds = (appointment.pendingUserFormsIds || []).filter(
241
+ formId => !removedFormIds.includes(formId)
242
+ );
243
+
194
244
  // Update appointment
195
245
  const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
196
246
  await updateDoc(appointmentRef, {
197
247
  'metadata.extendedProcedures': metadata.extendedProcedures,
198
248
  'metadata.appointmentProducts': updatedProducts,
249
+ linkedFormIds: updatedLinkedFormIds,
250
+ linkedForms: updatedLinkedForms,
251
+ pendingUserFormsIds: updatedPendingUserFormsIds,
199
252
  updatedAt: serverTimestamp(),
200
253
  });
201
254