@blackcode_sa/metaestetics-api 1.11.0 → 1.11.2
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 +5 -2
- package/dist/admin/index.d.ts +5 -2
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -10
- package/dist/index.mjs +6 -10
- package/package.json +1 -1
- package/src/types/appointment/index.ts +36 -40
- package/src/validations/appointment.schema.ts +118 -170
package/dist/admin/index.d.mts
CHANGED
|
@@ -1516,7 +1516,8 @@ interface BeforeAfterPerZone {
|
|
|
1516
1516
|
/** URL for after photo or null if not available */
|
|
1517
1517
|
after: MediaResource | null;
|
|
1518
1518
|
/** Optional note for the zone */
|
|
1519
|
-
|
|
1519
|
+
afterNote?: string | null;
|
|
1520
|
+
beforeNote?: string | null;
|
|
1520
1521
|
}
|
|
1521
1522
|
/**
|
|
1522
1523
|
* Interface for billing information per zone
|
|
@@ -1538,6 +1539,8 @@ interface BillingPerZone {
|
|
|
1538
1539
|
Subtotal: number;
|
|
1539
1540
|
/** Optional billing note */
|
|
1540
1541
|
Note: string | null;
|
|
1542
|
+
/** Ion/Batch number for traceability */
|
|
1543
|
+
IonNumber: string | null;
|
|
1541
1544
|
}
|
|
1542
1545
|
/**
|
|
1543
1546
|
* Interface for final billing calculations of the appointment
|
|
@@ -1612,7 +1615,7 @@ interface Appointment {
|
|
|
1612
1615
|
actualDurationMinutes?: number;
|
|
1613
1616
|
/** Cancellation Details */
|
|
1614
1617
|
cancellationReason?: string | null;
|
|
1615
|
-
canceledBy?:
|
|
1618
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
1616
1619
|
/** Notes */
|
|
1617
1620
|
internalNotes?: string | null;
|
|
1618
1621
|
patientNotes?: string | null;
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -1516,7 +1516,8 @@ interface BeforeAfterPerZone {
|
|
|
1516
1516
|
/** URL for after photo or null if not available */
|
|
1517
1517
|
after: MediaResource | null;
|
|
1518
1518
|
/** Optional note for the zone */
|
|
1519
|
-
|
|
1519
|
+
afterNote?: string | null;
|
|
1520
|
+
beforeNote?: string | null;
|
|
1520
1521
|
}
|
|
1521
1522
|
/**
|
|
1522
1523
|
* Interface for billing information per zone
|
|
@@ -1538,6 +1539,8 @@ interface BillingPerZone {
|
|
|
1538
1539
|
Subtotal: number;
|
|
1539
1540
|
/** Optional billing note */
|
|
1540
1541
|
Note: string | null;
|
|
1542
|
+
/** Ion/Batch number for traceability */
|
|
1543
|
+
IonNumber: string | null;
|
|
1541
1544
|
}
|
|
1542
1545
|
/**
|
|
1543
1546
|
* Interface for final billing calculations of the appointment
|
|
@@ -1612,7 +1615,7 @@ interface Appointment {
|
|
|
1612
1615
|
actualDurationMinutes?: number;
|
|
1613
1616
|
/** Cancellation Details */
|
|
1614
1617
|
cancellationReason?: string | null;
|
|
1615
|
-
canceledBy?:
|
|
1618
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
1616
1619
|
/** Notes */
|
|
1617
1620
|
internalNotes?: string | null;
|
|
1618
1621
|
patientNotes?: string | null;
|
package/dist/index.d.mts
CHANGED
|
@@ -2830,7 +2830,8 @@ interface BeforeAfterPerZone {
|
|
|
2830
2830
|
/** URL for after photo or null if not available */
|
|
2831
2831
|
after: MediaResource | null;
|
|
2832
2832
|
/** Optional note for the zone */
|
|
2833
|
-
|
|
2833
|
+
afterNote?: string | null;
|
|
2834
|
+
beforeNote?: string | null;
|
|
2834
2835
|
}
|
|
2835
2836
|
/**
|
|
2836
2837
|
* Interface for billing information per zone
|
|
@@ -2852,6 +2853,8 @@ interface BillingPerZone {
|
|
|
2852
2853
|
Subtotal: number;
|
|
2853
2854
|
/** Optional billing note */
|
|
2854
2855
|
Note: string | null;
|
|
2856
|
+
/** Ion/Batch number for traceability */
|
|
2857
|
+
IonNumber: string | null;
|
|
2855
2858
|
}
|
|
2856
2859
|
/**
|
|
2857
2860
|
* Interface for final billing calculations of the appointment
|
|
@@ -2926,7 +2929,7 @@ interface Appointment {
|
|
|
2926
2929
|
actualDurationMinutes?: number;
|
|
2927
2930
|
/** Cancellation Details */
|
|
2928
2931
|
cancellationReason?: string | null;
|
|
2929
|
-
canceledBy?:
|
|
2932
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
2930
2933
|
/** Notes */
|
|
2931
2934
|
internalNotes?: string | null;
|
|
2932
2935
|
patientNotes?: string | null;
|
|
@@ -3006,7 +3009,7 @@ interface UpdateAppointmentData {
|
|
|
3006
3009
|
procedureActualStartTime?: Timestamp | FieldValue | null;
|
|
3007
3010
|
actualDurationMinutes?: number;
|
|
3008
3011
|
cancellationReason?: string | null;
|
|
3009
|
-
canceledBy?:
|
|
3012
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
3010
3013
|
internalNotes?: string | null;
|
|
3011
3014
|
patientNotes?: string | FieldValue | null;
|
|
3012
3015
|
paymentStatus?: PaymentStatus;
|
package/dist/index.d.ts
CHANGED
|
@@ -2830,7 +2830,8 @@ interface BeforeAfterPerZone {
|
|
|
2830
2830
|
/** URL for after photo or null if not available */
|
|
2831
2831
|
after: MediaResource | null;
|
|
2832
2832
|
/** Optional note for the zone */
|
|
2833
|
-
|
|
2833
|
+
afterNote?: string | null;
|
|
2834
|
+
beforeNote?: string | null;
|
|
2834
2835
|
}
|
|
2835
2836
|
/**
|
|
2836
2837
|
* Interface for billing information per zone
|
|
@@ -2852,6 +2853,8 @@ interface BillingPerZone {
|
|
|
2852
2853
|
Subtotal: number;
|
|
2853
2854
|
/** Optional billing note */
|
|
2854
2855
|
Note: string | null;
|
|
2856
|
+
/** Ion/Batch number for traceability */
|
|
2857
|
+
IonNumber: string | null;
|
|
2855
2858
|
}
|
|
2856
2859
|
/**
|
|
2857
2860
|
* Interface for final billing calculations of the appointment
|
|
@@ -2926,7 +2929,7 @@ interface Appointment {
|
|
|
2926
2929
|
actualDurationMinutes?: number;
|
|
2927
2930
|
/** Cancellation Details */
|
|
2928
2931
|
cancellationReason?: string | null;
|
|
2929
|
-
canceledBy?:
|
|
2932
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
2930
2933
|
/** Notes */
|
|
2931
2934
|
internalNotes?: string | null;
|
|
2932
2935
|
patientNotes?: string | null;
|
|
@@ -3006,7 +3009,7 @@ interface UpdateAppointmentData {
|
|
|
3006
3009
|
procedureActualStartTime?: Timestamp | FieldValue | null;
|
|
3007
3010
|
actualDurationMinutes?: number;
|
|
3008
3011
|
cancellationReason?: string | null;
|
|
3009
|
-
canceledBy?:
|
|
3012
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
3010
3013
|
internalNotes?: string | null;
|
|
3011
3014
|
patientNotes?: string | FieldValue | null;
|
|
3012
3015
|
paymentStatus?: PaymentStatus;
|
package/dist/index.js
CHANGED
|
@@ -539,7 +539,8 @@ var finalizedDetailsSchema = import_zod3.z.object({
|
|
|
539
539
|
var beforeAfterPerZoneSchema = import_zod3.z.object({
|
|
540
540
|
before: mediaResourceSchema.nullable(),
|
|
541
541
|
after: mediaResourceSchema.nullable(),
|
|
542
|
-
|
|
542
|
+
afterNote: import_zod3.z.string().nullable().optional(),
|
|
543
|
+
beforeNote: import_zod3.z.string().nullable().optional()
|
|
543
544
|
});
|
|
544
545
|
var billingPerZoneSchema = import_zod3.z.object({
|
|
545
546
|
Product: import_zod3.z.string().min(MIN_STRING_LENGTH, "Product name is required"),
|
|
@@ -549,7 +550,8 @@ var billingPerZoneSchema = import_zod3.z.object({
|
|
|
549
550
|
UnitPrice: import_zod3.z.number().min(0, "Unit price must be non-negative"),
|
|
550
551
|
UnitCurency: import_zod3.z.nativeEnum(Currency),
|
|
551
552
|
Subtotal: import_zod3.z.number().min(0, "Subtotal must be non-negative"),
|
|
552
|
-
Note: import_zod3.z.string().nullable()
|
|
553
|
+
Note: import_zod3.z.string().nullable(),
|
|
554
|
+
IonNumber: import_zod3.z.string().nullable()
|
|
553
555
|
});
|
|
554
556
|
var finalBillingSchema = import_zod3.z.object({
|
|
555
557
|
subtotalAll: import_zod3.z.number().min(0, "Subtotal all must be non-negative"),
|
|
@@ -620,10 +622,7 @@ var updateAppointmentSchema = import_zod3.z.object({
|
|
|
620
622
|
practitionerId: import_zod3.z.string().min(MIN_STRING_LENGTH).optional(),
|
|
621
623
|
clinic_tz: import_zod3.z.string().min(MIN_STRING_LENGTH).optional(),
|
|
622
624
|
linkedForms: import_zod3.z.union([import_zod3.z.array(linkedFormInfoSchema).max(MAX_ARRAY_LENGTH), import_zod3.z.any()]).optional(),
|
|
623
|
-
media: import_zod3.z.union([
|
|
624
|
-
import_zod3.z.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH),
|
|
625
|
-
import_zod3.z.any()
|
|
626
|
-
]).optional(),
|
|
625
|
+
media: import_zod3.z.union([import_zod3.z.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH), import_zod3.z.any()]).optional(),
|
|
627
626
|
reviewInfo: import_zod3.z.union([patientReviewInfoSchema.nullable(), import_zod3.z.any()]).optional(),
|
|
628
627
|
finalizedDetails: import_zod3.z.union([finalizedDetailsSchema.nullable(), import_zod3.z.any()]).optional(),
|
|
629
628
|
isArchived: import_zod3.z.boolean().optional(),
|
|
@@ -664,10 +663,7 @@ var searchAppointmentsSchema = import_zod3.z.object({
|
|
|
664
663
|
(val) => val === void 0 || val instanceof Date || (val == null ? void 0 : val._seconds) !== void 0 || (val == null ? void 0 : val.seconds) !== void 0 || typeof val === "number" || typeof val === "string" || val && typeof val.toMillis === "function",
|
|
665
664
|
"End date must be a valid timestamp or Date object"
|
|
666
665
|
).optional(),
|
|
667
|
-
status: import_zod3.z.union([
|
|
668
|
-
appointmentStatusSchema,
|
|
669
|
-
import_zod3.z.array(appointmentStatusSchema).nonempty()
|
|
670
|
-
]).optional(),
|
|
666
|
+
status: import_zod3.z.union([appointmentStatusSchema, import_zod3.z.array(appointmentStatusSchema).nonempty()]).optional(),
|
|
671
667
|
limit: import_zod3.z.number().positive().int().optional().default(20),
|
|
672
668
|
startAfter: import_zod3.z.any().optional()
|
|
673
669
|
}).refine(
|
package/dist/index.mjs
CHANGED
|
@@ -411,7 +411,8 @@ var finalizedDetailsSchema = z3.object({
|
|
|
411
411
|
var beforeAfterPerZoneSchema = z3.object({
|
|
412
412
|
before: mediaResourceSchema.nullable(),
|
|
413
413
|
after: mediaResourceSchema.nullable(),
|
|
414
|
-
|
|
414
|
+
afterNote: z3.string().nullable().optional(),
|
|
415
|
+
beforeNote: z3.string().nullable().optional()
|
|
415
416
|
});
|
|
416
417
|
var billingPerZoneSchema = z3.object({
|
|
417
418
|
Product: z3.string().min(MIN_STRING_LENGTH, "Product name is required"),
|
|
@@ -421,7 +422,8 @@ var billingPerZoneSchema = z3.object({
|
|
|
421
422
|
UnitPrice: z3.number().min(0, "Unit price must be non-negative"),
|
|
422
423
|
UnitCurency: z3.nativeEnum(Currency),
|
|
423
424
|
Subtotal: z3.number().min(0, "Subtotal must be non-negative"),
|
|
424
|
-
Note: z3.string().nullable()
|
|
425
|
+
Note: z3.string().nullable(),
|
|
426
|
+
IonNumber: z3.string().nullable()
|
|
425
427
|
});
|
|
426
428
|
var finalBillingSchema = z3.object({
|
|
427
429
|
subtotalAll: z3.number().min(0, "Subtotal all must be non-negative"),
|
|
@@ -492,10 +494,7 @@ var updateAppointmentSchema = z3.object({
|
|
|
492
494
|
practitionerId: z3.string().min(MIN_STRING_LENGTH).optional(),
|
|
493
495
|
clinic_tz: z3.string().min(MIN_STRING_LENGTH).optional(),
|
|
494
496
|
linkedForms: z3.union([z3.array(linkedFormInfoSchema).max(MAX_ARRAY_LENGTH), z3.any()]).optional(),
|
|
495
|
-
media: z3.union([
|
|
496
|
-
z3.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH),
|
|
497
|
-
z3.any()
|
|
498
|
-
]).optional(),
|
|
497
|
+
media: z3.union([z3.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH), z3.any()]).optional(),
|
|
499
498
|
reviewInfo: z3.union([patientReviewInfoSchema.nullable(), z3.any()]).optional(),
|
|
500
499
|
finalizedDetails: z3.union([finalizedDetailsSchema.nullable(), z3.any()]).optional(),
|
|
501
500
|
isArchived: z3.boolean().optional(),
|
|
@@ -536,10 +535,7 @@ var searchAppointmentsSchema = z3.object({
|
|
|
536
535
|
(val) => val === void 0 || val instanceof Date || (val == null ? void 0 : val._seconds) !== void 0 || (val == null ? void 0 : val.seconds) !== void 0 || typeof val === "number" || typeof val === "string" || val && typeof val.toMillis === "function",
|
|
537
536
|
"End date must be a valid timestamp or Date object"
|
|
538
537
|
).optional(),
|
|
539
|
-
status: z3.union([
|
|
540
|
-
appointmentStatusSchema,
|
|
541
|
-
z3.array(appointmentStatusSchema).nonempty()
|
|
542
|
-
]).optional(),
|
|
538
|
+
status: z3.union([appointmentStatusSchema, z3.array(appointmentStatusSchema).nonempty()]).optional(),
|
|
543
539
|
limit: z3.number().positive().int().optional().default(20),
|
|
544
540
|
startAfter: z3.any().optional()
|
|
545
541
|
}).refine(
|
package/package.json
CHANGED
|
@@ -1,56 +1,49 @@
|
|
|
1
|
-
import { Timestamp, FieldValue } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "../../backoffice/types/static/pricing.types";
|
|
12
|
-
import { BlockingCondition } from "../../backoffice/types/static/blocking-condition.types";
|
|
13
|
-
import { Contraindication } from "../../backoffice/types/static/contraindication.types";
|
|
14
|
-
import { Requirement } from "../../backoffice/types/requirement.types";
|
|
15
|
-
import { FilledDocumentStatus } from "../documentation-templates";
|
|
16
|
-
import type { ProcedureFamily } from "../../backoffice";
|
|
17
|
-
import type { MediaResource } from "../../services/media/media.service";
|
|
1
|
+
import { Timestamp, FieldValue } from 'firebase/firestore';
|
|
2
|
+
import { ClinicInfo, PractitionerProfileInfo, PatientProfileInfo } from '../profile';
|
|
3
|
+
import { ProcedureSummaryInfo } from '../procedure';
|
|
4
|
+
import { Currency, type PricingMeasure } from '../../backoffice/types/static/pricing.types';
|
|
5
|
+
import { BlockingCondition } from '../../backoffice/types/static/blocking-condition.types';
|
|
6
|
+
import { Contraindication } from '../../backoffice/types/static/contraindication.types';
|
|
7
|
+
import { Requirement } from '../../backoffice/types/requirement.types';
|
|
8
|
+
import { FilledDocumentStatus } from '../documentation-templates';
|
|
9
|
+
import type { ProcedureFamily } from '../../backoffice';
|
|
10
|
+
import type { MediaResource } from '../../services/media/media.service';
|
|
18
11
|
|
|
19
12
|
/**
|
|
20
13
|
* Enum defining the possible statuses of an appointment.
|
|
21
14
|
*/
|
|
22
15
|
export enum AppointmentStatus {
|
|
23
|
-
PENDING =
|
|
24
|
-
CONFIRMED =
|
|
25
|
-
CHECKED_IN =
|
|
26
|
-
IN_PROGRESS =
|
|
27
|
-
COMPLETED =
|
|
28
|
-
CANCELED_PATIENT =
|
|
29
|
-
CANCELED_PATIENT_RESCHEDULED =
|
|
30
|
-
CANCELED_CLINIC =
|
|
31
|
-
NO_SHOW =
|
|
32
|
-
RESCHEDULED_BY_CLINIC =
|
|
16
|
+
PENDING = 'pending', // Initial state after booking, before confirmation (if applicable)
|
|
17
|
+
CONFIRMED = 'confirmed', // Confirmed by clinic/practitioner
|
|
18
|
+
CHECKED_IN = 'checked_in', // Patient has arrived
|
|
19
|
+
IN_PROGRESS = 'in_progress', // Procedure has started
|
|
20
|
+
COMPLETED = 'completed', // Procedure finished successfully
|
|
21
|
+
CANCELED_PATIENT = 'canceled_patient', // Canceled by the patient
|
|
22
|
+
CANCELED_PATIENT_RESCHEDULED = 'canceled_patient_rescheduled', // Canceled by the patient and rescheduled by the clinic
|
|
23
|
+
CANCELED_CLINIC = 'canceled_clinic', // Canceled by the clinic/practitioner
|
|
24
|
+
NO_SHOW = 'no_show', // Patient did not attend
|
|
25
|
+
RESCHEDULED_BY_CLINIC = 'rescheduled_by_clinic', // When appointment is rescheduled by the clinic, waiting for patient confirmation or cancellation (when reschedule is accepted, status goes to confirmed, if not accepted, then status goes to canceled_patient_rescheduled)
|
|
33
26
|
}
|
|
34
27
|
|
|
35
28
|
/**
|
|
36
29
|
* Enum defining the payment status of an appointment.
|
|
37
30
|
*/
|
|
38
31
|
export enum PaymentStatus {
|
|
39
|
-
UNPAID =
|
|
40
|
-
PAID =
|
|
41
|
-
PARTIALLY_PAID =
|
|
42
|
-
REFUNDED =
|
|
43
|
-
NOT_APPLICABLE =
|
|
32
|
+
UNPAID = 'unpaid',
|
|
33
|
+
PAID = 'paid',
|
|
34
|
+
PARTIALLY_PAID = 'partially_paid',
|
|
35
|
+
REFUNDED = 'refunded',
|
|
36
|
+
NOT_APPLICABLE = 'not_applicable', // For free services or other scenarios
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
/**
|
|
47
40
|
* Enum for different types of media that can be attached to an appointment.
|
|
48
41
|
*/
|
|
49
42
|
export enum MediaType {
|
|
50
|
-
BEFORE_PHOTO =
|
|
51
|
-
AFTER_PHOTO =
|
|
52
|
-
CONSENT_SCAN =
|
|
53
|
-
OTHER_DOCUMENT =
|
|
43
|
+
BEFORE_PHOTO = 'before_photo',
|
|
44
|
+
AFTER_PHOTO = 'after_photo',
|
|
45
|
+
CONSENT_SCAN = 'consent_scan',
|
|
46
|
+
OTHER_DOCUMENT = 'other_document',
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
/**
|
|
@@ -124,7 +117,8 @@ export interface BeforeAfterPerZone {
|
|
|
124
117
|
/** URL for after photo or null if not available */
|
|
125
118
|
after: MediaResource | null;
|
|
126
119
|
/** Optional note for the zone */
|
|
127
|
-
|
|
120
|
+
afterNote?: string | null;
|
|
121
|
+
beforeNote?: string | null;
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
/**
|
|
@@ -147,6 +141,8 @@ export interface BillingPerZone {
|
|
|
147
141
|
Subtotal: number;
|
|
148
142
|
/** Optional billing note */
|
|
149
143
|
Note: string | null;
|
|
144
|
+
/** Ion/Batch number for traceability */
|
|
145
|
+
IonNumber: string | null;
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
/**
|
|
@@ -231,7 +227,7 @@ export interface Appointment {
|
|
|
231
227
|
|
|
232
228
|
/** Cancellation Details */
|
|
233
229
|
cancellationReason?: string | null;
|
|
234
|
-
canceledBy?:
|
|
230
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
235
231
|
|
|
236
232
|
/** Notes */
|
|
237
233
|
internalNotes?: string | null;
|
|
@@ -326,7 +322,7 @@ export interface UpdateAppointmentData {
|
|
|
326
322
|
procedureActualStartTime?: Timestamp | FieldValue | null; // NEW
|
|
327
323
|
actualDurationMinutes?: number;
|
|
328
324
|
cancellationReason?: string | null;
|
|
329
|
-
canceledBy?:
|
|
325
|
+
canceledBy?: 'patient' | 'clinic' | 'practitioner' | 'system';
|
|
330
326
|
internalNotes?: string | null;
|
|
331
327
|
patientNotes?: string | FieldValue | null; // Allow FieldValue for deleting
|
|
332
328
|
paymentStatus?: PaymentStatus;
|
|
@@ -378,4 +374,4 @@ export interface SearchAppointmentsParams {
|
|
|
378
374
|
}
|
|
379
375
|
|
|
380
376
|
/** Firestore collection name */
|
|
381
|
-
export const APPOINTMENTS_COLLECTION =
|
|
377
|
+
export const APPOINTMENTS_COLLECTION = 'appointments';
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../types/appointment";
|
|
7
|
-
import { filledDocumentStatusSchema } from "./documentation-templates.schema";
|
|
8
|
-
import {
|
|
9
|
-
Currency,
|
|
10
|
-
PricingMeasure,
|
|
11
|
-
} from "../backoffice/types/static/pricing.types";
|
|
12
|
-
import { mediaResourceSchema } from "./media.schema";
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AppointmentStatus, PaymentStatus, MediaType } from '../types/appointment';
|
|
3
|
+
import { filledDocumentStatusSchema } from './documentation-templates.schema';
|
|
4
|
+
import { Currency, PricingMeasure } from '../backoffice/types/static/pricing.types';
|
|
5
|
+
import { mediaResourceSchema } from './media.schema';
|
|
13
6
|
|
|
14
7
|
// Define common constants locally if not available from common.schema.ts
|
|
15
8
|
const MIN_STRING_LENGTH = 1;
|
|
@@ -25,9 +18,9 @@ export const mediaTypeSchema = z.nativeEnum(MediaType);
|
|
|
25
18
|
// --- Schemas for Nested Objects from types/appointment/index.ts ---
|
|
26
19
|
|
|
27
20
|
export const appointmentMediaItemSchema = z.object({
|
|
28
|
-
id: z.string().min(MIN_STRING_LENGTH,
|
|
21
|
+
id: z.string().min(MIN_STRING_LENGTH, 'Media item ID is required'),
|
|
29
22
|
type: mediaTypeSchema,
|
|
30
|
-
url: z.string().url(
|
|
23
|
+
url: z.string().url('Media URL must be a valid URL'),
|
|
31
24
|
fileName: z.string().optional(),
|
|
32
25
|
uploadedAt: z
|
|
33
26
|
.any()
|
|
@@ -36,18 +29,13 @@ export const appointmentMediaItemSchema = z.object({
|
|
|
36
29
|
val instanceof Date ||
|
|
37
30
|
val?._seconds !== undefined ||
|
|
38
31
|
val?.seconds !== undefined ||
|
|
39
|
-
typeof val ===
|
|
40
|
-
typeof val ===
|
|
41
|
-
(val && typeof val.toMillis ===
|
|
42
|
-
|
|
32
|
+
typeof val === 'number' ||
|
|
33
|
+
typeof val === 'string' ||
|
|
34
|
+
(val && typeof val.toMillis === 'function'),
|
|
35
|
+
'uploadedAt must be a valid timestamp or Date object',
|
|
43
36
|
),
|
|
44
|
-
uploadedBy: z
|
|
45
|
-
|
|
46
|
-
.min(MIN_STRING_LENGTH, "Uploaded by user ID is required"),
|
|
47
|
-
description: z
|
|
48
|
-
.string()
|
|
49
|
-
.max(MAX_STRING_LENGTH, "Description too long")
|
|
50
|
-
.optional(),
|
|
37
|
+
uploadedBy: z.string().min(MIN_STRING_LENGTH, 'Uploaded by user ID is required'),
|
|
38
|
+
description: z.string().max(MAX_STRING_LENGTH, 'Description too long').optional(),
|
|
51
39
|
});
|
|
52
40
|
|
|
53
41
|
export const procedureExtendedInfoSchema = z.object({
|
|
@@ -70,17 +58,14 @@ export const procedureExtendedInfoSchema = z.object({
|
|
|
70
58
|
});
|
|
71
59
|
|
|
72
60
|
export const linkedFormInfoSchema = z.object({
|
|
73
|
-
formId: z.string().min(MIN_STRING_LENGTH,
|
|
74
|
-
templateId: z.string().min(MIN_STRING_LENGTH,
|
|
75
|
-
templateVersion: z
|
|
76
|
-
|
|
77
|
-
.int()
|
|
78
|
-
.positive("Template version must be a positive integer"),
|
|
79
|
-
title: z.string().min(MIN_STRING_LENGTH, "Form title is required"),
|
|
61
|
+
formId: z.string().min(MIN_STRING_LENGTH, 'Form ID is required'),
|
|
62
|
+
templateId: z.string().min(MIN_STRING_LENGTH, 'Template ID is required'),
|
|
63
|
+
templateVersion: z.number().int().positive('Template version must be a positive integer'),
|
|
64
|
+
title: z.string().min(MIN_STRING_LENGTH, 'Form title is required'),
|
|
80
65
|
isUserForm: z.boolean(),
|
|
81
66
|
isRequired: z.boolean().optional(),
|
|
82
67
|
status: filledDocumentStatusSchema,
|
|
83
|
-
path: z.string().min(MIN_STRING_LENGTH,
|
|
68
|
+
path: z.string().min(MIN_STRING_LENGTH, 'Form path is required'),
|
|
84
69
|
submittedAt: z
|
|
85
70
|
.any()
|
|
86
71
|
.refine(
|
|
@@ -89,10 +74,10 @@ export const linkedFormInfoSchema = z.object({
|
|
|
89
74
|
val instanceof Date ||
|
|
90
75
|
val?._seconds !== undefined ||
|
|
91
76
|
val?.seconds !== undefined ||
|
|
92
|
-
typeof val ===
|
|
93
|
-
typeof val ===
|
|
94
|
-
(val && typeof val.toMillis ===
|
|
95
|
-
|
|
77
|
+
typeof val === 'number' ||
|
|
78
|
+
typeof val === 'string' ||
|
|
79
|
+
(val && typeof val.toMillis === 'function'),
|
|
80
|
+
'submittedAt must be a valid timestamp or Date object',
|
|
96
81
|
)
|
|
97
82
|
.optional(),
|
|
98
83
|
completedAt: z
|
|
@@ -103,21 +88,18 @@ export const linkedFormInfoSchema = z.object({
|
|
|
103
88
|
val instanceof Date ||
|
|
104
89
|
val?._seconds !== undefined ||
|
|
105
90
|
val?.seconds !== undefined ||
|
|
106
|
-
typeof val ===
|
|
107
|
-
typeof val ===
|
|
108
|
-
(val && typeof val.toMillis ===
|
|
109
|
-
|
|
91
|
+
typeof val === 'number' ||
|
|
92
|
+
typeof val === 'string' ||
|
|
93
|
+
(val && typeof val.toMillis === 'function'),
|
|
94
|
+
'completedAt must be a valid timestamp or Date object',
|
|
110
95
|
)
|
|
111
96
|
.optional(),
|
|
112
97
|
});
|
|
113
98
|
|
|
114
99
|
export const patientReviewInfoSchema = z.object({
|
|
115
|
-
reviewId: z.string().min(MIN_STRING_LENGTH,
|
|
116
|
-
rating: z.number().min(1).max(5,
|
|
117
|
-
comment: z
|
|
118
|
-
.string()
|
|
119
|
-
.max(MAX_STRING_LENGTH_LONG, "Comment too long")
|
|
120
|
-
.optional(),
|
|
100
|
+
reviewId: z.string().min(MIN_STRING_LENGTH, 'Review ID is required'),
|
|
101
|
+
rating: z.number().min(1).max(5, 'Rating must be between 1 and 5'),
|
|
102
|
+
comment: z.string().max(MAX_STRING_LENGTH_LONG, 'Comment too long').optional(),
|
|
121
103
|
reviewedAt: z
|
|
122
104
|
.any()
|
|
123
105
|
.refine(
|
|
@@ -125,15 +107,15 @@ export const patientReviewInfoSchema = z.object({
|
|
|
125
107
|
val instanceof Date ||
|
|
126
108
|
val?._seconds !== undefined ||
|
|
127
109
|
val?.seconds !== undefined ||
|
|
128
|
-
typeof val ===
|
|
129
|
-
typeof val ===
|
|
130
|
-
(val && typeof val.toMillis ===
|
|
131
|
-
|
|
110
|
+
typeof val === 'number' ||
|
|
111
|
+
typeof val === 'string' ||
|
|
112
|
+
(val && typeof val.toMillis === 'function'),
|
|
113
|
+
'reviewedAt must be a valid timestamp or Date object',
|
|
132
114
|
),
|
|
133
115
|
});
|
|
134
116
|
|
|
135
117
|
export const finalizedDetailsSchema = z.object({
|
|
136
|
-
by: z.string().min(MIN_STRING_LENGTH,
|
|
118
|
+
by: z.string().min(MIN_STRING_LENGTH, 'Finalized by user ID is required'),
|
|
137
119
|
at: z
|
|
138
120
|
.any()
|
|
139
121
|
.refine(
|
|
@@ -141,15 +123,12 @@ export const finalizedDetailsSchema = z.object({
|
|
|
141
123
|
val instanceof Date ||
|
|
142
124
|
val?._seconds !== undefined ||
|
|
143
125
|
val?.seconds !== undefined ||
|
|
144
|
-
typeof val ===
|
|
145
|
-
typeof val ===
|
|
146
|
-
(val && typeof val.toMillis ===
|
|
147
|
-
|
|
126
|
+
typeof val === 'number' ||
|
|
127
|
+
typeof val === 'string' ||
|
|
128
|
+
(val && typeof val.toMillis === 'function'),
|
|
129
|
+
'Finalized at must be a valid timestamp or Date object',
|
|
148
130
|
),
|
|
149
|
-
notes: z
|
|
150
|
-
.string()
|
|
151
|
-
.max(MAX_STRING_LENGTH_LONG, "Finalization notes too long")
|
|
152
|
-
.optional(),
|
|
131
|
+
notes: z.string().max(MAX_STRING_LENGTH_LONG, 'Finalization notes too long').optional(),
|
|
153
132
|
});
|
|
154
133
|
|
|
155
134
|
/**
|
|
@@ -158,32 +137,34 @@ export const finalizedDetailsSchema = z.object({
|
|
|
158
137
|
export const beforeAfterPerZoneSchema = z.object({
|
|
159
138
|
before: mediaResourceSchema.nullable(),
|
|
160
139
|
after: mediaResourceSchema.nullable(),
|
|
161
|
-
|
|
140
|
+
afterNote: z.string().nullable().optional(),
|
|
141
|
+
beforeNote: z.string().nullable().optional(),
|
|
162
142
|
});
|
|
163
143
|
|
|
164
144
|
/**
|
|
165
145
|
* Schema for billing information per zone
|
|
166
146
|
*/
|
|
167
147
|
export const billingPerZoneSchema = z.object({
|
|
168
|
-
Product: z.string().min(MIN_STRING_LENGTH,
|
|
148
|
+
Product: z.string().min(MIN_STRING_LENGTH, 'Product name is required'),
|
|
169
149
|
ProductId: z.string().nullable(),
|
|
170
|
-
Quantity: z.number().min(0,
|
|
150
|
+
Quantity: z.number().min(0, 'Quantity must be non-negative'),
|
|
171
151
|
UnitOfMeasurement: z.nativeEnum(PricingMeasure),
|
|
172
|
-
UnitPrice: z.number().min(0,
|
|
152
|
+
UnitPrice: z.number().min(0, 'Unit price must be non-negative'),
|
|
173
153
|
UnitCurency: z.nativeEnum(Currency),
|
|
174
|
-
Subtotal: z.number().min(0,
|
|
154
|
+
Subtotal: z.number().min(0, 'Subtotal must be non-negative'),
|
|
175
155
|
Note: z.string().nullable(),
|
|
156
|
+
IonNumber: z.string().nullable(),
|
|
176
157
|
});
|
|
177
158
|
|
|
178
159
|
/**
|
|
179
160
|
* Schema for final billing calculations of the appointment
|
|
180
161
|
*/
|
|
181
162
|
export const finalBillingSchema = z.object({
|
|
182
|
-
subtotalAll: z.number().min(0,
|
|
183
|
-
taxRate: z.number().min(0).max(1,
|
|
184
|
-
taxPrice: z.number().min(0,
|
|
185
|
-
finalPrice: z.number().min(0,
|
|
186
|
-
finalQuantity: z.number().min(0,
|
|
163
|
+
subtotalAll: z.number().min(0, 'Subtotal all must be non-negative'),
|
|
164
|
+
taxRate: z.number().min(0).max(1, 'Tax rate must be between 0 and 1'),
|
|
165
|
+
taxPrice: z.number().min(0, 'Tax price must be non-negative'),
|
|
166
|
+
finalPrice: z.number().min(0, 'Final price must be non-negative'),
|
|
167
|
+
finalQuantity: z.number().min(0, 'Final quantity must be non-negative'),
|
|
187
168
|
currency: z.nativeEnum(Currency),
|
|
188
169
|
unitOfMeasurement: z.nativeEnum(PricingMeasure),
|
|
189
170
|
});
|
|
@@ -205,14 +186,10 @@ export const appointmentMetadataSchema = z.object({
|
|
|
205
186
|
*/
|
|
206
187
|
export const createAppointmentSchema = z
|
|
207
188
|
.object({
|
|
208
|
-
clinicBranchId: z
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.string()
|
|
213
|
-
.min(MIN_STRING_LENGTH, "Practitioner ID is required"),
|
|
214
|
-
patientId: z.string().min(MIN_STRING_LENGTH, "Patient ID is required"),
|
|
215
|
-
procedureId: z.string().min(MIN_STRING_LENGTH, "Procedure ID is required"),
|
|
189
|
+
clinicBranchId: z.string().min(MIN_STRING_LENGTH, 'Clinic branch ID is required'),
|
|
190
|
+
practitionerId: z.string().min(MIN_STRING_LENGTH, 'Practitioner ID is required'),
|
|
191
|
+
patientId: z.string().min(MIN_STRING_LENGTH, 'Patient ID is required'),
|
|
192
|
+
procedureId: z.string().min(MIN_STRING_LENGTH, 'Procedure ID is required'),
|
|
216
193
|
appointmentStartTime: z
|
|
217
194
|
.any()
|
|
218
195
|
.refine(
|
|
@@ -220,10 +197,10 @@ export const createAppointmentSchema = z
|
|
|
220
197
|
val instanceof Date ||
|
|
221
198
|
val?._seconds !== undefined ||
|
|
222
199
|
val?.seconds !== undefined ||
|
|
223
|
-
typeof val ===
|
|
224
|
-
typeof val ===
|
|
225
|
-
(val && typeof val.toMillis ===
|
|
226
|
-
|
|
200
|
+
typeof val === 'number' ||
|
|
201
|
+
typeof val === 'string' ||
|
|
202
|
+
(val && typeof val.toMillis === 'function'),
|
|
203
|
+
'Appointment start time must be a valid timestamp or Date object',
|
|
227
204
|
),
|
|
228
205
|
appointmentEndTime: z
|
|
229
206
|
.any()
|
|
@@ -232,27 +209,21 @@ export const createAppointmentSchema = z
|
|
|
232
209
|
val instanceof Date ||
|
|
233
210
|
val?._seconds !== undefined ||
|
|
234
211
|
val?.seconds !== undefined ||
|
|
235
|
-
typeof val ===
|
|
236
|
-
typeof val ===
|
|
237
|
-
(val && typeof val.toMillis ===
|
|
238
|
-
|
|
212
|
+
typeof val === 'number' ||
|
|
213
|
+
typeof val === 'string' ||
|
|
214
|
+
(val && typeof val.toMillis === 'function'),
|
|
215
|
+
'Appointment end time must be a valid timestamp or Date object',
|
|
239
216
|
),
|
|
240
|
-
cost: z.number().min(0,
|
|
241
|
-
currency: z.string().min(1,
|
|
242
|
-
patientNotes: z
|
|
243
|
-
.string()
|
|
244
|
-
.max(MAX_STRING_LENGTH, "Patient notes too long")
|
|
245
|
-
.nullable()
|
|
246
|
-
.optional(),
|
|
217
|
+
cost: z.number().min(0, 'Cost must be a non-negative number'),
|
|
218
|
+
currency: z.string().min(1, 'Currency is required'),
|
|
219
|
+
patientNotes: z.string().max(MAX_STRING_LENGTH, 'Patient notes too long').nullable().optional(),
|
|
247
220
|
initialStatus: appointmentStatusSchema,
|
|
248
|
-
initialPaymentStatus: paymentStatusSchema
|
|
249
|
-
|
|
250
|
-
.default(PaymentStatus.UNPAID),
|
|
251
|
-
clinic_tz: z.string().min(1, "Timezone is required"),
|
|
221
|
+
initialPaymentStatus: paymentStatusSchema.optional().default(PaymentStatus.UNPAID),
|
|
222
|
+
clinic_tz: z.string().min(1, 'Timezone is required'),
|
|
252
223
|
})
|
|
253
224
|
.refine((data) => data.appointmentEndTime > data.appointmentStartTime, {
|
|
254
|
-
message:
|
|
255
|
-
path: [
|
|
225
|
+
message: 'Appointment end time must be after start time',
|
|
226
|
+
path: ['appointmentEndTime'],
|
|
256
227
|
});
|
|
257
228
|
|
|
258
229
|
/**
|
|
@@ -268,30 +239,24 @@ export const updateAppointmentSchema = z
|
|
|
268
239
|
actualDurationMinutes: z
|
|
269
240
|
.number()
|
|
270
241
|
.int()
|
|
271
|
-
.positive(
|
|
242
|
+
.positive('Duration must be a positive integer')
|
|
272
243
|
.optional(),
|
|
273
244
|
cancellationReason: z
|
|
274
245
|
.string()
|
|
275
|
-
.max(MAX_STRING_LENGTH,
|
|
246
|
+
.max(MAX_STRING_LENGTH, 'Cancellation reason too long')
|
|
276
247
|
.nullable()
|
|
277
248
|
.optional(),
|
|
278
|
-
canceledBy: z
|
|
279
|
-
.enum(["patient", "clinic", "practitioner", "system"])
|
|
280
|
-
.optional(),
|
|
249
|
+
canceledBy: z.enum(['patient', 'clinic', 'practitioner', 'system']).optional(),
|
|
281
250
|
internalNotes: z
|
|
282
251
|
.string()
|
|
283
|
-
.max(MAX_STRING_LENGTH_LONG,
|
|
252
|
+
.max(MAX_STRING_LENGTH_LONG, 'Internal notes too long')
|
|
284
253
|
.nullable()
|
|
285
254
|
.optional(),
|
|
286
255
|
patientNotes: z.any().optional().nullable(),
|
|
287
256
|
paymentStatus: paymentStatusSchema.optional(),
|
|
288
257
|
paymentTransactionId: z.any().optional().nullable(),
|
|
289
|
-
completedPreRequirements: z
|
|
290
|
-
|
|
291
|
-
.optional(),
|
|
292
|
-
completedPostRequirements: z
|
|
293
|
-
.union([z.array(z.string()), z.any()])
|
|
294
|
-
.optional(),
|
|
258
|
+
completedPreRequirements: z.union([z.array(z.string()), z.any()]).optional(),
|
|
259
|
+
completedPostRequirements: z.union([z.array(z.string()), z.any()]).optional(),
|
|
295
260
|
linkedFormIds: z.union([z.array(z.string()), z.any()]).optional(),
|
|
296
261
|
pendingUserFormsIds: z.union([z.array(z.string()), z.any()]).optional(),
|
|
297
262
|
appointmentStartTime: z
|
|
@@ -302,10 +267,10 @@ export const updateAppointmentSchema = z
|
|
|
302
267
|
val instanceof Date ||
|
|
303
268
|
val?._seconds !== undefined ||
|
|
304
269
|
val?.seconds !== undefined ||
|
|
305
|
-
typeof val ===
|
|
306
|
-
typeof val ===
|
|
307
|
-
(val && typeof val.toMillis ===
|
|
308
|
-
|
|
270
|
+
typeof val === 'number' ||
|
|
271
|
+
typeof val === 'string' ||
|
|
272
|
+
(val && typeof val.toMillis === 'function'),
|
|
273
|
+
'Appointment start time must be a valid timestamp or Date object',
|
|
309
274
|
)
|
|
310
275
|
.optional(),
|
|
311
276
|
appointmentEndTime: z
|
|
@@ -316,10 +281,10 @@ export const updateAppointmentSchema = z
|
|
|
316
281
|
val instanceof Date ||
|
|
317
282
|
val?._seconds !== undefined ||
|
|
318
283
|
val?.seconds !== undefined ||
|
|
319
|
-
typeof val ===
|
|
320
|
-
typeof val ===
|
|
321
|
-
(val && typeof val.toMillis ===
|
|
322
|
-
|
|
284
|
+
typeof val === 'number' ||
|
|
285
|
+
typeof val === 'string' ||
|
|
286
|
+
(val && typeof val.toMillis === 'function'),
|
|
287
|
+
'Appointment end time must be a valid timestamp or Date object',
|
|
323
288
|
)
|
|
324
289
|
.optional(),
|
|
325
290
|
calendarEventId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
@@ -327,21 +292,10 @@ export const updateAppointmentSchema = z
|
|
|
327
292
|
clinicBranchId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
328
293
|
practitionerId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
329
294
|
clinic_tz: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
330
|
-
linkedForms: z
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
.union([
|
|
335
|
-
z.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH),
|
|
336
|
-
z.any(),
|
|
337
|
-
])
|
|
338
|
-
.optional(),
|
|
339
|
-
reviewInfo: z
|
|
340
|
-
.union([patientReviewInfoSchema.nullable(), z.any()])
|
|
341
|
-
.optional(),
|
|
342
|
-
finalizedDetails: z
|
|
343
|
-
.union([finalizedDetailsSchema.nullable(), z.any()])
|
|
344
|
-
.optional(),
|
|
295
|
+
linkedForms: z.union([z.array(linkedFormInfoSchema).max(MAX_ARRAY_LENGTH), z.any()]).optional(),
|
|
296
|
+
media: z.union([z.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH), z.any()]).optional(),
|
|
297
|
+
reviewInfo: z.union([patientReviewInfoSchema.nullable(), z.any()]).optional(),
|
|
298
|
+
finalizedDetails: z.union([finalizedDetailsSchema.nullable(), z.any()]).optional(),
|
|
345
299
|
isArchived: z.boolean().optional(),
|
|
346
300
|
updatedAt: z.any().optional(),
|
|
347
301
|
metadata: appointmentMetadataSchema.optional(),
|
|
@@ -359,9 +313,9 @@ export const updateAppointmentSchema = z
|
|
|
359
313
|
},
|
|
360
314
|
{
|
|
361
315
|
message:
|
|
362
|
-
|
|
363
|
-
path: [
|
|
364
|
-
}
|
|
316
|
+
'Cancellation reason and canceled by must be provided when canceling an appointment with patient or clinic origin.',
|
|
317
|
+
path: ['status'],
|
|
318
|
+
},
|
|
365
319
|
)
|
|
366
320
|
.refine(
|
|
367
321
|
(data) => {
|
|
@@ -371,10 +325,9 @@ export const updateAppointmentSchema = z
|
|
|
371
325
|
return true;
|
|
372
326
|
},
|
|
373
327
|
{
|
|
374
|
-
message:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
328
|
+
message: 'Appointment end time must be after start time if both are provided',
|
|
329
|
+
path: ['appointmentEndTime'],
|
|
330
|
+
},
|
|
378
331
|
);
|
|
379
332
|
|
|
380
333
|
/**
|
|
@@ -393,10 +346,10 @@ export const searchAppointmentsSchema = z
|
|
|
393
346
|
val instanceof Date ||
|
|
394
347
|
val?._seconds !== undefined ||
|
|
395
348
|
val?.seconds !== undefined ||
|
|
396
|
-
typeof val ===
|
|
397
|
-
typeof val ===
|
|
398
|
-
(val && typeof val.toMillis ===
|
|
399
|
-
|
|
349
|
+
typeof val === 'number' ||
|
|
350
|
+
typeof val === 'string' ||
|
|
351
|
+
(val && typeof val.toMillis === 'function'),
|
|
352
|
+
'Start date must be a valid timestamp or Date object',
|
|
400
353
|
)
|
|
401
354
|
.optional(),
|
|
402
355
|
endDate: z
|
|
@@ -407,17 +360,14 @@ export const searchAppointmentsSchema = z
|
|
|
407
360
|
val instanceof Date ||
|
|
408
361
|
val?._seconds !== undefined ||
|
|
409
362
|
val?.seconds !== undefined ||
|
|
410
|
-
typeof val ===
|
|
411
|
-
typeof val ===
|
|
412
|
-
(val && typeof val.toMillis ===
|
|
413
|
-
|
|
363
|
+
typeof val === 'number' ||
|
|
364
|
+
typeof val === 'string' ||
|
|
365
|
+
(val && typeof val.toMillis === 'function'),
|
|
366
|
+
'End date must be a valid timestamp or Date object',
|
|
414
367
|
)
|
|
415
368
|
.optional(),
|
|
416
369
|
status: z
|
|
417
|
-
.union([
|
|
418
|
-
appointmentStatusSchema,
|
|
419
|
-
z.array(appointmentStatusSchema).nonempty(),
|
|
420
|
-
])
|
|
370
|
+
.union([appointmentStatusSchema, z.array(appointmentStatusSchema).nonempty()])
|
|
421
371
|
.optional(),
|
|
422
372
|
limit: z.number().positive().int().optional().default(20),
|
|
423
373
|
startAfter: z.any().optional(),
|
|
@@ -431,9 +381,9 @@ export const searchAppointmentsSchema = z
|
|
|
431
381
|
},
|
|
432
382
|
{
|
|
433
383
|
message:
|
|
434
|
-
|
|
435
|
-
path: [
|
|
436
|
-
}
|
|
384
|
+
'At least one of patientId, practitionerId, or clinicBranchId must be provided if no date or status filters are set.',
|
|
385
|
+
path: ['patientId'],
|
|
386
|
+
},
|
|
437
387
|
)
|
|
438
388
|
.refine(
|
|
439
389
|
(data) => {
|
|
@@ -443,18 +393,16 @@ export const searchAppointmentsSchema = z
|
|
|
443
393
|
return true;
|
|
444
394
|
},
|
|
445
395
|
{
|
|
446
|
-
message:
|
|
447
|
-
path: [
|
|
448
|
-
}
|
|
396
|
+
message: 'End date must be after or the same as start date',
|
|
397
|
+
path: ['endDate'],
|
|
398
|
+
},
|
|
449
399
|
);
|
|
450
400
|
|
|
451
401
|
/**
|
|
452
402
|
* Schema for validating appointment reschedule data
|
|
453
403
|
*/
|
|
454
404
|
export const rescheduleAppointmentSchema = z.object({
|
|
455
|
-
appointmentId: z
|
|
456
|
-
.string()
|
|
457
|
-
.min(MIN_STRING_LENGTH, "Appointment ID is required"),
|
|
405
|
+
appointmentId: z.string().min(MIN_STRING_LENGTH, 'Appointment ID is required'),
|
|
458
406
|
newStartTime: z
|
|
459
407
|
.any()
|
|
460
408
|
.refine(
|
|
@@ -462,10 +410,10 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
462
410
|
val instanceof Date ||
|
|
463
411
|
val?._seconds !== undefined ||
|
|
464
412
|
val?.seconds !== undefined ||
|
|
465
|
-
typeof val ===
|
|
466
|
-
typeof val ===
|
|
467
|
-
(val && typeof val.toMillis ===
|
|
468
|
-
|
|
413
|
+
typeof val === 'number' ||
|
|
414
|
+
typeof val === 'string' ||
|
|
415
|
+
(val && typeof val.toMillis === 'function'),
|
|
416
|
+
'New start time must be a valid timestamp, Date object, number, or string',
|
|
469
417
|
),
|
|
470
418
|
newEndTime: z
|
|
471
419
|
.any()
|
|
@@ -474,9 +422,9 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
474
422
|
val instanceof Date ||
|
|
475
423
|
val?._seconds !== undefined ||
|
|
476
424
|
val?.seconds !== undefined ||
|
|
477
|
-
typeof val ===
|
|
478
|
-
typeof val ===
|
|
479
|
-
(val && typeof val.toMillis ===
|
|
480
|
-
|
|
425
|
+
typeof val === 'number' ||
|
|
426
|
+
typeof val === 'string' ||
|
|
427
|
+
(val && typeof val.toMillis === 'function'),
|
|
428
|
+
'New end time must be a valid timestamp, Date object, number, or string',
|
|
481
429
|
),
|
|
482
430
|
});
|