@blackcode_sa/metaestetics-api 1.12.62 → 1.12.64
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 +4 -2
- package/dist/admin/index.d.ts +4 -2
- package/dist/admin/index.js +4 -45
- package/dist/admin/index.mjs +4 -45
- package/dist/backoffice/index.d.mts +86 -1
- package/dist/backoffice/index.d.ts +86 -1
- package/dist/backoffice/index.js +308 -0
- package/dist/backoffice/index.mjs +306 -0
- package/dist/index.d.mts +99 -3
- package/dist/index.d.ts +99 -3
- package/dist/index.js +545 -281
- package/dist/index.mjs +867 -603
- package/package.json +119 -119
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1844 -1844
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +703 -703
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +641 -689
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +75 -75
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/index.ts +3 -3
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +710 -710
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +40 -40
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +318 -318
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +11 -8
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +395 -395
- package/src/backoffice/services/technology.service.ts +1083 -1070
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +62 -62
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +240 -240
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +163 -161
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -163
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2505 -2082
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1683 -1683
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +708 -708
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +13 -13
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +1682 -1682
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +636 -683
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/appointment/index.ts +481 -453
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +489 -489
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +44 -44
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +265 -265
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -273
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +130 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +493 -493
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +20 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -216
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +189 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,683 +1,636 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collection,
|
|
3
|
-
doc,
|
|
4
|
-
getDoc,
|
|
5
|
-
getDocs,
|
|
6
|
-
query,
|
|
7
|
-
where,
|
|
8
|
-
setDoc,
|
|
9
|
-
deleteDoc,
|
|
10
|
-
serverTimestamp,
|
|
11
|
-
} from 'firebase/firestore';
|
|
12
|
-
import { BaseService } from '../base.service';
|
|
13
|
-
import {
|
|
14
|
-
Review,
|
|
15
|
-
ClinicReview,
|
|
16
|
-
PractitionerReview,
|
|
17
|
-
ProcedureReview,
|
|
18
|
-
REVIEWS_COLLECTION,
|
|
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';
|
|
26
|
-
|
|
27
|
-
export class ReviewService extends BaseService {
|
|
28
|
-
constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
|
|
29
|
-
super(db, auth, app);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Creates a new review
|
|
34
|
-
* @param data - The review data to create
|
|
35
|
-
* @param appointmentId - ID of the completed appointment
|
|
36
|
-
* @returns The created review
|
|
37
|
-
*/
|
|
38
|
-
async createReview(
|
|
39
|
-
data: Omit<Review, 'id' | 'createdAt' | 'updatedAt' | 'appointmentId' | 'overallRating'>,
|
|
40
|
-
appointmentId: string,
|
|
41
|
-
): Promise<Review> {
|
|
42
|
-
try {
|
|
43
|
-
console.log('🔍 ReviewService.createReview - Input data:', {
|
|
44
|
-
appointmentId,
|
|
45
|
-
hasClinicReview: !!data.clinicReview,
|
|
46
|
-
hasPractitionerReview: !!data.practitionerReview,
|
|
47
|
-
hasProcedureReview: !!data.procedureReview,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
data.clinicReview.
|
|
63
|
-
data.clinicReview.
|
|
64
|
-
data.clinicReview.
|
|
65
|
-
data.clinicReview.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
data.practitionerReview.
|
|
76
|
-
data.practitionerReview.
|
|
77
|
-
data.practitionerReview.
|
|
78
|
-
data.practitionerReview.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
data.procedureReview.
|
|
89
|
-
data.procedureReview.
|
|
90
|
-
data.procedureReview.
|
|
91
|
-
data.procedureReview.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
data.
|
|
128
|
-
data.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
enhancedReview.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const q = query(
|
|
638
|
-
collection(this.db, REVIEWS_COLLECTION),
|
|
639
|
-
where('appointmentId', '==', appointmentId),
|
|
640
|
-
);
|
|
641
|
-
const snapshot = await getDocs(q);
|
|
642
|
-
|
|
643
|
-
if (snapshot.empty) {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
return snapshot.docs[0].data() as Review;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Deletes a review
|
|
652
|
-
* @param reviewId The ID of the review to delete
|
|
653
|
-
*/
|
|
654
|
-
async deleteReview(reviewId: string): Promise<void> {
|
|
655
|
-
const review = await this.getReview(reviewId);
|
|
656
|
-
if (!review) {
|
|
657
|
-
throw new Error(`Review with ID ${reviewId} not found`);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Delete the review
|
|
661
|
-
await deleteDoc(doc(this.db, REVIEWS_COLLECTION, reviewId));
|
|
662
|
-
|
|
663
|
-
// Note: Updates to related entities after deletion are now handled
|
|
664
|
-
// by cloud functions through the ReviewsAggregationService
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Calculates the average of an array of numbers
|
|
669
|
-
* @param numbers Array of numbers to average
|
|
670
|
-
* @returns The average, or 0 if the array is empty
|
|
671
|
-
*/
|
|
672
|
-
private calculateAverage(numbers: number[]): number {
|
|
673
|
-
if (numbers.length === 0) {
|
|
674
|
-
return 0;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const sum = numbers.reduce((a, b) => a + b, 0);
|
|
678
|
-
const avg = sum / numbers.length;
|
|
679
|
-
|
|
680
|
-
// Round to 1 decimal place
|
|
681
|
-
return Math.round(avg * 10) / 10;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
doc,
|
|
4
|
+
getDoc,
|
|
5
|
+
getDocs,
|
|
6
|
+
query,
|
|
7
|
+
where,
|
|
8
|
+
setDoc,
|
|
9
|
+
deleteDoc,
|
|
10
|
+
serverTimestamp,
|
|
11
|
+
} from 'firebase/firestore';
|
|
12
|
+
import { BaseService } from '../base.service';
|
|
13
|
+
import {
|
|
14
|
+
Review,
|
|
15
|
+
ClinicReview,
|
|
16
|
+
PractitionerReview,
|
|
17
|
+
ProcedureReview,
|
|
18
|
+
REVIEWS_COLLECTION,
|
|
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';
|
|
26
|
+
|
|
27
|
+
export class ReviewService extends BaseService {
|
|
28
|
+
constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
|
|
29
|
+
super(db, auth, app);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new review
|
|
34
|
+
* @param data - The review data to create
|
|
35
|
+
* @param appointmentId - ID of the completed appointment
|
|
36
|
+
* @returns The created review
|
|
37
|
+
*/
|
|
38
|
+
async createReview(
|
|
39
|
+
data: Omit<Review, 'id' | 'createdAt' | 'updatedAt' | 'appointmentId' | 'overallRating'>,
|
|
40
|
+
appointmentId: string,
|
|
41
|
+
): Promise<Review> {
|
|
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
|
+
|
|
53
|
+
// Validate input data
|
|
54
|
+
const validatedData = createReviewSchema.parse(data);
|
|
55
|
+
|
|
56
|
+
// Calculate overall rating based on all provided reviews
|
|
57
|
+
const ratings: number[] = [];
|
|
58
|
+
|
|
59
|
+
if (data.clinicReview) {
|
|
60
|
+
const clinicRatings = [
|
|
61
|
+
data.clinicReview.cleanliness,
|
|
62
|
+
data.clinicReview.facilities,
|
|
63
|
+
data.clinicReview.staffFriendliness,
|
|
64
|
+
data.clinicReview.waitingTime,
|
|
65
|
+
data.clinicReview.accessibility,
|
|
66
|
+
];
|
|
67
|
+
const clinicAverage = this.calculateAverage(clinicRatings);
|
|
68
|
+
data.clinicReview.overallRating = clinicAverage;
|
|
69
|
+
ratings.push(clinicAverage);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (data.practitionerReview) {
|
|
73
|
+
const practitionerRatings = [
|
|
74
|
+
data.practitionerReview.knowledgeAndExpertise,
|
|
75
|
+
data.practitionerReview.communicationSkills,
|
|
76
|
+
data.practitionerReview.bedSideManner,
|
|
77
|
+
data.practitionerReview.thoroughness,
|
|
78
|
+
data.practitionerReview.trustworthiness,
|
|
79
|
+
];
|
|
80
|
+
const practitionerAverage = this.calculateAverage(practitionerRatings);
|
|
81
|
+
data.practitionerReview.overallRating = practitionerAverage;
|
|
82
|
+
ratings.push(practitionerAverage);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (data.procedureReview) {
|
|
86
|
+
const procedureRatings = [
|
|
87
|
+
data.procedureReview.effectivenessOfTreatment,
|
|
88
|
+
data.procedureReview.outcomeExplanation,
|
|
89
|
+
data.procedureReview.painManagement,
|
|
90
|
+
data.procedureReview.followUpCare,
|
|
91
|
+
data.procedureReview.valueForMoney,
|
|
92
|
+
];
|
|
93
|
+
const procedureAverage = this.calculateAverage(procedureRatings);
|
|
94
|
+
data.procedureReview.overallRating = procedureAverage;
|
|
95
|
+
ratings.push(procedureAverage);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const overallRating = this.calculateAverage(ratings);
|
|
99
|
+
|
|
100
|
+
// Generate a unique ID for the main review
|
|
101
|
+
const reviewId = this.generateId();
|
|
102
|
+
|
|
103
|
+
// Add IDs to each review component
|
|
104
|
+
if (data.clinicReview) {
|
|
105
|
+
data.clinicReview.id = this.generateId();
|
|
106
|
+
data.clinicReview.fullReviewId = reviewId;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (data.practitionerReview) {
|
|
110
|
+
data.practitionerReview.id = this.generateId();
|
|
111
|
+
data.practitionerReview.fullReviewId = reviewId;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (data.procedureReview) {
|
|
115
|
+
data.procedureReview.id = this.generateId();
|
|
116
|
+
data.procedureReview.fullReviewId = reviewId;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create the review object with timestamps
|
|
120
|
+
const now = new Date();
|
|
121
|
+
const review: Review = {
|
|
122
|
+
id: reviewId,
|
|
123
|
+
appointmentId,
|
|
124
|
+
patientId: data.patientId,
|
|
125
|
+
clinicReview: data.clinicReview,
|
|
126
|
+
practitionerReview: data.practitionerReview,
|
|
127
|
+
procedureReview: data.procedureReview,
|
|
128
|
+
overallComment: data.overallComment,
|
|
129
|
+
overallRating,
|
|
130
|
+
createdAt: now,
|
|
131
|
+
updatedAt: now,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Validate complete review object
|
|
135
|
+
reviewSchema.parse(review);
|
|
136
|
+
|
|
137
|
+
// Save the review to Firestore
|
|
138
|
+
const docRef = doc(this.db, REVIEWS_COLLECTION, reviewId);
|
|
139
|
+
await setDoc(docRef, {
|
|
140
|
+
...review,
|
|
141
|
+
createdAt: serverTimestamp(),
|
|
142
|
+
updatedAt: serverTimestamp(),
|
|
143
|
+
});
|
|
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
|
+
|
|
152
|
+
// Note: Related entity updates (clinic, practitioner, procedure) are now handled
|
|
153
|
+
// by cloud functions through the ReviewsAggregationService
|
|
154
|
+
|
|
155
|
+
return review;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error instanceof z.ZodError) {
|
|
158
|
+
throw new Error(`Invalid review data: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Gets a review by ID with enhanced entity names
|
|
166
|
+
* @param reviewId The ID of the review to get
|
|
167
|
+
* @returns The review with entity names if found, null otherwise
|
|
168
|
+
*/
|
|
169
|
+
async getReview(reviewId: string): Promise<Review | null> {
|
|
170
|
+
console.log('🔍 ReviewService.getReview - Getting review:', reviewId);
|
|
171
|
+
|
|
172
|
+
const docRef = doc(this.db, REVIEWS_COLLECTION, reviewId);
|
|
173
|
+
const docSnap = await getDoc(docRef);
|
|
174
|
+
|
|
175
|
+
if (!docSnap.exists()) {
|
|
176
|
+
console.log('❌ ReviewService.getReview - Review not found:', reviewId);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
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
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Gets all reviews for a specific patient with enhanced entity names
|
|
243
|
+
* @param patientId The ID of the patient
|
|
244
|
+
* @returns Array of reviews for the patient with clinic, practitioner, and procedure names
|
|
245
|
+
*/
|
|
246
|
+
async getReviewsByPatient(patientId: string): Promise<Review[]> {
|
|
247
|
+
const q = query(collection(this.db, REVIEWS_COLLECTION), where('patientId', '==', patientId));
|
|
248
|
+
const snapshot = await getDocs(q);
|
|
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;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Gets all reviews for a specific clinic with enhanced entity names
|
|
308
|
+
* @param clinicId The ID of the clinic
|
|
309
|
+
* @returns Array of reviews containing clinic reviews with clinic, practitioner, and procedure names
|
|
310
|
+
*/
|
|
311
|
+
async getReviewsByClinic(clinicId: string): Promise<Review[]> {
|
|
312
|
+
console.log('🔍 ReviewService.getReviewsByClinic - Querying for clinic:', clinicId);
|
|
313
|
+
|
|
314
|
+
const q = query(
|
|
315
|
+
collection(this.db, REVIEWS_COLLECTION),
|
|
316
|
+
where('clinicReview.clinicId', '==', clinicId),
|
|
317
|
+
);
|
|
318
|
+
const snapshot = await getDocs(q);
|
|
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;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Gets all reviews for a specific practitioner with enhanced entity names
|
|
400
|
+
* @param practitionerId The ID of the practitioner
|
|
401
|
+
* @returns Array of reviews containing practitioner reviews with clinic, practitioner, and procedure names
|
|
402
|
+
*/
|
|
403
|
+
async getReviewsByPractitioner(practitionerId: string): Promise<Review[]> {
|
|
404
|
+
console.log(
|
|
405
|
+
'🔍 ReviewService.getReviewsByPractitioner - Querying for practitioner:',
|
|
406
|
+
practitionerId,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const q = query(
|
|
410
|
+
collection(this.db, REVIEWS_COLLECTION),
|
|
411
|
+
where('practitionerReview.practitionerId', '==', practitionerId),
|
|
412
|
+
);
|
|
413
|
+
const snapshot = await getDocs(q);
|
|
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;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Gets all reviews for a specific procedure with enhanced entity names
|
|
494
|
+
* @param procedureId The ID of the procedure
|
|
495
|
+
* @returns Array of reviews containing procedure reviews with clinic, practitioner, and procedure names
|
|
496
|
+
*/
|
|
497
|
+
async getReviewsByProcedure(procedureId: string): Promise<Review[]> {
|
|
498
|
+
console.log('🔍 ReviewService.getReviewsByProcedure - Querying for procedure:', procedureId);
|
|
499
|
+
|
|
500
|
+
const q = query(
|
|
501
|
+
collection(this.db, REVIEWS_COLLECTION),
|
|
502
|
+
where('procedureReview.procedureId', '==', procedureId),
|
|
503
|
+
);
|
|
504
|
+
const snapshot = await getDocs(q);
|
|
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;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Gets all reviews for a specific appointment
|
|
586
|
+
* @param appointmentId The ID of the appointment
|
|
587
|
+
* @returns The review for the appointment if found, null otherwise
|
|
588
|
+
*/
|
|
589
|
+
async getReviewByAppointment(appointmentId: string): Promise<Review | null> {
|
|
590
|
+
const q = query(
|
|
591
|
+
collection(this.db, REVIEWS_COLLECTION),
|
|
592
|
+
where('appointmentId', '==', appointmentId),
|
|
593
|
+
);
|
|
594
|
+
const snapshot = await getDocs(q);
|
|
595
|
+
|
|
596
|
+
if (snapshot.empty) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return snapshot.docs[0].data() as Review;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Deletes a review
|
|
605
|
+
* @param reviewId The ID of the review to delete
|
|
606
|
+
*/
|
|
607
|
+
async deleteReview(reviewId: string): Promise<void> {
|
|
608
|
+
const review = await this.getReview(reviewId);
|
|
609
|
+
if (!review) {
|
|
610
|
+
throw new Error(`Review with ID ${reviewId} not found`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Delete the review
|
|
614
|
+
await deleteDoc(doc(this.db, REVIEWS_COLLECTION, reviewId));
|
|
615
|
+
|
|
616
|
+
// Note: Updates to related entities after deletion are now handled
|
|
617
|
+
// by cloud functions through the ReviewsAggregationService
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Calculates the average of an array of numbers
|
|
622
|
+
* @param numbers Array of numbers to average
|
|
623
|
+
* @returns The average, or 0 if the array is empty
|
|
624
|
+
*/
|
|
625
|
+
private calculateAverage(numbers: number[]): number {
|
|
626
|
+
if (numbers.length === 0) {
|
|
627
|
+
return 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const sum = numbers.reduce((a, b) => a + b, 0);
|
|
631
|
+
const avg = sum / numbers.length;
|
|
632
|
+
|
|
633
|
+
// Round to 1 decimal place
|
|
634
|
+
return Math.round(avg * 10) / 10;
|
|
635
|
+
}
|
|
636
|
+
}
|