@blackcode_sa/metaestetics-api 1.13.10 → 1.13.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -468,16 +468,18 @@ export class ClinicService extends BaseService {
468
468
 
469
469
  /**
470
470
  * Dohvata kliniku po ID-u
471
+ * @param excludeDraftPractitioners If true, filters out draft/inactive practitioners. If false (default), returns all practitioners (for admin views)
471
472
  */
472
- async getClinic(clinicId: string): Promise<Clinic | null> {
473
- return ClinicUtils.getClinic(this.db, clinicId);
473
+ async getClinic(clinicId: string, excludeDraftPractitioners: boolean = false): Promise<Clinic | null> {
474
+ return ClinicUtils.getClinic(this.db, clinicId, excludeDraftPractitioners);
474
475
  }
475
476
 
476
477
  /**
477
478
  * Dohvata sve klinike u grupi
479
+ * @param excludeDraftPractitioners If true, filters out draft/inactive practitioners. If false (default), returns all practitioners (for admin views)
478
480
  */
479
- async getClinicsByGroup(groupId: string): Promise<Clinic[]> {
480
- return ClinicUtils.getClinicsByGroup(this.db, groupId);
481
+ async getClinicsByGroup(groupId: string, excludeDraftPractitioners: boolean = false): Promise<Clinic[]> {
482
+ return ClinicUtils.getClinicsByGroup(this.db, groupId, excludeDraftPractitioners);
481
483
  }
482
484
 
483
485
  /**
@@ -624,29 +626,32 @@ export class ClinicService extends BaseService {
624
626
  return clinic;
625
627
  }
626
628
 
627
- async getClinicById(clinicId: string): Promise<Clinic | null> {
628
- return ClinicUtils.getClinicById(this.db, clinicId);
629
+ async getClinicById(clinicId: string, excludeDraftPractitioners: boolean = false): Promise<Clinic | null> {
630
+ return ClinicUtils.getClinicById(this.db, clinicId, excludeDraftPractitioners);
629
631
  }
630
632
 
631
633
  async getAllClinics(
632
634
  pagination?: number,
633
- lastDoc?: any
635
+ lastDoc?: any,
636
+ excludeDraftPractitioners: boolean = false
634
637
  ): Promise<{ clinics: Clinic[]; lastDoc: any }> {
635
- return ClinicUtils.getAllClinics(this.db, pagination, lastDoc);
638
+ return ClinicUtils.getAllClinics(this.db, pagination, lastDoc, excludeDraftPractitioners);
636
639
  }
637
640
 
638
641
  async getAllClinicsInRange(
639
642
  center: { latitude: number; longitude: number },
640
643
  rangeInKm: number,
641
644
  pagination?: number,
642
- lastDoc?: any
645
+ lastDoc?: any,
646
+ excludeDraftPractitioners: boolean = false
643
647
  ): Promise<{ clinics: (Clinic & { distance: number })[]; lastDoc: any }> {
644
648
  return ClinicUtils.getAllClinicsInRange(
645
649
  this.db,
646
650
  center,
647
651
  rangeInKm,
648
652
  pagination,
649
- lastDoc
653
+ lastDoc,
654
+ excludeDraftPractitioners
650
655
  );
651
656
  }
652
657
 
@@ -24,7 +24,9 @@ import {
24
24
  ClinicGroup,
25
25
  ClinicBranchSetupData,
26
26
  ClinicLocation,
27
+ DoctorInfo,
27
28
  } from "../../../types/clinic";
29
+ import { PractitionerStatus } from "../../../types/practitioner";
28
30
  import {
29
31
  geohashForLocation,
30
32
  distanceBetween,
@@ -384,15 +386,81 @@ export async function createClinic(
384
386
  * @param clinicId - ID of the clinic to get
385
387
  * @returns The clinic or null if not found
386
388
  */
389
+ /**
390
+ * Filters out draft/inactive practitioners from doctorsInfo array
391
+ * @param doctorsInfo Array of doctor info objects
392
+ * @returns Filtered array excluding draft or inactive practitioners
393
+ */
394
+ function filterDoctorsInfo(doctorsInfo: DoctorInfo[]): DoctorInfo[] {
395
+ if (!doctorsInfo || doctorsInfo.length === 0) {
396
+ return [];
397
+ }
398
+
399
+ return doctorsInfo.filter((doctor) => {
400
+ // Filter out if status is DRAFT
401
+ if (doctor.status === PractitionerStatus.DRAFT) {
402
+ console.log(`[CLINIC_UTILS] Filtering out draft practitioner ${doctor.id} from doctorsInfo`);
403
+ return false;
404
+ }
405
+
406
+ // Filter out if isActive is explicitly false
407
+ if (doctor.isActive === false) {
408
+ console.log(`[CLINIC_UTILS] Filtering out inactive practitioner ${doctor.id} from doctorsInfo`);
409
+ return false;
410
+ }
411
+
412
+ // Include if status is ACTIVE or undefined (backward compatibility) and isActive is true or undefined
413
+ return true;
414
+ });
415
+ }
416
+
417
+ /**
418
+ * Filters embedded arrays in a Clinic object to exclude draft/inactive practitioners
419
+ * @param clinic Clinic object to filter
420
+ * @param excludeDraftPractitioners If true, filters out draft/inactive practitioners. If false (default), returns all practitioners (for admin views)
421
+ * @returns Clinic object with filtered embedded arrays
422
+ */
423
+ export function filterClinicEmbeddedArrays(clinic: Clinic, excludeDraftPractitioners: boolean = false): Clinic {
424
+ if (!clinic) {
425
+ return clinic;
426
+ }
427
+
428
+ // If excluding drafts is disabled (default), return clinic as-is (for admin views)
429
+ if (!excludeDraftPractitioners) {
430
+ return clinic;
431
+ }
432
+
433
+ const filteredClinic = { ...clinic };
434
+
435
+ // Filter doctorsInfo array
436
+ if (filteredClinic.doctorsInfo && filteredClinic.doctorsInfo.length > 0) {
437
+ const originalLength = filteredClinic.doctorsInfo.length;
438
+ filteredClinic.doctorsInfo = filterDoctorsInfo(filteredClinic.doctorsInfo);
439
+ if (filteredClinic.doctorsInfo.length !== originalLength) {
440
+ console.log(
441
+ `[CLINIC_UTILS] Filtered ${originalLength - filteredClinic.doctorsInfo.length} draft/inactive doctors from clinic ${clinic.id}`
442
+ );
443
+ }
444
+ }
445
+
446
+ // Note: proceduresInfo doesn't have embedded status/isActive, so we rely on
447
+ // Cloud Functions to not include procedures from draft/inactive practitioners.
448
+ // If needed, we could add additional filtering here by checking practitioner status.
449
+
450
+ return filteredClinic;
451
+ }
452
+
387
453
  export async function getClinic(
388
454
  db: Firestore,
389
- clinicId: string
455
+ clinicId: string,
456
+ excludeDraftPractitioners: boolean = false
390
457
  ): Promise<Clinic | null> {
391
458
  const docRef = doc(db, CLINICS_COLLECTION, clinicId);
392
459
  const docSnap = await getDoc(docRef);
393
460
 
394
461
  if (docSnap.exists()) {
395
- return docSnap.data() as Clinic;
462
+ const clinicData = docSnap.data() as Clinic;
463
+ return filterClinicEmbeddedArrays(clinicData, excludeDraftPractitioners);
396
464
  }
397
465
 
398
466
  return null;
@@ -406,7 +474,8 @@ export async function getClinic(
406
474
  */
407
475
  export async function getClinicsByGroup(
408
476
  db: Firestore,
409
- groupId: string
477
+ groupId: string,
478
+ excludeDraftPractitioners: boolean = false
410
479
  ): Promise<Clinic[]> {
411
480
  const q = query(
412
481
  collection(db, CLINICS_COLLECTION),
@@ -415,7 +484,10 @@ export async function getClinicsByGroup(
415
484
  );
416
485
 
417
486
  const querySnapshot = await getDocs(q);
418
- return querySnapshot.docs.map((doc) => doc.data() as Clinic);
487
+ return querySnapshot.docs.map((doc) => {
488
+ const clinicData = doc.data() as Clinic;
489
+ return filterClinicEmbeddedArrays(clinicData, excludeDraftPractitioners);
490
+ });
419
491
  }
420
492
 
421
493
  /**
@@ -789,7 +861,8 @@ export async function getActiveClinicsByAdmin(
789
861
  */
790
862
  export async function getClinicById(
791
863
  db: Firestore,
792
- clinicId: string
864
+ clinicId: string,
865
+ excludeDraftPractitioners: boolean = false
793
866
  ): Promise<Clinic | null> {
794
867
  try {
795
868
  const clinicRef = doc(db, CLINICS_COLLECTION, clinicId);
@@ -800,10 +873,11 @@ export async function getClinicById(
800
873
  }
801
874
 
802
875
  const clinicData = clinicSnapshot.data() as Clinic;
803
- return {
876
+ const clinicWithId = {
804
877
  ...clinicData,
805
878
  id: clinicSnapshot.id,
806
879
  };
880
+ return filterClinicEmbeddedArrays(clinicWithId, excludeDraftPractitioners);
807
881
  } catch (error) {
808
882
  console.error("[CLINIC_UTILS] Error getting clinic by ID:", error);
809
883
  throw error;
@@ -821,7 +895,8 @@ export async function getClinicById(
821
895
  export async function getAllClinics(
822
896
  db: Firestore,
823
897
  pagination?: number,
824
- lastDoc?: any
898
+ lastDoc?: any,
899
+ excludeDraftPractitioners: boolean = false
825
900
  ): Promise<{ clinics: Clinic[]; lastDoc: any }> {
826
901
  try {
827
902
  const clinicsCollection = collection(db, CLINICS_COLLECTION);
@@ -845,10 +920,11 @@ export async function getAllClinics(
845
920
 
846
921
  const clinics = clinicsSnapshot.docs.map((doc) => {
847
922
  const data = doc.data() as Clinic;
848
- return {
923
+ const clinicWithId = {
849
924
  ...data,
850
925
  id: doc.id,
851
926
  };
927
+ return filterClinicEmbeddedArrays(clinicWithId, excludeDraftPractitioners);
852
928
  });
853
929
 
854
930
  return {
@@ -876,7 +952,8 @@ export async function getAllClinicsInRange(
876
952
  center: { latitude: number; longitude: number },
877
953
  rangeInKm: number,
878
954
  pagination?: number,
879
- lastDoc?: any
955
+ lastDoc?: any,
956
+ excludeDraftPractitioners: boolean = false
880
957
  ): Promise<{ clinics: (Clinic & { distance: number })[]; lastDoc: any }> {
881
958
  const bounds = geohashQueryBounds(
882
959
  [center.latitude, center.longitude],
@@ -907,8 +984,9 @@ export async function getAllClinicsInRange(
907
984
  const distanceInKm = distance / 1000;
908
985
 
909
986
  if (distanceInKm <= rangeInKm) {
987
+ const filteredClinic = filterClinicEmbeddedArrays(clinic, excludeDraftPractitioners);
910
988
  matchingClinics.push({
911
- ...clinic,
989
+ ...filteredClinic,
912
990
  distance: distanceInKm,
913
991
  });
914
992
  }
@@ -12,6 +12,7 @@ import {
12
12
  } from 'firebase/firestore';
13
13
  import { Clinic, ClinicTag, CLINICS_COLLECTION } from '../../../types/clinic';
14
14
  import { geohashQueryBounds, distanceBetween } from 'geofire-common';
15
+ import { filterClinicEmbeddedArrays } from './clinic.utils';
15
16
 
16
17
  /**
17
18
  * Get clinics based on multiple filtering criteria with fallback strategies
@@ -36,6 +37,7 @@ export async function getClinicsByFilters(
36
37
  pagination?: number;
37
38
  lastDoc?: any;
38
39
  isActive?: boolean;
40
+ excludeDraftPractitioners?: boolean; // If true, filters out draft/inactive practitioners. If false (default), returns all
39
41
  },
40
42
  ): Promise<{
41
43
  clinics: (Clinic & { distance?: number })[];
@@ -94,6 +96,10 @@ export async function getClinicsByFilters(
94
96
  }
95
97
  let clinics = Array.from(uniqueMap.values());
96
98
 
99
+ // Filter embedded arrays (draft/inactive practitioners) only if explicitly requested
100
+ const excludeDrafts = filters.excludeDraftPractitioners === true; // Default to false (show all)
101
+ clinics = clinics.map(clinic => filterClinicEmbeddedArrays(clinic, excludeDrafts));
102
+
97
103
  // Apply all remaining filters and compute exact distance + sorting
98
104
  clinics = applyInMemoryFilters(clinics, filters);
99
105
 
@@ -162,6 +168,10 @@ export async function getClinicsByFilters(
162
168
  const querySnapshot = await getDocs(q);
163
169
  let clinics = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Clinic));
164
170
 
171
+ // Filter embedded arrays (draft/inactive practitioners) only if explicitly requested
172
+ const excludeDrafts = filters.excludeDraftPractitioners === true; // Default to false (show all)
173
+ clinics = clinics.map(clinic => filterClinicEmbeddedArrays(clinic, excludeDrafts));
174
+
165
175
  // Apply in-memory filters
166
176
  clinics = applyInMemoryFilters(clinics, filters);
167
177
 
@@ -205,6 +215,10 @@ export async function getClinicsByFilters(
205
215
  const querySnapshot = await getDocs(q);
206
216
  let clinics = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Clinic));
207
217
 
218
+ // Filter embedded arrays (draft/inactive practitioners) only if explicitly requested
219
+ const excludeDrafts = filters.excludeDraftPractitioners === true; // Default to false (show all)
220
+ clinics = clinics.map(clinic => filterClinicEmbeddedArrays(clinic, excludeDrafts));
221
+
208
222
  // Apply in-memory filters
209
223
  clinics = applyInMemoryFilters(clinics, filters);
210
224
 
@@ -439,6 +439,8 @@ export class ProcedureService extends BaseService {
439
439
  : '', // Default to empty string if not a processed URL
440
440
  rating: practitioner.reviewInfo?.averageRating || 0,
441
441
  services: practitioner.procedures || [],
442
+ status: practitioner.status, // Include practitioner status for client-side filtering
443
+ isActive: practitioner.isActive, // Include isActive flag for client-side filtering
442
444
  };
443
445
 
444
446
  // Create the procedure object
@@ -628,6 +630,8 @@ export class ProcedureService extends BaseService {
628
630
  : '',
629
631
  rating: practitioner.reviewInfo?.averageRating || 0,
630
632
  services: practitioner.procedures || [],
633
+ status: practitioner.status,
634
+ isActive: practitioner.isActive,
631
635
  };
632
636
 
633
637
  // Construct the new procedure object
@@ -1265,6 +1269,8 @@ export class ProcedureService extends BaseService {
1265
1269
  : '', // Default to empty string if not a processed URL
1266
1270
  rating: newPractitioner.reviewInfo?.averageRating || 0,
1267
1271
  services: newPractitioner.procedures || [],
1272
+ status: newPractitioner.status,
1273
+ isActive: newPractitioner.isActive,
1268
1274
  };
1269
1275
  }
1270
1276
 
@@ -2294,18 +2300,23 @@ export class ProcedureService extends BaseService {
2294
2300
  > {
2295
2301
  const proceduresRef = collection(this.db, PROCEDURES_COLLECTION);
2296
2302
  const snapshot = await getDocs(proceduresRef);
2297
- const proceduresForMap = snapshot.docs.map(doc => {
2298
- const data = doc.data();
2299
- return {
2300
- id: doc.id,
2301
- name: data.name,
2302
- clinicId: data.clinicInfo?.id,
2303
- clinicName: data.clinicInfo?.name,
2304
- address: data.clinicInfo?.location?.address || '',
2305
- latitude: data.clinicInfo?.location?.latitude,
2306
- longitude: data.clinicInfo?.location?.longitude,
2307
- };
2308
- });
2303
+ let procedures = snapshot.docs.map(doc => ({
2304
+ id: doc.id,
2305
+ ...doc.data(),
2306
+ } as Procedure));
2307
+
2308
+ // Filter out procedures with draft/inactive practitioners
2309
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
2310
+
2311
+ const proceduresForMap = procedures.map(procedure => ({
2312
+ id: procedure.id,
2313
+ name: procedure.name,
2314
+ clinicId: procedure.clinicInfo?.id,
2315
+ clinicName: procedure.clinicInfo?.name,
2316
+ address: procedure.clinicInfo?.location?.address || '',
2317
+ latitude: procedure.clinicInfo?.location?.latitude,
2318
+ longitude: procedure.clinicInfo?.location?.longitude,
2319
+ }));
2309
2320
  return proceduresForMap;
2310
2321
  }
2311
2322
 
@@ -2357,9 +2368,14 @@ export class ProcedureService extends BaseService {
2357
2368
 
2358
2369
  const querySnapshot = await getDocs(proceduresQuery);
2359
2370
 
2360
- return querySnapshot.docs.map(doc => ({
2371
+ let procedures = querySnapshot.docs.map(doc => ({
2361
2372
  id: doc.id,
2362
2373
  ...doc.data(),
2363
2374
  } as Procedure));
2375
+
2376
+ // Filter out procedures with draft/inactive practitioners
2377
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
2378
+
2379
+ return procedures;
2364
2380
  }
2365
2381
  }
@@ -3,6 +3,7 @@ import { Timestamp, FieldValue } from 'firebase/firestore';
3
3
  import type { ClinicInfo } from '../profile';
4
4
  import { ClinicReviewInfo } from '../reviews';
5
5
  import { ProcedureSummaryInfo } from '../procedure';
6
+ import { PractitionerStatus } from '../practitioner';
6
7
 
7
8
  export const CLINIC_GROUPS_COLLECTION = 'clinic_groups';
8
9
  export const CLINIC_ADMINS_COLLECTION = 'clinic_admins';
@@ -351,6 +352,8 @@ export interface DoctorInfo {
351
352
  photo: string | null;
352
353
  rating: number;
353
354
  services: string[]; // Used for search and filtering
355
+ status?: PractitionerStatus; // Practitioner status (DRAFT, ACTIVE, etc.)
356
+ isActive?: boolean; // Whether practitioner is active
354
357
  // TODO: Add aggregated fields, like rating, reviews, services this doctor provides in this clinic and similar
355
358
  }
356
359