@blackcode_sa/metaestetics-api 1.6.4 → 1.6.5

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.
@@ -93,100 +93,6 @@ export class AppointmentService extends BaseService {
93
93
  this.functions = getFunctions(app, "europe-west6"); // Initialize Firebase Functions with the correct region
94
94
  }
95
95
 
96
- /**
97
- * Test method using the callable function version of getAvailableBookingSlots
98
- * For development and testing purposes only - not for production use
99
- *
100
- * @param clinicId ID of the clinic
101
- * @param practitionerId ID of the practitioner
102
- * @param procedureId ID of the procedure
103
- * @param startDate Start date of the time range to check
104
- * @param endDate End date of the time range to check
105
- * @returns Test result from the callable function
106
- */
107
- async testGetAvailableBookingSlots(
108
- clinicId: string,
109
- practitionerId: string,
110
- procedureId: string,
111
- startDate: Date,
112
- endDate: Date
113
- ): Promise<any> {
114
- try {
115
- console.log(
116
- `[APPOINTMENT_SERVICE] Testing callable function for clinic: ${clinicId}, practitioner: ${practitionerId}, procedure: ${procedureId}`
117
- );
118
-
119
- // Get the callable function
120
- const getAvailableBookingSlotsCallable = httpsCallable(
121
- this.functions,
122
- "getAvailableBookingSlots"
123
- );
124
-
125
- // Call the function with the required parameters
126
- const result = await getAvailableBookingSlotsCallable({
127
- clinicId,
128
- practitionerId,
129
- procedureId,
130
- timeframe: {
131
- start: startDate.getTime(),
132
- end: endDate.getTime(),
133
- },
134
- });
135
-
136
- console.log(
137
- "[APPOINTMENT_SERVICE] Callable function test result:",
138
- result.data
139
- );
140
-
141
- return result.data;
142
- } catch (error) {
143
- console.error(
144
- "[APPOINTMENT_SERVICE] Error testing callable function:",
145
- error
146
- );
147
- throw error;
148
- }
149
- }
150
-
151
- /**
152
- * Gets available booking slots for a specific clinic, practitioner, and procedure.
153
- *
154
- * @param clinicId ID of the clinic
155
- * @param practitionerId ID of the practitioner
156
- * @param procedureId ID of the procedure
157
- * @param startDate Start date of the time range to check
158
- * @param endDate End date of the time range to check
159
- * @returns Array of available booking slots
160
- */
161
- async getAvailableBookingSlots(
162
- clinicId: string,
163
- practitionerId: string,
164
- procedureId: string,
165
- startDate: Date,
166
- endDate: Date
167
- ): Promise<AvailableSlot[]> {
168
- try {
169
- console.log(
170
- `[APPOINTMENT_SERVICE] Getting available booking slots for clinic: ${clinicId}, practitioner: ${practitionerId}, procedure: ${procedureId}`
171
- );
172
-
173
- // Just call our HTTP implementation since the callable function isn't working in the browser
174
- return this.getAvailableBookingSlotsHttp(
175
- clinicId,
176
- practitionerId,
177
- procedureId,
178
- startDate,
179
- endDate
180
- );
181
- } catch (error) {
182
- console.error(
183
- "[APPOINTMENT_SERVICE] Error getting available booking slots:",
184
- error
185
- );
186
- throw error;
187
- }
188
- }
189
-
190
96
  /**
191
97
  * Gets available booking slots for a specific clinic, practitioner, and procedure using HTTP request.
192
98
  * This is an alternative implementation using direct HTTP request instead of callable function.
@@ -328,42 +234,108 @@ export class AppointmentService extends BaseService {
328
234
  }
329
235
 
330
236
  /**
331
- * Creates a new appointment.
237
+ * Creates an appointment via the Cloud Function orchestrateAppointmentCreation
332
238
  *
333
- * @param data Data needed to create the appointment
239
+ * @param data - CreateAppointmentData object
334
240
  * @returns The created appointment
335
241
  */
