@blackcode_sa/metaestetics-api 1.5.32 → 1.5.33

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.
@@ -0,0 +1,161 @@
1
+ import { Timestamp, FieldValue } from "firebase/firestore";
2
+ import {
3
+ ClinicInfo,
4
+ PractitionerProfileInfo,
5
+ PatientProfileInfo,
6
+ } from "../profile";
7
+ import { ProcedureSummaryInfo } from "../procedure";
8
+ import { Currency } from "../../backoffice/types/static/pricing.types";
9
+ import { CalendarEventStatus } from "../calendar";
10
+ import { BlockingCondition } from "../../backoffice/types/static/blocking-condition.types";
11
+ import { Contraindication } from "../../backoffice/types/static/contraindication.types";
12
+ import { Requirement } from "../../backoffice/types/requirement.types";
13
+
14
+ /**
15
+ * Enum defining the possible statuses of an appointment.
16
+ */
17
+ export enum AppointmentStatus {
18
+ SCHEDULED = "scheduled", // Initial state after booking, before confirmation (if applicable)
19
+ CONFIRMED = "confirmed", // Confirmed by clinic/practitioner
20
+ CHECKED_IN = "checked_in", // Patient has arrived
21
+ IN_PROGRESS = "in_progress", // Procedure has started
22
+ COMPLETED = "completed", // Procedure finished successfully
23
+ CANCELED_PATIENT = "canceled_patient", // Canceled by the patient
24
+ CANCELED_CLINIC = "canceled_clinic", // Canceled by the clinic/practitioner
25
+ NO_SHOW = "no_show", // Patient did not attend
26
+ RESCHEDULED = "rescheduled", // Original appointment replaced by a new one
27
+ }
28
+
29
+ /**
30
+ * Enum defining the payment status of an appointment.
31
+ */
32
+ export enum PaymentStatus {
33
+ UNPAID = "unpaid",
34
+ PAID = "paid",
35
+ PARTIALLY_PAID = "partially_paid",
36
+ REFUNDED = "refunded",
37
+ NOT_APPLICABLE = "not_applicable", // For free services or other scenarios
38
+ }
39
+
40
+ /**
41
+ * Represents a booked appointment, aggregating key information and relevant procedure rules.
42
+ */
43
+ export interface Appointment {
44
+ /** Unique identifier for the appointment */
45
+ id: string;
46
+ /** Reference to the associated CalendarEvent */
47
+ calendarEventId: string;
48
+
49
+ /** ID of the clinic branch */
50
+ clinicBranchId: string;
51
+ /** Aggregated clinic information (snapshot) */
52
+ clinicInfo: ClinicInfo;
53
+
54
+ /** ID of the practitioner */
55
+ practitionerId: string;
56
+ /** Aggregated practitioner information (snapshot) */
57
+ practitionerInfo: PractitionerProfileInfo;
58
+
59
+ /** ID of the patient */
60
+ patientId: string;
61
+ /** Aggregated patient information (snapshot) */
62
+ patientInfo: PatientProfileInfo;
63
+
64
+ /** ID of the procedure */
65
+ procedureId: string;
66
+ /** Aggregated procedure information including product/brand (snapshot) */
67
+ procedureInfo: ProcedureSummaryInfo;
68
+
69
+ /** Status of the appointment */
70
+ status: AppointmentStatus;
71
+
72
+ /** Timestamps */
73
+ bookingTime: Timestamp;
74
+ confirmationTime?: Timestamp | null;
75
+ appointmentStartTime: Timestamp;
76
+ appointmentEndTime: Timestamp;
77
+ actualDurationMinutes?: number;
78
+
79
+ /** Cancellation Details */
80
+ cancellationReason?: string | null;
81
+ canceledBy?: "patient" | "clinic" | "practitioner" | "system";
82
+
83
+ /** Notes */
84
+ internalNotes?: string | null;
85
+ patientNotes?: string | null;
86
+
87
+ /** Payment Details */
88
+ cost: number;
89
+ currency: Currency;
90
+ paymentStatus: PaymentStatus;
91
+ paymentTransactionId?: string | null;
92
+
93
+ /** Procedure-related conditions and requirements */
94
+ blockingConditions: BlockingCondition[];
95
+ contraindications: Contraindication[];
96
+ preProcedureRequirements: Requirement[];
97
+ postProcedureRequirements: Requirement[];
98
+
99
+ /** Tracking information for requirements completion */
100
+ completedPreRequirements?: string[]; // IDs of completed pre-requirements
101
+ completedPostRequirements?: string[]; // IDs of completed post-requirements
102
+
103
+ /** Timestamps */
104
+ createdAt: Timestamp;
105
+ updatedAt: Timestamp;
106
+
107
+ /** Recurring appointment information */
108
+ isRecurring?: boolean;
109
+ recurringAppointmentId?: string | null;
110
+ }
111
+
112
+ /**
113
+ * Data needed to create a new Appointment
114
+ */
115
+ export interface CreateAppointmentData {
116
+ calendarEventId: string;
117
+ clinicBranchId: string;
118
+ practitionerId: string;
119
+ patientId: string;
120
+ procedureId: string;
121
+ appointmentStartTime: Timestamp;
122
+ appointmentEndTime: Timestamp;
123
+ cost: number;
124
+ currency: Currency;
125
+ patientNotes?: string | null;
126
+ initialStatus: AppointmentStatus;
127
+ initialPaymentStatus?: PaymentStatus;
128
+ }
129
+
130
+ /**
131
+ * Data allowed for updating an Appointment
132
+ */
133
+ export interface UpdateAppointmentData {
134
+ status?: AppointmentStatus;
135
+ confirmationTime?: Timestamp | null;
136
+ actualDurationMinutes?: number;
137
+ cancellationReason?: string | null;
138
+ canceledBy?: "patient" | "clinic" | "practitioner" | "system";
139
+ internalNotes?: string | null;
140
+ paymentStatus?: PaymentStatus;
141
+ paymentTransactionId?: string | null;
142
+ completedPreRequirements?: string[]; // IDs of pre-requirements to mark as completed
143
+ completedPostRequirements?: string[]; // IDs of post-requirements to mark as completed
144
+ }
145
+
146
+ /**
147
+ * Parameters for searching appointments
148
+ */
149
+ export interface SearchAppointmentsParams {
150
+ patientId?: string;
151
+ practitionerId?: string;
152
+ clinicBranchId?: string;
153
+ startDate?: Date;
154
+ endDate?: Date;
155
+ status?: AppointmentStatus | AppointmentStatus[];
156
+ limit?: number;
157
+ startAfter?: any;
158
+ }
159
+
160
+ /** Firestore collection name */
161
+ export const APPOINTMENTS_COLLECTION = "appointments";
@@ -132,6 +132,8 @@ export interface ProcedureSummaryInfo {
132
132
  categoryName: string; // Use names for aggregation
133
133
  subcategoryName: string; // Use names for aggregation
134
134
  technologyName: string; // Use names for aggregation
135
+ brandName?: string; // Added: Name of the brand used (if applicable)
136
+ productName?: string; // Added: Name of the product used (if applicable)
135
137
  price: number;
136
138
  pricingMeasure: PricingMeasure;
137
139
  currency: Currency;
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+ import { AppointmentStatus, PaymentStatus } from "../types/appointment";
3
+
4
+ /**
5
+ * Schema for validating appointment creation data
6
+ */
7
+ export const createAppointmentSchema = z.object({
8
+ calendarEventId: z.string().min(1, "Calendar event ID is required"),
9
+ clinicBranchId: z.string().min(1, "Clinic branch ID is required"),
10
+ practitionerId: z.string().min(1, "Practitioner ID is required"),
11
+ patientId: z.string().min(1, "Patient ID is required"),
12
+ procedureId: z.string().min(1, "Procedure ID is required"),
13
+ appointmentStartTime: z
14
+ .any()
15
+ .refine(
16
+ (val) => val instanceof Date || val?._seconds !== undefined,
17
+ "Appointment start time must be a valid timestamp"
18
+ ),
19
+ appointmentEndTime: z
20
+ .any()
21
+ .refine(
22
+ (val) => val instanceof Date || val?._seconds !== undefined,
23
+ "Appointment end time must be a valid timestamp"
24
+ ),
25
+ cost: z.number().min(0, "Cost must be a non-negative number"),
26
+ currency: z.string().min(1, "Currency is required"),
27
+ patientNotes: z.string().nullable().optional(),
28
+ initialStatus: z.nativeEnum(AppointmentStatus, {
29
+ errorMap: () => ({ message: "Invalid appointment status" }),
30
+ }),
31
+ initialPaymentStatus: z
32
+ .nativeEnum(PaymentStatus, {
33
+ errorMap: () => ({ message: "Invalid payment status" }),
34
+ })
35
+ .optional()
36
+ .default(PaymentStatus.UNPAID),
37
+ });
38
+
39
+ /**
40
+ * Schema for validating appointment update data
41
+ */
42
+ export const updateAppointmentSchema = z
43
+ .object({
44
+ status: z
45
+ .nativeEnum(AppointmentStatus, {
46
+ errorMap: () => ({ message: "Invalid appointment status" }),
47
+ })
48
+ .optional(),
49
+ confirmationTime: z
50
+ .any()
51
+ .refine(
52
+ (val) =>
53
+ val === null ||
54
+ val === undefined ||
55
+ val instanceof Date ||
56
+ val?._seconds !== undefined,
57
+ "Confirmation time must be a valid timestamp or null"
58
+ )
59
+ .optional(),
60
+ actualDurationMinutes: z
61
+ .number()
62
+ .positive("Duration must be positive")
63
+ .optional(),
64
+ cancellationReason: z.string().nullable().optional(),
65
+ canceledBy: z
66
+ .enum(["patient", "clinic", "practitioner", "system"])
67
+ .optional(),
68
+ internalNotes: z.string().nullable().optional(),
69
+ paymentStatus: z
70
+ .nativeEnum(PaymentStatus, {
71
+ errorMap: () => ({ message: "Invalid payment status" }),
72
+ })
73
+ .optional(),
74
+ paymentTransactionId: z.string().nullable().optional(),
75
+ completedPreRequirements: z.array(z.string()).optional(),
76
+ completedPostRequirements: z.array(z.string()).optional(),
77
+ })
78
+ .refine(
79
+ (data) => {
80
+ // If status is being set to canceled, make sure reason and canceledBy are provided
81
+ if (
82
+ data.status === AppointmentStatus.CANCELED_CLINIC ||
83
+ data.status === AppointmentStatus.CANCELED_PATIENT
84
+ ) {
85
+ return !!data.cancellationReason && !!data.canceledBy;
86
+ }
87
+ return true;
88
+ },
89
+ {
90
+ message:
91
+ "Cancellation reason and canceled by must be provided when canceling an appointment",
92
+ path: ["status"],
93
+ }
94
+ );
95
+
96
+ /**
97
+ * Schema for validating appointment search parameters
98
+ */
99
+ export const searchAppointmentsSchema = z
100
+ .object({
101
+ patientId: z.string().optional(),
102
+ practitionerId: z.string().optional(),
103
+ clinicBranchId: z.string().optional(),
104
+ startDate: z.date().optional(),
105
+ endDate: z.date().optional(),
106
+ status: z
107
+ .union([
108
+ z.nativeEnum(AppointmentStatus),
109
+ z.array(z.nativeEnum(AppointmentStatus)),
110
+ ])
111
+ .optional(),
112
+ limit: z.number().positive().optional(),
113
+ startAfter: z.any().optional(),
114
+ })
115
+ .refine(
116
+ (data) => {
117
+ // Ensure at least one search parameter is provided
118
+ return !!(data.patientId || data.practitionerId || data.clinicBranchId);
119
+ },
120
+ {
121
+ message:
122
+ "At least one of patientId, practitionerId, or clinicBranchId must be provided",
123
+ path: ["searchCriteria"],
124
+ }
125
+ );