@blackcode_sa/metaestetics-api 1.5.28 → 1.5.29
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 +1199 -1
- package/dist/admin/index.d.ts +1199 -1
- package/dist/admin/index.js +1337 -2
- package/dist/admin/index.mjs +1333 -2
- package/dist/backoffice/index.d.mts +99 -7
- package/dist/backoffice/index.d.ts +99 -7
- package/dist/index.d.mts +4035 -2364
- package/dist/index.d.ts +4035 -2364
- package/dist/index.js +2616 -1929
- package/dist/index.mjs +2646 -1952
- package/package.json +1 -1
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +642 -0
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -0
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -0
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +508 -0
- package/src/admin/index.ts +53 -4
- package/src/index.ts +28 -4
- package/src/services/clinic/clinic.service.ts +320 -107
- package/src/services/clinic/utils/clinic.utils.ts +66 -117
- package/src/services/clinic/utils/filter.utils.d.ts +23 -0
- package/src/services/clinic/utils/filter.utils.ts +264 -0
- package/src/services/practitioner/practitioner.service.ts +616 -5
- package/src/services/procedure/procedure.service.ts +599 -352
- package/src/services/reviews/reviews.service.ts +842 -0
- package/src/types/clinic/index.ts +24 -56
- package/src/types/practitioner/index.ts +34 -33
- package/src/types/procedure/index.ts +32 -0
- package/src/types/profile/index.ts +1 -1
- package/src/types/reviews/index.ts +126 -0
- package/src/validations/clinic.schema.ts +37 -64
- package/src/validations/practitioner.schema.ts +42 -32
- package/src/validations/procedure.schema.ts +11 -3
- package/src/validations/reviews.schema.ts +189 -0
- package/src/services/clinic/utils/review.utils.ts +0 -93
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
PractitionerStatus,
|
|
9
9
|
PractitionerTokenStatus,
|
|
10
10
|
} from "../types/practitioner";
|
|
11
|
+
import { practitionerReviewInfoSchema } from "./reviews.schema";
|
|
12
|
+
import { clinicInfoSchema } from "./clinic.schema";
|
|
13
|
+
import { ProcedureFamily } from "../backoffice/types/static/procedure-family.types";
|
|
14
|
+
import {
|
|
15
|
+
Currency,
|
|
16
|
+
PricingMeasure,
|
|
17
|
+
} from "../backoffice/types/static/pricing.types";
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* Šema za validaciju osnovnih informacija o zdravstvenom radniku
|
|
@@ -18,7 +25,7 @@ export const practitionerBasicInfoSchema = z.object({
|
|
|
18
25
|
title: z.string().min(2).max(100),
|
|
19
26
|
email: z.string().email(),
|
|
20
27
|
phoneNumber: z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
|
|
21
|
-
dateOfBirth: z.instanceof(Timestamp),
|
|
28
|
+
dateOfBirth: z.instanceof(Timestamp).or(z.date()),
|
|
22
29
|
gender: z.enum(["male", "female", "other"]),
|
|
23
30
|
profileImageUrl: z.string().url().optional(),
|
|
24
31
|
bio: z.string().max(1000).optional(),
|
|
@@ -33,8 +40,8 @@ export const practitionerCertificationSchema = z.object({
|
|
|
33
40
|
specialties: z.array(z.nativeEnum(CertificationSpecialty)),
|
|
34
41
|
licenseNumber: z.string().min(3).max(50),
|
|
35
42
|
issuingAuthority: z.string().min(2).max(100),
|
|
36
|
-
issueDate: z.instanceof(Timestamp),
|
|
37
|
-
expiryDate: z.instanceof(Timestamp).optional(),
|
|
43
|
+
issueDate: z.instanceof(Timestamp).or(z.date()),
|
|
44
|
+
expiryDate: z.instanceof(Timestamp).or(z.date()).optional(),
|
|
38
45
|
verificationStatus: z.enum(["pending", "verified", "rejected"]),
|
|
39
46
|
});
|
|
40
47
|
|
|
@@ -60,8 +67,8 @@ export const practitionerWorkingHoursSchema = z.object({
|
|
|
60
67
|
friday: timeSlotSchema,
|
|
61
68
|
saturday: timeSlotSchema,
|
|
62
69
|
sunday: timeSlotSchema,
|
|
63
|
-
createdAt: z.instanceof(Timestamp),
|
|
64
|
-
updatedAt: z.instanceof(Timestamp),
|
|
70
|
+
createdAt: z.instanceof(Timestamp).or(z.date()),
|
|
71
|
+
updatedAt: z.instanceof(Timestamp).or(z.date()),
|
|
65
72
|
});
|
|
66
73
|
|
|
67
74
|
/**
|
|
@@ -79,35 +86,30 @@ export const practitionerClinicWorkingHoursSchema = z.object({
|
|
|
79
86
|
sunday: timeSlotSchema,
|
|
80
87
|
}),
|
|
81
88
|
isActive: z.boolean(),
|
|
82
|
-
createdAt: z.instanceof(Timestamp),
|
|
83
|
-
updatedAt: z.instanceof(Timestamp),
|
|
89
|
+
createdAt: z.instanceof(Timestamp).or(z.date()),
|
|
90
|
+
updatedAt: z.instanceof(Timestamp).or(z.date()),
|
|
84
91
|
});
|
|
85
92
|
|
|
86
93
|
/**
|
|
87
|
-
*
|
|
94
|
+
* Schema matching ProcedureSummaryInfo interface
|
|
88
95
|
*/
|
|
89
|
-
export const
|
|
96
|
+
export const procedureSummaryInfoSchema = z.object({
|
|
90
97
|
id: z.string().min(1),
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
name: z.string().min(1),
|
|
99
|
+
description: z.string().optional(),
|
|
100
|
+
photo: z.string().optional(),
|
|
101
|
+
family: z.nativeEnum(ProcedureFamily),
|
|
102
|
+
categoryName: z.string(),
|
|
103
|
+
subcategoryName: z.string(),
|
|
104
|
+
technologyName: z.string(),
|
|
105
|
+
price: z.number().nonnegative(),
|
|
106
|
+
pricingMeasure: z.nativeEnum(PricingMeasure),
|
|
107
|
+
currency: z.nativeEnum(Currency),
|
|
108
|
+
duration: z.number().int().positive(),
|
|
93
109
|
clinicId: z.string().min(1),
|
|
94
|
-
|
|
95
|
-
comment: z.string().max(1000),
|
|
96
|
-
createdAt: z.instanceof(Timestamp),
|
|
97
|
-
updatedAt: z.instanceof(Timestamp),
|
|
98
|
-
isVerified: z.boolean(),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Šema za validaciju procedura koje zdravstveni radnik izvodi u klinici
|
|
103
|
-
*/
|
|
104
|
-
export const practitionerClinicProceduresSchema = z.object({
|
|
110
|
+
clinicName: z.string().min(1),
|
|
105
111
|
practitionerId: z.string().min(1),
|
|
106
|
-
|
|
107
|
-
procedures: z.array(z.string()).min(1),
|
|
108
|
-
isActive: z.boolean(),
|
|
109
|
-
createdAt: z.instanceof(Timestamp),
|
|
110
|
-
updatedAt: z.instanceof(Timestamp),
|
|
112
|
+
practitionerName: z.string().min(1),
|
|
111
113
|
});
|
|
112
114
|
|
|
113
115
|
/**
|
|
@@ -120,11 +122,15 @@ export const practitionerSchema = z.object({
|
|
|
120
122
|
certification: practitionerCertificationSchema,
|
|
121
123
|
clinics: z.array(z.string()),
|
|
122
124
|
clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema),
|
|
125
|
+
clinicsInfo: z.array(clinicInfoSchema),
|
|
126
|
+
procedures: z.array(z.string()),
|
|
127
|
+
proceduresInfo: z.array(procedureSummaryInfoSchema),
|
|
128
|
+
reviewInfo: practitionerReviewInfoSchema,
|
|
123
129
|
isActive: z.boolean(),
|
|
124
130
|
isVerified: z.boolean(),
|
|
125
131
|
status: z.nativeEnum(PractitionerStatus),
|
|
126
|
-
createdAt: z.instanceof(Timestamp),
|
|
127
|
-
updatedAt: z.instanceof(Timestamp),
|
|
132
|
+
createdAt: z.instanceof(Timestamp).or(z.date()),
|
|
133
|
+
updatedAt: z.instanceof(Timestamp).or(z.date()),
|
|
128
134
|
});
|
|
129
135
|
|
|
130
136
|
/**
|
|
@@ -136,6 +142,8 @@ export const createPractitionerSchema = z.object({
|
|
|
136
142
|
certification: practitionerCertificationSchema,
|
|
137
143
|
clinics: z.array(z.string()).optional(),
|
|
138
144
|
clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
145
|
+
clinicsInfo: z.array(clinicInfoSchema).optional(),
|
|
146
|
+
proceduresInfo: z.array(procedureSummaryInfoSchema).optional(),
|
|
139
147
|
isActive: z.boolean(),
|
|
140
148
|
isVerified: z.boolean(),
|
|
141
149
|
status: z.nativeEnum(PractitionerStatus).optional(),
|
|
@@ -149,6 +157,8 @@ export const createDraftPractitionerSchema = z.object({
|
|
|
149
157
|
certification: practitionerCertificationSchema,
|
|
150
158
|
clinics: z.array(z.string()).optional(),
|
|
151
159
|
clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
160
|
+
clinicsInfo: z.array(clinicInfoSchema).optional(),
|
|
161
|
+
proceduresInfo: z.array(procedureSummaryInfoSchema).optional(),
|
|
152
162
|
isActive: z.boolean().optional().default(false),
|
|
153
163
|
isVerified: z.boolean().optional().default(false),
|
|
154
164
|
});
|
|
@@ -164,10 +174,10 @@ export const practitionerTokenSchema = z.object({
|
|
|
164
174
|
clinicId: z.string().min(1),
|
|
165
175
|
status: z.nativeEnum(PractitionerTokenStatus),
|
|
166
176
|
createdBy: z.string().min(1),
|
|
167
|
-
createdAt: z.instanceof(Timestamp),
|
|
168
|
-
expiresAt: z.instanceof(Timestamp),
|
|
177
|
+
createdAt: z.instanceof(Timestamp).or(z.date()),
|
|
178
|
+
expiresAt: z.instanceof(Timestamp).or(z.date()),
|
|
169
179
|
usedBy: z.string().optional(),
|
|
170
|
-
usedAt: z.instanceof(Timestamp).optional(),
|
|
180
|
+
usedAt: z.instanceof(Timestamp).or(z.date()).optional(),
|
|
171
181
|
});
|
|
172
182
|
|
|
173
183
|
/**
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PricingMeasure,
|
|
6
6
|
} from "../backoffice/types/static/pricing.types";
|
|
7
7
|
import { clinicInfoSchema, doctorInfoSchema } from "./clinic.schema";
|
|
8
|
+
import { procedureReviewInfoSchema } from "./reviews.schema";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Schema for creating a new procedure
|
|
@@ -29,13 +30,19 @@ export const createProcedureSchema = z.object({
|
|
|
29
30
|
* Schema for updating an existing procedure
|
|
30
31
|
*/
|
|
31
32
|
export const updateProcedureSchema = z.object({
|
|
32
|
-
name: z.string().min(
|
|
33
|
-
description: z.string().min(
|
|
33
|
+
name: z.string().min(3).max(100).optional(),
|
|
34
|
+
description: z.string().min(3).max(1000).optional(),
|
|
34
35
|
price: z.number().min(0).optional(),
|
|
35
36
|
currency: z.nativeEnum(Currency).optional(),
|
|
36
37
|
pricingMeasure: z.nativeEnum(PricingMeasure).optional(),
|
|
37
|
-
duration: z.number().min(
|
|
38
|
+
duration: z.number().min(0).optional(),
|
|
38
39
|
isActive: z.boolean().optional(),
|
|
40
|
+
practitionerId: z.string().optional(),
|
|
41
|
+
categoryId: z.string().optional(),
|
|
42
|
+
subcategoryId: z.string().optional(),
|
|
43
|
+
technologyId: z.string().optional(),
|
|
44
|
+
productId: z.string().optional(),
|
|
45
|
+
clinicBranchId: z.string().optional(),
|
|
39
46
|
});
|
|
40
47
|
|
|
41
48
|
/**
|
|
@@ -55,6 +62,7 @@ export const procedureSchema = createProcedureSchema.extend({
|
|
|
55
62
|
documentationTemplates: z.array(z.any()), // We'll validate documentation templates separately
|
|
56
63
|
clinicInfo: clinicInfoSchema, // Clinic info validation
|
|
57
64
|
doctorInfo: doctorInfoSchema, // Doctor info validation
|
|
65
|
+
reviewInfo: procedureReviewInfoSchema, // Procedure review info validation
|
|
58
66
|
isActive: z.boolean(),
|
|
59
67
|
createdAt: z.date(),
|
|
60
68
|
updatedAt: z.date(),
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base review schema with common properties
|
|
5
|
+
*/
|
|
6
|
+
const baseReviewSchema = z.object({
|
|
7
|
+
id: z.string().min(1),
|
|
8
|
+
patientId: z.string().min(1),
|
|
9
|
+
fullReviewId: z.string().min(1),
|
|
10
|
+
createdAt: z.date(),
|
|
11
|
+
updatedAt: z.date(),
|
|
12
|
+
comment: z.string().min(1).max(2000),
|
|
13
|
+
isVerified: z.boolean(),
|
|
14
|
+
isPublished: z.boolean(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base review creation schema
|
|
19
|
+
*/
|
|
20
|
+
const baseReviewCreateSchema = z.object({
|
|
21
|
+
patientId: z.string().min(1),
|
|
22
|
+
comment: z.string().min(1).max(2000),
|
|
23
|
+
isVerified: z.boolean().default(false),
|
|
24
|
+
isPublished: z.boolean().default(true),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clinic review schema
|
|
29
|
+
*/
|
|
30
|
+
export const clinicReviewSchema = baseReviewSchema.extend({
|
|
31
|
+
clinicId: z.string().min(1),
|
|
32
|
+
cleanliness: z.number().min(1).max(5),
|
|
33
|
+
facilities: z.number().min(1).max(5),
|
|
34
|
+
staffFriendliness: z.number().min(1).max(5),
|
|
35
|
+
waitingTime: z.number().min(1).max(5),
|
|
36
|
+
accessibility: z.number().min(1).max(5),
|
|
37
|
+
overallRating: z.number().min(1).max(5),
|
|
38
|
+
wouldRecommend: z.boolean(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clinic review creation schema
|
|
43
|
+
*/
|
|
44
|
+
export const createClinicReviewSchema = baseReviewCreateSchema.extend({
|
|
45
|
+
clinicId: z.string().min(1),
|
|
46
|
+
cleanliness: z.number().min(1).max(5),
|
|
47
|
+
facilities: z.number().min(1).max(5),
|
|
48
|
+
staffFriendliness: z.number().min(1).max(5),
|
|
49
|
+
waitingTime: z.number().min(1).max(5),
|
|
50
|
+
accessibility: z.number().min(1).max(5),
|
|
51
|
+
wouldRecommend: z.boolean(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Practitioner review schema
|
|
56
|
+
*/
|
|
57
|
+
export const practitionerReviewSchema = baseReviewSchema.extend({
|
|
58
|
+
practitionerId: z.string().min(1),
|
|
59
|
+
knowledgeAndExpertise: z.number().min(1).max(5),
|
|
60
|
+
communicationSkills: z.number().min(1).max(5),
|
|
61
|
+
bedSideManner: z.number().min(1).max(5),
|
|
62
|
+
thoroughness: z.number().min(1).max(5),
|
|
63
|
+
trustworthiness: z.number().min(1).max(5),
|
|
64
|
+
overallRating: z.number().min(1).max(5),
|
|
65
|
+
wouldRecommend: z.boolean(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Practitioner review creation schema
|
|
70
|
+
*/
|
|
71
|
+
export const createPractitionerReviewSchema = baseReviewCreateSchema.extend({
|
|
72
|
+
practitionerId: z.string().min(1),
|
|
73
|
+
knowledgeAndExpertise: z.number().min(1).max(5),
|
|
74
|
+
communicationSkills: z.number().min(1).max(5),
|
|
75
|
+
bedSideManner: z.number().min(1).max(5),
|
|
76
|
+
thoroughness: z.number().min(1).max(5),
|
|
77
|
+
trustworthiness: z.number().min(1).max(5),
|
|
78
|
+
wouldRecommend: z.boolean(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Procedure review schema
|
|
83
|
+
*/
|
|
84
|
+
export const procedureReviewSchema = baseReviewSchema.extend({
|
|
85
|
+
procedureId: z.string().min(1),
|
|
86
|
+
effectivenessOfTreatment: z.number().min(1).max(5),
|
|
87
|
+
outcomeExplanation: z.number().min(1).max(5),
|
|
88
|
+
painManagement: z.number().min(1).max(5),
|
|
89
|
+
followUpCare: z.number().min(1).max(5),
|
|
90
|
+
valueForMoney: z.number().min(1).max(5),
|
|
91
|
+
overallRating: z.number().min(1).max(5),
|
|
92
|
+
wouldRecommend: z.boolean(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Procedure review creation schema
|
|
97
|
+
*/
|
|
98
|
+
export const createProcedureReviewSchema = baseReviewCreateSchema.extend({
|
|
99
|
+
procedureId: z.string().min(1),
|
|
100
|
+
effectivenessOfTreatment: z.number().min(1).max(5),
|
|
101
|
+
outcomeExplanation: z.number().min(1).max(5),
|
|
102
|
+
painManagement: z.number().min(1).max(5),
|
|
103
|
+
followUpCare: z.number().min(1).max(5),
|
|
104
|
+
valueForMoney: z.number().min(1).max(5),
|
|
105
|
+
wouldRecommend: z.boolean(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Review info (condensed) schema for clinic
|
|
110
|
+
*/
|
|
111
|
+
export const clinicReviewInfoSchema = z.object({
|
|
112
|
+
totalReviews: z.number().min(0),
|
|
113
|
+
averageRating: z.number().min(0).max(5),
|
|
114
|
+
cleanliness: z.number().min(0).max(5),
|
|
115
|
+
facilities: z.number().min(0).max(5),
|
|
116
|
+
staffFriendliness: z.number().min(0).max(5),
|
|
117
|
+
waitingTime: z.number().min(0).max(5),
|
|
118
|
+
accessibility: z.number().min(0).max(5),
|
|
119
|
+
recommendationPercentage: z.number().min(0).max(100),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Review info (condensed) schema for practitioner
|
|
124
|
+
*/
|
|
125
|
+
export const practitionerReviewInfoSchema = z.object({
|
|
126
|
+
totalReviews: z.number().min(0),
|
|
127
|
+
averageRating: z.number().min(0).max(5),
|
|
128
|
+
knowledgeAndExpertise: z.number().min(0).max(5),
|
|
129
|
+
communicationSkills: z.number().min(0).max(5),
|
|
130
|
+
bedSideManner: z.number().min(0).max(5),
|
|
131
|
+
thoroughness: z.number().min(0).max(5),
|
|
132
|
+
trustworthiness: z.number().min(0).max(5),
|
|
133
|
+
recommendationPercentage: z.number().min(0).max(100),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Review info (condensed) schema for procedure
|
|
138
|
+
*/
|
|
139
|
+
export const procedureReviewInfoSchema = z.object({
|
|
140
|
+
totalReviews: z.number().min(0),
|
|
141
|
+
averageRating: z.number().min(0).max(5),
|
|
142
|
+
effectivenessOfTreatment: z.number().min(0).max(5),
|
|
143
|
+
outcomeExplanation: z.number().min(0).max(5),
|
|
144
|
+
painManagement: z.number().min(0).max(5),
|
|
145
|
+
followUpCare: z.number().min(0).max(5),
|
|
146
|
+
valueForMoney: z.number().min(0).max(5),
|
|
147
|
+
recommendationPercentage: z.number().min(0).max(100),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Combined review schema
|
|
152
|
+
*/
|
|
153
|
+
export const reviewSchema = z.object({
|
|
154
|
+
id: z.string().min(1),
|
|
155
|
+
appointmentId: z.string().min(1),
|
|
156
|
+
patientId: z.string().min(1),
|
|
157
|
+
createdAt: z.date(),
|
|
158
|
+
updatedAt: z.date(),
|
|
159
|
+
clinicReview: clinicReviewSchema.optional(),
|
|
160
|
+
practitionerReview: practitionerReviewSchema.optional(),
|
|
161
|
+
procedureReview: procedureReviewSchema.optional(),
|
|
162
|
+
overallComment: z.string().min(1).max(2000),
|
|
163
|
+
overallRating: z.number().min(1).max(5),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Combined review creation schema
|
|
168
|
+
*/
|
|
169
|
+
export const createReviewSchema = z
|
|
170
|
+
.object({
|
|
171
|
+
patientId: z.string().min(1),
|
|
172
|
+
clinicReview: createClinicReviewSchema.optional(),
|
|
173
|
+
practitionerReview: createPractitionerReviewSchema.optional(),
|
|
174
|
+
procedureReview: createProcedureReviewSchema.optional(),
|
|
175
|
+
overallComment: z.string().min(1).max(2000),
|
|
176
|
+
})
|
|
177
|
+
.refine(
|
|
178
|
+
(data) => {
|
|
179
|
+
// Ensure at least one review type is provided
|
|
180
|
+
return (
|
|
181
|
+
data.clinicReview || data.practitionerReview || data.procedureReview
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
message:
|
|
186
|
+
"At least one review type (clinic, practitioner, or procedure) must be provided",
|
|
187
|
+
path: ["reviewType"],
|
|
188
|
+
}
|
|
189
|
+
);
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collection,
|
|
3
|
-
doc,
|
|
4
|
-
setDoc,
|
|
5
|
-
Timestamp,
|
|
6
|
-
Firestore,
|
|
7
|
-
getDoc,
|
|
8
|
-
addDoc,
|
|
9
|
-
} from "firebase/firestore";
|
|
10
|
-
import { Clinic, ClinicReview } from "../../../types/clinic";
|
|
11
|
-
import { clinicReviewSchema } from "../../../validations/clinic.schema";
|
|
12
|
-
import { getClinic, updateClinic } from "./clinic.utils";
|
|
13
|
-
import { FirebaseApp } from "firebase/app";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Adds a review to a clinic
|
|
17
|
-
* @param db - Firestore database instance
|
|
18
|
-
* @param clinicId - ID of the clinic to add the review to
|
|
19
|
-
* @param review - Review data
|
|
20
|
-
* @param app - Firebase app instance
|
|
21
|
-
* @returns The created review
|
|
22
|
-
*/
|
|
23
|
-
export async function addReview(
|
|
24
|
-
db: Firestore,
|
|
25
|
-
clinicId: string,
|
|
26
|
-
review: Omit<
|
|
27
|
-
ClinicReview,
|
|
28
|
-
"id" | "clinicId" | "createdAt" | "updatedAt" | "isVerified"
|
|
29
|
-
>,
|
|
30
|
-
app: FirebaseApp
|
|
31
|
-
): Promise<ClinicReview> {
|
|
32
|
-
// Proveravamo da li klinika postoji
|
|
33
|
-
const clinicRef = doc(db, "clinics", clinicId);
|
|
34
|
-
const clinicSnap = await getDoc(clinicRef);
|
|
35
|
-
|
|
36
|
-
if (!clinicSnap.exists()) {
|
|
37
|
-
throw new Error("Clinic not found");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const clinic = clinicSnap.data();
|
|
41
|
-
|
|
42
|
-
// Kreiramo recenziju
|
|
43
|
-
const now = Timestamp.now();
|
|
44
|
-
const reviewData: ClinicReview = {
|
|
45
|
-
...review,
|
|
46
|
-
id: doc(collection(db, "clinic_reviews")).id,
|
|
47
|
-
clinicId,
|
|
48
|
-
createdAt: now,
|
|
49
|
-
updatedAt: now,
|
|
50
|
-
isVerified: false,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Validacija
|
|
54
|
-
clinicReviewSchema.parse(reviewData);
|
|
55
|
-
|
|
56
|
-
// Čuvamo recenziju
|
|
57
|
-
await addDoc(collection(db, "clinic_reviews"), reviewData);
|
|
58
|
-
|
|
59
|
-
// Ažuriramo prosečnu ocenu klinike
|
|
60
|
-
const newRating = clinic.rating
|
|
61
|
-
? {
|
|
62
|
-
average:
|
|
63
|
-
(clinic.rating.average * clinic.rating.count + review.rating) /
|
|
64
|
-
(clinic.rating.count + 1),
|
|
65
|
-
count: clinic.rating.count + 1,
|
|
66
|
-
}
|
|
67
|
-
: { average: review.rating, count: 1 };
|
|
68
|
-
|
|
69
|
-
await updateClinic(
|
|
70
|
-
db,
|
|
71
|
-
clinicId,
|
|
72
|
-
{
|
|
73
|
-
rating: newRating,
|
|
74
|
-
reviews: [...clinic.reviews, reviewData.id],
|
|
75
|
-
// Add the review info to reviewsInfo array
|
|
76
|
-
reviewsInfo: [
|
|
77
|
-
...clinic.reviewsInfo,
|
|
78
|
-
{
|
|
79
|
-
id: reviewData.id,
|
|
80
|
-
patientId: reviewData.patientId,
|
|
81
|
-
rating: reviewData.rating,
|
|
82
|
-
comment: reviewData.comment,
|
|
83
|
-
createdAt: reviewData.createdAt,
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
"system", // System update, no admin ID needed
|
|
88
|
-
null, // No clinic admin service needed for system updates
|
|
89
|
-
app
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
return reviewData;
|
|
93
|
-
}
|