@blackcode_sa/metaestetics-api 1.8.15 → 1.8.16

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 CHANGED
@@ -4266,6 +4266,11 @@ declare class ProcedureService extends BaseService {
4266
4266
  })[];
4267
4267
  lastDoc: any;
4268
4268
  }>;
4269
+ /**
4270
+ * Applies in-memory filters to procedures array
4271
+ * Used when Firestore queries fail or for complex filtering
4272
+ */
4273
+ private applyInMemoryFilters;
4269
4274
  private handleGeoQuery;
4270
4275
  /**
4271
4276
  * Creates a consultation procedure without requiring a product
@@ -4465,6 +4470,11 @@ declare class PractitionerService extends BaseService {
4465
4470
  practitioners: Practitioner[];
4466
4471
  lastDoc: any;
4467
4472
  }>;
4473
+ /**
4474
+ * Applies in-memory filters to practitioners array
4475
+ * Used when Firestore queries fail or for complex filtering
4476
+ */
4477
+ private applyInMemoryFilters;
4468
4478
  /**
4469
4479
  * Enables free consultation for a practitioner in a specific clinic
4470
4480
  * Creates a free consultation procedure with hardcoded parameters
package/dist/index.d.ts CHANGED
@@ -4266,6 +4266,11 @@ declare class ProcedureService extends BaseService {
4266
4266
  })[];
4267
4267
  lastDoc: any;
4268
4268
  }>;
4269
+ /**
4270
+ * Applies in-memory filters to procedures array
4271
+ * Used when Firestore queries fail or for complex filtering
4272
+ */
4273
+ private applyInMemoryFilters;
4269
4274
  private handleGeoQuery;
