@blackcode_sa/metaestetics-api 1.5.32 → 1.5.34
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 +226 -1
- package/dist/admin/index.d.ts +226 -1
- package/dist/admin/index.js +597 -14
- package/dist/admin/index.mjs +596 -14
- package/dist/backoffice/index.d.mts +2 -0
- package/dist/backoffice/index.d.ts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/admin/booking/booking.admin.ts +234 -0
- package/src/admin/booking/booking.calculator.ts +686 -0
- package/src/admin/booking/booking.types.ts +56 -0
- package/src/admin/booking/index.ts +3 -0
- package/src/admin/index.ts +9 -0
- package/src/services/appointment/appointment.service.ts +603 -0
- package/src/services/appointment/index.ts +2 -0
- package/src/services/appointment/utils/appointment.utils.ts +590 -0
- package/src/types/appointment/index.ts +161 -0
- package/src/types/procedure/index.ts +2 -0
- package/src/validations/appointment.schema.ts +125 -0
|
@@ -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
|
+
);
|