@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.
- package/dist/admin/index.d.mts +127 -125
- package/dist/admin/index.d.ts +127 -125
- package/dist/backoffice/index.d.mts +23 -23
- package/dist/backoffice/index.d.ts +23 -23
- package/dist/index.d.mts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +98 -33
- package/dist/index.mjs +98 -33
- package/package.json +1 -1
- package/src/services/clinic/clinic.service.ts +15 -10
- package/src/services/clinic/utils/clinic.utils.ts +88 -10
- package/src/services/clinic/utils/filter.utils.ts +14 -0
- package/src/services/procedure/procedure.service.ts +29 -13
- package/src/types/clinic/index.ts +3 -0
|
@@ -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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
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
|
-
|
|
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
|
|