@blackcode_sa/metaestetics-api 1.12.20 → 1.12.22
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 +14 -0
- package/dist/admin/index.d.ts +14 -0
- package/dist/admin/index.js +194 -180
- package/dist/admin/index.mjs +194 -180
- package/dist/index.d.mts +57 -9
- package/dist/index.d.ts +57 -9
- package/dist/index.js +2340 -1862
- package/dist/index.mjs +2412 -1934
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +459 -457
- package/src/services/appointment/appointment.service.ts +297 -0
- package/src/services/reviews/README.md +129 -0
- package/src/services/reviews/reviews.service.ts +331 -12
- package/src/types/appointment/index.ts +11 -0
- package/src/types/reviews/index.ts +2 -1
- package/src/validations/appointment.schema.ts +38 -18
|
@@ -40,6 +40,16 @@ export class ReviewService extends BaseService {
|
|
|
40
40
|
appointmentId: string,
|
|
41
41
|
): Promise<Review> {
|
|
42
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
|
+
|
|
43
53
|
// Validate input data
|
|
44
54
|
const validatedData = createReviewSchema.parse(data);
|
|
45
55
|
|
|
@@ -132,6 +142,13 @@ export class ReviewService extends BaseService {
|
|
|
132
142
|
updatedAt: serverTimestamp(),
|
|
133
143
|
});
|
|
134
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
|
+
|
|
135
152
|
// Note: Related entity updates (clinic, practitioner, procedure) are now handled
|
|
136
153
|
// by cloud functions through the ReviewsAggregationService
|
|
137
154
|
|
|
@@ -145,19 +162,80 @@ export class ReviewService extends BaseService {
|
|
|
145
162
|
}
|
|
146
163
|
|
|
147
164
|
/**
|
|
148
|
-
* Gets a review by ID
|
|
165
|
+
* Gets a review by ID with enhanced entity names
|
|
149
166
|
* @param reviewId The ID of the review to get
|
|
150
|
-
* @returns The review if found, null otherwise
|
|
167
|
+
* @returns The review with entity names if found, null otherwise
|
|
151
168
|
*/
|
|
152
169
|
async getReview(reviewId: string): Promise<Review | null> {
|
|
170
|
+
console.log('🔍 ReviewService.getReview - Getting review:', reviewId);
|
|
171
|
+
|
|
153
172
|
const docRef = doc(this.db, REVIEWS_COLLECTION, reviewId);
|
|
154
173
|
const docSnap = await getDoc(docRef);
|
|
155
174
|
|
|
156
175
|
if (!docSnap.exists()) {
|
|
176
|
+
console.log('❌ ReviewService.getReview - Review not found:', reviewId);
|
|
157
177
|
return null;
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
|
|
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
|
+
}
|
|
161
239
|
}
|
|
162
240
|
|
|
163
241
|
/**
|
|
@@ -206,6 +284,11 @@ export class ReviewService extends BaseService {
|
|
|
206
284
|
};
|
|
207
285
|
}
|
|
208
286
|
|
|
287
|
+
// Add patient name to the main review object
|
|
288
|
+
if (appointment.patientInfo) {
|
|
289
|
+
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
290
|
+
}
|
|
291
|
+
|
|
209
292
|
return enhancedReview;
|
|
210
293
|
}
|
|
211
294
|
|
|
@@ -221,45 +304,281 @@ export class ReviewService extends BaseService {
|
|
|
221
304
|
}
|
|
222
305
|
|
|
223
306
|
/**
|
|
224
|
-
* Gets all reviews for a specific clinic
|
|
307
|
+
* Gets all reviews for a specific clinic with enhanced entity names
|
|
225
308
|
* @param clinicId The ID of the clinic
|
|
226
|
-
* @returns Array of reviews containing clinic reviews
|
|
309
|
+
* @returns Array of reviews containing clinic reviews with clinic, practitioner, and procedure names
|
|
227
310
|
*/
|
|
228
311
|
async getReviewsByClinic(clinicId: string): Promise<Review[]> {
|
|
312
|
+
console.log('🔍 ReviewService.getReviewsByClinic - Querying for clinic:', clinicId);
|
|
313
|
+
|
|
229
314
|
const q = query(
|
|
230
315
|
collection(this.db, REVIEWS_COLLECTION),
|
|
231
316
|
where('clinicReview.clinicId', '==', clinicId),
|
|
232
317
|
);
|
|
233
318
|
const snapshot = await getDocs(q);
|
|
234
|
-
|
|
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;
|
|
235
396
|
}
|
|
236
397
|
|
|
237
398
|
/**
|
|
238
|
-
* Gets all reviews for a specific practitioner
|
|
399
|
+
* Gets all reviews for a specific practitioner with enhanced entity names
|
|
239
400
|
* @param practitionerId The ID of the practitioner
|
|
240
|
-
* @returns Array of reviews containing practitioner reviews
|
|
401
|
+
* @returns Array of reviews containing practitioner reviews with clinic, practitioner, and procedure names
|
|
241
402
|
*/
|
|
242
403
|
async getReviewsByPractitioner(practitionerId: string): Promise<Review[]> {
|
|
404
|
+
console.log(
|
|
405
|
+
'🔍 ReviewService.getReviewsByPractitioner - Querying for practitioner:',
|
|
406
|
+
practitionerId,
|
|
407
|
+
);
|
|
408
|
+
|
|
243
409
|
const q = query(
|
|
244
410
|
collection(this.db, REVIEWS_COLLECTION),
|
|
245
411
|
where('practitionerReview.practitionerId', '==', practitionerId),
|
|
246
412
|
);
|
|
247
413
|
const snapshot = await getDocs(q);
|
|
248
|
-
|
|
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;
|
|
249
490
|
}
|
|
250
491
|
|
|
251
492
|
/**
|
|
252
|
-
* Gets all reviews for a specific procedure
|
|
493
|
+
* Gets all reviews for a specific procedure with enhanced entity names
|
|
253
494
|
* @param procedureId The ID of the procedure
|
|
254
|
-
* @returns Array of reviews containing procedure reviews
|
|
495
|
+
* @returns Array of reviews containing procedure reviews with clinic, practitioner, and procedure names
|
|
255
496
|
*/
|
|
256
497
|
async getReviewsByProcedure(procedureId: string): Promise<Review[]> {
|
|
498
|
+
console.log('🔍 ReviewService.getReviewsByProcedure - Querying for procedure:', procedureId);
|
|
499
|
+
|
|
257
500
|
const q = query(
|
|
258
501
|
collection(this.db, REVIEWS_COLLECTION),
|
|
259
502
|
where('procedureReview.procedureId', '==', procedureId),
|
|
260
503
|
);
|
|
261
504
|
const snapshot = await getDocs(q);
|
|
262
|
-
|
|
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;
|
|
263
582
|
}
|
|
264
583
|
|
|
265
584
|
/**
|
|
@@ -120,6 +120,17 @@ export interface BeforeAfterPerZone {
|
|
|
120
120
|
beforeNote?: string | null;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Interface for zone photo upload data
|
|
125
|
+
*/
|
|
126
|
+
export interface ZonePhotoUploadData {
|
|
127
|
+
appointmentId: string;
|
|
128
|
+
zoneId: string;
|
|
129
|
+
photoType: 'before' | 'after';
|
|
130
|
+
file: File | Blob;
|
|
131
|
+
notes?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
123
134
|
/**
|
|
124
135
|
* Interface for billing information per zone
|
|
125
136
|
*/
|
|
@@ -117,6 +117,7 @@ export interface Review {
|
|
|
117
117
|
id: string;
|
|
118
118
|
appointmentId: string;
|
|
119
119
|
patientId: string;
|
|
120
|
+
patientName?: string; // Enhanced field: patient name from appointment
|
|
120
121
|
createdAt: Date;
|
|
121
122
|
updatedAt: Date;
|
|
122
123
|
clinicReview?: ClinicReview;
|
|
@@ -126,4 +127,4 @@ export interface Review {
|
|
|
126
127
|
overallRating: number; // Average of all available ratings
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
export const REVIEWS_COLLECTION =
|
|
130
|
+
export const REVIEWS_COLLECTION = 'reviews';
|
|
@@ -25,7 +25,7 @@ export const appointmentMediaItemSchema = z.object({
|
|
|
25
25
|
uploadedAt: z
|
|
26
26
|
.any()
|
|
27
27
|
.refine(
|
|
28
|
-
|
|
28
|
+
val =>
|
|
29
29
|
val instanceof Date ||
|
|
30
30
|
val?._seconds !== undefined ||
|
|
31
31
|
val?.seconds !== undefined ||
|
|
@@ -69,7 +69,7 @@ export const linkedFormInfoSchema = z.object({
|
|
|
69
69
|
submittedAt: z
|
|
70
70
|
.any()
|
|
71
71
|
.refine(
|
|
72
|
-
|
|
72
|
+
val =>
|
|
73
73
|
val === undefined ||
|
|
74
74
|
val instanceof Date ||
|
|
75
75
|
val?._seconds !== undefined ||
|
|
@@ -83,7 +83,7 @@ export const linkedFormInfoSchema = z.object({
|
|
|
83
83
|
completedAt: z
|
|
84
84
|
.any()
|
|
85
85
|
.refine(
|
|
86
|
-
|
|
86
|
+
val =>
|
|
87
87
|
val === undefined ||
|
|
88
88
|
val instanceof Date ||
|
|
89
89
|
val?._seconds !== undefined ||
|
|
@@ -103,7 +103,7 @@ export const patientReviewInfoSchema = z.object({
|
|
|
103
103
|
reviewedAt: z
|
|
104
104
|
.any()
|
|
105
105
|
.refine(
|
|
106
|
-
|
|
106
|
+
val =>
|
|
107
107
|
val instanceof Date ||
|
|
108
108
|
val?._seconds !== undefined ||
|
|
109
109
|
val?.seconds !== undefined ||
|
|
@@ -119,7 +119,7 @@ export const finalizedDetailsSchema = z.object({
|
|
|
119
119
|
at: z
|
|
120
120
|
.any()
|
|
121
121
|
.refine(
|
|
122
|
-
|
|
122
|
+
val =>
|
|
123
123
|
val instanceof Date ||
|
|
124
124
|
val?._seconds !== undefined ||
|
|
125
125
|
val?.seconds !== undefined ||
|
|
@@ -194,7 +194,7 @@ export const createAppointmentSchema = z
|
|
|
194
194
|
appointmentStartTime: z
|
|
195
195
|
.any()
|
|
196
196
|
.refine(
|
|
197
|
-
|
|
197
|
+
val =>
|
|
198
198
|
val instanceof Date ||
|
|
199
199
|
val?._seconds !== undefined ||
|
|
200
200
|
val?.seconds !== undefined ||
|
|
@@ -206,7 +206,7 @@ export const createAppointmentSchema = z
|
|
|
206
206
|
appointmentEndTime: z
|
|
207
207
|
.any()
|
|
208
208
|
.refine(
|
|
209
|
-
|
|
209
|
+
val =>
|
|
210
210
|
val instanceof Date ||
|
|
211
211
|
val?._seconds !== undefined ||
|
|
212
212
|
val?.seconds !== undefined ||
|
|
@@ -222,7 +222,7 @@ export const createAppointmentSchema = z
|
|
|
222
222
|
initialPaymentStatus: paymentStatusSchema.optional().default(PaymentStatus.UNPAID),
|
|
223
223
|
clinic_tz: z.string().min(1, 'Timezone is required'),
|
|
224
224
|
})
|
|
225
|
-
.refine(
|
|
225
|
+
.refine(data => data.appointmentEndTime > data.appointmentStartTime, {
|
|
226
226
|
message: 'Appointment end time must be after start time',
|
|
227
227
|
path: ['appointmentEndTime'],
|
|
228
228
|
});
|
|
@@ -263,7 +263,7 @@ export const updateAppointmentSchema = z
|
|
|
263
263
|
appointmentStartTime: z
|
|
264
264
|
.any()
|
|
265
265
|
.refine(
|
|
266
|
-
|
|
266
|
+
val =>
|
|
267
267
|
val === undefined ||
|
|
268
268
|
val instanceof Date ||
|
|
269
269
|
val?._seconds !== undefined ||
|
|
@@ -277,7 +277,7 @@ export const updateAppointmentSchema = z
|
|
|
277
277
|
appointmentEndTime: z
|
|
278
278
|
.any()
|
|
279
279
|
.refine(
|
|
280
|
-
|
|
280
|
+
val =>
|
|
281
281
|
val === undefined ||
|
|
282
282
|
val instanceof Date ||
|
|
283
283
|
val?._seconds !== undefined ||
|
|
@@ -302,7 +302,7 @@ export const updateAppointmentSchema = z
|
|
|
302
302
|
metadata: appointmentMetadataSchema.optional(),
|
|
303
303
|
})
|
|
304
304
|
.refine(
|
|
305
|
-
|
|
305
|
+
data => {
|
|
306
306
|
if (
|
|
307
307
|
data.status === AppointmentStatus.CANCELED_CLINIC ||
|
|
308
308
|
data.status === AppointmentStatus.CANCELED_PATIENT ||
|
|
@@ -319,7 +319,7 @@ export const updateAppointmentSchema = z
|
|
|
319
319
|
},
|
|
320
320
|
)
|
|
321
321
|
.refine(
|
|
322
|
-
|
|
322
|
+
data => {
|
|
323
323
|
if (data.appointmentStartTime && data.appointmentEndTime) {
|
|
324
324
|
return data.appointmentEndTime > data.appointmentStartTime;
|
|
325
325
|
}
|
|
@@ -342,7 +342,7 @@ export const searchAppointmentsSchema = z
|
|
|
342
342
|
startDate: z
|
|
343
343
|
.any()
|
|
344
344
|
.refine(
|
|
345
|
-
|
|
345
|
+
val =>
|
|
346
346
|
val === undefined ||
|
|
347
347
|
val instanceof Date ||
|
|
348
348
|
val?._seconds !== undefined ||
|
|
@@ -356,7 +356,7 @@ export const searchAppointmentsSchema = z
|
|
|
356
356
|
endDate: z
|
|
357
357
|
.any()
|
|
358
358
|
.refine(
|
|
359
|
-
|
|
359
|
+
val =>
|
|
360
360
|
val === undefined ||
|
|
361
361
|
val instanceof Date ||
|
|
362
362
|
val?._seconds !== undefined ||
|
|
@@ -374,7 +374,7 @@ export const searchAppointmentsSchema = z
|
|
|
374
374
|
startAfter: z.any().optional(),
|
|
375
375
|
})
|
|
376
376
|
.refine(
|
|
377
|
-
|
|
377
|
+
data => {
|
|
378
378
|
if (!data.startDate && !data.endDate && !data.status) {
|
|
379
379
|
return !!(data.patientId || data.practitionerId || data.clinicBranchId);
|
|
380
380
|
}
|
|
@@ -387,7 +387,7 @@ export const searchAppointmentsSchema = z
|
|
|
387
387
|
},
|
|
388
388
|
)
|
|
389
389
|
.refine(
|
|
390
|
-
|
|
390
|
+
data => {
|
|
391
391
|
if (data.startDate && data.endDate) {
|
|
392
392
|
return data.endDate >= data.startDate;
|
|
393
393
|
}
|
|
@@ -407,7 +407,7 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
407
407
|
newStartTime: z
|
|
408
408
|
.any()
|
|
409
409
|
.refine(
|
|
410
|
-
|
|
410
|
+
val =>
|
|
411
411
|
val instanceof Date ||
|
|
412
412
|
val?._seconds !== undefined ||
|
|
413
413
|
val?.seconds !== undefined ||
|
|
@@ -419,7 +419,7 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
419
419
|
newEndTime: z
|
|
420
420
|
.any()
|
|
421
421
|
.refine(
|
|
422
|
-
|
|
422
|
+
val =>
|
|
423
423
|
val instanceof Date ||
|
|
424
424
|
val?._seconds !== undefined ||
|
|
425
425
|
val?.seconds !== undefined ||
|
|
@@ -429,3 +429,23 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
429
429
|
'New end time must be a valid timestamp, Date object, number, or string',
|
|
430
430
|
),
|
|
431
431
|
});
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Schema for validating zone photo upload data
|
|
435
|
+
*/
|
|
436
|
+
export const zonePhotoUploadSchema = z.object({
|
|
437
|
+
appointmentId: z.string().min(MIN_STRING_LENGTH, 'Appointment ID is required'),
|
|
438
|
+
zoneId: z.string().min(MIN_STRING_LENGTH, 'Zone ID is required'),
|
|
439
|
+
photoType: z.enum(['before', 'after'], {
|
|
440
|
+
required_error: 'Photo type must be either "before" or "after"',
|
|
441
|
+
}),
|
|
442
|
+
file: z.any().refine(file => {
|
|
443
|
+
// Check if it's a File or Blob object
|
|
444
|
+
return (
|
|
445
|
+
file instanceof File ||
|
|
446
|
+
file instanceof Blob ||
|
|
447
|
+
(file && typeof file.size === 'number' && typeof file.type === 'string')
|
|
448
|
+
);
|
|
449
|
+
}, 'File must be a valid File or Blob object'),
|
|
450
|
+
notes: z.string().max(MAX_STRING_LENGTH_LONG, 'Notes too long').optional(),
|
|
451
|
+
});
|