4270
4275
  /**
4271
4276
  * Creates a consultation procedure without requiring a product
@@ -4465,6 +4470,11 @@ declare class PractitionerService extends BaseService {
4465
4470
  practitioners: Practitioner[];
4466
4471
  lastDoc: any;
4467
4472
  }>;
4473
+ /**
4474
+ * Applies in-memory filters to practitioners array
4475
+ * Used when Firestore queries fail or for complex filtering
4476
+ */
4477
+ private applyInMemoryFilters;
4468
4478
  /**
4469
4479
  * Enables free consultation for a practitioner in a specific clinic
4470
4480
  * Creates a free consultation procedure with hardcoded parameters
package/dist/index.js CHANGED
@@ -6889,8 +6889,9 @@ var PractitionerService = class extends BaseService {
6889
6889
  }
6890
6890
  constraints.push((0, import_firestore21.where)("isActive", "==", true));
6891
6891
  if (filters.certifications && filters.certifications.length > 0) {
6892
+ const certificationsToMatch = filters.certifications;
6892
6893
  constraints.push(
6893
- (0, import_firestore21.where)("certification.specialties", "array-contains-any", filters.certifications)
6894
+ (0, import_firestore21.where)("certification.specialties", "array-contains-any", certificationsToMatch)
6894
6895
  );
6895
6896
  }
6896
6897
  if (filters.minRating !== void 0) {
@@ -6933,18 +6934,7 @@ var PractitionerService = class extends BaseService {
6933
6934
  });
6934
6935
  practitioners = practitioners.slice(0, filters.pagination || 10);
6935
6936
  }
6936
- if (filters.nameSearch && filters.nameSearch.trim()) {
6937
- const searchTerm = filters.nameSearch.trim().toLowerCase();
6938
- practitioners = practitioners.filter((practitioner) => {
6939
- var _a, _b;
6940
- const firstName = (((_a = practitioner.basicInfo) == null ? void 0 : _a.firstName) || "").toLowerCase();
6941
- const lastName = (((_b = practitioner.basicInfo) == null ? void 0 : _b.lastName) || "").toLowerCase();
6942
- const fullName = `${firstName} ${lastName}`.trim();
6943
- const fullNameLower = practitioner.fullNameLower || "";
6944
- return firstName.includes(searchTerm) || lastName.includes(searchTerm) || fullName.includes(searchTerm) || fullNameLower.includes(searchTerm);
6945
- });
6946
- console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
6947
- }
6937
+ practitioners = this.applyInMemoryFilters(practitioners, filters);
6948
6938
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6949
6939
  console.log(`[PRACTITIONER_SERVICE] Strategy 2 success: ${practitioners.length} practitioners`);
6950
6940
  if (practitioners.length < (filters.pagination || 10)) {
@@ -6964,18 +6954,7 @@ var PractitionerService = class extends BaseService {
6964
6954
  const q = (0, import_firestore21.query)((0, import_firestore21.collection)(this.db, PRACTITIONERS_COLLECTION), ...constraints);
6965
6955
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6966
6956
  let practitioners = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
6967
- if (filters.nameSearch && filters.nameSearch.trim()) {
6968
- const searchTerm = filters.nameSearch.trim().toLowerCase();
6969
- practitioners = practitioners.filter((practitioner) => {
6970
- var _a, _b;
6971
- const firstName = (((_a = practitioner.basicInfo) == null ? void 0 : _a.firstName) || "").toLowerCase();
6972
- const lastName = (((_b = practitioner.basicInfo) == null ? void 0 : _b.lastName) || "").toLowerCase();
6973
- const fullName = `${firstName} ${lastName}`.trim();
6974
- const fullNameLower = practitioner.fullNameLower || "";
6975
- return firstName.includes(searchTerm) || lastName.includes(searchTerm) || fullName.includes(searchTerm) || fullNameLower.includes(searchTerm);
6976
- });
6977
- console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
6978
- }
6957
+ practitioners = this.applyInMemoryFilters(practitioners, filters);
6979
6958
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6980
6959
  console.log(`[PRACTITIONER_SERVICE] Strategy 3 success: ${practitioners.length} practitioners`);
6981
6960
  if (practitioners.length < (filters.pagination || 10)) {
@@ -6992,6 +6971,98 @@ var PractitionerService = class extends BaseService {
6992
6971
  return { practitioners: [], lastDoc: null };
6993
6972
  }
6994
6973
  }
6974
+ /**
6975
+ * Applies in-memory filters to practitioners array
6976
+ * Used when Firestore queries fail or for complex filtering
6977
+ */
6978
+ applyInMemoryFilters(practitioners, filters) {
6979
+ let filteredPractitioners = [...practitioners];
6980
+ if (filters.nameSearch && filters.nameSearch.trim()) {
6981
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
6982
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
6983
+ var _a, _b;
6984
+ const firstName = (((_a = practitioner.basicInfo) == null ? void 0 : _a.firstName) || "").toLowerCase();
6985
+ const lastName = (((_b = practitioner.basicInfo) == null ? void 0 : _b.lastName) || "").toLowerCase();
6986
+ const fullName = `${firstName} ${lastName}`.trim();
6987
+ const fullNameLower = practitioner.fullNameLower || "";
6988
+ return firstName.includes(searchTerm) || lastName.includes(searchTerm) || fullName.includes(searchTerm) || fullNameLower.includes(searchTerm);
6989
+ });
6990
+ console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${filteredPractitioners.length}`);
6991
+ }
6992
+ if (filters.certifications && filters.certifications.length > 0) {
6993
+ const certificationsToMatch = filters.certifications;
6994
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
6995
+ var _a;
6996
+ const practitionerCerts = ((_a = practitioner.certification) == null ? void 0 : _a.specialties) || [];
6997
+ return certificationsToMatch.some((cert) => practitionerCerts.includes(cert));
6998
+ });
6999
+ console.log(`[PRACTITIONER_SERVICE] Applied certifications filter, results: ${filteredPractitioners.length}`);
7000
+ }
7001
+ if (filters.specialties && filters.specialties.length > 0) {
7002
+ const specialtiesToMatch = filters.specialties;
7003
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7004
+ var _a;
7005
+ const practitionerSpecs = ((_a = practitioner.certification) == null ? void 0 : _a.specialties) || [];
7006
+ return specialtiesToMatch.some((spec) => practitionerSpecs.includes(spec));
7007
+ });
7008
+ console.log(`[PRACTITIONER_SERVICE] Applied specialties filter, results: ${filteredPractitioners.length}`);
7009
+ }
7010
+ if (filters.minRating !== void 0 || filters.maxRating !== void 0) {
7011
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7012
+ var _a;
7013
+ const rating = ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0;
7014
+ if (filters.minRating !== void 0 && rating < filters.minRating) return false;
7015
+ if (filters.maxRating !== void 0 && rating > filters.maxRating) return false;
7016
+ return true;
7017
+ });
7018
+ console.log(`[PRACTITIONER_SERVICE] Applied rating filter, results: ${filteredPractitioners.length}`);
7019
+ }
7020
+ if (filters.procedureFamily) {
7021
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7022
+ const proceduresInfo = practitioner.proceduresInfo || [];
7023
+ return proceduresInfo.some((proc) => proc.family === filters.procedureFamily);
7024
+ });
7025
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure family filter, results: ${filteredPractitioners.length}`);
7026
+ }
7027
+ if (filters.procedureCategory) {
7028
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7029
+ const proceduresInfo = practitioner.proceduresInfo || [];
7030
+ return proceduresInfo.some((proc) => proc.categoryName === filters.procedureCategory);
7031
+ });
7032
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure category filter, results: ${filteredPractitioners.length}`);
7033
+ }
7034
+ if (filters.procedureSubcategory) {
7035
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7036
+ const proceduresInfo = practitioner.proceduresInfo || [];
7037
+ return proceduresInfo.some((proc) => proc.subcategoryName === filters.procedureSubcategory);
7038
+ });
7039
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure subcategory filter, results: ${filteredPractitioners.length}`);
7040
+ }
7041
+ if (filters.procedureTechnology) {
7042
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7043
+ const proceduresInfo = practitioner.proceduresInfo || [];
7044
+ return proceduresInfo.some((proc) => proc.technologyName === filters.procedureTechnology);
7045
+ });
7046
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure technology filter, results: ${filteredPractitioners.length}`);
7047
+ }
7048
+ if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
7049
+ const location = filters.location;
7050
+ const radiusInKm = filters.radiusInKm;
7051
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7052
+ const clinics = practitioner.clinicsInfo || [];
7053
+ return clinics.some((clinic) => {
7054
+ const distance = (0, import_geofire_common2.distanceBetween)(
7055
+ [location.latitude, location.longitude],
7056
+ [clinic.location.latitude, clinic.location.longitude]
7057
+ );
7058
+ const distanceInKm = distance / 1e3;
7059
+ return distanceInKm <= radiusInKm;
7060
+ });
7061
+ });
7062
+ console.log(`[PRACTITIONER_SERVICE] Applied geo filter, results: ${filteredPractitioners.length}`);
7063
+ }
7064
+ return filteredPractitioners;
7065
+ }
6995
7066
  /**
6996
7067
  * Enables free consultation for a practitioner in a specific clinic
6997
7068
  * Creates a free consultation procedure with hardcoded parameters
@@ -15173,7 +15244,8 @@ var ProcedureService = class extends BaseService {
15173
15244
  constraints.push((0, import_firestore45.where)("reviewInfo.averageRating", "<=", filters.maxRating));
15174
15245
  }
15175
15246
  if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
15176
- constraints.push((0, import_firestore45.where)("treatmentBenefits", "array-contains-any", filters.treatmentBenefits));
15247
+ const benefitsToMatch = filters.treatmentBenefits;
15248
+ constraints.push((0, import_firestore45.where)("treatmentBenefits", "array-contains-any", benefitsToMatch));
15177
15249
  }
15178
15250
  return constraints;
15179
15251
  };
@@ -15256,15 +15328,7 @@ var ProcedureService = class extends BaseService {
15256
15328
  const q = (0, import_firestore45.query)((0, import_firestore45.collection)(this.db, PROCEDURES_COLLECTION), ...constraints);
15257
15329
  const querySnapshot = await (0, import_firestore45.getDocs)(q);
15258
15330
  let procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15259
- if (filters.nameSearch && filters.nameSearch.trim()) {
15260
- const searchTerm = filters.nameSearch.trim().toLowerCase();
15261
- procedures = procedures.filter((procedure) => {
15262
- const name = (procedure.name || "").toLowerCase();
15263
- const nameLower = procedure.nameLower || "";
15264
- return name.includes(searchTerm) || nameLower.includes(searchTerm);
15265
- });
15266
- console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
15267
- }
15331
+ procedures = this.applyInMemoryFilters(procedures, filters);
15268
15332
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
15269
15333
  console.log(`[PROCEDURE_SERVICE] Strategy 3 success: ${procedures.length} procedures`);
15270
15334
  if (procedures.length < (filters.pagination || 10)) {
@@ -15284,15 +15348,7 @@ var ProcedureService = class extends BaseService {
15284
15348
  const q = (0, import_firestore45.query)((0, import_firestore45.collection)(this.db, PROCEDURES_COLLECTION), ...constraints);
15285
15349
  const querySnapshot = await (0, import_firestore45.getDocs)(q);
15286
15350
  let procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15287
- if (filters.nameSearch && filters.nameSearch.trim()) {
15288
- const searchTerm = filters.nameSearch.trim().toLowerCase();
15289
- procedures = procedures.filter((procedure) => {
15290
- const name = (procedure.name || "").toLowerCase();
15291
- const nameLower = procedure.nameLower || "";
15292
- return name.includes(searchTerm) || nameLower.includes(searchTerm);
15293
- });
15294
- console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
15295
- }
15351
+ procedures = this.applyInMemoryFilters(procedures, filters);
15296
15352
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
15297
15353
  console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
15298
15354
  if (procedures.length < (filters.pagination || 10)) {
@@ -15309,6 +15365,94 @@ var ProcedureService = class extends BaseService {
15309
15365
  return { procedures: [], lastDoc: null };
15310
15366
  }
15311
15367
  }
15368
+ /**
15369
+ * Applies in-memory filters to procedures array
15370
+ * Used when Firestore queries fail or for complex filtering
15371
+ */
15372
+ applyInMemoryFilters(procedures, filters) {
15373
+ let filteredProcedures = [...procedures];
15374
+ if (filters.nameSearch && filters.nameSearch.trim()) {
15375
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
15376
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15377
+ const name = (procedure.name || "").toLowerCase();
15378
+ const nameLower = procedure.nameLower || "";
15379
+ return name.includes(searchTerm) || nameLower.includes(searchTerm);
15380
+ });
15381
+ console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${filteredProcedures.length}`);
15382
+ }
15383
+ if (filters.minPrice !== void 0 || filters.maxPrice !== void 0) {
15384
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15385
+ const price = procedure.price || 0;
15386
+ if (filters.minPrice !== void 0 && price < filters.minPrice) return false;
15387
+ if (filters.maxPrice !== void 0 && price > filters.maxPrice) return false;
15388
+ return true;
15389
+ });
15390
+ console.log(`[PROCEDURE_SERVICE] Applied price filter (${filters.minPrice}-${filters.maxPrice}), results: ${filteredProcedures.length}`);
15391
+ }
15392
+ if (filters.minRating !== void 0 || filters.maxRating !== void 0) {
15393
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15394
+ var _a;
15395
+ const rating = ((_a = procedure.reviewInfo) == null ? void 0 : _a.averageRating) || 0;
15396
+ if (filters.minRating !== void 0 && rating < filters.minRating) return false;
15397
+ if (filters.maxRating !== void 0 && rating > filters.maxRating) return false;
15398
+ return true;
15399
+ });
15400
+ console.log(`[PROCEDURE_SERVICE] Applied rating filter, results: ${filteredProcedures.length}`);
15401
+ }
15402
+ if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
15403
+ const benefitsToMatch = filters.treatmentBenefits;
15404
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15405
+ const procedureBenefits = procedure.treatmentBenefits || [];
15406
+ return benefitsToMatch.some((benefit) => procedureBenefits.includes(benefit));
15407
+ });
15408
+ console.log(`[PROCEDURE_SERVICE] Applied benefits filter, results: ${filteredProcedures.length}`);
15409
+ }
15410
+ if (filters.procedureFamily) {
15411
+ filteredProcedures = filteredProcedures.filter((procedure) => procedure.family === filters.procedureFamily);
15412
+ console.log(`[PROCEDURE_SERVICE] Applied family filter, results: ${filteredProcedures.length}`);
15413
+ }
15414
+ if (filters.procedureCategory) {
15415
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15416
+ var _a;
15417
+ return ((_a = procedure.category) == null ? void 0 : _a.id) === filters.procedureCategory;
15418
+ });
15419
+ console.log(`[PROCEDURE_SERVICE] Applied category filter, results: ${filteredProcedures.length}`);
15420
+ }
15421
+ if (filters.procedureSubcategory) {
15422
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15423
+ var _a;
15424
+ return ((_a = procedure.subcategory) == null ? void 0 : _a.id) === filters.procedureSubcategory;
15425
+ });
15426
+ console.log(`[PROCEDURE_SERVICE] Applied subcategory filter, results: ${filteredProcedures.length}`);
15427
+ }
15428
+ if (filters.procedureTechnology) {
15429
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15430
+ var _a;
15431
+ return ((_a = procedure.technology) == null ? void 0 : _a.id) === filters.procedureTechnology;
15432
+ });
15433
+ console.log(`[PROCEDURE_SERVICE] Applied technology filter, results: ${filteredProcedures.length}`);
15434
+ }
15435
+ if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
15436
+ const location = filters.location;
15437
+ const radiusInKm = filters.radiusInKm;
15438
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15439
+ var _a;
15440
+ const clinicLocation = (_a = procedure.clinicInfo) == null ? void 0 : _a.location;
15441
+ if (!(clinicLocation == null ? void 0 : clinicLocation.latitude) || !(clinicLocation == null ? void 0 : clinicLocation.longitude)) {
15442
+ return false;
15443
+ }
15444
+ const distance = (0, import_geofire_common8.distanceBetween)(
15445
+ [location.latitude, location.longitude],
15446
+ [clinicLocation.latitude, clinicLocation.longitude]
15447
+ ) / 1e3;
15448
+ procedure.distance = distance;
15449
+ return distance <= radiusInKm;
15450
+ });
15451
+ console.log(`[PROCEDURE_SERVICE] Applied geo filter, results: ${filteredProcedures.length}`);
15452
+ filteredProcedures.sort((a, b) => (a.distance || 0) - (b.distance || 0));
15453
+ }
15454
+ return filteredProcedures;
15455
+ }
15312
15456
  handleGeoQuery(filters) {
15313
15457
  console.log("[PROCEDURE_SERVICE] Executing geo query with enhanced debugging");
15314
15458
  try {
@@ -15329,21 +15473,7 @@ var ProcedureService = class extends BaseService {
15329
15473
  const q = (0, import_firestore45.query)((0, import_firestore45.collection)(this.db, PROCEDURES_COLLECTION), ...constraints);
15330
15474
  return (0, import_firestore45.getDocs)(q).then((querySnapshot) => {
15331
15475
  let procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15332
- procedures = procedures.filter((procedure) => {
15333
- var _a;
15334
- const clinicLocation = (_a = procedure.clinicInfo) == null ? void 0 : _a.location;
15335
- if (!(clinicLocation == null ? void 0 : clinicLocation.latitude) || !(clinicLocation == null ? void 0 : clinicLocation.longitude)) {
15336
- return false;
15337
- }
15338
- const distance = (0, import_geofire_common8.distanceBetween)(
15339
- [location.latitude, location.longitude],
15340
- [clinicLocation.latitude, clinicLocation.longitude]
15341
- ) / 1e3;
15342
- procedure.distance = distance;
15343
- return distance <= radiusInKm;
15344
- });
15345
- procedures.sort((a, b) => (a.distance || 0) - (b.distance || 0));
15346
- procedures = procedures.slice(0, filters.pagination || 10);
15476
+ procedures = this.applyInMemoryFilters(procedures, filters);
15347
15477
  console.log(`[PROCEDURE_SERVICE] Geo query success: ${procedures.length} procedures within ${radiusInKm}km`);
15348
15478
  const lastDoc = procedures.length < (filters.pagination || 10) ? null : querySnapshot.docs[querySnapshot.docs.length - 1];
15349
15479
  return { procedures, lastDoc };
package/dist/index.mjs CHANGED
@@ -6935,8 +6935,9 @@ var PractitionerService = class extends BaseService {
6935
6935
  }
6936
6936
  constraints.push(where10("isActive", "==", true));
6937
6937
  if (filters.certifications && filters.certifications.length > 0) {
6938
+ const certificationsToMatch = filters.certifications;
6938
6939
  constraints.push(
6939
- where10("certification.specialties", "array-contains-any", filters.certifications)
6940
+ where10("certification.specialties", "array-contains-any", certificationsToMatch)
6940
6941
  );
6941
6942
  }
6942
6943
  if (filters.minRating !== void 0) {
@@ -6979,18 +6980,7 @@ var PractitionerService = class extends BaseService {
6979
6980
  });
6980
6981
  practitioners = practitioners.slice(0, filters.pagination || 10);
6981
6982
  }
6982
- if (filters.nameSearch && filters.nameSearch.trim()) {
6983
- const searchTerm = filters.nameSearch.trim().toLowerCase();
6984
- practitioners = practitioners.filter((practitioner) => {
6985
- var _a, _b;
6986
- const firstName = (((_a = practitioner.basicInfo) == null ? void 0 : _a.firstName) || "").toLowerCase();
6987
- const lastName = (((_b = practitioner.basicInfo) == null ? void 0 : _b.lastName) || "").toLowerCase();
6988
- const fullName = `${firstName} ${lastName}`.trim();
6989
- const fullNameLower = practitioner.fullNameLower || "";
6990
- return firstName.includes(searchTerm) || lastName.includes(searchTerm) || fullName.includes(searchTerm) || fullNameLower.includes(searchTerm);
6991
- });
6992
- console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
6993
- }
6983
+ practitioners = this.applyInMemoryFilters(practitioners, filters);
6994
6984
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6995
6985
  console.log(`[PRACTITIONER_SERVICE] Strategy 2 success: ${practitioners.length} practitioners`);
6996
6986
  if (practitioners.length < (filters.pagination || 10)) {
@@ -7010,18 +7000,7 @@ var PractitionerService = class extends BaseService {
7010
7000
  const q = query10(collection10(this.db, PRACTITIONERS_COLLECTION), ...constraints);
7011
7001
  const querySnapshot = await getDocs10(q);
7012
7002
  let practitioners = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
7013
- if (filters.nameSearch && filters.nameSearch.trim()) {
7014
- const searchTerm = filters.nameSearch.trim().toLowerCase();
7015
- practitioners = practitioners.filter((practitioner) => {
7016
- var _a, _b;
7017
- const firstName = (((_a = practitioner.basicInfo) == null ? void 0 : _a.firstName) || "").toLowerCase();
7018
- const lastName = (((_b = practitioner.basicInfo) == null ? void 0 : _b.lastName) || "").toLowerCase();
7019
- const fullName = `${firstName} ${lastName}`.trim();
7020
- const fullNameLower = practitioner.fullNameLower || "";
7021
- return firstName.includes(searchTerm) || lastName.includes(searchTerm) || fullName.includes(searchTerm) || fullNameLower.includes(searchTerm);
7022
- });
7023
- console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
7024
- }
7003
+ practitioners = this.applyInMemoryFilters(practitioners, filters);
7025
7004
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
7026
7005
  console.log(`[PRACTITIONER_SERVICE] Strategy 3 success: ${practitioners.length} practitioners`);
7027
7006
  if (practitioners.length < (filters.pagination || 10)) {
@@ -7038,6 +7017,98 @@ var PractitionerService = class extends BaseService {
7038
7017
  return { practitioners: [], lastDoc: null };
7039
7018
  }
7040
7019
  }
7020
+ /**
7021
+ * Applies in-memory filters to practitioners array
7022
+ * Used when Firestore queries fail or for complex filtering
7023
+ */
7024
+ applyInMemoryFilters(practitioners, filters) {
7025
+ let filteredPractitioners = [...practitioners];
7026
+ if (filters.nameSearch && filters.nameSearch.trim()) {
7027
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
7028
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7029
+ var _a, _b;
7030
+ const firstName = (((_a = practitioner.basicInfo) == null ? void 0 : _a.firstName) || "").toLowerCase();
7031
+ const lastName = (((_b = practitioner.basicInfo) == null ? void 0 : _b.lastName) || "").toLowerCase();
7032
+ const fullName = `${firstName} ${lastName}`.trim();
7033
+ const fullNameLower = practitioner.fullNameLower || "";
7034
+ return firstName.includes(searchTerm) || lastName.includes(searchTerm) || fullName.includes(searchTerm) || fullNameLower.includes(searchTerm);
7035
+ });
7036
+ console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${filteredPractitioners.length}`);
7037
+ }
7038
+ if (filters.certifications && filters.certifications.length > 0) {
7039
+ const certificationsToMatch = filters.certifications;
7040
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7041
+ var _a;
7042
+ const practitionerCerts = ((_a = practitioner.certification) == null ? void 0 : _a.specialties) || [];
7043
+ return certificationsToMatch.some((cert) => practitionerCerts.includes(cert));
7044
+ });
7045
+ console.log(`[PRACTITIONER_SERVICE] Applied certifications filter, results: ${filteredPractitioners.length}`);
7046
+ }
7047
+ if (filters.specialties && filters.specialties.length > 0) {
7048
+ const specialtiesToMatch = filters.specialties;
7049
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7050
+ var _a;
7051
+ const practitionerSpecs = ((_a = practitioner.certification) == null ? void 0 : _a.specialties) || [];
7052
+ return specialtiesToMatch.some((spec) => practitionerSpecs.includes(spec));
7053
+ });
7054
+ console.log(`[PRACTITIONER_SERVICE] Applied specialties filter, results: ${filteredPractitioners.length}`);
7055
+ }
7056
+ if (filters.minRating !== void 0 || filters.maxRating !== void 0) {
7057
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7058
+ var _a;
7059
+ const rating = ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0;
7060
+ if (filters.minRating !== void 0 && rating < filters.minRating) return false;
7061
+ if (filters.maxRating !== void 0 && rating > filters.maxRating) return false;
7062
+ return true;
7063
+ });
7064
+ console.log(`[PRACTITIONER_SERVICE] Applied rating filter, results: ${filteredPractitioners.length}`);
7065
+ }
7066
+ if (filters.procedureFamily) {
7067
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7068
+ const proceduresInfo = practitioner.proceduresInfo || [];
7069
+ return proceduresInfo.some((proc) => proc.family === filters.procedureFamily);
7070
+ });
7071
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure family filter, results: ${filteredPractitioners.length}`);
7072
+ }
7073
+ if (filters.procedureCategory) {
7074
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7075
+ const proceduresInfo = practitioner.proceduresInfo || [];
7076
+ return proceduresInfo.some((proc) => proc.categoryName === filters.procedureCategory);
7077
+ });
7078
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure category filter, results: ${filteredPractitioners.length}`);
7079
+ }
7080
+ if (filters.procedureSubcategory) {
7081
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7082
+ const proceduresInfo = practitioner.proceduresInfo || [];
7083
+ return proceduresInfo.some((proc) => proc.subcategoryName === filters.procedureSubcategory);
7084
+ });
7085
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure subcategory filter, results: ${filteredPractitioners.length}`);
7086
+ }
7087
+ if (filters.procedureTechnology) {
7088
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7089
+ const proceduresInfo = practitioner.proceduresInfo || [];
7090
+ return proceduresInfo.some((proc) => proc.technologyName === filters.procedureTechnology);
7091
+ });
7092
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure technology filter, results: ${filteredPractitioners.length}`);
7093
+ }
7094
+ if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
7095
+ const location = filters.location;
7096
+ const radiusInKm = filters.radiusInKm;
7097
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
7098
+ const clinics = practitioner.clinicsInfo || [];
7099
+ return clinics.some((clinic) => {
7100
+ const distance = distanceBetween(
7101
+ [location.latitude, location.longitude],
7102
+ [clinic.location.latitude, clinic.location.longitude]
7103
+ );
7104
+ const distanceInKm = distance / 1e3;
7105
+ return distanceInKm <= radiusInKm;
7106
+ });
7107
+ });
7108
+ console.log(`[PRACTITIONER_SERVICE] Applied geo filter, results: ${filteredPractitioners.length}`);
7109
+ }
7110
+ return filteredPractitioners;
7111
+ }
7041
7112
  /**
7042
7113
  * Enables free consultation for a practitioner in a specific clinic
7043
7114
  * Creates a free consultation procedure with hardcoded parameters
@@ -15424,7 +15495,8 @@ var ProcedureService = class extends BaseService {
15424
15495
  constraints.push(where29("reviewInfo.averageRating", "<=", filters.maxRating));
15425
15496
  }
15426
15497
  if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
15427
- constraints.push(where29("treatmentBenefits", "array-contains-any", filters.treatmentBenefits));
15498
+ const benefitsToMatch = filters.treatmentBenefits;
15499
+ constraints.push(where29("treatmentBenefits", "array-contains-any", benefitsToMatch));
15428
15500
  }
15429
15501
  return constraints;
15430
15502
  };
@@ -15507,15 +15579,7 @@ var ProcedureService = class extends BaseService {
15507
15579
  const q = query29(collection29(this.db, PROCEDURES_COLLECTION), ...constraints);
15508
15580
  const querySnapshot = await getDocs29(q);
15509
15581
  let procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15510
- if (filters.nameSearch && filters.nameSearch.trim()) {
15511
- const searchTerm = filters.nameSearch.trim().toLowerCase();
15512
- procedures = procedures.filter((procedure) => {
15513
- const name = (procedure.name || "").toLowerCase();
15514
- const nameLower = procedure.nameLower || "";
15515
- return name.includes(searchTerm) || nameLower.includes(searchTerm);
15516
- });
15517
- console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
15518
- }
15582
+ procedures = this.applyInMemoryFilters(procedures, filters);
15519
15583
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
15520
15584
  console.log(`[PROCEDURE_SERVICE] Strategy 3 success: ${procedures.length} procedures`);
15521
15585
  if (procedures.length < (filters.pagination || 10)) {
@@ -15535,15 +15599,7 @@ var ProcedureService = class extends BaseService {
15535
15599
  const q = query29(collection29(this.db, PROCEDURES_COLLECTION), ...constraints);
15536
15600
  const querySnapshot = await getDocs29(q);
15537
15601
  let procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15538
- if (filters.nameSearch && filters.nameSearch.trim()) {
15539
- const searchTerm = filters.nameSearch.trim().toLowerCase();
15540
- procedures = procedures.filter((procedure) => {
15541
- const name = (procedure.name || "").toLowerCase();
15542
- const nameLower = procedure.nameLower || "";
15543
- return name.includes(searchTerm) || nameLower.includes(searchTerm);
15544
- });
15545
- console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
15546
- }
15602
+ procedures = this.applyInMemoryFilters(procedures, filters);
15547
15603
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
15548
15604
  console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
15549
15605
  if (procedures.length < (filters.pagination || 10)) {
@@ -15560,6 +15616,94 @@ var ProcedureService = class extends BaseService {
15560
15616
  return { procedures: [], lastDoc: null };
15561
15617
  }
15562
15618
  }
15619
+ /**
15620
+ * Applies in-memory filters to procedures array
15621
+ * Used when Firestore queries fail or for complex filtering
15622
+ */
15623
+ applyInMemoryFilters(procedures, filters) {
15624
+ let filteredProcedures = [...procedures];
15625
+ if (filters.nameSearch && filters.nameSearch.trim()) {
15626
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
15627
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15628
+ const name = (procedure.name || "").toLowerCase();
15629
+ const nameLower = procedure.nameLower || "";
15630
+ return name.includes(searchTerm) || nameLower.includes(searchTerm);
15631
+ });
15632
+ console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${filteredProcedures.length}`);
15633
+ }
15634
+ if (filters.minPrice !== void 0 || filters.maxPrice !== void 0) {
15635
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15636
+ const price = procedure.price || 0;
15637
+ if (filters.minPrice !== void 0 && price < filters.minPrice) return false;
15638
+ if (filters.maxPrice !== void 0 && price > filters.maxPrice) return false;
15639
+ return true;
15640
+ });
15641
+ console.log(`[PROCEDURE_SERVICE] Applied price filter (${filters.minPrice}-${filters.maxPrice}), results: ${filteredProcedures.length}`);
15642
+ }
15643
+ if (filters.minRating !== void 0 || filters.maxRating !== void 0) {
15644
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15645
+ var _a;
15646
+ const rating = ((_a = procedure.reviewInfo) == null ? void 0 : _a.averageRating) || 0;
15647
+ if (filters.minRating !== void 0 && rating < filters.minRating) return false;
15648
+ if (filters.maxRating !== void 0 && rating > filters.maxRating) return false;
15649
+ return true;
15650
+ });
15651
+ console.log(`[PROCEDURE_SERVICE] Applied rating filter, results: ${filteredProcedures.length}`);
15652
+ }
15653
+ if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
15654
+ const benefitsToMatch = filters.treatmentBenefits;
15655
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15656
+ const procedureBenefits = procedure.treatmentBenefits || [];
15657
+ return benefitsToMatch.some((benefit) => procedureBenefits.includes(benefit));
15658
+ });
15659
+ console.log(`[PROCEDURE_SERVICE] Applied benefits filter, results: ${filteredProcedures.length}`);
15660
+ }
15661
+ if (filters.procedureFamily) {
15662
+ filteredProcedures = filteredProcedures.filter((procedure) => procedure.family === filters.procedureFamily);
15663
+ console.log(`[PROCEDURE_SERVICE] Applied family filter, results: ${filteredProcedures.length}`);
15664
+ }
15665
+ if (filters.procedureCategory) {
15666
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15667
+ var _a;
15668
+ return ((_a = procedure.category) == null ? void 0 : _a.id) === filters.procedureCategory;
15669
+ });
15670
+ console.log(`[PROCEDURE_SERVICE] Applied category filter, results: ${filteredProcedures.length}`);
15671
+ }
15672
+ if (filters.procedureSubcategory) {
15673
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15674
+ var _a;
15675
+ return ((_a = procedure.subcategory) == null ? void 0 : _a.id) === filters.procedureSubcategory;
15676
+ });
15677
+ console.log(`[PROCEDURE_SERVICE] Applied subcategory filter, results: ${filteredProcedures.length}`);
15678
+ }
15679
+ if (filters.procedureTechnology) {
15680
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15681
+ var _a;
15682
+ return ((_a = procedure.technology) == null ? void 0 : _a.id) === filters.procedureTechnology;
15683
+ });
15684
+ console.log(`[PROCEDURE_SERVICE] Applied technology filter, results: ${filteredProcedures.length}`);
15685
+ }
15686
+ if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
15687
+ const location = filters.location;
15688
+ const radiusInKm = filters.radiusInKm;
15689
+ filteredProcedures = filteredProcedures.filter((procedure) => {
15690
+ var _a;
15691
+ const clinicLocation = (_a = procedure.clinicInfo) == null ? void 0 : _a.location;
15692
+ if (!(clinicLocation == null ? void 0 : clinicLocation.latitude) || !(clinicLocation == null ? void 0 : clinicLocation.longitude)) {
15693
+ return false;
15694
+ }
15695
+ const distance = distanceBetween6(
15696
+ [location.latitude, location.longitude],
15697
+ [clinicLocation.latitude, clinicLocation.longitude]
15698
+ ) / 1e3;
15699
+ procedure.distance = distance;
15700
+ return distance <= radiusInKm;
15701
+ });
15702
+ console.log(`[PROCEDURE_SERVICE] Applied geo filter, results: ${filteredProcedures.length}`);
15703
+ filteredProcedures.sort((a, b) => (a.distance || 0) - (b.distance || 0));
15704
+ }
15705
+ return filteredProcedures;
15706
+ }
15563
15707
  handleGeoQuery(filters) {
15564
15708
  console.log("[PROCEDURE_SERVICE] Executing geo query with enhanced debugging");
15565
15709
  try {
@@ -15580,21 +15724,7 @@ var ProcedureService = class extends BaseService {
15580
15724
  const q = query29(collection29(this.db, PROCEDURES_COLLECTION), ...constraints);
15581
15725
  return getDocs29(q).then((querySnapshot) => {
15582
15726
  let procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15583
- procedures = procedures.filter((procedure) => {
15584
- var _a;
15585
- const clinicLocation = (_a = procedure.clinicInfo) == null ? void 0 : _a.location;
15586
- if (!(clinicLocation == null ? void 0 : clinicLocation.latitude) || !(clinicLocation == null ? void 0 : clinicLocation.longitude)) {
15587
- return false;
15588
- }
15589
- const distance = distanceBetween6(
15590
- [location.latitude, location.longitude],
15591
- [clinicLocation.latitude, clinicLocation.longitude]
15592
- ) / 1e3;
15593
- procedure.distance = distance;
15594
- return distance <= radiusInKm;
15595
- });
15596
- procedures.sort((a, b) => (a.distance || 0) - (b.distance || 0));
15597
- procedures = procedures.slice(0, filters.pagination || 10);
15727
+ procedures = this.applyInMemoryFilters(procedures, filters);
15598
15728
  console.log(`[PROCEDURE_SERVICE] Geo query success: ${procedures.length} procedures within ${radiusInKm}km`);
15599
15729
  const lastDoc = procedures.length < (filters.pagination || 10) ? null : querySnapshot.docs[querySnapshot.docs.length - 1];
15600
15730
  return { procedures, lastDoc };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.8.15",
4
+ "version": "1.8.16",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -1116,8 +1116,9 @@ export class PractitionerService extends BaseService {
1116
1116
 
1117
1117
  // Add other filters that work well with Firestore
1118
1118
  if (filters.certifications && filters.certifications.length > 0) {
1119
+ const certificationsToMatch = filters.certifications as CertificationSpecialty[];
1119
1120
  constraints.push(
1120
- where("certification.specialties", "array-contains-any", filters.certifications)
1121
+ where("certification.specialties", "array-contains-any", certificationsToMatch)
1121
1122
  );
1122
1123
  }
1123
1124
 
@@ -1171,22 +1172,8 @@ export class PractitionerService extends BaseService {
1171
1172
  practitioners = practitioners.slice(0, filters.pagination || 10);
1172
1173
  }
1173
1174
 
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
- }
1175
+ // Apply all remaining client-side filters using centralized function
1176
+ practitioners = this.applyInMemoryFilters(practitioners, filters);
1190
1177
 
1191
1178
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1192
1179
  console.log(`[PRACTITIONER_SERVICE] Strategy 2 success: ${practitioners.length} practitioners`);
@@ -1213,22 +1200,8 @@ export class PractitionerService extends BaseService {
1213
1200
  const querySnapshot = await getDocs(q);
1214
1201
  let practitioners = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Practitioner));
1215
1202
 
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);
1229
- });
1230
- console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${practitioners.length}`);
1231
- }
1203
+ // Apply all client-side filters using centralized function
1204
+ practitioners = this.applyInMemoryFilters(practitioners, filters);
1232
1205
 
1233
1206
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1234
1207
  console.log(`[PRACTITIONER_SERVICE] Strategy 3 success: ${practitioners.length} practitioners`);
@@ -1252,6 +1225,118 @@ export class PractitionerService extends BaseService {
1252
1225
  }
1253
1226
  }
1254
1227
 
1228
+ /**
1229
+ * Applies in-memory filters to practitioners array
1230
+ * Used when Firestore queries fail or for complex filtering
1231
+ */
1232
+ private applyInMemoryFilters(practitioners: Practitioner[], filters: any): Practitioner[] {
1233
+ let filteredPractitioners = [...practitioners]; // Create copy to avoid mutating original
1234
+
1235
+ // Name search filter
1236
+ if (filters.nameSearch && filters.nameSearch.trim()) {
1237
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
1238
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1239
+ const firstName = (practitioner.basicInfo?.firstName || '').toLowerCase();
1240
+ const lastName = (practitioner.basicInfo?.lastName || '').toLowerCase();
1241
+ const fullName = `${firstName} ${lastName}`.trim();
1242
+ const fullNameLower = practitioner.fullNameLower || '';
1243
+
1244
+ return firstName.includes(searchTerm) ||
1245
+ lastName.includes(searchTerm) ||
1246
+ fullName.includes(searchTerm) ||
1247
+ fullNameLower.includes(searchTerm);
1248
+ });
1249
+ console.log(`[PRACTITIONER_SERVICE] Applied name filter, results: ${filteredPractitioners.length}`);
1250
+ }
1251
+
1252
+ // Certifications filtering
1253
+ if (filters.certifications && filters.certifications.length > 0) {
1254
+ const certificationsToMatch = filters.certifications;
1255
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1256
+ const practitionerCerts = practitioner.certification?.specialties || [];
1257
+ return certificationsToMatch.some((cert: any) => practitionerCerts.includes(cert as CertificationSpecialty));
1258
+ });
1259
+ console.log(`[PRACTITIONER_SERVICE] Applied certifications filter, results: ${filteredPractitioners.length}`);
1260
+ }
1261
+
1262
+ // Specialties filtering
1263
+ if (filters.specialties && filters.specialties.length > 0) {
1264
+ const specialtiesToMatch = filters.specialties;
1265
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1266
+ const practitionerSpecs = practitioner.certification?.specialties || [];
1267
+ return specialtiesToMatch.some((spec: any) => practitionerSpecs.includes(spec));
1268
+ });
1269
+ console.log(`[PRACTITIONER_SERVICE] Applied specialties filter, results: ${filteredPractitioners.length}`);
1270
+ }
1271
+
1272
+ // Rating filtering
1273
+ if (filters.minRating !== undefined || filters.maxRating !== undefined) {
1274
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1275
+ const rating = practitioner.reviewInfo?.averageRating || 0;
1276
+ if (filters.minRating !== undefined && rating < filters.minRating) return false;
1277
+ if (filters.maxRating !== undefined && rating > filters.maxRating) return false;
1278
+ return true;
1279
+ });
1280
+ console.log(`[PRACTITIONER_SERVICE] Applied rating filter, results: ${filteredPractitioners.length}`);
1281
+ }
1282
+
1283
+ // Procedure family filtering
1284
+ if (filters.procedureFamily) {
1285
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1286
+ const proceduresInfo = practitioner.proceduresInfo || [];
1287
+ return proceduresInfo.some(proc => proc.family === filters.procedureFamily);
1288
+ });
1289
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure family filter, results: ${filteredPractitioners.length}`);
1290
+ }
1291
+
1292
+ // Procedure category filtering
1293
+ if (filters.procedureCategory) {
1294
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1295
+ const proceduresInfo = practitioner.proceduresInfo || [];
1296
+ return proceduresInfo.some(proc => proc.categoryName === filters.procedureCategory);
1297
+ });
1298
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure category filter, results: ${filteredPractitioners.length}`);
1299
+ }
1300
+
1301
+ // Procedure subcategory filtering
1302
+ if (filters.procedureSubcategory) {
1303
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1304
+ const proceduresInfo = practitioner.proceduresInfo || [];
1305
+ return proceduresInfo.some(proc => proc.subcategoryName === filters.procedureSubcategory);
1306
+ });
1307
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure subcategory filter, results: ${filteredPractitioners.length}`);
1308
+ }
1309
+
1310
+ // Procedure technology filtering
1311
+ if (filters.procedureTechnology) {
1312
+ filteredPractitioners = filteredPractitioners.filter(practitioner => {
1313
+ const proceduresInfo = practitioner.proceduresInfo || [];
1314
+ return proceduresInfo.some(proc => proc.technologyName === filters.procedureTechnology);
1315
+ });
1316
+ console.log(`[PRACTITIONER_SERVICE] Applied procedure technology filter, results: ${filteredPractitioners.length}`);
1317
+ }
1318
+
1319
+ // Geo-radius filter
1320
+ if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
1321
+ const location = filters.location;
1322
+ const radiusInKm = filters.radiusInKm;
1323
+ filteredPractitioners = filteredPractitioners.filter((practitioner) => {
1324
+ const clinics = practitioner.clinicsInfo || [];
1325
+ return clinics.some((clinic) => {
1326
+ const distance = distanceBetween(
1327
+ [location.latitude, location.longitude],
1328
+ [clinic.location.latitude, clinic.location.longitude]
1329
+ );
1330
+ const distanceInKm = distance / 1000;
1331
+ return distanceInKm <= radiusInKm;
1332
+ });
1333
+ });
1334
+ console.log(`[PRACTITIONER_SERVICE] Applied geo filter, results: ${filteredPractitioners.length}`);
1335
+ }
1336
+
1337
+ return filteredPractitioners;
1338
+ }
1339
+
1255
1340
  /**
1256
1341
  * Enables free consultation for a practitioner in a specific clinic
1257
1342
  * Creates a free consultation procedure with hardcoded parameters
@@ -960,7 +960,8 @@ export class ProcedureService extends BaseService {
960
960
  constraints.push(where("reviewInfo.averageRating", "<=", filters.maxRating));
961
961
  }
962
962
  if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
963
- constraints.push(where("treatmentBenefits", "array-contains-any", filters.treatmentBenefits));
963
+ const benefitsToMatch = filters.treatmentBenefits;
964
+ constraints.push(where("treatmentBenefits", "array-contains-any", benefitsToMatch));
964
965
  }
965
966
 
966
967
  return constraints;
@@ -1063,16 +1064,8 @@ export class ProcedureService extends BaseService {
1063
1064
  const querySnapshot = await getDocs(q);
1064
1065
  let procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
1065
1066
 
1066
- // Poboljšan client-side name filtering
1067
- if (filters.nameSearch && filters.nameSearch.trim()) {
1068
- const searchTerm = filters.nameSearch.trim().toLowerCase();
1069
- procedures = procedures.filter(procedure => {
1070
- const name = (procedure.name || '').toLowerCase();
1071
- const nameLower = procedure.nameLower || '';
1072
- return name.includes(searchTerm) || nameLower.includes(searchTerm);
1073
- });
1074
- console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
1075
- }
1067
+ // Apply all client-side filters using centralized function
1068
+ procedures = this.applyInMemoryFilters(procedures, filters);
1076
1069
 
1077
1070
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1078
1071
  console.log(`[PROCEDURE_SERVICE] Strategy 3 success: ${procedures.length} procedures`);
@@ -1099,16 +1092,8 @@ export class ProcedureService extends BaseService {
1099
1092
  const querySnapshot = await getDocs(q);
1100
1093
  let procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
1101
1094
 
1102
- // Poboljšan client-side name filtering
1103
- if (filters.nameSearch && filters.nameSearch.trim()) {
1104
- const searchTerm = filters.nameSearch.trim().toLowerCase();
1105
- procedures = procedures.filter(procedure => {
1106
- const name = (procedure.name || '').toLowerCase();
1107
- const nameLower = procedure.nameLower || '';
1108
- return name.includes(searchTerm) || nameLower.includes(searchTerm);
1109
- });
1110
- console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
1111
- }
1095
+ // Apply all client-side filters using centralized function
1096
+ procedures = this.applyInMemoryFilters(procedures, filters);
1112
1097
 
1113
1098
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1114
1099
  console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
@@ -1132,6 +1117,109 @@ export class ProcedureService extends BaseService {
1132
1117
  }
1133
1118
  }
1134
1119
 
1120
+ /**
1121
+ * Applies in-memory filters to procedures array
1122
+ * Used when Firestore queries fail or for complex filtering
1123
+ */
1124
+ private applyInMemoryFilters(procedures: Procedure[], filters: any): (Procedure & { distance?: number })[] {
1125
+ let filteredProcedures = [...procedures]; // Create copy to avoid mutating original
1126
+
1127
+ // Name search filter
1128
+ if (filters.nameSearch && filters.nameSearch.trim()) {
1129
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
1130
+ filteredProcedures = filteredProcedures.filter(procedure => {
1131
+ const name = (procedure.name || '').toLowerCase();
1132
+ const nameLower = procedure.nameLower || '';
1133
+ return name.includes(searchTerm) || nameLower.includes(searchTerm);
1134
+ });
1135
+ console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${filteredProcedures.length}`);
1136
+ }
1137
+
1138
+ // Price filtering
1139
+ if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
1140
+ filteredProcedures = filteredProcedures.filter(procedure => {
1141
+ const price = procedure.price || 0;
1142
+ if (filters.minPrice !== undefined && price < filters.minPrice) return false;
1143
+ if (filters.maxPrice !== undefined && price > filters.maxPrice) return false;
1144
+ return true;
1145
+ });
1146
+ console.log(`[PROCEDURE_SERVICE] Applied price filter (${filters.minPrice}-${filters.maxPrice}), results: ${filteredProcedures.length}`);
1147
+ }
1148
+
1149
+ // Rating filtering
1150
+ if (filters.minRating !== undefined || filters.maxRating !== undefined) {
1151
+ filteredProcedures = filteredProcedures.filter(procedure => {
1152
+ const rating = procedure.reviewInfo?.averageRating || 0;
1153
+ if (filters.minRating !== undefined && rating < filters.minRating) return false;
1154
+ if (filters.maxRating !== undefined && rating > filters.maxRating) return false;
1155
+ return true;
1156
+ });
1157
+ console.log(`[PROCEDURE_SERVICE] Applied rating filter, results: ${filteredProcedures.length}`);
1158
+ }
1159
+
1160
+ // Treatment benefits filtering
1161
+ if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
1162
+ const benefitsToMatch = filters.treatmentBenefits;
1163
+ filteredProcedures = filteredProcedures.filter(procedure => {
1164
+ const procedureBenefits = procedure.treatmentBenefits || [];
1165
+ return benefitsToMatch.some((benefit: any) => procedureBenefits.includes(benefit));
1166
+ });
1167
+ console.log(`[PROCEDURE_SERVICE] Applied benefits filter, results: ${filteredProcedures.length}`);
1168
+ }
1169
+
1170
+ // Procedure family filtering
1171
+ if (filters.procedureFamily) {
1172
+ filteredProcedures = filteredProcedures.filter(procedure => procedure.family === filters.procedureFamily);
1173
+ console.log(`[PROCEDURE_SERVICE] Applied family filter, results: ${filteredProcedures.length}`);
1174
+ }
1175
+
1176
+ // Category filtering
1177
+ if (filters.procedureCategory) {
1178
+ filteredProcedures = filteredProcedures.filter(procedure => procedure.category?.id === filters.procedureCategory);
1179
+ console.log(`[PROCEDURE_SERVICE] Applied category filter, results: ${filteredProcedures.length}`);
1180
+ }
1181
+
1182
+ // Subcategory filtering
1183
+ if (filters.procedureSubcategory) {
1184
+ filteredProcedures = filteredProcedures.filter(procedure => procedure.subcategory?.id === filters.procedureSubcategory);
1185
+ console.log(`[PROCEDURE_SERVICE] Applied subcategory filter, results: ${filteredProcedures.length}`);
1186
+ }
1187
+
1188
+ // Technology filtering
1189
+ if (filters.procedureTechnology) {
1190
+ filteredProcedures = filteredProcedures.filter(procedure => procedure.technology?.id === filters.procedureTechnology);
1191
+ console.log(`[PROCEDURE_SERVICE] Applied technology filter, results: ${filteredProcedures.length}`);
1192
+ }
1193
+
1194
+ // Geo-radius filter
1195
+ if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
1196
+ const location = filters.location;
1197
+ const radiusInKm = filters.radiusInKm;
1198
+ filteredProcedures = filteredProcedures.filter(procedure => {
1199
+ const clinicLocation = procedure.clinicInfo?.location;
1200
+ if (!clinicLocation?.latitude || !clinicLocation?.longitude) {
1201
+ return false;
1202
+ }
1203
+
1204
+ const distance = distanceBetween(
1205
+ [location.latitude, location.longitude],
1206
+ [clinicLocation.latitude, clinicLocation.longitude]
1207
+ ) / 1000; // Convert to km
1208
+
1209
+ // Attach distance for frontend sorting/display
1210
+ (procedure as any).distance = distance;
1211
+
1212
+ return distance <= radiusInKm;
1213
+ });
1214
+ console.log(`[PROCEDURE_SERVICE] Applied geo filter, results: ${filteredProcedures.length}`);
1215
+
1216
+ // Sort by distance when geo filtering is applied
1217
+ filteredProcedures.sort((a, b) => ((a as any).distance || 0) - ((b as any).distance || 0));
1218
+ }
1219
+
1220
+ return filteredProcedures as (Procedure & { distance?: number })[];
1221
+ }
1222
+
1135
1223
  private handleGeoQuery(filters: any): Promise<{ procedures: (Procedure & { distance?: number })[]; lastDoc: any }> {
1136
1224
  console.log('[PROCEDURE_SERVICE] Executing geo query with enhanced debugging');
1137
1225
 
@@ -1160,29 +1248,8 @@ export class ProcedureService extends BaseService {
1160
1248
  return getDocs(q).then(querySnapshot => {
1161
1249
  let procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
1162
1250
 
1163
- // Apply geo filtering
1164
- procedures = procedures.filter(procedure => {
1165
- const clinicLocation = procedure.clinicInfo?.location;
1166
- if (!clinicLocation?.latitude || !clinicLocation?.longitude) {
1167
- return false;
1168
- }
1169
-
1170
- const distance = distanceBetween(
1171
- [location.latitude, location.longitude],
1172
- [clinicLocation.latitude, clinicLocation.longitude]
1173
- ) / 1000; // Convert to km
1174
-
1175
- // Add distance to procedure object
1176
- (procedure as any).distance = distance;
1177
-
1178
- return distance <= radiusInKm;
1179
- });
1180
-
1181
- // Sort by distance
1182
- procedures.sort((a, b) => ((a as any).distance || 0) - ((b as any).distance || 0));
1183
-
1184
- // Limit to pagination size
1185
- procedures = procedures.slice(0, filters.pagination || 10);
1251
+ // Apply all filters using centralized function (includes geo filtering)
1252
+ procedures = this.applyInMemoryFilters(procedures, filters);
1186
1253
 
1187
1254
  console.log(`[PROCEDURE_SERVICE] Geo query success: ${procedures.length} procedures within ${radiusInKm}km`);
1188
1255