@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.
@@ -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
- // 1. Prepare Firestore constraints
1045
- const constraints: any[] = [];
1046
- if (!filters.includeDraftPractitioners) {
1047
- constraints.push(where("status", "==", PractitionerStatus.ACTIVE));
1048
- }
1049
- constraints.push(where("isActive", "==", true));
1050
-
1051
- // Certifications
1052
- if (filters.certifications && filters.certifications.length > 0) {
1053
- constraints.push(
1054
- where(
1055
- "certification.certifications",
1056
- "array-contains-any",
1057
- filters.certifications
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
- // Text search by fullNameLower
1064
+ // Strategy 1: Try fullNameLower search if nameSearch exists
1063
1065
  if (filters.nameSearch && filters.nameSearch.trim()) {
1064
- const searchTerm = filters.nameSearch.trim().toLowerCase();
1065
- constraints.push(where("fullNameLower", ">=", searchTerm));
1066
- constraints.push(where("fullNameLower", "<=", searchTerm + "\uf8ff"));
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
- // Procedure filters (if mapped to fields)
1070
- if (filters.procedureTechnology) {
1071
- constraints.push(where("proceduresInfo.technologyName", "==", filters.procedureTechnology));
1072
- } else if (filters.procedureSubcategory) {
1073
- constraints.push(where("proceduresInfo.subcategoryName", "==", filters.procedureSubcategory));
1074
- } else if (filters.procedureCategory) {
1075
- constraints.push(where("proceduresInfo.categoryName", "==", filters.procedureCategory));
1076
- } else if (filters.procedureFamily) {
1077
- constraints.push(where("proceduresInfo.family", "==", filters.procedureFamily));
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
- // Rating filters
1081
- if (filters.minRating !== undefined) {
1082
- constraints.push(where("reviewInfo.averageRating", ">=", filters.minRating));
1083
- }
1084
- if (filters.maxRating !== undefined) {
1085
- constraints.push(where("reviewInfo.averageRating", "<=", filters.maxRating));
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
- // Pagination and ordering
1089
- constraints.push(orderBy("fullNameLower"));
1090
- if (filters.lastDoc) {
1091
- if (typeof filters.lastDoc.data === "function") {
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
- constraints.push(startAfter(filters.lastDoc));
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
- // 2. Firestore query
1102
- const q = query(collection(this.db, PRACTITIONERS_COLLECTION), ...constraints);
1103
- const querySnapshot = await getDocs(q);
1104
- let practitioners = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Practitioner));
1105
-
1106
- // 3. In-memory filter ONLY for geo-radius (if needed)
1107
- if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
1108
- const location = filters.location;
1109
- const radiusInKm = filters.radiusInKm;
1110
- practitioners = practitioners.filter((practitioner) => {
1111
- // Use the aggregated clinicsInfo to check if any clinic is within range
1112
- const clinics = practitioner.clinicsInfo || [];
1113
- return clinics.some((clinic) => {
1114
- // Calculate distance
1115
- const distance = distanceBetween(
1116
- [location.latitude, location.longitude],
1117
- [clinic.location.latitude, clinic.location.longitude]
1118
- );
1119
- // Convert to kilometers
1120
- const distanceInKm = distance / 1000;
1121
- // Check if within radius
1122
- return distanceInKm <= radiusInKm;
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
- // 4. Return results and lastDoc
1128
- const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1129
- return {
1130
- practitioners,
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
- "[PRACTITIONER_SERVICE] Error filtering practitioners:",
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