@blackcode_sa/metaestetics-api 1.12.7 → 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.
@@ -13,11 +13,12 @@ import {
13
13
  limit,
14
14
  startAfter,
15
15
  getDocs,
16
- } from "firebase/firestore";
17
- import { Auth } from "firebase/auth";
18
- import { FirebaseApp } from "firebase/app";
19
- import { Functions, getFunctions, httpsCallable } from "firebase/functions";
20
- import { BaseService } from "../base.service";
16
+ getCountFromServer,
17
+ } from 'firebase/firestore';
18
+ import { Auth } from 'firebase/auth';
19
+ import { FirebaseApp } from 'firebase/app';
20
+ import { Functions, getFunctions, httpsCallable } from 'firebase/functions';
21
+ import { BaseService } from '../base.service';
21
22
  import {
22
23
  Appointment,
23
24
  AppointmentStatus,
@@ -28,26 +29,26 @@ import {
28
29
  PatientReviewInfo,
29
30
  type CreateAppointmentHttpData,
30
31
  APPOINTMENTS_COLLECTION,
31
- } from "../../types/appointment";
32
+ } from '../../types/appointment';
32
33
  import {
33
34
  updateAppointmentSchema,
34
35
  searchAppointmentsSchema,
35
36
  rescheduleAppointmentSchema,
36
- } from "../../validations/appointment.schema";
37
+ } from '../../validations/appointment.schema';
37
38
 
38
39
  // Import other services needed (dependency injection pattern)
39
- import { CalendarServiceV2 } from "../calendar/calendar.v2.service";
40
- import { PatientService } from "../patient/patient.service";
41
- import { PractitionerService } from "../practitioner/practitioner.service";
42
- import { ClinicService } from "../clinic/clinic.service";
43
- import { FilledDocumentService } from "../documentation-templates/filled-document.service";
40
+ import { CalendarServiceV2 } from '../calendar/calendar.v2.service';
41
+ import { PatientService } from '../patient/patient.service';
42
+ import { PractitionerService } from '../practitioner/practitioner.service';
43
+ import { ClinicService } from '../clinic/clinic.service';
44
+ import { FilledDocumentService } from '../documentation-templates/filled-document.service';
44
45
 
45
46
  // Import utility functions
46
47
  import {
47
48
  updateAppointmentUtil,
48
49
  getAppointmentByIdUtil,
49
50
  searchAppointmentsUtil,
50
- } from "./utils/appointment.utils";
51
+ } from './utils/appointment.utils';
51
52
 
52
53
  /**
53
54
  * Interface for available booking slot
@@ -88,7 +89,7 @@ export class AppointmentService extends BaseService {
88
89
  patientService: PatientService,
89
90
  practitionerService: PractitionerService,
90
91
  clinicService: ClinicService,
91
- filledDocumentService: FilledDocumentService
92
+ filledDocumentService: FilledDocumentService,
92
93
  ) {
93
94
  super(db, auth, app);
94
95
  this.calendarService = calendarService;
@@ -96,7 +97,7 @@ export class AppointmentService extends BaseService {
96
97
  this.practitionerService = practitionerService;
97
98
  this.clinicService = clinicService;
98
99
  this.filledDocumentService = filledDocumentService;
99
- this.functions = getFunctions(app, "europe-west6"); // Initialize Firebase Functions with the correct region
100
+ this.functions = getFunctions(app, 'europe-west6'); // Initialize Firebase Functions with the correct region
100
101
  }
101
102
 
102
103
  /**
@@ -115,36 +116,26 @@ export class AppointmentService extends BaseService {
115
116
  practitionerId: string,
116
117
  procedureId: string,
117
118
  startDate: Date,
118
- endDate: Date
119
+ endDate: Date,
119
120
  ): Promise<AvailableSlot[]> {
120
121
  try {
121
122
  console.log(
122
- `[APPOINTMENT_SERVICE] Getting available booking slots via HTTP for clinic: ${clinicId}, practitioner: ${practitionerId}, procedure: ${procedureId}`
123
+ `[APPOINTMENT_SERVICE] Getting available booking slots via HTTP for clinic: ${clinicId}, practitioner: ${practitionerId}, procedure: ${procedureId}`,
123
124
  );
124
125
 
125
126
  // Validate input parameters
126
- if (
127
- !clinicId ||
128
- !practitionerId ||
129
- !procedureId ||
130
- !startDate ||
131
- !endDate
132
- ) {
133
- throw new Error(
134
- "Missing required parameters for booking slots calculation"
135
- );
127
+ if (!clinicId || !practitionerId || !procedureId || !startDate || !endDate) {
128
+ throw new Error('Missing required parameters for booking slots calculation');
136
129
  }
137
130
 
138
131
  if (endDate <= startDate) {
139
- throw new Error("End date must be after start date");
132
+ throw new Error('End date must be after start date');
140
133
  }
141
134
 
142
135
  // Check if user is authenticated
143
136
  const currentUser = this.auth.currentUser;
144
137
  if (!currentUser) {
145
- throw new Error(
146
- "User must be authenticated to get available booking slots"
147
- );
138
+ throw new Error('User must be authenticated to get available booking slots');
148
139
  }
149
140
 
150
141
  // Construct the function URL for the Express app endpoint
@@ -156,9 +147,7 @@ export class AppointmentService extends BaseService {
156
147
  const idToken = await currentUser.getIdToken();
157
148
 
158
149
  // Log that we're getting a token
159
- console.log(
160
- `[APPOINTMENT_SERVICE] Got user token, user ID: ${currentUser.uid}`
161
- );
150
+ console.log(`[APPOINTMENT_SERVICE] Got user token, user ID: ${currentUser.uid}`);
162
151
 
163
152
  // Alternate direct URL (if needed):
164
153
  // const functionUrl = `https://getavailablebookingslotshttp-grqala5m6a-oa.a.run.app`;
@@ -174,37 +163,33 @@ export class AppointmentService extends BaseService {
174
163
  },
175
164
  };
176
165
 
177
- console.log(
178
- `[APPOINTMENT_SERVICE] Making fetch request to ${functionUrl}`
179
- );
166
+ console.log(`[APPOINTMENT_SERVICE] Making fetch request to ${functionUrl}`);
180
167
 
181
168
  // Make the HTTP request with expanded CORS options for browser
182
169
  const response = await fetch(functionUrl, {
183
- method: "POST",
184
- mode: "cors", // Important for cross-origin requests
185
- cache: "no-cache", // Don't cache this request
186
- credentials: "omit", // Don't send cookies since we're using token auth
170
+ method: 'POST',
171
+ mode: 'cors', // Important for cross-origin requests
172
+ cache: 'no-cache', // Don't cache this request
173
+ credentials: 'omit', // Don't send cookies since we're using token auth
187
174
  headers: {
188
- "Content-Type": "application/json",
175
+ 'Content-Type': 'application/json',
189
176
  Authorization: `Bearer ${idToken}`,
190
177
  },
191
- redirect: "follow",
192
- referrerPolicy: "no-referrer",
178
+ redirect: 'follow',
179
+ referrerPolicy: 'no-referrer',
193
180
  body: JSON.stringify(requestData),
194
181
  });
195
182
 
196
183
  console.log(
197
- `[APPOINTMENT_SERVICE] Received response ${response.status}: ${response.statusText}`
184
+ `[APPOINTMENT_SERVICE] Received response ${response.status}: ${response.statusText}`,
198
185
  );
199
186
 
200
187
  // Check if the request was successful
201
188
  if (!response.ok) {
202
189
  const errorText = await response.text();
203
- console.error(
204
- `[APPOINTMENT_SERVICE] Error response details: ${errorText}`
205
- );
190
+ console.error(`[APPOINTMENT_SERVICE] Error response details: ${errorText}`);
206
191
  throw new Error(
207
- `Failed to get available booking slots: ${response.status} ${response.statusText} - ${errorText}`
192
+ `Failed to get available booking slots: ${response.status} ${response.statusText} - ${errorText}`,
208
193
  );
209
194
  }
210
195
 
@@ -213,28 +198,19 @@ export class AppointmentService extends BaseService {
213
198
  console.log(`[APPOINTMENT_SERVICE] Response parsed successfully`, result);
214
199
 
215
200
  if (!result.success) {
216
- throw new Error(
217
- result.error || "Failed to get available booking slots"
218
- );
201
+ throw new Error(result.error || 'Failed to get available booking slots');
219
202
  }
220
203
 
221
204
  // Convert timestamp numbers to Date objects
222
- const slots: AvailableSlot[] = result.availableSlots.map(
223
- (slot: { start: number }) => ({
224
- start: new Date(slot.start),
225
- })
226
- );
205
+ const slots: AvailableSlot[] = result.availableSlots.map((slot: { start: number }) => ({
206
+ start: new Date(slot.start),
207
+ }));
227
208
 
228
- console.log(
229
- `[APPOINTMENT_SERVICE] Found ${slots.length} available booking slots via HTTP`
230
- );
209
+ console.log(`[APPOINTMENT_SERVICE] Found ${slots.length} available booking slots via HTTP`);
231
210
 
232
211
  return slots;
233
212
  } catch (error) {
234
- console.error(
235
- "[APPOINTMENT_SERVICE] Error getting available booking slots via HTTP:",
236
- error
237
- );
213
+ console.error('[APPOINTMENT_SERVICE] Error getting available booking slots via HTTP:', error);
238
214
  throw error;
239
215
  }
240
216
  }
@@ -245,18 +221,14 @@ export class AppointmentService extends BaseService {
245
221
  * @param data - CreateAppointmentData object
246
222
  * @returns The created appointment
247
223
  */
