@blackcode_sa/metaestetics-api 1.8.13 → 1.8.15
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/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +536 -213
- package/dist/index.mjs +537 -214
- package/package.json +1 -1
- package/src/services/clinic/utils/filter.utils.ts +242 -57
- package/src/services/practitioner/practitioner.service.ts +195 -82
- package/src/services/procedure/procedure.service.ts +279 -123
- package/src/admin/scripts/migrateProcedures.js +0 -23
- package/src/admin/scripts/serviceAccountKey.json +0 -13
|
@@ -726,7 +726,7 @@ export class PractitionerService extends BaseService {
|
|
|
726
726
|
const currentPractitioner = practitionerDoc.data() as Practitioner;
|
|
727
727
|
|
|
728
728
|
// Process basicInfo if it's being updated to handle profile photo uploads
|
|
729
|
-
let processedData = { ...validData };
|
|
729
|
+
let processedData: UpdatePractitionerData & { fullNameLower?: string } = { ...validData };
|
|
730
730
|
if (validData.basicInfo) {
|
|
731
731
|
processedData.basicInfo = await this.processBasicInfo(
|
|
732
732
|
validData.basicInfo as PractitionerBasicInfo & {
|
|
@@ -734,10 +734,12 @@ export class PractitionerService extends BaseService {
|
|
|
734
734
|
},
|
|
735
735
|
practitionerId
|
|
736
736
|
);
|
|
737
|
+
// Always update fullNameLower when basicInfo changes
|
|
738
|
+
processedData.fullNameLower = `${processedData.basicInfo.firstName} ${processedData.basicInfo.lastName}`.toLowerCase();
|
|
737
739
|
}
|
|
738
740
|
|
|
739
741
|
// Prepare update data
|
|
740
|
-
const updateData = {
|
|
742
|
+
const updateData: any = {
|
|
741
743
|
...processedData,
|
|
742
744
|
updatedAt: serverTimestamp(),
|
|
743
745
|
};
|
|
@@ -1041,101 +1043,212 @@ export class PractitionerService extends BaseService {
|
|
|
1041
1043
|
includeDraftPractitioners?: boolean;
|
|
1042
1044
|
}): Promise<{ practitioners: Practitioner[]; lastDoc: any }> {
|
|
1043
1045
|
try {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1046
|
+
console.log("[PRACTITIONER_SERVICE] Starting practitioner filtering with fallback strategies");
|
|
1047
|
+
|
|
1048
|
+
// Geo query debug i validacija
|
|
1049
|
+
if (filters.location && filters.radiusInKm) {
|
|
1050
|
+
console.log('[PRACTITIONER_SERVICE] Executing geo query:', {
|
|
1051
|
+
location: filters.location,
|
|
1052
|
+
radius: filters.radiusInKm,
|
|
1053
|
+
serviceName: 'PractitionerService'
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
// Validacija location podataka
|
|
1057
|
+
if (!filters.location.latitude || !filters.location.longitude) {
|
|
1058
|
+
console.warn('[PRACTITIONER_SERVICE] Invalid location data:', filters.location);
|
|
1059
|
+
filters.location = undefined;
|
|
1060
|
+
filters.radiusInKm = undefined;
|
|
1061
|
+
}
|
|
1060
1062
|
}
|
|
1061
1063
|
|
|
1062
|
-
//
|
|
1064
|
+
// Strategy 1: Try fullNameLower search if nameSearch exists
|
|
1063
1065
|
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1066
|
+
try {
|
|
1067
|
+
console.log("[PRACTITIONER_SERVICE] Strategy 1: Trying fullNameLower search");
|
|
1068
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
1069
|
+
const constraints: any[] = [];
|
|
1070
|
+
|
|
1071
|
+
if (!filters.includeDraftPractitioners) {
|
|
1072
|
+
constraints.push(where("status", "==", PractitionerStatus.ACTIVE));
|
|
1073
|
+
}
|
|
1074
|
+
constraints.push(where("isActive", "==", true));
|
|
1075
|
+
constraints.push(where("fullNameLower", ">=", searchTerm));
|
|
1076
|
+
constraints.push(where("fullNameLower", "<=", searchTerm + "\uf8ff"));
|
|
1077
|
+
constraints.push(orderBy("fullNameLower"));
|
|
1078
|
+
|
|
1079
|
+
if (filters.lastDoc) {
|
|
1080
|
+
if (typeof filters.lastDoc.data === "function") {
|
|
1081
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1082
|
+
} else if (Array.isArray(filters.lastDoc)) {
|
|
1083
|
+
constraints.push(startAfter(...filters.lastDoc));
|
|
1084
|
+
} else {
|
|
1085
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
constraints.push(limit(filters.pagination || 10));
|
|
1089
|
+
|
|
1090
|
+
const q = query(collection(this.db, PRACTITIONERS_COLLECTION), ...constraints);
|
|
1091
|
+
const querySnapshot = await getDocs(q);
|
|
1092
|
+
const practitioners = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Practitioner));
|
|
1093
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1094
|
+
|
|
1095
|
+
console.log(`[PRACTITIONER_SERVICE] Strategy 1 success: ${practitioners.length} practitioners`);
|
|
1096
|
+
|
|
1097
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
1098
|
+
if (practitioners.length < (filters.pagination || 10)) {
|
|
1099
|
+
return { practitioners, lastDoc: null };
|
|
1100
|
+
}
|
|
1101
|
+
return { practitioners, lastDoc };
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
console.log("[PRACTITIONER_SERVICE] Strategy 1 failed:", error);
|
|
1104
|
+
}
|
|
1067
1105
|
}
|
|
1068
1106
|
|
|
1069
|
-
//
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
constraints.push(where("
|
|
1078
|
-
}
|
|
1107
|
+
// Strategy 2: Basic query with createdAt ordering (no name search)
|
|
1108
|
+
try {
|
|
1109
|
+
console.log("[PRACTITIONER_SERVICE] Strategy 2: Basic query with createdAt ordering");
|
|
1110
|
+
const constraints: any[] = [];
|
|
1111
|
+
|
|
1112
|
+
if (!filters.includeDraftPractitioners) {
|
|
1113
|
+
constraints.push(where("status", "==", PractitionerStatus.ACTIVE));
|
|
1114
|
+
}
|
|
1115
|
+
constraints.push(where("isActive", "==", true));
|
|
1079
1116
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1117
|
+
// Add other filters that work well with Firestore
|
|
1118
|
+
if (filters.certifications && filters.certifications.length > 0) {
|
|
1119
|
+
constraints.push(
|
|
1120
|
+
where("certification.specialties", "array-contains-any", filters.certifications)
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (filters.minRating !== undefined) {
|
|
1125
|
+
constraints.push(where("reviewInfo.averageRating", ">=", filters.minRating));
|
|
1126
|
+
}
|
|
1127
|
+
if (filters.maxRating !== undefined) {
|
|
1128
|
+
constraints.push(where("reviewInfo.averageRating", "<=", filters.maxRating));
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
constraints.push(orderBy("createdAt", "desc"));
|
|
1087
1132
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
constraints.push(startAfter(filters.lastDoc));
|
|
1093
|
-
} else if (Array.isArray(filters.lastDoc)) {
|
|
1094
|
-
constraints.push(startAfter(...filters.lastDoc));
|
|
1133
|
+
// Pagination sa createdAt - poboljšano za geo queries
|
|
1134
|
+
if (filters.location && filters.radiusInKm) {
|
|
1135
|
+
// Ne koristiti lastDoc za geo queries, već preuzmi više rezultata
|
|
1136
|
+
constraints.push(limit((filters.pagination || 10) * 2)); // Dvostruko više za geo filter
|
|
1095
1137
|
} else {
|
|
1096
|
-
|
|
1138
|
+
if (filters.lastDoc) {
|
|
1139
|
+
if (typeof filters.lastDoc.data === "function") {
|
|
1140
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1141
|
+
} else if (Array.isArray(filters.lastDoc)) {
|
|
1142
|
+
constraints.push(startAfter(...filters.lastDoc));
|
|
1143
|
+
} else {
|
|
1144
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
constraints.push(limit(filters.pagination || 10));
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const q = query(collection(this.db, PRACTITIONERS_COLLECTION), ...constraints);
|
|
1151
|
+
const querySnapshot = await getDocs(q);
|
|
1152
|
+
let practitioners = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Practitioner));
|
|
1153
|
+
|
|
1154
|
+
// Apply geo filter if needed (this is the only in-memory filter we keep)
|
|
1155
|
+
if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
|
|
1156
|
+
const location = filters.location;
|
|
1157
|
+
const radiusInKm = filters.radiusInKm;
|
|
1158
|
+
practitioners = practitioners.filter((practitioner) => {
|
|
1159
|
+
const clinics = practitioner.clinicsInfo || [];
|
|
1160
|
+
return clinics.some((clinic) => {
|
|
1161
|
+
const distance = distanceBetween(
|
|
1162
|
+
[location.latitude, location.longitude],
|
|
1163
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
1164
|
+
);
|
|
1165
|
+
const distanceInKm = distance / 1000;
|
|
1166
|
+
return distanceInKm <= radiusInKm;
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
// Ograniči na pagination broj nakon geo filtera
|
|
1171
|
+
practitioners = practitioners.slice(0, filters.pagination || 10);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Apply client-side name filtering if needed
|
|
1175
|
+
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
1176
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
1177
|
+
practitioners = practitioners.filter(practitioner => {
|
|
1178
|
+
const firstName = (practitioner.basicInfo?.firstName || '').toLowerCase();
|
|
1179
|
+
const lastName = (practitioner.basicInfo?.lastName || '').toLowerCase();
|
|
1180
|
+
const fullName = `${firstName} ${lastName}`.trim();
|
|
1181
|
+
const fullNameLower = practitioner.fullNameLower || '';
|
|
1182
|
+
|
|
1183
|
+
return firstName.includes(searchTerm) ||
|
|
1184
|
+
lastName.includes(searchTerm) ||
|
|
1185
|
+
fullName.includes(searchTerm) ||
|
|
1186
|
+
fullNameLower.includes(searchTerm);
|
|
1187
|
+
});
|
|
1188
|
+
console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1192
|
+
console.log(`[PRACTITIONER_SERVICE] Strategy 2 success: ${practitioners.length} practitioners`);
|
|
1193
|
+
|
|
1194
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
1195
|
+
if (practitioners.length < (filters.pagination || 10)) {
|
|
1196
|
+
return { practitioners, lastDoc: null };
|
|
1097
1197
|
}
|
|
1198
|
+
return { practitioners, lastDoc };
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
console.log("[PRACTITIONER_SERVICE] Strategy 2 failed:", error);
|
|
1098
1201
|
}
|
|
1099
|
-
constraints.push(limit(filters.pagination || 5));
|
|
1100
1202
|
|
|
1101
|
-
//
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
);
|
|
1119
|
-
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1203
|
+
// Strategy 3: Minimal query fallback
|
|
1204
|
+
try {
|
|
1205
|
+
console.log("[PRACTITIONER_SERVICE] Strategy 3: Minimal query fallback");
|
|
1206
|
+
const constraints: any[] = [
|
|
1207
|
+
where("isActive", "==", true),
|
|
1208
|
+
orderBy("createdAt", "desc"),
|
|
1209
|
+
limit(filters.pagination || 10)
|
|
1210
|
+
];
|
|
1211
|
+
|
|
1212
|
+
const q = query(collection(this.db, PRACTITIONERS_COLLECTION), ...constraints);
|
|
1213
|
+
const querySnapshot = await getDocs(q);
|
|
1214
|
+
let practitioners = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Practitioner));
|
|
1215
|
+
|
|
1216
|
+
// Apply client-side name filtering if needed
|
|
1217
|
+
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
1218
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
1219
|
+
practitioners = practitioners.filter(practitioner => {
|
|
1220
|
+
const firstName = (practitioner.basicInfo?.firstName || '').toLowerCase();
|
|
1221
|
+
const lastName = (practitioner.basicInfo?.lastName || '').toLowerCase();
|
|
1222
|
+
const fullName = `${firstName} ${lastName}`.trim();
|
|
1223
|
+
const fullNameLower = practitioner.fullNameLower || '';
|
|
1224
|
+
|
|
1225
|
+
return firstName.includes(searchTerm) ||
|
|
1226
|
+
lastName.includes(searchTerm) ||
|
|
1227
|
+
fullName.includes(searchTerm) ||
|
|
1228
|
+
fullNameLower.includes(searchTerm);
|
|
1123
1229
|
});
|
|
1124
|
-
|
|
1230
|
+
console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1234
|
+
console.log(`[PRACTITIONER_SERVICE] Strategy 3 success: ${practitioners.length} practitioners`);
|
|
1235
|
+
|
|
1236
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
1237
|
+
if (practitioners.length < (filters.pagination || 10)) {
|
|
1238
|
+
return { practitioners, lastDoc: null };
|
|
1239
|
+
}
|
|
1240
|
+
return { practitioners, lastDoc };
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
console.log("[PRACTITIONER_SERVICE] Strategy 3 failed:", error);
|
|
1125
1243
|
}
|
|
1126
1244
|
|
|
1127
|
-
//
|
|
1128
|
-
|
|
1129
|
-
return {
|
|
1130
|
-
|
|
1131
|
-
lastDoc,
|
|
1132
|
-
};
|
|
1245
|
+
// All strategies failed
|
|
1246
|
+
console.log("[PRACTITIONER_SERVICE] All strategies failed, returning empty result");
|
|
1247
|
+
return { practitioners: [], lastDoc: null };
|
|
1248
|
+
|
|
1133
1249
|
} catch (error) {
|
|
1134
|
-
console.error(
|
|
1135
|
-
|
|
1136
|
-
error
|
|
1137
|
-
);
|
|
1138
|
-
throw error;
|
|
1250
|
+
console.error("[PRACTITIONER_SERVICE] Error filtering practitioners:", error);
|
|
1251
|
+
return { practitioners: [], lastDoc: null };
|
|
1139
1252
|
}
|
|
1140
1253
|
}
|
|
1141
1254
|
|