336
- async createAppointment(data: CreateAppointmentData): Promise<Appointment> {
242
+ async createAppointmentHttp(
243
+ data: CreateAppointmentData
244
+ ): Promise<Appointment> {
337
245
  try {
338
- console.log("[APPOINTMENT_SERVICE] Creating appointment");
246
+ console.log(
247
+ "[APPOINTMENT_SERVICE] Creating appointment via cloud function"
248
+ );
339
249
 
340
- // Validate input data
341
- const validatedData = await createAppointmentSchema.parseAsync(data);
250
+ // Get the authenticated user's ID token
251
+ const currentUser = this.auth.currentUser;
252
+ if (!currentUser) {
253
+ throw new Error("User must be authenticated to create an appointment");
254
+ }
255
+ const idToken = await currentUser.getIdToken();
342
256
 
343
- // Fetch all required aggregated information
344
- const aggregatedInfo = await fetchAggregatedInfoUtil(
345
- this.db,
346
- validatedData.clinicBranchId,
347
- validatedData.practitionerId,
348
- validatedData.patientId,
349
- validatedData.procedureId
350
- );
257
+ // Construct the function URL for the Express app endpoint
258
+ const functionUrl = `https://europe-west6-metaestetics.cloudfunctions.net/bookingApi/orchestrateAppointmentCreation`;
351
259
 
352
- // Create the appointment using the utility function
353
- const appointment = await createAppointmentUtil(
354
- this.db,
355
- validatedData as CreateAppointmentData,
356
- aggregatedInfo,
357
- this.generateId.bind(this)
260
+ // Prepare request data for the Cloud Function
261
+ // Map CreateAppointmentData to OrchestrateAppointmentCreationData format
262
+ const requestData = {
263
+ patientId: data.patientId,
264
+ procedureId: data.procedureId,
265
+ appointmentStartTime: data.appointmentStartTime.toMillis
266
+ ? data.appointmentStartTime.toMillis()
267
+ : new Date(data.appointmentStartTime as any).getTime(),
268
+ appointmentEndTime: data.appointmentEndTime.toMillis
269
+ ? data.appointmentEndTime.toMillis()
270
+ : new Date(data.appointmentEndTime as any).getTime(),
271
+ patientNotes: data.patientNotes || null,
272
+ };
273
+
274
+ console.log(
275
+ `[APPOINTMENT_SERVICE] Making fetch request to ${functionUrl}`
358
276
  );
359
277
 
278
+ // Make the HTTP request with expanded CORS options for browser
279
+ const response = await fetch(functionUrl, {
280
+ method: "POST",
281
+ mode: "cors",
282
+ cache: "no-cache",
283
+ credentials: "omit",
284
+ headers: {
285
+ "Content-Type": "application/json",
286
+ Authorization: `Bearer ${idToken}`,
287
+ },
288
+ redirect: "follow",
289
+ referrerPolicy: "no-referrer",
290
+ body: JSON.stringify(requestData),
291
+ });
292
+
360
293
  console.log(
361
- `[APPOINTMENT_SERVICE] Appointment created with ID: ${appointment.id}`
294
+ `[APPOINTMENT_SERVICE] Received response ${response.status}: ${response.statusText}`
362
295
  );
363
296
 
364
- return appointment;
297
+ // Check if the request was successful
298
+ if (!response.ok) {
299
+ const errorText = await response.text();
300
+ console.error(
301
+ `[APPOINTMENT_SERVICE] Error response details: ${errorText}`
302
+ );
303
+ throw new Error(
304
+ `Failed to create appointment: ${response.status} ${response.statusText} - ${errorText}`
305
+ );
306
+ }
307
+
308
+ // Parse the response
309
+ const result = await response.json();
310
+
311
+ if (!result.success) {
312
+ throw new Error(result.error || "Failed to create appointment");
313
+ }
314
+
315
+ // If the backend returns the full appointment data
316
+ if (result.appointmentData) {
317
+ console.log(
318
+ `[APPOINTMENT_SERVICE] Appointment created with ID: ${result.appointmentId}`
319
+ );
320
+ return result.appointmentData;
321
+ }
322
+
323
+ // If only the ID is returned, fetch the complete appointment
324
+ const createdAppointment = await this.getAppointmentById(
325
+ result.appointmentId
326
+ );
327
+ if (!createdAppointment) {
328
+ throw new Error(
329
+ `Failed to retrieve created appointment with ID: ${result.appointmentId}`
330
+ );
331
+ }
332
+
333
+ return createdAppointment;
365
334
  } catch (error) {
366
- console.error("[APPOINTMENT_SERVICE] Error creating appointment:", error);
335
+ console.error(
336
+ "[APPOINTMENT_SERVICE] Error creating appointment via cloud function:",
337
+ error
338
+ );
367
339
  throw error;
368
340
  }
369
341
  }
@@ -1008,50 +980,6 @@ export class AppointmentService extends BaseService {
1008
980
  return this.updateAppointment(appointmentId, updateData);
1009
981
  }
1010
982
 
1011
- /**
1012
- * Marks pre-procedure requirements as completed.
1013
- *
1014
- * @param appointmentId ID of the appointment
1015
- * @param requirementIds IDs of the requirements to mark as completed
1016
- * @returns The updated appointment
1017
- */
1018
- async completePreRequirements(
1019
- appointmentId: string,
1020
- requirementIds: string[]
1021
- ): Promise<Appointment> {
1022
- console.log(
1023
- `[APPOINTMENT_SERVICE] Marking pre-requirements as completed for appointment: ${appointmentId}`
1024
- );
1025
-
1026
- const updateData: UpdateAppointmentData = {
1027
- completedPreRequirements: requirementIds,
1028
- };
1029
-
1030
- return this.updateAppointment(appointmentId, updateData);
1031
- }
1032
-
1033
- /**
1034
- * Marks post-procedure requirements as completed.
1035
- *
1036
- * @param appointmentId ID of the appointment
1037
- * @param requirementIds IDs of the requirements to mark as completed
1038
- * @returns The updated appointment
1039
- */
1040
- async completePostRequirements(
1041
- appointmentId: string,
1042
- requirementIds: string[]
1043
- ): Promise<Appointment> {
1044
- console.log(
1045
- `[APPOINTMENT_SERVICE] Marking post-requirements as completed for appointment: ${appointmentId}`
1046
- );
1047
-
1048
- const updateData: UpdateAppointmentData = {
1049
- completedPostRequirements: requirementIds,
1050
- };
1051
-
1052
- return this.updateAppointment(appointmentId, updateData);
1053
- }
1054
-
1055
983
  /**
1056
984
  * Updates the internal notes of an appointment.
1057
985
  *
@@ -1073,25 +1001,4 @@ export class AppointmentService extends BaseService {
1073
1001
 
1074
1002
  return this.updateAppointment(appointmentId, updateData);
1075
1003
  }
1076
-
1077
- /**
1078
- * Debug helper: Get the current user's ID token for testing purposes
1079
- * Use this token in Postman with Authorization: Bearer TOKEN
1080
- */
1081
- async getDebugToken(): Promise<string | null> {
1082
- try {
1083
- const currentUser = this.auth.currentUser;
1084
- if (!currentUser) {
1085
- console.log("[APPOINTMENT_SERVICE] No user is signed in");
1086
- return null;
1087
- }
1088
-
1089
- const idToken = await currentUser.getIdToken();
1090
- console.log("[APPOINTMENT_SERVICE] Debug token:", idToken);
1091
- return idToken;
1092
- } catch (error) {
1093
- console.error("[APPOINTMENT_SERVICE] Error getting debug token:", error);
1094
- return null;
1095
- }
1096
- }
1097
1004
  }
@@ -179,6 +179,7 @@ export class ProcedureService extends BaseService {
179
179
  technology,
180
180
  product,
181
181
  blockingConditions: technology.blockingConditions,
182
+ contraindications: technology.contraindications || [],
182
183
  treatmentBenefits: technology.benefits,
183
184
  preRequirements: technology.requirements.pre,
184
185
  postRequirements: technology.requirements.post,
@@ -178,6 +178,7 @@ export interface Appointment {
178
178
  completedPostRequirements?: string[]; // IDs of completed post-requirements
179
179
 
180
180
  /** NEW: Linked forms (consent, procedure-specific forms, etc.) */
181
+ linkedFormIds?: string[];
181
182
  linkedForms?: LinkedFormInfo[];
182
183
  pendingUserFormsIds?: string[]; // Determines if there are any user forms that are pending for this appointment, blocks the appointment from being checked in (only for user forms with isRequired = true)
183
184
 
@@ -210,7 +211,6 @@ export interface Appointment {
210
211
  * Data needed to create a new Appointment
211
212
  */
212
213
  export interface CreateAppointmentData {
213
- calendarEventId: string;
214
214
  clinicBranchId: string;
215
215
  practitionerId: string;
216
216
  patientId: string;
@@ -250,6 +250,7 @@ export interface UpdateAppointmentData {
250
250
  practitionerId?: string; // If practitioner is changed
251
251
 
252
252
  /** NEW: For updating linked forms - typically managed by dedicated methods */
253
+ linkedFormIds?: string[] | FieldValue;
253
254
  linkedForms?: LinkedFormInfo[] | FieldValue;
254
255
 
255
256
  /** NEW: For updating media items - typically managed by dedicated methods */
@@ -16,6 +16,7 @@ import { ClinicInfo } from "../profile";
16
16
  import { DoctorInfo } from "../clinic";
17
17
  import { PRACTITIONERS_COLLECTION } from "../practitioner";
18
18
  import { ProcedureReviewInfo } from "../reviews";
19
+ import type { Contraindication } from "../../backoffice/types/static/contraindication.types";
19
20
 
20
21
  /**
21
22
  * Procedure represents a specific medical procedure that can be performed by a practitioner in a clinic
@@ -26,6 +27,8 @@ export interface Procedure {
26
27
  id: string;
27
28
  /** Name of the procedure */
28
29
  name: string;
30
+ /** Photos of the procedure */
31
+ photos?: string[];
29
32
  /** Detailed description of the procedure */
30
33
  description: string;
31
34
  /** Family of procedures this belongs to (aesthetics/surgery) */
@@ -50,6 +53,8 @@ export interface Procedure {
50
53
  blockingConditions: BlockingCondition[];
51
54
  /** Treatment benefits of this procedure */
52
55
  treatmentBenefits: TreatmentBenefit[];
56
+ /** Contraindications of this procedure */
57
+ contraindications: Contraindication[];
53
58
  /** Pre-procedure requirements */
54
59
  preRequirements: Requirement[];
55
60
  /** Post-procedure requirements */
@@ -93,6 +98,7 @@ export interface CreateProcedureData {
93
98
  duration: number;
94
99
  practitionerId: string;
95
100
  clinicBranchId: string;
101
+ photos?: string[];
96
102
  }
97
103
 
98
104
  /**
@@ -112,6 +118,7 @@ export interface UpdateProcedureData {
112
118
  technologyId?: string;
113
119
  productId?: string;
114
120
  clinicBranchId?: string;
121
+ photos?: string[];
115
122
  }
116
123
 
117
124
  /**
@@ -138,9 +138,6 @@ export const finalizedDetailsSchema = z.object({
138
138
  */
139
139
  export const createAppointmentSchema = z
140
140
  .object({
141
- calendarEventId: z
142
- .string()
143
- .min(MIN_STRING_LENGTH, "Calendar event ID is required"),
144
141
  clinicBranchId: z
145
142
  .string()
146
143
  .min(MIN_STRING_LENGTH, "Clinic branch ID is required"),
@@ -221,6 +218,7 @@ export const updateAppointmentSchema = z
221
218
  completedPostRequirements: z
222
219
  .union([z.array(z.string()), z.any()])
223
220
  .optional(),
221
+ linkedFormIds: z.union([z.array(z.string()), z.any()]).optional(),
224
222
  pendingUserFormsIds: z.union([z.array(z.string()), z.any()]).optional(),
225
223
  appointmentStartTime: z
226
224
  .any()
@@ -24,6 +24,7 @@ export const createProcedureSchema = z.object({
24
24
  duration: z.number().min(1).max(480), // Max 8 hours
25
25
  practitionerId: z.string().min(1),
26
26
  clinicBranchId: z.string().min(1),
27
+ photos: z.array(z.string()).optional(),
27
28
  });
28
29
 
29
30
  /**
@@ -43,6 +44,7 @@ export const updateProcedureSchema = z.object({
43
44
  technologyId: z.string().optional(),
44
45
  productId: z.string().optional(),
45
46
  clinicBranchId: z.string().optional(),
47
+ photos: z.array(z.string()).optional(),
46
48
  });
47
49
 
48
50
  /**
@@ -55,6 +57,7 @@ export const procedureSchema = createProcedureSchema.extend({
55
57
  technology: z.any(), // We'll validate the full technology object separately
56
58
  product: z.any(), // We'll validate the full product object separately
57
59
  blockingConditions: z.array(z.any()), // We'll validate blocking conditions separately
60
+ contraindications: z.array(z.any()), // We'll validate contraindications separately
58
61
  treatmentBenefits: z.array(z.any()), // We'll validate treatment benefits separately
59
62
  preRequirements: z.array(z.any()), // We'll validate requirements separately
60
63
  postRequirements: z.array(z.any()), // We'll validate requirements separately