@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.
- package/dist/admin/index.d.mts +34 -1
- package/dist/admin/index.d.ts +34 -1
- package/dist/admin/index.js +104 -0
- package/dist/admin/index.mjs +104 -0
- package/dist/backoffice/index.d.mts +40 -0
- package/dist/backoffice/index.d.ts +40 -0
- package/dist/backoffice/index.js +118 -18
- package/dist/backoffice/index.mjs +118 -20
- package/dist/index.d.mts +61 -2
- package/dist/index.d.ts +61 -2
- package/dist/index.js +249 -37
- package/dist/index.mjs +249 -39
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +140 -0
- package/src/backoffice/services/README.md +17 -0
- package/src/backoffice/services/analytics.service.proposal.md +859 -0
- package/src/backoffice/services/analytics.service.summary.md +143 -0
- package/src/backoffice/services/category.service.ts +49 -6
- package/src/backoffice/services/subcategory.service.ts +50 -6
- package/src/backoffice/services/technology.service.ts +53 -6
- package/src/services/appointment/appointment.service.ts +59 -6
- package/src/services/procedure/procedure.service.ts +120 -7
- package/src/types/notifications/index.ts +21 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
1501
|
-
this.subcategoryService.
|
|
1502
|
-
this.technologyService.
|
|
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;
|