@blackcode_sa/metaestetics-api 1.12.67 → 1.12.69

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.
@@ -1088,6 +1088,14 @@ export class ProcedureService extends BaseService {
1088
1088
  console.log('[PROCEDURE_SERVICE] Strategy 1: Trying nameLower search');
1089
1089
  const searchTerm = filters.nameSearch.trim().toLowerCase();
1090
1090
  const constraints = getBaseConstraints();
1091
+
1092
+ // Check if we have nested field filters that might conflict with orderBy
1093
+ const hasNestedFilters = !!(filters.procedureTechnology || filters.procedureCategory || filters.procedureSubcategory);
1094
+
1095
+ if (hasNestedFilters) {
1096
+ console.log('[PROCEDURE_SERVICE] Strategy 1: Has nested filters, will apply client-side after query');
1097
+ }
1098
+
1091
1099
  constraints.push(where('nameLower', '>=', searchTerm));
1092
1100
  constraints.push(where('nameLower', '<=', searchTerm + '\uf8ff'));
1093
1101
  constraints.push(orderBy('nameLower'));
@@ -1105,9 +1113,15 @@ export class ProcedureService extends BaseService {
1105
1113
 
1106
1114
  const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1107
1115
  const querySnapshot = await getDocs(q);
1108
- const procedures = querySnapshot.docs.map(
1116
+ let procedures = querySnapshot.docs.map(
1109
1117
  doc => ({ ...doc.data(), id: doc.id } as Procedure),
1110
1118
  );
1119
+
1120
+ // Apply client-side filters for nested fields if needed
1121
+ if (hasNestedFilters) {
1122
+ procedures = this.applyInMemoryFilters(procedures, filters);
1123
+ }
1124
+
1111
1125
  const lastDoc =
1112
1126
  querySnapshot.docs.length > 0
1113
1127
  ? querySnapshot.docs[querySnapshot.docs.length - 1]
@@ -1131,6 +1145,14 @@ export class ProcedureService extends BaseService {
1131
1145
  console.log('[PROCEDURE_SERVICE] Strategy 2: Trying name field search');
1132
1146
  const searchTerm = filters.nameSearch.trim().toLowerCase();
1133
1147
  const constraints = getBaseConstraints();
1148
+
1149
+ // Check if we have nested field filters that might conflict with orderBy
1150
+ const hasNestedFilters = !!(filters.procedureTechnology || filters.procedureCategory || filters.procedureSubcategory);
1151
+
1152
+ if (hasNestedFilters) {
1153
+ console.log('[PROCEDURE_SERVICE] Strategy 2: Has nested filters, will apply client-side after query');
1154
+ }
1155
+
1134
1156
  constraints.push(where('name', '>=', searchTerm));
1135
1157
  constraints.push(where('name', '<=', searchTerm + '\uf8ff'));
1136
1158
  constraints.push(orderBy('name'));
@@ -1148,9 +1170,15 @@ export class ProcedureService extends BaseService {
1148
1170
 
1149
1171
  const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1150
1172
  const querySnapshot = await getDocs(q);
1151
- const procedures = querySnapshot.docs.map(
1173
+ let procedures = querySnapshot.docs.map(
1152
1174
  doc => ({ ...doc.data(), id: doc.id } as Procedure),
1153
1175
  );
1176
+
1177
+ // Apply client-side filters for nested fields if needed
1178
+ if (hasNestedFilters) {
1179
+ procedures = this.applyInMemoryFilters(procedures, filters);
1180
+ }
1181
+
1154
1182
  const lastDoc =
1155
1183
  querySnapshot.docs.length > 0
1156
1184
  ? querySnapshot.docs[querySnapshot.docs.length - 1]
@@ -1169,11 +1197,66 @@ export class ProcedureService extends BaseService {
1169
1197
  }
1170
1198
 
1171
1199
  // Strategy 3: orderBy createdAt with client-side filtering
1200
+ // NOTE: This strategy excludes nested field filters (technology.id, category.id, subcategory.id)
1201
+ // from Firestore query because Firestore doesn't support orderBy on different field
1202
+ // when using where on nested fields without a composite index.
1203
+ // These filters are applied client-side instead.
1172
1204
  try {
1173
1205
  console.log(
1174
1206
  '[PROCEDURE_SERVICE] Strategy 3: Using createdAt orderBy with client-side filtering',
1207
+ {
1208
+ procedureTechnology: filters.procedureTechnology,
1209
+ hasTechnologyFilter: !!filters.procedureTechnology,
1210
+ },
1211
+ );
1212
+
1213
+ // Build constraints WITHOUT nested field filters (these will be applied client-side)
1214
+ const constraints: QueryConstraint[] = [];
1215
+
1216
+ // Active status filter
1217
+ if (filters.isActive !== undefined) {
1218
+ constraints.push(where('isActive', '==', filters.isActive));
1219
+ } else {
1220
+ constraints.push(where('isActive', '==', true));
1221
+ }
1222
+
1223
+ // Only include non-nested field filters in Firestore query
1224
+ if (filters.procedureFamily) {
1225
+ constraints.push(where('family', '==', filters.procedureFamily));
1226
+ }
1227
+ if (filters.practitionerId) {
1228
+ constraints.push(where('practitionerId', '==', filters.practitionerId));
1229
+ }
1230
+ if (filters.clinicId) {
1231
+ constraints.push(where('clinicBranchId', '==', filters.clinicId));
1232
+ }
1233
+ if (filters.minPrice !== undefined) {
1234
+ constraints.push(where('price', '>=', filters.minPrice));
1235
+ }
1236
+ if (filters.maxPrice !== undefined) {
1237
+ constraints.push(where('price', '<=', filters.maxPrice));
1238
+ }
1239
+ if (filters.minRating !== undefined) {
1240
+ constraints.push(where('reviewInfo.averageRating', '>=', filters.minRating));
1241
+ }
1242
+ if (filters.maxRating !== undefined) {
1243
+ constraints.push(where('reviewInfo.averageRating', '<=', filters.maxRating));
1244
+ }
1245
+ if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
1246
+ const benefitIdsToMatch = filters.treatmentBenefits;
1247
+ constraints.push(where('treatmentBenefitIds', 'array-contains-any', benefitIdsToMatch));
1248
+ }
1249
+
1250
+ // NOTE: We intentionally EXCLUDE these nested field filters from Firestore query:
1251
+ // - filters.procedureTechnology (technology.id)
1252
+ // - filters.procedureCategory (category.id)
1253
+ // - filters.procedureSubcategory (subcategory.id)
1254
+ // These will be applied client-side in applyInMemoryFilters
1255
+
1256
+ console.log(
1257
+ '[PROCEDURE_SERVICE] Strategy 3 Firestore constraints (nested filters excluded):',
1258
+ constraints.map(c => (c as any).fieldPath || 'unknown'),
1175
1259
  );
1176
- const constraints = getBaseConstraints();
1177
1260
  constraints.push(orderBy('createdAt', 'desc'));
1178
1261
 
1179
1262
  if (filters.lastDoc) {
@@ -1194,7 +1277,20 @@ export class ProcedureService extends BaseService {
1194
1277
  );
1195
1278
 
1196
1279
  // Apply all client-side filters using centralized function
1280
+ console.log('[PROCEDURE_SERVICE] Before applyInMemoryFilters (Strategy 3):', {
1281
+ procedureCount: procedures.length,
1282
+ procedureTechnology: filters.procedureTechnology,
1283
+ filtersObject: {
1284
+ procedureTechnology: filters.procedureTechnology,
1285
+ procedureFamily: filters.procedureFamily,
1286
+ procedureCategory: filters.procedureCategory,
1287
+ procedureSubcategory: filters.procedureSubcategory,
1288
+ },
1289
+ });
1197
1290
  procedures = this.applyInMemoryFilters(procedures, filters);
1291
+ console.log('[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):', {
1292
+ procedureCount: procedures.length,
1293
+ });
1198
1294
 
1199
1295
  const lastDoc =
1200
1296
  querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
@@ -1265,6 +1361,14 @@ export class ProcedureService extends BaseService {
1265
1361
  ): (Procedure & { distance?: number })[] {
1266
1362
  let filteredProcedures = [...procedures]; // Create copy to avoid mutating original
1267
1363
 
1364
+ // Debug: Log what filters we received
1365
+ console.log('[PROCEDURE_SERVICE] applyInMemoryFilters called:', {
1366
+ procedureCount: procedures.length,
1367
+ procedureTechnology: filters.procedureTechnology,
1368
+ hasTechnologyFilter: !!filters.procedureTechnology,
1369
+ allFilterKeys: Object.keys(filters).filter(k => filters[k] !== undefined && filters[k] !== null),
1370
+ });
1371
+
1268
1372
  // Name search filter
1269
1373
  if (filters.nameSearch && filters.nameSearch.trim()) {
1270
1374
  const searchTerm = filters.nameSearch.trim().toLowerCase();
@@ -1348,12 +1452,21 @@ export class ProcedureService extends BaseService {
1348
1452
 
1349
1453
  // Technology filtering
1350
1454
  if (filters.procedureTechnology) {
1455
+ const beforeCount = filteredProcedures.length;
1351
1456
  filteredProcedures = filteredProcedures.filter(
1352
1457
  procedure => procedure.technology?.id === filters.procedureTechnology,
1353
1458
  );
1354
1459
  console.log(
1355
- `[PROCEDURE_SERVICE] Applied technology filter, results: ${filteredProcedures.length}`,
1460
+ `[PROCEDURE_SERVICE] Applied technology filter (${filters.procedureTechnology}), before: ${beforeCount}, after: ${filteredProcedures.length}`,
1356
1461
  );
1462
+ // Log sample technology IDs for debugging
1463
+ if (beforeCount > filteredProcedures.length) {
1464
+ const filteredOut = procedures
1465
+ .filter(p => p.technology?.id !== filters.procedureTechnology)
1466
+ .slice(0, 3)
1467
+ .map(p => ({ id: p.id, techId: p.technology?.id, name: p.name }));
1468
+ console.log('[PROCEDURE_SERVICE] Filtered out sample procedures:', filteredOut);
1469
+ }
1357
1470
  }
1358
1471
 
1359
1472
  // Practitioner filtering
@@ -1497,9 +1610,9 @@ export class ProcedureService extends BaseService {
1497
1610
  // Get references to related entities (Category, Subcategory, Technology)
1498
1611
  // For consultation, we don't need a product
1499
1612
  const [category, subcategory, technology] = await Promise.all([
1500
- this.categoryService.getById(data.categoryId),
1501
- this.subcategoryService.getById(data.categoryId, data.subcategoryId),
1502
- this.technologyService.getById(data.technologyId),
1613
+ this.categoryService.getByIdInternal(data.categoryId),
1614
+ this.subcategoryService.getByIdInternal(data.categoryId, data.subcategoryId),
1615
+ this.technologyService.getByIdInternal(data.technologyId),
1503
1616
  ]);
1504
1617
 
1505
1618
  if (!category || !subcategory || !technology) {
@@ -21,6 +21,7 @@ export enum NotificationType {
21
21
 
22
22
  // --- Patient Engagement ---
23
23
  REVIEW_REQUEST = "reviewRequest", // Request for patient review post-appointment
24
+ PROCEDURE_RECOMMENDATION = "procedureRecommendation", // Doctor recommended a procedure for follow-up
24
25
 
25
26
  // --- Payment Related (Examples) ---
26
27
  PAYMENT_DUE = "paymentDue",
@@ -231,6 +232,25 @@ export interface ReviewRequestNotification extends BaseNotification {
231
232
  procedureName?: string;
232
233
  }
233
234
 
235
+ /**
236
+ * Notification for when a doctor recommends a procedure for follow-up.
237
+ * Example: "Dr. Smith recommended [Procedure Name] for you. Suggested timeframe: in 2 weeks"
238
+ */
239
+ export interface ProcedureRecommendationNotification extends BaseNotification {
240
+ notificationType: NotificationType.PROCEDURE_RECOMMENDATION;
241
+ appointmentId: string; // The appointment where recommendation was made
242
+ recommendationId: string; // Format: `${appointmentId}:${index}`
243
+ procedureId: string;
244
+ procedureName: string;
245
+ practitionerName: string;
246
+ clinicName: string;
247
+ note?: string; // Doctor's note about the recommendation
248
+ timeframe: {
249
+ value: number;
250
+ unit: 'day' | 'week' | 'month' | 'year';
251
+ };
252
+ }
253
+
234
254
  /**
235
255
  * Generic notification for direct messages or announcements.
236
256
  */
@@ -261,5 +281,6 @@ export type Notification =
261
281
  | FormReminderNotification
262
282
  | FormSubmissionConfirmationNotification
263
283
  | ReviewRequestNotification
284
+ | ProcedureRecommendationNotification
264
285
  | GeneralMessageNotification
265
286
  | PaymentConfirmationNotification;