248
- async createAppointmentHttp(
249
- data: CreateAppointmentHttpData
250
- ): Promise<Appointment> {
224
+ async createAppointmentHttp(data: CreateAppointmentHttpData): Promise<Appointment> {
251
225
  try {
252
- console.log(
253
- "[APPOINTMENT_SERVICE] Creating appointment via cloud function"
254
- );
226
+ console.log('[APPOINTMENT_SERVICE] Creating appointment via cloud function');
255
227
 
256
228
  // Get the authenticated user's ID token
257
229
  const currentUser = this.auth.currentUser;
258
230
  if (!currentUser) {
259
- throw new Error("User must be authenticated to create an appointment");
231
+ throw new Error('User must be authenticated to create an appointment');
260
232
  }
261
233
  const idToken = await currentUser.getIdToken();
262
234
 
@@ -277,37 +249,33 @@ export class AppointmentService extends BaseService {
277
249
  patientNotes: data?.patientNotes || null,
278
250
  };
279
251
 
280
- console.log(
281
- `[APPOINTMENT_SERVICE] Making fetch request to ${functionUrl}`
282
- );
252
+ console.log(`[APPOINTMENT_SERVICE] Making fetch request to ${functionUrl}`);
283
253
 
284
254
  // Make the HTTP request with expanded CORS options for browser
285
255
  const response = await fetch(functionUrl, {
286
- method: "POST",
287
- mode: "cors",
288
- cache: "no-cache",
289
- credentials: "omit",
256
+ method: 'POST',
257
+ mode: 'cors',
258
+ cache: 'no-cache',
259
+ credentials: 'omit',
290
260
  headers: {
291
- "Content-Type": "application/json",
261
+ 'Content-Type': 'application/json',
292
262
  Authorization: `Bearer ${idToken}`,
293
263
  },
294
- redirect: "follow",
295
- referrerPolicy: "no-referrer",
264
+ redirect: 'follow',
265
+ referrerPolicy: 'no-referrer',
296
266
  body: JSON.stringify(requestData),
297
267
  });
298
268
 
299
269
  console.log(
300
- `[APPOINTMENT_SERVICE] Received response ${response.status}: ${response.statusText}`
270
+ `[APPOINTMENT_SERVICE] Received response ${response.status}: ${response.statusText}`,
301
271
  );
302
272
 
303
273
  // Check if the request was successful
304
274
  if (!response.ok) {
305
275
  const errorText = await response.text();
306
- console.error(
307
- `[APPOINTMENT_SERVICE] Error response details: ${errorText}`
308
- );
276
+ console.error(`[APPOINTMENT_SERVICE] Error response details: ${errorText}`);
309
277
  throw new Error(
310
- `Failed to create appointment: ${response.status} ${response.statusText} - ${errorText}`
278
+ `Failed to create appointment: ${response.status} ${response.statusText} - ${errorText}`,
311
279
  );
312
280
  }
313
281
 
@@ -315,33 +283,24 @@ export class AppointmentService extends BaseService {
315
283
  const result = await response.json();
316
284
 
317
285
  if (!result.success) {
318
- throw new Error(result.error || "Failed to create appointment");
286
+ throw new Error(result.error || 'Failed to create appointment');
319
287
  }
320
288
 
321
289
  // If the backend returns the full appointment data
322
290
  if (result.appointmentData) {
323
- console.log(
324
- `[APPOINTMENT_SERVICE] Appointment created with ID: ${result.appointmentId}`
325
- );
291
+ console.log(`[APPOINTMENT_SERVICE] Appointment created with ID: ${result.appointmentId}`);
326
292
  return result.appointmentData;
327
293
  }
328
294
 
329
295
  // If only the ID is returned, fetch the complete appointment
330
- const createdAppointment = await this.getAppointmentById(
331
- result.appointmentId
332
- );
296
+ const createdAppointment = await this.getAppointmentById(result.appointmentId);
333
297
  if (!createdAppointment) {
334
- throw new Error(
335
- `Failed to retrieve created appointment with ID: ${result.appointmentId}`
336
- );
298
+ throw new Error(`Failed to retrieve created appointment with ID: ${result.appointmentId}`);
337
299
  }
338
300
 
339
301
  return createdAppointment;
340
302
  } catch (error) {
341
- console.error(
342
- "[APPOINTMENT_SERVICE] Error creating appointment via cloud function:",
343
- error
344
- );
303
+ console.error('[APPOINTMENT_SERVICE] Error creating appointment via cloud function:', error);
345
304
  throw error;
346
305
  }
347
306
  }
@@ -354,24 +313,17 @@ export class AppointmentService extends BaseService {
354
313
  */
355
314
  async getAppointmentById(appointmentId: string): Promise<Appointment | null> {
356
315
  try {
357
- console.log(
358
- `[APPOINTMENT_SERVICE] Getting appointment with ID: ${appointmentId}`
359
- );
316
+ console.log(`[APPOINTMENT_SERVICE] Getting appointment with ID: ${appointmentId}`);
360
317
 
361
318
  const appointment = await getAppointmentByIdUtil(this.db, appointmentId);
362
319
 
363
320
  console.log(
364
- `[APPOINTMENT_SERVICE] Appointment ${appointmentId} ${
365
- appointment ? "found" : "not found"
366
- }`
321
+ `[APPOINTMENT_SERVICE] Appointment ${appointmentId} ${appointment ? 'found' : 'not found'}`,
367
322
  );
368
323
 
369
324
  return appointment;
370
325
  } catch (error) {
371
- console.error(
372
- `[APPOINTMENT_SERVICE] Error getting appointment ${appointmentId}:`,
373
- error
374
- );
326
+ console.error(`[APPOINTMENT_SERVICE] Error getting appointment ${appointmentId}:`, error);
375
327
  throw error;
376
328
  }
377
329
  }
@@ -385,33 +337,22 @@ export class AppointmentService extends BaseService {
385
337
  */
386
338
  async updateAppointment(
387
339
  appointmentId: string,
388
- data: UpdateAppointmentData
340
+ data: UpdateAppointmentData,
389
341
  ): Promise<Appointment> {
390
342
  try {
391
- console.log(
392
- `[APPOINTMENT_SERVICE] Updating appointment with ID: ${appointmentId}`
393
- );
343
+ console.log(`[APPOINTMENT_SERVICE] Updating appointment with ID: ${appointmentId}`);
394
344
 
395
345
  // Validate input data
396
346
  const validatedData = await updateAppointmentSchema.parseAsync(data);
397
347
 
398
348
  // Update the appointment using the utility function
399
- const updatedAppointment = await updateAppointmentUtil(
400
- this.db,
401
- appointmentId,
402
- validatedData
403
- );
349
+ const updatedAppointment = await updateAppointmentUtil(this.db, appointmentId, validatedData);
404
350
 
405
- console.log(
406
- `[APPOINTMENT_SERVICE] Appointment ${appointmentId} updated successfully`
407
- );
351
+ console.log(`[APPOINTMENT_SERVICE] Appointment ${appointmentId} updated successfully`);
408
352
 
409
353
  return updatedAppointment;
410
354
  } catch (error) {
411
- console.error(
412
- `[APPOINTMENT_SERVICE] Error updating appointment ${appointmentId}:`,
413
- error
414
- );
355
+ console.error(`[APPOINTMENT_SERVICE] Error updating appointment ${appointmentId}:`, error);
415
356
  throw error;
416
357
  }
417
358
  }
@@ -427,10 +368,7 @@ export class AppointmentService extends BaseService {
427
368
  lastDoc: DocumentSnapshot | null;
428
369
  }> {
429
370
  try {
430
- console.log(
431
- "[APPOINTMENT_SERVICE] Searching appointments with params:",
432
- params
433
- );
371
+ console.log('[APPOINTMENT_SERVICE] Searching appointments with params:', params);
434
372
 
435
373
  // Validate search parameters
436
374
  await searchAppointmentsSchema.parseAsync(params);
@@ -438,16 +376,11 @@ export class AppointmentService extends BaseService {
438
376
  // Search for appointments using the utility function
439
377
  const result = await searchAppointmentsUtil(this.db, params);
440
378
 
441
- console.log(
442
- `[APPOINTMENT_SERVICE] Found ${result.appointments.length} appointments`
443
- );
379
+ console.log(`[APPOINTMENT_SERVICE] Found ${result.appointments.length} appointments`);
444
380
 
445
381
  return result;
446
382
  } catch (error) {
447
- console.error(
448
- "[APPOINTMENT_SERVICE] Error searching appointments:",
449
- error
450
- );
383
+ console.error('[APPOINTMENT_SERVICE] Error searching appointments:', error);
451
384
  throw error;
452
385
  }
453
386
  }
@@ -467,14 +400,12 @@ export class AppointmentService extends BaseService {
467
400
  status?: AppointmentStatus | AppointmentStatus[];
468
401
  limit?: number;
469
402
  startAfter?: DocumentSnapshot;
470
- }
403
+ },
471
404
  ): Promise<{
472
405
  appointments: Appointment[];
473
406
  lastDoc: DocumentSnapshot | null;
474
407
  }> {
475
- console.log(
476
- `[APPOINTMENT_SERVICE] Getting appointments for patient: ${patientId}`
477
- );
408
+ console.log(`[APPOINTMENT_SERVICE] Getting appointments for patient: ${patientId}`);
478
409
 
479
410
  const searchParams: SearchAppointmentsParams = {
480
411
  patientId,
@@ -503,14 +434,12 @@ export class AppointmentService extends BaseService {
503
434
  status?: AppointmentStatus | AppointmentStatus[];
504
435
  limit?: number;
505
436
  startAfter?: DocumentSnapshot;
506
- }
437
+ },
507
438
  ): Promise<{
508
439
  appointments: Appointment[];
509
440
  lastDoc: DocumentSnapshot | null;
510
441
  }> {
511
- console.log(
512
- `[APPOINTMENT_SERVICE] Getting appointments for practitioner: ${practitionerId}`
513
- );
442
+ console.log(`[APPOINTMENT_SERVICE] Getting appointments for practitioner: ${practitionerId}`);
514
443
 
515
444
  const searchParams: SearchAppointmentsParams = {
516
445
  practitionerId,
@@ -540,14 +469,12 @@ export class AppointmentService extends BaseService {
540
469
  status?: AppointmentStatus | AppointmentStatus[];
541
470
  limit?: number;
542
471
  startAfter?: DocumentSnapshot;
543
- }
472
+ },
544
473
  ): Promise<{
545
474
  appointments: Appointment[];
546
475
  lastDoc: DocumentSnapshot | null;
547
476
  }> {
548
- console.log(
549
- `[APPOINTMENT_SERVICE] Getting appointments for clinic: ${clinicBranchId}`
550
- );
477
+ console.log(`[APPOINTMENT_SERVICE] Getting appointments for clinic: ${clinicBranchId}`);
551
478
 
552
479
  const searchParams: SearchAppointmentsParams = {
553
480
  clinicBranchId,
@@ -575,11 +502,11 @@ export class AppointmentService extends BaseService {
575
502
  newStatus: AppointmentStatus,
576
503
  details?: {
577
504
  cancellationReason?: string;
578
- canceledBy?: "patient" | "clinic" | "practitioner" | "system";
579
- }
505
+ canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
506
+ },
580
507
  ): Promise<Appointment> {
581
508
  console.log(
582
- `[APPOINTMENT_SERVICE] Updating status of appointment ${appointmentId} to ${newStatus}`
509
+ `[APPOINTMENT_SERVICE] Updating status of appointment ${appointmentId} to ${newStatus}`,
583
510
  );
584
511
  const updateData: UpdateAppointmentData = {
585
512
  status: newStatus,
@@ -592,10 +519,10 @@ export class AppointmentService extends BaseService {
592
519
  newStatus === AppointmentStatus.CANCELED_PATIENT_RESCHEDULED
593
520
  ) {
594
521
  if (!details?.cancellationReason) {
595
- throw new Error("Cancellation reason is required when canceling.");
522
+ throw new Error('Cancellation reason is required when canceling.');
596
523
  }
597
524
  if (!details?.canceledBy) {
598
- throw new Error("Canceled by is required when canceling.");
525
+ throw new Error('Canceled by is required when canceling.');
599
526
  }
600
527
  updateData.cancellationReason = details.cancellationReason;
601
528
  updateData.canceledBy = details.canceledBy;
@@ -617,61 +544,35 @@ export class AppointmentService extends BaseService {
617
544
  * Confirms a PENDING appointment by an Admin/Clinic.
618
545
  */
619
546
  async confirmAppointmentAdmin(appointmentId: string): Promise<Appointment> {
620
- console.log(
621
- `[APPOINTMENT_SERVICE] Admin confirming appointment: ${appointmentId}`
622
- );
547
+ console.log(`[APPOINTMENT_SERVICE] Admin confirming appointment: ${appointmentId}`);
623
548
  const appointment = await this.getAppointmentById(appointmentId);
624
- if (!appointment)
625
- throw new Error(`Appointment ${appointmentId} not found.`);
549
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
626
550
  if (appointment.status !== AppointmentStatus.PENDING) {
627
- throw new Error(
628
- `Appointment ${appointmentId} is not in PENDING state to be confirmed.`
629
- );
551
+ throw new Error(`Appointment ${appointmentId} is not in PENDING state to be confirmed.`);
630
552
  }
631
- return this.updateAppointmentStatus(
632
- appointmentId,
633
- AppointmentStatus.CONFIRMED
634
- );
553
+ return this.updateAppointmentStatus(appointmentId, AppointmentStatus.CONFIRMED);
635
554
  }
636
555
 
637
556
  /**
638
557
  * Cancels an appointment by the User (Patient).
639
558
  */
640
- async cancelAppointmentUser(
641
- appointmentId: string,
642
- reason: string
643
- ): Promise<Appointment> {
644
- console.log(
645
- `[APPOINTMENT_SERVICE] User canceling appointment: ${appointmentId}`
646
- );
647
- return this.updateAppointmentStatus(
648
- appointmentId,
649
- AppointmentStatus.CANCELED_PATIENT,
650
- {
651
- cancellationReason: reason,
652
- canceledBy: "patient",
653
- }
654
- );
559
+ async cancelAppointmentUser(appointmentId: string, reason: string): Promise<Appointment> {
560
+ console.log(`[APPOINTMENT_SERVICE] User canceling appointment: ${appointmentId}`);
561
+ return this.updateAppointmentStatus(appointmentId, AppointmentStatus.CANCELED_PATIENT, {
562
+ cancellationReason: reason,
563
+ canceledBy: 'patient',
564
+ });
655
565
  }
656
566
 
657
567
  /**
658
568
  * Cancels an appointment by an Admin/Clinic.
659
569
  */
660
- async cancelAppointmentAdmin(
661
- appointmentId: string,
662
- reason: string
663
- ): Promise<Appointment> {
664
- console.log(
665
- `[APPOINTMENT_SERVICE] Admin canceling appointment: ${appointmentId}`
666
- );
667
- return this.updateAppointmentStatus(
668
- appointmentId,
669
- AppointmentStatus.CANCELED_CLINIC,
670
- {
671
- cancellationReason: reason,
672
- canceledBy: "clinic",
673
- }
674
- );
570
+ async cancelAppointmentAdmin(appointmentId: string, reason: string): Promise<Appointment> {
571
+ console.log(`[APPOINTMENT_SERVICE] Admin canceling appointment: ${appointmentId}`);
572
+ return this.updateAppointmentStatus(appointmentId, AppointmentStatus.CANCELED_CLINIC, {
573
+ cancellationReason: reason,
574
+ canceledBy: 'clinic',
575
+ });
675
576
  }
676
577
 
677
578
  /**
@@ -683,23 +584,17 @@ export class AppointmentService extends BaseService {
683
584
  newStartTime: any; // Accept any type (number, string, Timestamp, etc.)
684
585
  newEndTime: any; // Accept any type (number, string, Timestamp, etc.)
685
586
  }): Promise<Appointment> {
686
- console.log(
687
- `[APPOINTMENT_SERVICE] Admin rescheduling appointment: ${params.appointmentId}`
688
- );
587
+ console.log(`[APPOINTMENT_SERVICE] Admin rescheduling appointment: ${params.appointmentId}`);
689
588
 
690
589
  // Validate input data
691
- const validatedParams = await rescheduleAppointmentSchema.parseAsync(
692
- params
693
- );
590
+ const validatedParams = await rescheduleAppointmentSchema.parseAsync(params);
694
591
 
695
592
  // Convert input to Timestamp objects
696
- const startTimestamp = this.convertToTimestamp(
697
- validatedParams.newStartTime
698
- );
593
+ const startTimestamp = this.convertToTimestamp(validatedParams.newStartTime);
699
594
  const endTimestamp = this.convertToTimestamp(validatedParams.newEndTime);
700
595
 
701
596
  if (endTimestamp.toMillis() <= startTimestamp.toMillis()) {
702
- throw new Error("New end time must be after new start time.");
597
+ throw new Error('New end time must be after new start time.');
703
598
  }
704
599
 
705
600
  const updateData: UpdateAppointmentData = {
@@ -725,17 +620,17 @@ export class AppointmentService extends BaseService {
725
620
  });
726
621
 
727
622
  // If it's already a Timestamp object with methods
728
- if (value && typeof value.toMillis === "function") {
623
+ if (value && typeof value.toMillis === 'function') {
729
624
  return value;
730
625
  }
731
626
 
732
627
  // If it's a number (milliseconds since epoch)
733
- if (typeof value === "number") {
628
+ if (typeof value === 'number') {
734
629
  return Timestamp.fromMillis(value);
735
630
  }
736
631
 
737
632
  // If it's a string (ISO date string)
738
- if (typeof value === "string") {
633
+ if (typeof value === 'string') {
739
634
  return Timestamp.fromDate(new Date(value));
740
635
  }
741
636
 
@@ -745,44 +640,30 @@ export class AppointmentService extends BaseService {
745
640
  }
746
641
 
747
642
  // If it has _seconds property (serialized Timestamp) - THIS IS WHAT FRONTEND SENDS
748
- if (value && typeof value._seconds === "number") {
643
+ if (value && typeof value._seconds === 'number') {
749
644
  return new Timestamp(value._seconds, value._nanoseconds || 0);
750
645
  }
751
646
 
752
647
  // If it has seconds property (serialized Timestamp)
753
- if (value && typeof value.seconds === "number") {
648
+ if (value && typeof value.seconds === 'number') {
754
649
  return new Timestamp(value.seconds, value.nanoseconds || 0);
755
650
  }
756
651
 
757
- throw new Error(
758
- `Invalid timestamp format: ${typeof value}, value: ${JSON.stringify(
759
- value
760
- )}`
761
- );
652
+ throw new Error(`Invalid timestamp format: ${typeof value}, value: ${JSON.stringify(value)}`);
762
653
  }
763
654
 
764
655
  /**
765
656
  * User confirms a reschedule proposed by the clinic.
766
657
  * Status changes from RESCHEDULED_BY_CLINIC to CONFIRMED.
767
658
  */
768
- async rescheduleAppointmentConfirmUser(
769
- appointmentId: string
770
- ): Promise<Appointment> {
771
- console.log(
772
- `[APPOINTMENT_SERVICE] User confirming reschedule for: ${appointmentId}`
773
- );
659
+ async rescheduleAppointmentConfirmUser(appointmentId: string): Promise<Appointment> {
660
+ console.log(`[APPOINTMENT_SERVICE] User confirming reschedule for: ${appointmentId}`);
774
661
  const appointment = await this.getAppointmentById(appointmentId);
775
- if (!appointment)
776
- throw new Error(`Appointment ${appointmentId} not found.`);
662
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
777
663
  if (appointment.status !== AppointmentStatus.RESCHEDULED_BY_CLINIC) {
778
- throw new Error(
779
- `Appointment ${appointmentId} is not in RESCHEDULED_BY_CLINIC state.`
780
- );
664
+ throw new Error(`Appointment ${appointmentId} is not in RESCHEDULED_BY_CLINIC state.`);
781
665
  }
782
- return this.updateAppointmentStatus(
783
- appointmentId,
784
- AppointmentStatus.CONFIRMED
785
- );
666
+ return this.updateAppointmentStatus(appointmentId, AppointmentStatus.CONFIRMED);
786
667
  }
787
668
 
788
669
  /**
@@ -791,26 +672,21 @@ export class AppointmentService extends BaseService {
791
672
  */
792
673
  async rescheduleAppointmentRejectUser(
793
674
  appointmentId: string,
794
- reason: string
675
+ reason: string,
795
676
  ): Promise<Appointment> {
796
- console.log(
797
- `[APPOINTMENT_SERVICE] User rejecting reschedule for: ${appointmentId}`
798
- );
677
+ console.log(`[APPOINTMENT_SERVICE] User rejecting reschedule for: ${appointmentId}`);
799
678
  const appointment = await this.getAppointmentById(appointmentId);
800
- if (!appointment)
801
- throw new Error(`Appointment ${appointmentId} not found.`);
679
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
802
680
  if (appointment.status !== AppointmentStatus.RESCHEDULED_BY_CLINIC) {
803
- throw new Error(
804
- `Appointment ${appointmentId} is not in RESCHEDULED_BY_CLINIC state.`
805
- );
681
+ throw new Error(`Appointment ${appointmentId} is not in RESCHEDULED_BY_CLINIC state.`);
806
682
  }
807
683
  return this.updateAppointmentStatus(
808
684
  appointmentId,
809
685
  AppointmentStatus.CANCELED_PATIENT_RESCHEDULED,
810
686
  {
811
687
  cancellationReason: reason,
812
- canceledBy: "patient",
813
- }
688
+ canceledBy: 'patient',
689
+ },
814
690
  );
815
691
  }
816
692
 
@@ -819,23 +695,15 @@ export class AppointmentService extends BaseService {
819
695
  * Requires all pending user forms to be completed.
820
696
  */
821
697
  async checkInPatientAdmin(appointmentId: string): Promise<Appointment> {
822
- console.log(
823
- `[APPOINTMENT_SERVICE] Admin checking in patient for: ${appointmentId}`
824
- );
698
+ console.log(`[APPOINTMENT_SERVICE] Admin checking in patient for: ${appointmentId}`);
825
699
  const appointment = await this.getAppointmentById(appointmentId);
826
- if (!appointment)
827
- throw new Error(`Appointment ${appointmentId} not found.`);
700
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
828
701
 
829
- if (
830
- appointment.pendingUserFormsIds &&
831
- appointment.pendingUserFormsIds.length > 0
832
- ) {
702
+ if (appointment.pendingUserFormsIds && appointment.pendingUserFormsIds.length > 0) {
833
703
  throw new Error(
834
704
  `Cannot check in: Patient has ${
835
705
  appointment.pendingUserFormsIds.length
836
- } pending required form(s). IDs: ${appointment.pendingUserFormsIds.join(
837
- ", "
838
- )}`
706
+ } pending required form(s). IDs: ${appointment.pendingUserFormsIds.join(', ')}`,
839
707
  );
840
708
  }
841
709
  if (
@@ -843,30 +711,22 @@ export class AppointmentService extends BaseService {
843
711
  appointment.status !== AppointmentStatus.RESCHEDULED_BY_CLINIC
844
712
  ) {
845
713
  console.warn(
846
- `Checking in appointment ${appointmentId} with status ${appointment.status}. Ensure this is intended.`
714
+ `Checking in appointment ${appointmentId} with status ${appointment.status}. Ensure this is intended.`,
847
715
  );
848
716
  }
849
717
 
850
- return this.updateAppointmentStatus(
851
- appointmentId,
852
- AppointmentStatus.CHECKED_IN
853
- );
718
+ return this.updateAppointmentStatus(appointmentId, AppointmentStatus.CHECKED_IN);
854
719
  }
855
720
 
856
721
  /**
857
722
  * Doctor starts the appointment procedure.
858
723
  */
859
724
  async startAppointmentDoctor(appointmentId: string): Promise<Appointment> {
860
- console.log(
861
- `[APPOINTMENT_SERVICE] Doctor starting appointment: ${appointmentId}`
862
- );
725
+ console.log(`[APPOINTMENT_SERVICE] Doctor starting appointment: ${appointmentId}`);
863
726
  const appointment = await this.getAppointmentById(appointmentId);
864
- if (!appointment)
865
- throw new Error(`Appointment ${appointmentId} not found.`);
727
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
866
728
  if (appointment.status !== AppointmentStatus.CHECKED_IN) {
867
- throw new Error(
868
- `Appointment ${appointmentId} must be CHECKED_IN to start.`
869
- );
729
+ throw new Error(`Appointment ${appointmentId} must be CHECKED_IN to start.`);
870
730
  }
871
731
  // Update status and set procedureActualStartTime
872
732
  const updateData: UpdateAppointmentData = {
@@ -883,33 +743,24 @@ export class AppointmentService extends BaseService {
883
743
  async completeAppointmentDoctor(
884
744
  appointmentId: string,
885
745
  finalizationNotes: string,
886
- actualDurationMinutesInput?: number // Renamed to avoid conflict if we calculate
746
+ actualDurationMinutesInput?: number, // Renamed to avoid conflict if we calculate
887
747
  ): Promise<Appointment> {
888
- console.log(
889
- `[APPOINTMENT_SERVICE] Doctor completing appointment: ${appointmentId}`
890
- );
748
+ console.log(`[APPOINTMENT_SERVICE] Doctor completing appointment: ${appointmentId}`);
891
749
  const currentUser = this.auth.currentUser;
892
- if (!currentUser)
893
- throw new Error("Authentication required to complete appointment.");
750
+ if (!currentUser) throw new Error('Authentication required to complete appointment.');
894
751
 
895
752
  const appointment = await this.getAppointmentById(appointmentId);
896
- if (!appointment)
897
- throw new Error(`Appointment ${appointmentId} not found.`);
753
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
898
754
 
899
755
  let calculatedDurationMinutes = actualDurationMinutesInput;
900
756
  const procedureCompletionTime = Timestamp.now();
901
757
 
902
758
  // Calculate duration if not provided and actual start time is available
903
- if (
904
- calculatedDurationMinutes === undefined &&
905
- appointment.procedureActualStartTime
906
- ) {
759
+ if (calculatedDurationMinutes === undefined && appointment.procedureActualStartTime) {
907
760
  const startTimeMillis = appointment.procedureActualStartTime.toMillis();
908
761
  const endTimeMillis = procedureCompletionTime.toMillis();
909
762
  if (endTimeMillis > startTimeMillis) {
910
- calculatedDurationMinutes = Math.round(
911
- (endTimeMillis - startTimeMillis) / 60000
912
- );
763
+ calculatedDurationMinutes = Math.round((endTimeMillis - startTimeMillis) / 60000);
913
764
  }
914
765
  }
915
766
 
@@ -932,25 +783,16 @@ export class AppointmentService extends BaseService {
932
783
  * Admin marks an appointment as No-Show.
933
784
  */
934
785
  async markNoShowAdmin(appointmentId: string): Promise<Appointment> {
935
- console.log(
936
- `[APPOINTMENT_SERVICE] Admin marking no-show for: ${appointmentId}`
937
- );
786
+ console.log(`[APPOINTMENT_SERVICE] Admin marking no-show for: ${appointmentId}`);
938
787
  const appointment = await this.getAppointmentById(appointmentId);
939
- if (!appointment)
940
- throw new Error(`Appointment ${appointmentId} not found.`);
941
- if (
942
- Timestamp.now().toMillis() < appointment.appointmentStartTime.toMillis()
943
- ) {
944
- throw new Error("Cannot mark no-show before appointment start time.");
788
+ if (!appointment) throw new Error(`Appointment ${appointmentId} not found.`);
789
+ if (Timestamp.now().toMillis() < appointment.appointmentStartTime.toMillis()) {
790
+ throw new Error('Cannot mark no-show before appointment start time.');
945
791
  }
946
- return this.updateAppointmentStatus(
947
- appointmentId,
948
- AppointmentStatus.NO_SHOW,
949
- {
950
- cancellationReason: "Patient did not show up for the appointment.",
951
- canceledBy: "clinic",
952
- }
953
- );
792
+ return this.updateAppointmentStatus(appointmentId, AppointmentStatus.NO_SHOW, {
793
+ cancellationReason: 'Patient did not show up for the appointment.',
794
+ canceledBy: 'clinic',
795
+ });
954
796
  }
955
797
 
956
798
  /**
@@ -958,13 +800,11 @@ export class AppointmentService extends BaseService {
958
800
  */
959
801
  async addMediaToAppointment(
960
802
  appointmentId: string,
961
- mediaItemData: Omit<AppointmentMediaItem, "id" | "uploadedAt">
803
+ mediaItemData: Omit<AppointmentMediaItem, 'id' | 'uploadedAt'>,
962
804
  ): Promise<Appointment> {
963
- console.log(
964
- `[APPOINTMENT_SERVICE] Adding media to appointment ${appointmentId}`
965
- );
805
+ console.log(`[APPOINTMENT_SERVICE] Adding media to appointment ${appointmentId}`);
966
806
  const currentUser = this.auth.currentUser;
967
- if (!currentUser) throw new Error("Authentication required.");
807
+ if (!currentUser) throw new Error('Authentication required.');
968
808
 
969
809
  const newMediaItem: AppointmentMediaItem = {
970
810
  ...mediaItemData,
@@ -985,16 +825,16 @@ export class AppointmentService extends BaseService {
985
825
  */
986
826
  async removeMediaFromAppointment(
987
827
  appointmentId: string,
988
- mediaItemId: string
828
+ mediaItemId: string,
989
829
  ): Promise<Appointment> {
990
830
  console.log(
991
- `[APPOINTMENT_SERVICE] Removing media ${mediaItemId} from appointment ${appointmentId}`
831
+ `[APPOINTMENT_SERVICE] Removing media ${mediaItemId} from appointment ${appointmentId}`,
992
832
  );
993
833
  const appointment = await this.getAppointmentById(appointmentId);
994
834
  if (!appointment || !appointment.media) {
995
- throw new Error("Appointment or media list not found.");
835
+ throw new Error('Appointment or media list not found.');
996
836
  }
997
- const mediaToRemove = appointment.media.find((m) => m.id === mediaItemId);
837
+ const mediaToRemove = appointment.media.find(m => m.id === mediaItemId);
998
838
  if (!mediaToRemove) {
999
839
  throw new Error(`Media item ${mediaItemId} not found in appointment.`);
1000
840
  }
@@ -1011,11 +851,9 @@ export class AppointmentService extends BaseService {
1011
851
  */
1012
852
  async addReviewToAppointment(
1013
853
  appointmentId: string,
1014
- reviewData: Omit<PatientReviewInfo, "reviewedAt" | "reviewId">
854
+ reviewData: Omit<PatientReviewInfo, 'reviewedAt' | 'reviewId'>,
1015
855
  ): Promise<Appointment> {
1016
- console.log(
1017
- `[APPOINTMENT_SERVICE] Adding review to appointment ${appointmentId}`
1018
- );
856
+ console.log(`[APPOINTMENT_SERVICE] Adding review to appointment ${appointmentId}`);
1019
857
  const newReviewInfo: PatientReviewInfo = {
1020
858
  ...reviewData,
1021
859
  reviewId: this.generateId(),
@@ -1034,10 +872,10 @@ export class AppointmentService extends BaseService {
1034
872
  async updatePaymentStatus(
1035
873
  appointmentId: string,
1036
874
  paymentStatus: PaymentStatus,
1037
- paymentTransactionId?: string
875
+ paymentTransactionId?: string,
1038
876
  ): Promise<Appointment> {
1039
877
  console.log(
1040
- `[APPOINTMENT_SERVICE] Updating payment status of appointment ${appointmentId} to ${paymentStatus}`
878
+ `[APPOINTMENT_SERVICE] Updating payment status of appointment ${appointmentId} to ${paymentStatus}`,
1041
879
  );
1042
880
  const updateData: UpdateAppointmentData = {
1043
881
  paymentStatus,
@@ -1054,13 +892,8 @@ export class AppointmentService extends BaseService {
1054
892
  * @param notes Updated internal notes
1055
893
  * @returns The updated appointment
1056
894
  */
1057
- async updateInternalNotes(
1058
- appointmentId: string,
1059
- notes: string | null
1060
- ): Promise<Appointment> {
1061
- console.log(
1062
- `[APPOINTMENT_SERVICE] Updating internal notes for appointment: ${appointmentId}`
1063
- );
895
+ async updateInternalNotes(appointmentId: string, notes: string | null): Promise<Appointment> {
896
+ console.log(`[APPOINTMENT_SERVICE] Updating internal notes for appointment: ${appointmentId}`);
1064
897
 
1065
898
  const updateData: UpdateAppointmentData = {
1066
899
  internalNotes: notes,
@@ -1084,15 +917,13 @@ export class AppointmentService extends BaseService {
1084
917
  endDate?: Date;
1085
918
  limit?: number;
1086
919
  startAfter?: DocumentSnapshot;
1087
- }
920
+ },
1088
921
  ): Promise<{
1089
922
  appointments: Appointment[];
1090
923
  lastDoc: DocumentSnapshot | null;
1091
924
  }> {
1092
925
  try {
1093
- console.log(
1094
- `[APPOINTMENT_SERVICE] Getting upcoming appointments for patient: ${patientId}`
1095
- );
926
+ console.log(`[APPOINTMENT_SERVICE] Getting upcoming appointments for patient: ${patientId}`);
1096
927
 
1097
928
  // Default to current date/time if no startDate provided
1098
929
  const effectiveStartDate = options?.startDate || new Date();
@@ -1110,32 +941,20 @@ export class AppointmentService extends BaseService {
1110
941
  const constraints: QueryConstraint[] = [];
1111
942
 
1112
943
  // Patient ID filter
1113
- constraints.push(where("patientId", "==", patientId));
944
+ constraints.push(where('patientId', '==', patientId));
1114
945
 
1115
946
  // Status filter - multiple statuses
1116
- constraints.push(where("status", "in", upcomingStatuses));
947
+ constraints.push(where('status', 'in', upcomingStatuses));
1117
948
 
1118
949
  // Date range filters
1119
- constraints.push(
1120
- where(
1121
- "appointmentStartTime",
1122
- ">=",
1123
- Timestamp.fromDate(effectiveStartDate)
1124
- )
1125
- );
950
+ constraints.push(where('appointmentStartTime', '>=', Timestamp.fromDate(effectiveStartDate)));
1126
951
 
1127
952
  if (options?.endDate) {
1128
- constraints.push(
1129
- where(
1130
- "appointmentStartTime",
1131
- "<=",
1132
- Timestamp.fromDate(options.endDate)
1133
- )
1134
- );
953
+ constraints.push(where('appointmentStartTime', '<=', Timestamp.fromDate(options.endDate)));
1135
954
  }
1136
955
 
1137
956
  // Order by appointment start time (ascending for upcoming - closest first)
1138
- constraints.push(orderBy("appointmentStartTime", "asc"));
957
+ constraints.push(orderBy('appointmentStartTime', 'asc'));
1139
958
 
1140
959
  // Add pagination if specified
1141
960
  if (options?.limit) {
@@ -1147,32 +966,25 @@ export class AppointmentService extends BaseService {
1147
966
  }
1148
967
 
1149
968
  // Execute query
1150
- const q = query(
1151
- collection(this.db, APPOINTMENTS_COLLECTION),
1152
- ...constraints
1153
- );
969
+ const q = query(collection(this.db, APPOINTMENTS_COLLECTION), ...constraints);
1154
970
  const querySnapshot = await getDocs(q);
1155
971
 
1156
972
  // Extract results
1157
- const appointments = querySnapshot.docs.map(
1158
- (doc) => doc.data() as Appointment
1159
- );
973
+ const appointments = querySnapshot.docs.map(doc => doc.data() as Appointment);
1160
974
 
1161
975
  // Get last document for pagination
1162
976
  const lastDoc =
1163
- querySnapshot.docs.length > 0
1164
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
1165
- : null;
977
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1166
978
 
1167
979
  console.log(
1168
- `[APPOINTMENT_SERVICE] Found ${appointments.length} upcoming appointments for patient ${patientId}`
980
+ `[APPOINTMENT_SERVICE] Found ${appointments.length} upcoming appointments for patient ${patientId}`,
1169
981
  );
1170
982
 
1171
983
  return { appointments, lastDoc };
1172
984
  } catch (error) {
1173
985
  console.error(
1174
986
  `[APPOINTMENT_SERVICE] Error getting upcoming appointments for patient ${patientId}:`,
1175
- error
987
+ error,
1176
988
  );
1177
989
  throw error;
1178
990
  }
@@ -1196,15 +1008,13 @@ export class AppointmentService extends BaseService {
1196
1008
  showNoShow?: boolean; // Whether to include no-show appointments
1197
1009
  limit?: number;
1198
1010
  startAfter?: DocumentSnapshot;
1199
- }
1011
+ },
1200
1012
  ): Promise<{
1201
1013
  appointments: Appointment[];
1202
1014
  lastDoc: DocumentSnapshot | null;
1203
1015
  }> {
1204
1016
  try {
1205
- console.log(
1206
- `[APPOINTMENT_SERVICE] Getting past appointments for patient: ${patientId}`
1207
- );
1017
+ console.log(`[APPOINTMENT_SERVICE] Getting past appointments for patient: ${patientId}`);
1208
1018
 
1209
1019
  // Default to current date/time if no endDate provided
1210
1020
  const effectiveEndDate = options?.endDate || new Date();
@@ -1217,7 +1027,7 @@ export class AppointmentService extends BaseService {
1217
1027
  pastStatuses.push(
1218
1028
  AppointmentStatus.CANCELED_PATIENT,
1219
1029
  AppointmentStatus.CANCELED_PATIENT_RESCHEDULED,
1220
- AppointmentStatus.CANCELED_CLINIC
1030
+ AppointmentStatus.CANCELED_CLINIC,
1221
1031
  );
1222
1032
  }
1223
1033
 
@@ -1230,32 +1040,22 @@ export class AppointmentService extends BaseService {
1230
1040
  const constraints: QueryConstraint[] = [];
1231
1041
 
1232
1042
  // Patient ID filter
1233
- constraints.push(where("patientId", "==", patientId));
1043
+ constraints.push(where('patientId', '==', patientId));
1234
1044
 
1235
1045
  // Status filter - multiple statuses
1236
- constraints.push(where("status", "in", pastStatuses));
1046
+ constraints.push(where('status', 'in', pastStatuses));
1237
1047
 
1238
1048
  // Date range filters
1239
1049
  if (options?.startDate) {
1240
1050
  constraints.push(
1241
- where(
1242
- "appointmentStartTime",
1243
- ">=",
1244
- Timestamp.fromDate(options.startDate)
1245
- )
1051
+ where('appointmentStartTime', '>=', Timestamp.fromDate(options.startDate)),
1246
1052
  );
1247
1053
  }
1248
1054
 
1249
- constraints.push(
1250
- where(
1251
- "appointmentStartTime",
1252
- "<=",
1253
- Timestamp.fromDate(effectiveEndDate)
1254
- )
1255
- );
1055
+ constraints.push(where('appointmentStartTime', '<=', Timestamp.fromDate(effectiveEndDate)));
1256
1056
 
1257
1057
  // Order by appointment start time (descending for past - most recent first)
1258
- constraints.push(orderBy("appointmentStartTime", "desc"));
1058
+ constraints.push(orderBy('appointmentStartTime', 'desc'));
1259
1059
 
1260
1060
  // Add pagination if specified
1261
1061
  if (options?.limit) {
@@ -1267,32 +1067,79 @@ export class AppointmentService extends BaseService {
1267
1067
  }
1268
1068
 
1269
1069
  // Execute query
1270
- const q = query(
1271
- collection(this.db, APPOINTMENTS_COLLECTION),
1272
- ...constraints
1273
- );
1070
+ const q = query(collection(this.db, APPOINTMENTS_COLLECTION), ...constraints);
1274
1071
  const querySnapshot = await getDocs(q);
1275
1072
 
1276
1073
  // Extract results
1277
- const appointments = querySnapshot.docs.map(
1278
- (doc) => doc.data() as Appointment
1279
- );
1074
+ const appointments = querySnapshot.docs.map(doc => doc.data() as Appointment);
1280
1075
 
1281
1076
  // Get last document for pagination
1282
1077
  const lastDoc =
1283
- querySnapshot.docs.length > 0
1284
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
1285
- : null;
1078
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1286
1079
 
1287
1080
  console.log(
1288
- `[APPOINTMENT_SERVICE] Found ${appointments.length} past appointments for patient ${patientId}`
1081
+ `[APPOINTMENT_SERVICE] Found ${appointments.length} past appointments for patient ${patientId}`,
1289
1082
  );
1290
1083
 
1291
1084
  return { appointments, lastDoc };
1292
1085
  } catch (error) {
1293
1086
  console.error(
1294
1087
  `[APPOINTMENT_SERVICE] Error getting past appointments for patient ${patientId}:`,
1295
- error
1088
+ error,
1089
+ );
1090
+ throw error;
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * Counts completed appointments for a patient with optional clinic filtering.
1096
+ *
1097
+ * @param patientId ID of the patient.
1098
+ * @param clinicBranchId Optional ID of the clinic branch to either include or exclude.
1099
+ * @param excludeClinic Optional boolean. If true (default), excludes the specified clinic. If false, includes only that clinic.
1100
+ * @returns The count of completed appointments.
1101
+ */
1102
+ async countCompletedAppointments(
1103
+ patientId: string,
1104
+ clinicBranchId?: string,
1105
+ excludeClinic = true,
1106
+ ): Promise<number> {
1107
+ try {
1108
+ console.log(
1109
+ `[APPOINTMENT_SERVICE] Counting completed appointments for patient: ${patientId}`,
1110
+ { clinicBranchId, excludeClinic },
1111
+ );
1112
+
1113
+ // Build query constraints
1114
+ const constraints: QueryConstraint[] = [
1115
+ where('patientId', '==', patientId),
1116
+ where('status', '==', AppointmentStatus.COMPLETED),
1117
+ ];
1118
+
1119
+ if (clinicBranchId) {
1120
+ if (excludeClinic) {
1121
+ // Exclude appointments from the specified clinic
1122
+ constraints.push(where('clinicBranchId', '!=', clinicBranchId));
1123
+ } else {
1124
+ // Include only appointments from the specified clinic
1125
+ constraints.push(where('clinicBranchId', '==', clinicBranchId));
1126
+ }
1127
+ }
1128
+
1129
+ // Execute query to get only the count
1130
+ const q = query(collection(this.db, APPOINTMENTS_COLLECTION), ...constraints);
1131
+ const snapshot = await getCountFromServer(q);
1132
+ const count = snapshot.data().count;
1133
+
1134
+ console.log(
1135
+ `[APPOINTMENT_SERVICE] Found ${count} completed appointments for patient ${patientId}`,
1136
+ );
1137
+
1138
+ return count;
1139
+ } catch (error) {
1140
+ console.error(
1141
+ `[APPOINTMENT_SERVICE] Error counting completed appointments for patient ${patientId}:`,
1142
+ error,
1296
1143
  );
1297
1144
  throw error;
1298
1145
  }