@blackcode_sa/metaestetics-api 1.12.21 → 1.12.23
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 +7 -3
- package/dist/admin/index.d.ts +7 -3
- package/dist/admin/index.js +406 -14
- package/dist/admin/index.mjs +406 -14
- package/dist/index.d.mts +15 -11
- package/dist/index.d.ts +15 -11
- package/dist/index.js +298 -19
- package/dist/index.mjs +298 -19
- package/package.json +1 -1
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +459 -104
- package/src/services/reviews/README.md +129 -0
- package/src/services/reviews/reviews.service.ts +399 -39
- package/src/types/reviews/index.ts +5 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Reviews Service - Enhanced with Entity Names
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Reviews Service has been enhanced to automatically include entity names (clinic, practitioner, procedure, and patient names) in all review responses. This eliminates the need for frontend applications to make additional API calls to fetch entity details.
|
|
6
|
+
|
|
7
|
+
## Enhanced Methods
|
|
8
|
+
|
|
9
|
+
All review retrieval methods now include entity names from associated appointments:
|
|
10
|
+
|
|
11
|
+
### 1. `getReview(reviewId: string)`
|
|
12
|
+
- **Enhanced**: ✅ Returns single review with entity names
|
|
13
|
+
- **Entity Names**: Clinic name, practitioner name, procedure name, patient name
|
|
14
|
+
- **Source**: Fetched from associated appointment data
|
|
15
|
+
|
|
16
|
+
### 2. `getReviewsByPatient(patientId: string)`
|
|
17
|
+
- **Enhanced**: ✅ Returns patient reviews with entity names
|
|
18
|
+
- **Entity Names**: Clinic name, practitioner name, procedure name, patient name
|
|
19
|
+
- **Use Case**: Patient app - "My Reviews" screen
|
|
20
|
+
|
|
21
|
+
### 3. `getReviewsByPractitioner(practitionerId: string)`
|
|
22
|
+
- **Enhanced**: ✅ Returns practitioner reviews with entity names
|
|
23
|
+
- **Entity Names**: Clinic name, practitioner name, procedure name, patient name
|
|
24
|
+
- **Use Case**: Doctor app - "Reviews" screen
|
|
25
|
+
|
|
26
|
+
### 4. `getReviewsByClinic(clinicId: string)`
|
|
27
|
+
- **Enhanced**: ✅ Returns clinic reviews with entity names
|
|
28
|
+
- **Entity Names**: Clinic name, practitioner name, procedure name, patient name
|
|
29
|
+
- **Use Case**: Clinic dashboard - "Reviews" section
|
|
30
|
+
|
|
31
|
+
### 5. `getReviewsByProcedure(procedureId: string)`
|
|
32
|
+
- **Enhanced**: ✅ Returns procedure reviews with entity names
|
|
33
|
+
- **Entity Names**: Clinic name, practitioner name, procedure name, patient name
|
|
34
|
+
- **Use Case**: Procedure analytics and reporting
|
|
35
|
+
|
|
36
|
+
## Enhanced Review Structure
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
interface Review {
|
|
40
|
+
id: string;
|
|
41
|
+
appointmentId: string;
|
|
42
|
+
patientId: string;
|
|
43
|
+
patientName?: string; // 🆕 Enhanced field
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
updatedAt: Date;
|
|
46
|
+
clinicReview?: {
|
|
47
|
+
// ... existing fields
|
|
48
|
+
clinicName?: string; // 🆕 Enhanced field
|
|
49
|
+
};
|
|
50
|
+
practitionerReview?: {
|
|
51
|
+
// ... existing fields
|
|
52
|
+
practitionerName?: string; // 🆕 Enhanced field
|
|
53
|
+
};
|
|
54
|
+
procedureReview?: {
|
|
55
|
+
// ... existing fields
|
|
56
|
+
procedureName?: string; // 🆕 Enhanced field
|
|
57
|
+
};
|
|
58
|
+
overallComment: string;
|
|
59
|
+
overallRating: number;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Data Sources
|
|
64
|
+
|
|
65
|
+
Entity names are fetched from the associated appointment's aggregated information:
|
|
66
|
+
|
|
67
|
+
- **Clinic Name**: `appointment.clinicInfo.name`
|
|
68
|
+
- **Practitioner Name**: `appointment.practitionerInfo.name`
|
|
69
|
+
- **Procedure Name**: `appointment.procedureInfo.name`
|
|
70
|
+
- **Patient Name**: `appointment.patientInfo.fullName`
|
|
71
|
+
|
|
72
|
+
## Error Handling
|
|
73
|
+
|
|
74
|
+
- If appointment is not found, review is returned without enhancement
|
|
75
|
+
- Individual enhancement failures are logged but don't break the entire operation
|
|
76
|
+
- Graceful fallback ensures API reliability
|
|
77
|
+
|
|
78
|
+
## Logging
|
|
79
|
+
|
|
80
|
+
Enhanced logging provides detailed information about:
|
|
81
|
+
- Query parameters
|
|
82
|
+
- Number of reviews found
|
|
83
|
+
- Enhancement success/failure
|
|
84
|
+
- Entity names availability
|
|
85
|
+
|
|
86
|
+
## Performance Considerations
|
|
87
|
+
|
|
88
|
+
- Each review requires one additional Firestore read for appointment data
|
|
89
|
+
- Enhancement is performed in parallel for multiple reviews
|
|
90
|
+
- Consider caching strategies for high-volume applications
|
|
91
|
+
|
|
92
|
+
## Migration Notes
|
|
93
|
+
|
|
94
|
+
- **Backward Compatible**: Existing code continues to work
|
|
95
|
+
- **Optional Fields**: All enhanced fields are optional
|
|
96
|
+
- **No Breaking Changes**: API signature remains the same
|
|
97
|
+
|
|
98
|
+
## Usage Examples
|
|
99
|
+
|
|
100
|
+
### Doctor App - Reviews Screen
|
|
101
|
+
```typescript
|
|
102
|
+
const reviews = await reviewService.getReviewsByPractitioner(practitionerId);
|
|
103
|
+
// Now includes practitioner names, clinic names, procedure names, and patient names
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Patient App - My Reviews
|
|
107
|
+
```typescript
|
|
108
|
+
const reviews = await reviewService.getReviewsByPatient(patientId);
|
|
109
|
+
// Now includes all entity names for better UX
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Clinic Dashboard
|
|
113
|
+
```typescript
|
|
114
|
+
const reviews = await reviewService.getReviewsByClinic(clinicId);
|
|
115
|
+
// Now includes practitioner and procedure names for each review
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Testing
|
|
119
|
+
|
|
120
|
+
All enhanced methods include comprehensive logging for debugging:
|
|
121
|
+
- Input parameters
|
|
122
|
+
- Query results
|
|
123
|
+
- Enhancement status
|
|
124
|
+
- Entity name availability
|
|
125
|
+
|
|
126
|
+
Monitor logs with:
|
|
127
|
+
```bash
|
|
128
|
+
firebase functions:log --only reviewService
|
|
129
|
+
```
|
|
@@ -8,23 +8,21 @@ import {
|
|
|
8
8
|
setDoc,
|
|
9
9
|
deleteDoc,
|
|
10
10
|
serverTimestamp,
|
|
11
|
-
} from
|
|
12
|
-
import { BaseService } from
|
|
11
|
+
} from 'firebase/firestore';
|
|
12
|
+
import { BaseService } from '../base.service';
|
|
13
13
|
import {
|
|
14
14
|
Review,
|
|
15
15
|
ClinicReview,
|
|
16
16
|
PractitionerReview,
|
|
17
17
|
ProcedureReview,
|
|
18
18
|
REVIEWS_COLLECTION,
|
|
19
|
-
} from
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { Firestore } from "firebase/firestore";
|
|
27
|
-
import { FirebaseApp } from "firebase/app";
|
|
19
|
+
} from '../../types/reviews';
|
|
20
|
+
import { createReviewSchema, reviewSchema } from '../../validations/reviews.schema';
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import { Auth } from 'firebase/auth';
|
|
23
|
+
import { Firestore } from 'firebase/firestore';
|
|
24
|
+
import { FirebaseApp } from 'firebase/app';
|
|
25
|
+
import { Appointment, APPOINTMENTS_COLLECTION } from '../../types/appointment';
|
|
28
26
|
|
|
29
27
|
export class ReviewService extends BaseService {
|
|
30
28
|
constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
|
|
@@ -38,13 +36,20 @@ export class ReviewService extends BaseService {
|
|
|
38
36
|
* @returns The created review
|
|
39
37
|
*/
|
|
40
38
|
async createReview(
|
|
41
|
-
data: Omit<
|
|
42
|
-
|
|
43
|
-
"id" | "createdAt" | "updatedAt" | "appointmentId" | "overallRating"
|
|
44
|
-
>,
|
|
45
|
-
appointmentId: string
|
|
39
|
+
data: Omit<Review, 'id' | 'createdAt' | 'updatedAt' | 'appointmentId' | 'overallRating'>,
|
|
40
|
+
appointmentId: string,
|
|
46
41
|
): Promise<Review> {
|
|
47
42
|
try {
|
|
43
|
+
console.log('🔍 ReviewService.createReview - Input data:', {
|
|
44
|
+
appointmentId,
|
|
45
|
+
hasClinicReview: !!data.clinicReview,
|
|
46
|
+
hasPractitionerReview: !!data.practitionerReview,
|
|
47
|
+
hasProcedureReview: !!data.procedureReview,
|
|
48
|
+
practitionerId: data.practitionerReview?.practitionerId,
|
|
49
|
+
clinicId: data.clinicReview?.clinicId,
|
|
50
|
+
procedureId: data.procedureReview?.procedureId,
|
|
51
|
+
});
|
|
52
|
+
|
|
48
53
|
// Validate input data
|
|
49
54
|
const validatedData = createReviewSchema.parse(data);
|
|
50
55
|
|
|
@@ -137,6 +142,13 @@ export class ReviewService extends BaseService {
|
|
|
137
142
|
updatedAt: serverTimestamp(),
|
|
138
143
|
});
|
|
139
144
|
|
|
145
|
+
console.log('✅ ReviewService.createReview - Review saved to Firestore:', {
|
|
146
|
+
reviewId,
|
|
147
|
+
practitionerId: review.practitionerReview?.practitionerId,
|
|
148
|
+
clinicId: review.clinicReview?.clinicId,
|
|
149
|
+
procedureId: review.procedureReview?.procedureId,
|
|
150
|
+
});
|
|
151
|
+
|
|
140
152
|
// Note: Related entity updates (clinic, practitioner, procedure) are now handled
|
|
141
153
|
// by cloud functions through the ReviewsAggregationService
|
|
142
154
|
|
|
@@ -150,75 +162,423 @@ export class ReviewService extends BaseService {
|
|
|
150
162
|
}
|
|
151
163
|
|
|
152
164
|
/**
|
|
153
|
-
* Gets a review by ID
|
|
165
|
+
* Gets a review by ID with enhanced entity names
|
|
154
166
|
* @param reviewId The ID of the review to get
|
|
155
|
-
* @returns The review if found, null otherwise
|
|
167
|
+
* @returns The review with entity names if found, null otherwise
|
|
156
168
|
*/
|
|
157
169
|
async getReview(reviewId: string): Promise<Review | null> {
|
|
170
|
+
console.log('🔍 ReviewService.getReview - Getting review:', reviewId);
|
|
171
|
+
|
|
158
172
|
const docRef = doc(this.db, REVIEWS_COLLECTION, reviewId);
|
|
159
173
|
const docSnap = await getDoc(docRef);
|
|
160
174
|
|
|
161
175
|
if (!docSnap.exists()) {
|
|
176
|
+
console.log('❌ ReviewService.getReview - Review not found:', reviewId);
|
|
162
177
|
return null;
|
|
163
178
|
}
|
|
164
179
|
|
|
165
|
-
|
|
180
|
+
const review = { ...docSnap.data(), id: reviewId } as Review;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Fetch the associated appointment to enhance with entity names
|
|
184
|
+
const appointmentDoc = await getDoc(
|
|
185
|
+
doc(this.db, APPOINTMENTS_COLLECTION, review.appointmentId),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (appointmentDoc.exists()) {
|
|
189
|
+
const appointment = appointmentDoc.data() as Appointment;
|
|
190
|
+
|
|
191
|
+
// Create enhanced review with entity names
|
|
192
|
+
const enhancedReview = { ...review };
|
|
193
|
+
|
|
194
|
+
if (enhancedReview.clinicReview && appointment.clinicInfo) {
|
|
195
|
+
enhancedReview.clinicReview = {
|
|
196
|
+
...enhancedReview.clinicReview,
|
|
197
|
+
clinicName: appointment.clinicInfo.name,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (enhancedReview.practitionerReview && appointment.practitionerInfo) {
|
|
202
|
+
enhancedReview.practitionerReview = {
|
|
203
|
+
...enhancedReview.practitionerReview,
|
|
204
|
+
practitionerName: appointment.practitionerInfo.name,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (enhancedReview.procedureReview && appointment.procedureInfo) {
|
|
209
|
+
enhancedReview.procedureReview = {
|
|
210
|
+
...enhancedReview.procedureReview,
|
|
211
|
+
procedureName: appointment.procedureInfo.name,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Add patient name to the main review object
|
|
216
|
+
if (appointment.patientInfo) {
|
|
217
|
+
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log('✅ ReviewService.getReview - Enhanced review:', {
|
|
221
|
+
reviewId,
|
|
222
|
+
hasEntityNames: !!(
|
|
223
|
+
enhancedReview.clinicReview?.clinicName ||
|
|
224
|
+
enhancedReview.practitionerReview?.practitionerName ||
|
|
225
|
+
enhancedReview.procedureReview?.procedureName ||
|
|
226
|
+
enhancedReview.patientName
|
|
227
|
+
),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return enhancedReview;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log('⚠️ ReviewService.getReview - Appointment not found for review:', reviewId);
|
|
234
|
+
return review;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.warn(`Failed to enhance review ${reviewId} with entity names:`, error);
|
|
237
|
+
return review;
|
|
238
|
+
}
|
|
166
239
|
}
|
|
167
240
|
|
|
168
241
|
/**
|
|
169
|
-
* Gets all reviews for a specific patient
|
|
242
|
+
* Gets all reviews for a specific patient with enhanced entity names
|
|
170
243
|
* @param patientId The ID of the patient
|
|
171
|
-
* @returns Array of reviews for the patient
|
|
244
|
+
* @returns Array of reviews for the patient with clinic, practitioner, and procedure names
|
|
172
245
|
*/
|
|
173
246
|
async getReviewsByPatient(patientId: string): Promise<Review[]> {
|
|
174
|
-
const q = query(
|
|
175
|
-
collection(this.db, REVIEWS_COLLECTION),
|
|
176
|
-
where("patientId", "==", patientId)
|
|
177
|
-
);
|
|
247
|
+
const q = query(collection(this.db, REVIEWS_COLLECTION), where('patientId', '==', patientId));
|
|
178
248
|
const snapshot = await getDocs(q);
|
|
179
|
-
|
|
249
|
+
const reviews = snapshot.docs.map(doc => doc.data() as Review);
|
|
250
|
+
|
|
251
|
+
// Enhance reviews with entity names from appointments
|
|
252
|
+
const enhancedReviews = await Promise.all(
|
|
253
|
+
reviews.map(async review => {
|
|
254
|
+
try {
|
|
255
|
+
// Fetch the associated appointment
|
|
256
|
+
const appointmentDoc = await getDoc(
|
|
257
|
+
doc(this.db, APPOINTMENTS_COLLECTION, review.appointmentId),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (appointmentDoc.exists()) {
|
|
261
|
+
const appointment = appointmentDoc.data() as Appointment;
|
|
262
|
+
|
|
263
|
+
// Create enhanced review with entity names
|
|
264
|
+
const enhancedReview = { ...review };
|
|
265
|
+
|
|
266
|
+
if (enhancedReview.clinicReview && appointment.clinicInfo) {
|
|
267
|
+
enhancedReview.clinicReview = {
|
|
268
|
+
...enhancedReview.clinicReview,
|
|
269
|
+
clinicName: appointment.clinicInfo.name,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (enhancedReview.practitionerReview && appointment.practitionerInfo) {
|
|
274
|
+
enhancedReview.practitionerReview = {
|
|
275
|
+
...enhancedReview.practitionerReview,
|
|
276
|
+
practitionerName: appointment.practitionerInfo.name,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (enhancedReview.procedureReview && appointment.procedureInfo) {
|
|
281
|
+
enhancedReview.procedureReview = {
|
|
282
|
+
...enhancedReview.procedureReview,
|
|
283
|
+
procedureName: appointment.procedureInfo.name,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add patient name to the main review object
|
|
288
|
+
if (appointment.patientInfo) {
|
|
289
|
+
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return enhancedReview;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return review;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.warn(`Failed to enhance review ${review.id} with entity names:`, error);
|
|
298
|
+
return review;
|
|
299
|
+
}
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
return enhancedReviews;
|
|
180
304
|
}
|
|
181
305
|
|
|
182
306
|
/**
|
|
183
|
-
* Gets all reviews for a specific clinic
|
|
307
|
+
* Gets all reviews for a specific clinic with enhanced entity names
|
|
184
308
|
* @param clinicId The ID of the clinic
|
|
185
|
-
* @returns Array of reviews containing clinic reviews
|
|
309
|
+
* @returns Array of reviews containing clinic reviews with clinic, practitioner, and procedure names
|
|
186
310
|
*/
|
|
187
311
|
async getReviewsByClinic(clinicId: string): Promise<Review[]> {
|
|
312
|
+
console.log('🔍 ReviewService.getReviewsByClinic - Querying for clinic:', clinicId);
|
|
313
|
+
|
|
188
314
|
const q = query(
|
|
189
315
|
collection(this.db, REVIEWS_COLLECTION),
|
|
190
|
-
where(
|
|
316
|
+
where('clinicReview.clinicId', '==', clinicId),
|
|
191
317
|
);
|
|
192
318
|
const snapshot = await getDocs(q);
|
|
193
|
-
|
|
319
|
+
const reviews = snapshot.docs.map(doc => {
|
|
320
|
+
const data = doc.data() as Review;
|
|
321
|
+
return { ...data, id: doc.id };
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
console.log('🔍 ReviewService.getReviewsByClinic - Found reviews before enhancement:', {
|
|
325
|
+
clinicId,
|
|
326
|
+
reviewCount: reviews.length,
|
|
327
|
+
reviewIds: reviews.map(r => r.id),
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Enhance reviews with entity names from appointments
|
|
331
|
+
const enhancedReviews = await Promise.all(
|
|
332
|
+
reviews.map(async review => {
|
|
333
|
+
try {
|
|
334
|
+
// Fetch the associated appointment
|
|
335
|
+
const appointmentDoc = await getDoc(
|
|
336
|
+
doc(this.db, APPOINTMENTS_COLLECTION, review.appointmentId),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (appointmentDoc.exists()) {
|
|
340
|
+
const appointment = appointmentDoc.data() as Appointment;
|
|
341
|
+
|
|
342
|
+
// Create enhanced review with entity names
|
|
343
|
+
const enhancedReview = { ...review };
|
|
344
|
+
|
|
345
|
+
if (enhancedReview.clinicReview && appointment.clinicInfo) {
|
|
346
|
+
enhancedReview.clinicReview = {
|
|
347
|
+
...enhancedReview.clinicReview,
|
|
348
|
+
clinicName: appointment.clinicInfo.name,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (enhancedReview.practitionerReview && appointment.practitionerInfo) {
|
|
353
|
+
enhancedReview.practitionerReview = {
|
|
354
|
+
...enhancedReview.practitionerReview,
|
|
355
|
+
practitionerName: appointment.practitionerInfo.name,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (enhancedReview.procedureReview && appointment.procedureInfo) {
|
|
360
|
+
enhancedReview.procedureReview = {
|
|
361
|
+
...enhancedReview.procedureReview,
|
|
362
|
+
procedureName: appointment.procedureInfo.name,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Add patient name to the main review object
|
|
367
|
+
if (appointment.patientInfo) {
|
|
368
|
+
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return enhancedReview;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return review;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.warn(`Failed to enhance review ${review.id} with entity names:`, error);
|
|
377
|
+
return review;
|
|
378
|
+
}
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
console.log('✅ ReviewService.getReviewsByClinic - Enhanced reviews:', {
|
|
383
|
+
clinicId,
|
|
384
|
+
reviewCount: enhancedReviews.length,
|
|
385
|
+
reviewIds: enhancedReviews.map(r => r.id),
|
|
386
|
+
hasEntityNames: enhancedReviews.some(
|
|
387
|
+
r =>
|
|
388
|
+
r.clinicReview?.clinicName ||
|
|
389
|
+
r.practitionerReview?.practitionerName ||
|
|
390
|
+
r.procedureReview?.procedureName ||
|
|
391
|
+
r.patientName,
|
|
392
|
+
),
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return enhancedReviews;
|
|
194
396
|
}
|
|
195
397
|
|
|
196
398
|
/**
|
|
197
|
-
* Gets all reviews for a specific practitioner
|
|
399
|
+
* Gets all reviews for a specific practitioner with enhanced entity names
|
|
198
400
|
* @param practitionerId The ID of the practitioner
|
|
199
|
-
* @returns Array of reviews containing practitioner reviews
|
|
401
|
+
* @returns Array of reviews containing practitioner reviews with clinic, practitioner, and procedure names
|
|
200
402
|
*/
|
|
201
403
|
async getReviewsByPractitioner(practitionerId: string): Promise<Review[]> {
|
|
404
|
+
console.log(
|
|
405
|
+
'🔍 ReviewService.getReviewsByPractitioner - Querying for practitioner:',
|
|
406
|
+
practitionerId,
|
|
407
|
+
);
|
|
408
|
+
|
|
202
409
|
const q = query(
|
|
203
410
|
collection(this.db, REVIEWS_COLLECTION),
|
|
204
|
-
where(
|
|
411
|
+
where('practitionerReview.practitionerId', '==', practitionerId),
|
|
205
412
|
);
|
|
206
413
|
const snapshot = await getDocs(q);
|
|
207
|
-
|
|
414
|
+
const reviews = snapshot.docs.map(doc => {
|
|
415
|
+
const data = doc.data() as Review;
|
|
416
|
+
return { ...data, id: doc.id };
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
console.log('🔍 ReviewService.getReviewsByPractitioner - Found reviews before enhancement:', {
|
|
420
|
+
practitionerId,
|
|
421
|
+
reviewCount: reviews.length,
|
|
422
|
+
reviewIds: reviews.map(r => r.id),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Enhance reviews with entity names from appointments
|
|
426
|
+
const enhancedReviews = await Promise.all(
|
|
427
|
+
reviews.map(async review => {
|
|
428
|
+
try {
|
|
429
|
+
// Fetch the associated appointment
|
|
430
|
+
const appointmentDoc = await getDoc(
|
|
431
|
+
doc(this.db, APPOINTMENTS_COLLECTION, review.appointmentId),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
if (appointmentDoc.exists()) {
|
|
435
|
+
const appointment = appointmentDoc.data() as Appointment;
|
|
436
|
+
|
|
437
|
+
// Create enhanced review with entity names
|
|
438
|
+
const enhancedReview = { ...review };
|
|
439
|
+
|
|
440
|
+
if (enhancedReview.clinicReview && appointment.clinicInfo) {
|
|
441
|
+
enhancedReview.clinicReview = {
|
|
442
|
+
...enhancedReview.clinicReview,
|
|
443
|
+
clinicName: appointment.clinicInfo.name,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (enhancedReview.practitionerReview && appointment.practitionerInfo) {
|
|
448
|
+
enhancedReview.practitionerReview = {
|
|
449
|
+
...enhancedReview.practitionerReview,
|
|
450
|
+
practitionerName: appointment.practitionerInfo.name,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (enhancedReview.procedureReview && appointment.procedureInfo) {
|
|
455
|
+
enhancedReview.procedureReview = {
|
|
456
|
+
...enhancedReview.procedureReview,
|
|
457
|
+
procedureName: appointment.procedureInfo.name,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Add patient name to the main review object
|
|
462
|
+
if (appointment.patientInfo) {
|
|
463
|
+
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return enhancedReview;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return review;
|
|
470
|
+
} catch (error) {
|
|
471
|
+
console.warn(`Failed to enhance review ${review.id} with entity names:`, error);
|
|
472
|
+
return review;
|
|
473
|
+
}
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
console.log('✅ ReviewService.getReviewsByPractitioner - Enhanced reviews:', {
|
|
478
|
+
practitionerId,
|
|
479
|
+
reviewCount: enhancedReviews.length,
|
|
480
|
+
reviewIds: enhancedReviews.map(r => r.id),
|
|
481
|
+
hasEntityNames: enhancedReviews.some(
|
|
482
|
+
r =>
|
|
483
|
+
r.clinicReview?.clinicName ||
|
|
484
|
+
r.practitionerReview?.practitionerName ||
|
|
485
|
+
r.procedureReview?.procedureName,
|
|
486
|
+
),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
return enhancedReviews;
|
|
208
490
|
}
|
|
209
491
|
|
|
210
492
|
/**
|
|
211
|
-
* Gets all reviews for a specific procedure
|
|
493
|
+
* Gets all reviews for a specific procedure with enhanced entity names
|
|
212
494
|
* @param procedureId The ID of the procedure
|
|
213
|
-
* @returns Array of reviews containing procedure reviews
|
|
495
|
+
* @returns Array of reviews containing procedure reviews with clinic, practitioner, and procedure names
|
|
214
496
|
*/
|
|
215
497
|
async getReviewsByProcedure(procedureId: string): Promise<Review[]> {
|
|
498
|
+
console.log('🔍 ReviewService.getReviewsByProcedure - Querying for procedure:', procedureId);
|
|
499
|
+
|
|
216
500
|
const q = query(
|
|
217
501
|
collection(this.db, REVIEWS_COLLECTION),
|
|
218
|
-
where(
|
|
502
|
+
where('procedureReview.procedureId', '==', procedureId),
|
|
219
503
|
);
|
|
220
504
|
const snapshot = await getDocs(q);
|
|
221
|
-
|
|
505
|
+
const reviews = snapshot.docs.map(doc => {
|
|
506
|
+
const data = doc.data() as Review;
|
|
507
|
+
return { ...data, id: doc.id };
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
console.log('🔍 ReviewService.getReviewsByProcedure - Found reviews before enhancement:', {
|
|
511
|
+
procedureId,
|
|
512
|
+
reviewCount: reviews.length,
|
|
513
|
+
reviewIds: reviews.map(r => r.id),
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Enhance reviews with entity names from appointments
|
|
517
|
+
const enhancedReviews = await Promise.all(
|
|
518
|
+
reviews.map(async review => {
|
|
519
|
+
try {
|
|
520
|
+
// Fetch the associated appointment
|
|
521
|
+
const appointmentDoc = await getDoc(
|
|
522
|
+
doc(this.db, APPOINTMENTS_COLLECTION, review.appointmentId),
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
if (appointmentDoc.exists()) {
|
|
526
|
+
const appointment = appointmentDoc.data() as Appointment;
|
|
527
|
+
|
|
528
|
+
// Create enhanced review with entity names
|
|
529
|
+
const enhancedReview = { ...review };
|
|
530
|
+
|
|
531
|
+
if (enhancedReview.clinicReview && appointment.clinicInfo) {
|
|
532
|
+
enhancedReview.clinicReview = {
|
|
533
|
+
...enhancedReview.clinicReview,
|
|
534
|
+
clinicName: appointment.clinicInfo.name,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (enhancedReview.practitionerReview && appointment.practitionerInfo) {
|
|
539
|
+
enhancedReview.practitionerReview = {
|
|
540
|
+
...enhancedReview.practitionerReview,
|
|
541
|
+
practitionerName: appointment.practitionerInfo.name,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (enhancedReview.procedureReview && appointment.procedureInfo) {
|
|
546
|
+
enhancedReview.procedureReview = {
|
|
547
|
+
...enhancedReview.procedureReview,
|
|
548
|
+
procedureName: appointment.procedureInfo.name,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Add patient name to the main review object
|
|
553
|
+
if (appointment.patientInfo) {
|
|
554
|
+
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return enhancedReview;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return review;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.warn(`Failed to enhance review ${review.id} with entity names:`, error);
|
|
563
|
+
return review;
|
|
564
|
+
}
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
console.log('✅ ReviewService.getReviewsByProcedure - Enhanced reviews:', {
|
|
569
|
+
procedureId,
|
|
570
|
+
reviewCount: enhancedReviews.length,
|
|
571
|
+
reviewIds: enhancedReviews.map(r => r.id),
|
|
572
|
+
hasEntityNames: enhancedReviews.some(
|
|
573
|
+
r =>
|
|
574
|
+
r.clinicReview?.clinicName ||
|
|
575
|
+
r.practitionerReview?.practitionerName ||
|
|
576
|
+
r.procedureReview?.procedureName ||
|
|
577
|
+
r.patientName,
|
|
578
|
+
),
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
return enhancedReviews;
|
|
222
582
|
}
|
|
223
583
|
|
|
224
584
|
/**
|
|
@@ -229,7 +589,7 @@ export class ReviewService extends BaseService {
|
|
|
229
589
|
async getReviewByAppointment(appointmentId: string): Promise<Review | null> {
|
|
230
590
|
const q = query(
|
|
231
591
|
collection(this.db, REVIEWS_COLLECTION),
|
|
232
|
-
where(
|
|
592
|
+
where('appointmentId', '==', appointmentId),
|
|
233
593
|
);
|
|
234
594
|
const snapshot = await getDocs(q);
|
|
235
595
|
|
|
@@ -22,6 +22,7 @@ interface BaseReview {
|
|
|
22
22
|
*/
|
|
23
23
|
export interface ClinicReview extends BaseReview {
|
|
24
24
|
clinicId: string;
|
|
25
|
+
clinicName?: string; // Enhanced field: clinic name from appointment
|
|
25
26
|
cleanliness: number; // 1-5 stars
|
|
26
27
|
facilities: number; // 1-5 stars
|
|
27
28
|
staffFriendliness: number; // 1-5 stars
|
|
@@ -37,6 +38,7 @@ export interface ClinicReview extends BaseReview {
|
|
|
37
38
|
*/
|
|
38
39
|
export interface PractitionerReview extends BaseReview {
|
|
39
40
|
practitionerId: string;
|
|
41
|
+
practitionerName?: string; // Enhanced field: practitioner name from appointment
|
|
40
42
|
knowledgeAndExpertise: number; // 1-5 stars
|
|
41
43
|
communicationSkills: number; // 1-5 stars
|
|
42
44
|
bedSideManner: number; // 1-5 stars
|
|
@@ -52,6 +54,7 @@ export interface PractitionerReview extends BaseReview {
|
|
|
52
54
|
*/
|
|
53
55
|
export interface ProcedureReview extends BaseReview {
|
|
54
56
|
procedureId: string;
|
|
57
|
+
procedureName?: string; // Enhanced field: procedure name from appointment
|
|
55
58
|
effectivenessOfTreatment: number; // 1-5 stars
|
|
56
59
|
outcomeExplanation: number; // 1-5 stars
|
|
57
60
|
painManagement: number; // 1-5 stars
|
|
@@ -114,6 +117,7 @@ export interface Review {
|
|
|
114
117
|
id: string;
|
|
115
118
|
appointmentId: string;
|
|
116
119
|
patientId: string;
|
|
120
|
+
patientName?: string; // Enhanced field: patient name from appointment
|
|
117
121
|
createdAt: Date;
|
|
118
122
|
updatedAt: Date;
|
|
119
123
|
clinicReview?: ClinicReview;
|
|
@@ -123,4 +127,4 @@ export interface Review {
|
|
|
123
127
|
overallRating: number; // Average of all available ratings
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
export const REVIEWS_COLLECTION =
|
|
130
|
+
export const REVIEWS_COLLECTION = 'reviews';
|