@blackcode_sa/metaestetics-api 1.6.3 → 1.6.5
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 +439 -25
- package/dist/admin/index.d.ts +439 -25
- package/dist/admin/index.js +36107 -2493
- package/dist/admin/index.mjs +36093 -2461
- package/dist/backoffice/index.d.mts +254 -1
- package/dist/backoffice/index.d.ts +254 -1
- package/dist/backoffice/index.js +86 -12
- package/dist/backoffice/index.mjs +86 -13
- package/dist/index.d.mts +1434 -621
- package/dist/index.d.ts +1434 -621
- package/dist/index.js +1381 -970
- package/dist/index.mjs +1433 -1016
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +321 -0
- package/src/admin/booking/booking.admin.ts +376 -3
- package/src/admin/index.ts +15 -1
- package/src/admin/notifications/notifications.admin.ts +1 -1
- package/src/admin/requirements/README.md +128 -0
- package/src/admin/requirements/patient-requirements.admin.service.ts +482 -0
- package/src/backoffice/types/product.types.ts +2 -0
- package/src/index.ts +16 -1
- package/src/services/appointment/appointment.service.ts +386 -250
- package/src/services/clinic/clinic-admin.service.ts +3 -0
- package/src/services/clinic/clinic-group.service.ts +8 -0
- package/src/services/documentation-templates/documentation-template.service.ts +24 -16
- package/src/services/documentation-templates/filled-document.service.ts +253 -136
- package/src/services/patient/patientRequirements.service.ts +285 -0
- package/src/services/procedure/procedure.service.ts +1 -0
- package/src/types/appointment/index.ts +136 -11
- package/src/types/documentation-templates/index.ts +34 -2
- package/src/types/notifications/README.md +77 -0
- package/src/types/notifications/index.ts +154 -27
- package/src/types/patient/patient-requirements.ts +81 -0
- package/src/types/procedure/index.ts +7 -0
- package/src/validations/appointment.schema.ts +298 -62
- package/src/validations/documentation-templates/template.schema.ts +55 -0
- package/src/validations/documentation-templates.schema.ts +9 -14
- package/src/validations/notification.schema.ts +3 -3
- package/src/validations/patient/patient-requirements.schema.ts +75 -0
- package/src/validations/procedure.schema.ts +3 -0
|
@@ -1,86 +1,275 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AppointmentStatus,
|
|
4
|
+
PaymentStatus,
|
|
5
|
+
MediaType,
|
|
6
|
+
} from "../types/appointment";
|
|
7
|
+
import { filledDocumentStatusSchema } from "./documentation-templates.schema";
|
|
3
8
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
// Define common constants locally if not available from common.schema.ts
|
|
10
|
+
const MIN_STRING_LENGTH = 1;
|
|
11
|
+
const MAX_STRING_LENGTH = 1024; // Example value
|
|
12
|
+
const MAX_STRING_LENGTH_LONG = 4096; // Example value for longer notes/comments
|
|
13
|
+
const MAX_ARRAY_LENGTH = 100; // Example value
|
|
14
|
+
|
|
15
|
+
// --- Enum Schemas ---
|
|
16
|
+
export const appointmentStatusSchema = z.nativeEnum(AppointmentStatus);
|
|
17
|
+
export const paymentStatusSchema = z.nativeEnum(PaymentStatus);
|
|
18
|
+
export const mediaTypeSchema = z.nativeEnum(MediaType);
|
|
19
|
+
|
|
20
|
+
// --- Schemas for Nested Objects from types/appointment/index.ts ---
|
|
21
|
+
|
|
22
|
+
export const appointmentMediaItemSchema = z.object({
|
|
23
|
+
id: z.string().min(MIN_STRING_LENGTH, "Media item ID is required"),
|
|
24
|
+
type: mediaTypeSchema,
|
|
25
|
+
url: z.string().url("Media URL must be a valid URL"),
|
|
26
|
+
fileName: z.string().optional(),
|
|
27
|
+
uploadedAt: z
|
|
14
28
|
.any()
|
|
15
29
|
.refine(
|
|
16
|
-
(val) =>
|
|
17
|
-
|
|
30
|
+
(val) =>
|
|
31
|
+
val instanceof Date ||
|
|
32
|
+
val?._seconds !== undefined ||
|
|
33
|
+
typeof val === "number",
|
|
34
|
+
"uploadedAt must be a valid timestamp or Date object"
|
|
18
35
|
),
|
|
19
|
-
|
|
36
|
+
uploadedBy: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(MIN_STRING_LENGTH, "Uploaded by user ID is required"),
|
|
39
|
+
description: z
|
|
40
|
+
.string()
|
|
41
|
+
.max(MAX_STRING_LENGTH, "Description too long")
|
|
42
|
+
.optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const procedureExtendedInfoSchema = z.object({
|
|
46
|
+
id: z.string().min(MIN_STRING_LENGTH),
|
|
47
|
+
name: z.string().min(MIN_STRING_LENGTH),
|
|
48
|
+
description: z.string(),
|
|
49
|
+
cost: z.number().min(0),
|
|
50
|
+
duration: z.number().min(0),
|
|
51
|
+
procedureFamily: z.any(),
|
|
52
|
+
procedureCategoryId: z.string(),
|
|
53
|
+
procedureCategoryName: z.string(),
|
|
54
|
+
procedureSubCategoryId: z.string(),
|
|
55
|
+
procedureSubCategoryName: z.string(),
|
|
56
|
+
procedureTechnologyId: z.string(),
|
|
57
|
+
procedureTechnologyName: z.string(),
|
|
58
|
+
procedureProductBrandId: z.string(),
|
|
59
|
+
procedureProductBrandName: z.string(),
|
|
60
|
+
procedureProductId: z.string(),
|
|
61
|
+
procedureProductName: z.string(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const linkedFormInfoSchema = z.object({
|
|
65
|
+
formId: z.string().min(MIN_STRING_LENGTH, "Form ID is required"),
|
|
66
|
+
templateId: z.string().min(MIN_STRING_LENGTH, "Template ID is required"),
|
|
67
|
+
templateVersion: z
|
|
68
|
+
.number()
|
|
69
|
+
.int()
|
|
70
|
+
.positive("Template version must be a positive integer"),
|
|
71
|
+
title: z.string().min(MIN_STRING_LENGTH, "Form title is required"),
|
|
72
|
+
isUserForm: z.boolean(),
|
|
73
|
+
status: filledDocumentStatusSchema,
|
|
74
|
+
path: z.string().min(MIN_STRING_LENGTH, "Form path is required"),
|
|
75
|
+
submittedAt: z
|
|
76
|
+
.any()
|
|
77
|
+
.refine(
|
|
78
|
+
(val) =>
|
|
79
|
+
val === undefined ||
|
|
80
|
+
val instanceof Date ||
|
|
81
|
+
val?._seconds !== undefined ||
|
|
82
|
+
typeof val === "number",
|
|
83
|
+
"submittedAt must be a valid timestamp or Date object"
|
|
84
|
+
)
|
|
85
|
+
.optional(),
|
|
86
|
+
completedAt: z
|
|
20
87
|
.any()
|
|
21
88
|
.refine(
|
|
22
|
-
(val) =>
|
|
23
|
-
|
|
89
|
+
(val) =>
|
|
90
|
+
val === undefined ||
|
|
91
|
+
val instanceof Date ||
|
|
92
|
+
val?._seconds !== undefined ||
|
|
93
|
+
typeof val === "number",
|
|
94
|
+
"completedAt must be a valid timestamp or Date object"
|
|
95
|
+
)
|
|
96
|
+
.optional(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const patientReviewInfoSchema = z.object({
|
|
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
|
|
103
|
+
.string()
|
|
104
|
+
.max(MAX_STRING_LENGTH_LONG, "Comment too long")
|
|
105
|
+
.optional(),
|
|
106
|
+
reviewedAt: z
|
|
107
|
+
.any()
|
|
108
|
+
.refine(
|
|
109
|
+
(val) =>
|
|
110
|
+
val instanceof Date ||
|
|
111
|
+
val?._seconds !== undefined ||
|
|
112
|
+
typeof val === "number",
|
|
113
|
+
"reviewedAt must be a valid timestamp or Date object"
|
|
24
114
|
),
|
|
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
115
|
});
|
|
38
116
|
|
|
117
|
+
export const finalizedDetailsSchema = z.object({
|
|
118
|
+
by: z.string().min(MIN_STRING_LENGTH, "Finalized by user ID is required"),
|
|
119
|
+
at: z
|
|
120
|
+
.any()
|
|
121
|
+
.refine(
|
|
122
|
+
(val) =>
|
|
123
|
+
val instanceof Date ||
|
|
124
|
+
val?._seconds !== undefined ||
|
|
125
|
+
typeof val === "number",
|
|
126
|
+
"Finalized at must be a valid timestamp or Date object"
|
|
127
|
+
),
|
|
128
|
+
notes: z
|
|
129
|
+
.string()
|
|
130
|
+
.max(MAX_STRING_LENGTH_LONG, "Finalization notes too long")
|
|
131
|
+
.optional(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// --- Main Appointment Schemas ---
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Schema for validating appointment creation data
|
|
138
|
+
*/
|
|
139
|
+
export const createAppointmentSchema = z
|
|
140
|
+
.object({
|
|
141
|
+
clinicBranchId: z
|
|
142
|
+
.string()
|
|
143
|
+
.min(MIN_STRING_LENGTH, "Clinic branch ID is required"),
|
|
144
|
+
practitionerId: z
|
|
145
|
+
.string()
|
|
146
|
+
.min(MIN_STRING_LENGTH, "Practitioner ID is required"),
|
|
147
|
+
patientId: z.string().min(MIN_STRING_LENGTH, "Patient ID is required"),
|
|
148
|
+
procedureId: z.string().min(MIN_STRING_LENGTH, "Procedure ID is required"),
|
|
149
|
+
appointmentStartTime: z
|
|
150
|
+
.any()
|
|
151
|
+
.refine(
|
|
152
|
+
(val) =>
|
|
153
|
+
val instanceof Date ||
|
|
154
|
+
val?._seconds !== undefined ||
|
|
155
|
+
typeof val === "number",
|
|
156
|
+
"Appointment start time must be a valid timestamp or Date object"
|
|
157
|
+
),
|
|
158
|
+
appointmentEndTime: z
|
|
159
|
+
.any()
|
|
160
|
+
.refine(
|
|
161
|
+
(val) =>
|
|
162
|
+
val instanceof Date ||
|
|
163
|
+
val?._seconds !== undefined ||
|
|
164
|
+
typeof val === "number",
|
|
165
|
+
"Appointment end time must be a valid timestamp or Date object"
|
|
166
|
+
),
|
|
167
|
+
cost: z.number().min(0, "Cost must be a non-negative number"),
|
|
168
|
+
currency: z.string().min(1, "Currency is required"),
|
|
169
|
+
patientNotes: z
|
|
170
|
+
.string()
|
|
171
|
+
.max(MAX_STRING_LENGTH, "Patient notes too long")
|
|
172
|
+
.nullable()
|
|
173
|
+
.optional(),
|
|
174
|
+
initialStatus: appointmentStatusSchema,
|
|
175
|
+
initialPaymentStatus: paymentStatusSchema
|
|
176
|
+
.optional()
|
|
177
|
+
.default(PaymentStatus.UNPAID),
|
|
178
|
+
})
|
|
179
|
+
.refine((data) => data.appointmentEndTime > data.appointmentStartTime, {
|
|
180
|
+
message: "Appointment end time must be after start time",
|
|
181
|
+
path: ["appointmentEndTime"],
|
|
182
|
+
});
|
|
183
|
+
|
|
39
184
|
/**
|
|
40
185
|
* Schema for validating appointment update data
|
|
41
186
|
*/
|
|
42
187
|
export const updateAppointmentSchema = z
|
|
43
188
|
.object({
|
|
44
|
-
status:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
189
|
+
status: appointmentStatusSchema.optional(),
|
|
190
|
+
confirmationTime: z.any().optional().nullable(),
|
|
191
|
+
cancellationTime: z.any().optional().nullable(),
|
|
192
|
+
rescheduleTime: z.any().optional().nullable(),
|
|
193
|
+
procedureActualStartTime: z.any().optional().nullable(),
|
|
194
|
+
actualDurationMinutes: z
|
|
195
|
+
.number()
|
|
196
|
+
.int()
|
|
197
|
+
.positive("Duration must be a positive integer")
|
|
198
|
+
.optional(),
|
|
199
|
+
cancellationReason: z
|
|
200
|
+
.string()
|
|
201
|
+
.max(MAX_STRING_LENGTH, "Cancellation reason too long")
|
|
202
|
+
.nullable()
|
|
203
|
+
.optional(),
|
|
204
|
+
canceledBy: z
|
|
205
|
+
.enum(["patient", "clinic", "practitioner", "system"])
|
|
206
|
+
.optional(),
|
|
207
|
+
internalNotes: z
|
|
208
|
+
.string()
|
|
209
|
+
.max(MAX_STRING_LENGTH_LONG, "Internal notes too long")
|
|
210
|
+
.nullable()
|
|
211
|
+
.optional(),
|
|
212
|
+
patientNotes: z.any().optional().nullable(),
|
|
213
|
+
paymentStatus: paymentStatusSchema.optional(),
|
|
214
|
+
paymentTransactionId: z.any().optional().nullable(),
|
|
215
|
+
completedPreRequirements: z
|
|
216
|
+
.union([z.array(z.string()), z.any()])
|
|
48
217
|
.optional(),
|
|
49
|
-
|
|
218
|
+
completedPostRequirements: z
|
|
219
|
+
.union([z.array(z.string()), z.any()])
|
|
220
|
+
.optional(),
|
|
221
|
+
linkedFormIds: z.union([z.array(z.string()), z.any()]).optional(),
|
|
222
|
+
pendingUserFormsIds: z.union([z.array(z.string()), z.any()]).optional(),
|
|
223
|
+
appointmentStartTime: z
|
|
50
224
|
.any()
|
|
51
225
|
.refine(
|
|
52
226
|
(val) =>
|
|
53
|
-
val === null ||
|
|
54
227
|
val === undefined ||
|
|
55
228
|
val instanceof Date ||
|
|
56
|
-
val?._seconds !== undefined
|
|
57
|
-
|
|
229
|
+
val?._seconds !== undefined ||
|
|
230
|
+
typeof val === "number",
|
|
231
|
+
"Appointment start time must be a valid timestamp or Date object"
|
|
58
232
|
)
|
|
59
233
|
.optional(),
|
|
60
|
-
|
|
61
|
-
.
|
|
62
|
-
.
|
|
234
|
+
appointmentEndTime: z
|
|
235
|
+
.any()
|
|
236
|
+
.refine(
|
|
237
|
+
(val) =>
|
|
238
|
+
val === undefined ||
|
|
239
|
+
val instanceof Date ||
|
|
240
|
+
val?._seconds !== undefined ||
|
|
241
|
+
typeof val === "number",
|
|
242
|
+
"Appointment end time must be a valid timestamp or Date object"
|
|
243
|
+
)
|
|
63
244
|
.optional(),
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
245
|
+
calendarEventId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
246
|
+
cost: z.number().min(0).optional(),
|
|
247
|
+
clinicBranchId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
248
|
+
practitionerId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
249
|
+
linkedForms: z
|
|
250
|
+
.union([z.array(linkedFormInfoSchema).max(MAX_ARRAY_LENGTH), z.any()])
|
|
67
251
|
.optional(),
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
252
|
+
media: z
|
|
253
|
+
.union([
|
|
254
|
+
z.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH),
|
|
255
|
+
z.any(),
|
|
256
|
+
])
|
|
257
|
+
.optional(),
|
|
258
|
+
reviewInfo: z
|
|
259
|
+
.union([patientReviewInfoSchema.nullable(), z.any()])
|
|
260
|
+
.optional(),
|
|
261
|
+
finalizedDetails: z
|
|
262
|
+
.union([finalizedDetailsSchema.nullable(), z.any()])
|
|
73
263
|
.optional(),
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
completedPostRequirements: z.array(z.string()).optional(),
|
|
264
|
+
isArchived: z.boolean().optional(),
|
|
265
|
+
updatedAt: z.any().optional(),
|
|
77
266
|
})
|
|
78
267
|
.refine(
|
|
79
268
|
(data) => {
|
|
80
|
-
// If status is being set to canceled, make sure reason and canceledBy are provided
|
|
81
269
|
if (
|
|
82
270
|
data.status === AppointmentStatus.CANCELED_CLINIC ||
|
|
83
|
-
data.status === AppointmentStatus.CANCELED_PATIENT
|
|
271
|
+
data.status === AppointmentStatus.CANCELED_PATIENT ||
|
|
272
|
+
data.status === AppointmentStatus.CANCELED_PATIENT_RESCHEDULED
|
|
84
273
|
) {
|
|
85
274
|
return !!data.cancellationReason && !!data.canceledBy;
|
|
86
275
|
}
|
|
@@ -88,9 +277,22 @@ export const updateAppointmentSchema = z
|
|
|
88
277
|
},
|
|
89
278
|
{
|
|
90
279
|
message:
|
|
91
|
-
"Cancellation reason and canceled by must be provided when canceling an appointment",
|
|
280
|
+
"Cancellation reason and canceled by must be provided when canceling an appointment with patient or clinic origin.",
|
|
92
281
|
path: ["status"],
|
|
93
282
|
}
|
|
283
|
+
)
|
|
284
|
+
.refine(
|
|
285
|
+
(data) => {
|
|
286
|
+
if (data.appointmentStartTime && data.appointmentEndTime) {
|
|
287
|
+
return data.appointmentEndTime > data.appointmentStartTime;
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
message:
|
|
293
|
+
"Appointment end time must be after start time if both are provided",
|
|
294
|
+
path: ["appointmentEndTime"],
|
|
295
|
+
}
|
|
94
296
|
);
|
|
95
297
|
|
|
96
298
|
/**
|
|
@@ -101,25 +303,59 @@ export const searchAppointmentsSchema = z
|
|
|
101
303
|
patientId: z.string().optional(),
|
|
102
304
|
practitionerId: z.string().optional(),
|
|
103
305
|
clinicBranchId: z.string().optional(),
|
|
104
|
-
startDate: z
|
|
105
|
-
|
|
306
|
+
startDate: z
|
|
307
|
+
.any()
|
|
308
|
+
.refine(
|
|
309
|
+
(val) =>
|
|
310
|
+
val === undefined ||
|
|
311
|
+
val instanceof Date ||
|
|
312
|
+
val?._seconds !== undefined ||
|
|
313
|
+
typeof val === "number",
|
|
314
|
+
"Start date must be a valid timestamp or Date object"
|
|
315
|
+
)
|
|
316
|
+
.optional(),
|
|
317
|
+
endDate: z
|
|
318
|
+
.any()
|
|
319
|
+
.refine(
|
|
320
|
+
(val) =>
|
|
321
|
+
val === undefined ||
|
|
322
|
+
val instanceof Date ||
|
|
323
|
+
val?._seconds !== undefined ||
|
|
324
|
+
typeof val === "number",
|
|
325
|
+
"End date must be a valid timestamp or Date object"
|
|
326
|
+
)
|
|
327
|
+
.optional(),
|
|
106
328
|
status: z
|
|
107
329
|
.union([
|
|
108
|
-
|
|
109
|
-
z.array(
|
|
330
|
+
appointmentStatusSchema,
|
|
331
|
+
z.array(appointmentStatusSchema).nonempty(),
|
|
110
332
|
])
|
|
111
333
|
.optional(),
|
|
112
|
-
limit: z.number().positive().optional(),
|
|
334
|
+
limit: z.number().positive().int().optional().default(20),
|
|
113
335
|
startAfter: z.any().optional(),
|
|
114
336
|
})
|
|
115
337
|
.refine(
|
|
116
338
|
(data) => {
|
|
117
|
-
|
|
118
|
-
|
|
339
|
+
if (!data.startDate && !data.endDate && !data.status) {
|
|
340
|
+
return !!(data.patientId || data.practitionerId || data.clinicBranchId);
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
119
343
|
},
|
|
120
344
|
{
|
|
121
345
|
message:
|
|
122
|
-
"At least one of patientId, practitionerId, or clinicBranchId must be provided",
|
|
123
|
-
path: ["
|
|
346
|
+
"At least one of patientId, practitionerId, or clinicBranchId must be provided if no date or status filters are set.",
|
|
347
|
+
path: ["patientId"],
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
.refine(
|
|
351
|
+
(data) => {
|
|
352
|
+
if (data.startDate && data.endDate) {
|
|
353
|
+
return data.endDate >= data.startDate;
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
message: "End date must be after or the same as start date",
|
|
359
|
+
path: ["endDate"],
|
|
124
360
|
}
|
|
125
361
|
);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DocumentElementType,
|
|
4
4
|
HeadingLevel,
|
|
5
5
|
ListType,
|
|
6
|
+
FilledDocumentStatus,
|
|
6
7
|
} from "../../types/documentation-templates";
|
|
7
8
|
|
|
8
9
|
// Base element schema
|
|
@@ -93,6 +94,12 @@ const signatureElementSchema = baseElementSchema.extend({
|
|
|
93
94
|
label: z.string().min(1).max(200),
|
|
94
95
|
});
|
|
95
96
|
|
|
97
|
+
// NEW: Digital signature element schema
|
|
98
|
+
const digitalSignatureElementSchema = baseElementSchema.extend({
|
|
99
|
+
type: z.literal(DocumentElementType.DITIGAL_SIGNATURE), // Matching your type enum
|
|
100
|
+
label: z.string().min(1).max(200),
|
|
101
|
+
});
|
|
102
|
+
|
|
96
103
|
// File upload element schema
|
|
97
104
|
const fileUploadElementSchema = baseElementSchema.extend({
|
|
98
105
|
type: z.literal(DocumentElementType.FILE_UPLOAD),
|
|
@@ -114,6 +121,7 @@ export const documentElementSchema = z.discriminatedUnion("type", [
|
|
|
114
121
|
textInputElementSchema,
|
|
115
122
|
datePickerElementSchema,
|
|
116
123
|
signatureElementSchema,
|
|
124
|
+
digitalSignatureElementSchema,
|
|
117
125
|
fileUploadElementSchema,
|
|
118
126
|
]);
|
|
119
127
|
|
|
@@ -130,6 +138,7 @@ export const documentElementWithoutIdSchema = z.discriminatedUnion("type", [
|
|
|
130
138
|
textInputElementSchema.omit({ id: true }),
|
|
131
139
|
datePickerElementSchema.omit({ id: true }),
|
|
132
140
|
signatureElementSchema.omit({ id: true }),
|
|
141
|
+
digitalSignatureElementSchema.omit({ id: true }),
|
|
133
142
|
fileUploadElementSchema.omit({ id: true }),
|
|
134
143
|
]);
|
|
135
144
|
|
|
@@ -139,6 +148,9 @@ export const createDocumentTemplateSchema = z.object({
|
|
|
139
148
|
description: z.string().max(1000).optional(),
|
|
140
149
|
elements: z.array(documentElementWithoutIdSchema).min(1).max(100),
|
|
141
150
|
tags: z.array(z.string().min(1).max(50)).max(20).optional(),
|
|
151
|
+
isUserForm: z.boolean().optional().default(false),
|
|
152
|
+
isRequired: z.boolean().optional().default(false),
|
|
153
|
+
sortingOrder: z.number().int().optional().default(0),
|
|
142
154
|
});
|
|
143
155
|
|
|
144
156
|
// Schema for updating a document template
|
|
@@ -148,6 +160,9 @@ export const updateDocumentTemplateSchema = z.object({
|
|
|
148
160
|
elements: z.array(documentElementWithoutIdSchema).min(1).max(100).optional(),
|
|
149
161
|
tags: z.array(z.string().min(1).max(50)).max(20).optional(),
|
|
150
162
|
isActive: z.boolean().optional(),
|
|
163
|
+
isUserForm: z.boolean().optional(),
|
|
164
|
+
isRequired: z.boolean().optional(),
|
|
165
|
+
sortingOrder: z.number().int().optional(),
|
|
151
166
|
});
|
|
152
167
|
|
|
153
168
|
// Schema for a document template
|
|
@@ -162,4 +177,44 @@ export const documentTemplateSchema = z.object({
|
|
|
162
177
|
tags: z.array(z.string().min(1).max(50)).max(20).optional(),
|
|
163
178
|
version: z.number().int().positive(),
|
|
164
179
|
isActive: z.boolean(),
|
|
180
|
+
isUserForm: z.boolean().optional().default(false),
|
|
181
|
+
isRequired: z.boolean().optional().default(false),
|
|
182
|
+
sortingOrder: z.number().int().optional().default(0),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Filled Document Schemas
|
|
186
|
+
export const filledDocumentStatusSchema = z.nativeEnum(FilledDocumentStatus);
|
|
187
|
+
|
|
188
|
+
export const filledDocumentSchema = z.object({
|
|
189
|
+
id: z.string(),
|
|
190
|
+
templateId: z.string(),
|
|
191
|
+
templateVersion: z.number(),
|
|
192
|
+
isUserForm: z.boolean(),
|
|
193
|
+
procedureId: z.string(),
|
|
194
|
+
appointmentId: z.string(),
|
|
195
|
+
patientId: z.string(),
|
|
196
|
+
practitionerId: z.string(),
|
|
197
|
+
clinicId: z.string(),
|
|
198
|
+
createdAt: z.number(),
|
|
199
|
+
updatedAt: z.number(),
|
|
200
|
+
values: z.record(z.string(), z.any()),
|
|
201
|
+
status: filledDocumentStatusSchema,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
export const createFilledDocumentDataSchema = z.object({
|
|
205
|
+
templateId: z.string(),
|
|
206
|
+
procedureId: z.string(),
|
|
207
|
+
appointmentId: z.string(),
|
|
208
|
+
patientId: z.string(),
|
|
209
|
+
practitionerId: z.string(),
|
|
210
|
+
clinicId: z.string(),
|
|
211
|
+
values: z.record(z.string(), z.any()).optional().default({}),
|
|
212
|
+
status: filledDocumentStatusSchema
|
|
213
|
+
.optional()
|
|
214
|
+
.default(FilledDocumentStatus.DRAFT),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export const updateFilledDocumentDataSchema = z.object({
|
|
218
|
+
values: z.record(z.string(), z.any()).optional(),
|
|
219
|
+
status: filledDocumentStatusSchema.optional(),
|
|
165
220
|
});
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
documentElementSchema,
|
|
6
|
-
documentElementWithoutIdSchema,
|
|
7
|
-
} from "./documentation-templates/template.schema";
|
|
1
|
+
/**
|
|
2
|
+
* This file re-exports all schemas related to documentation templates and filled documents
|
|
3
|
+
* from their specific definition files.
|
|
4
|
+
*/
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
documentElementWithoutIdSchema,
|
|
15
|
-
};
|
|
6
|
+
// Assuming all relevant schemas, including filledDocumentStatusSchema,
|
|
7
|
+
// documentTemplateSchema, createDocumentTemplateSchema, updateDocumentTemplateSchema,
|
|
8
|
+
// filledDocumentSchema, createFilledDocumentDataSchema, updateFilledDocumentDataSchema etc.,
|
|
9
|
+
// are defined and exported from './documentation-templates/template.schema.ts'
|
|
10
|
+
export * from "./documentation-templates/template.schema";
|
|
@@ -24,7 +24,7 @@ export const baseNotificationSchema = z.object({
|
|
|
24
24
|
* Validaciona šema za pre-requirement notifikaciju
|
|
25
25
|
*/
|
|
26
26
|
export const preRequirementNotificationSchema = baseNotificationSchema.extend({
|
|
27
|
-
notificationType: z.literal(NotificationType.
|
|
27
|
+
notificationType: z.literal(NotificationType.REQUIREMENT_INSTRUCTION_DUE),
|
|
28
28
|
treatmentId: z.string(),
|
|
29
29
|
requirements: z.array(z.string()),
|
|
30
30
|
deadline: z.any(), // Timestamp
|
|
@@ -34,7 +34,7 @@ export const preRequirementNotificationSchema = baseNotificationSchema.extend({
|
|
|
34
34
|
* Validaciona šema za post-requirement notifikaciju
|
|
35
35
|
*/
|
|
36
36
|
export const postRequirementNotificationSchema = baseNotificationSchema.extend({
|
|
37
|
-
notificationType: z.literal(NotificationType.
|
|
37
|
+
notificationType: z.literal(NotificationType.REQUIREMENT_INSTRUCTION_DUE),
|
|
38
38
|
treatmentId: z.string(),
|
|
39
39
|
requirements: z.array(z.string()),
|
|
40
40
|
deadline: z.any(), // Timestamp
|
|
@@ -56,7 +56,7 @@ export const appointmentReminderNotificationSchema =
|
|
|
56
56
|
* Validaciona šema za appointment notifikaciju
|
|
57
57
|
*/
|
|
58
58
|
export const appointmentNotificationSchema = baseNotificationSchema.extend({
|
|
59
|
-
notificationType: z.literal(NotificationType.
|
|
59
|
+
notificationType: z.literal(NotificationType.APPOINTMENT_STATUS_CHANGE),
|
|
60
60
|
appointmentId: z.string(),
|
|
61
61
|
appointmentStatus: z.string(),
|
|
62
62
|
previousStatus: z.string(),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
PatientInstructionStatus,
|
|
4
|
+
PatientRequirementOverallStatus,
|
|
5
|
+
} from "../../types/patient/patient-requirements";
|
|
6
|
+
import {
|
|
7
|
+
requirementTypeSchema,
|
|
8
|
+
timeUnitSchema, // Corrected import name to lowercase 'u'
|
|
9
|
+
} from "../../backoffice/validations/schemas";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Zod schema for `PatientInstructionStatus` enum.
|
|
13
|
+
*/
|
|
14
|
+
export const patientInstructionStatusSchema = z.nativeEnum(
|
|
15
|
+
PatientInstructionStatus
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Zod schema for `PatientRequirementInstruction` interface.
|
|
20
|
+
*/
|
|
21
|
+
export const patientRequirementInstructionSchema = z.object({
|
|
22
|
+
instructionId: z.string().min(1, "Instruction ID is required."),
|
|
23
|
+
instructionText: z.string().min(1, "Instruction text is required."),
|
|
24
|
+
dueTime: z.any(), // Firestore Timestamp
|
|
25
|
+
actionableWindow: z.number().default(1),
|
|
26
|
+
status: patientInstructionStatusSchema,
|
|
27
|
+
originalNotifyAtValue: z.number(),
|
|
28
|
+
originalTimeframeUnit: timeUnitSchema, // Use the correctly imported timeUnitSchema
|
|
29
|
+
notificationId: z.string().optional(),
|
|
30
|
+
actionTakenAt: z.any().optional().nullable(), // Firestore Timestamp or null
|
|
31
|
+
updatedAt: z.any(), // Firestore Timestamp
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Zod schema for `PatientRequirementOverallStatus` enum.
|
|
36
|
+
*/
|
|
37
|
+
export const patientRequirementOverallStatusSchema = z.nativeEnum(
|
|
38
|
+
PatientRequirementOverallStatus
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define requirementImportanceSchema locally as it's not exported directly from backoffice.
|
|
43
|
+
*/
|
|
44
|
+
export const requirementImportanceSchema = z.enum(["low", "medium", "high"]);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Zod schema for `PatientRequirementInstance` interface.
|
|
48
|
+
*/
|
|
49
|
+
export const patientRequirementInstanceSchema = z.object({
|
|
50
|
+
id: z.string().min(1, "Instance ID is required."),
|
|
51
|
+
patientId: z.string().min(1, "Patient ID is required."),
|
|
52
|
+
appointmentId: z.string().min(1, "Appointment ID is required."),
|
|
53
|
+
originalRequirementId: z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1, "Original Requirement ID is required."),
|
|
56
|
+
requirementType: requirementTypeSchema,
|
|
57
|
+
requirementName: z.string().min(1, "Requirement name is required."),
|
|
58
|
+
requirementDescription: z.string(),
|
|
59
|
+
requirementImportance: requirementImportanceSchema, // Use the locally defined schema
|
|
60
|
+
overallStatus: patientRequirementOverallStatusSchema,
|
|
61
|
+
instructions: z
|
|
62
|
+
.array(patientRequirementInstructionSchema)
|
|
63
|
+
.min(1, "At least one instruction is required."),
|
|
64
|
+
createdAt: z.any(), // Firestore Timestamp
|
|
65
|
+
updatedAt: z.any(), // Firestore Timestamp
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Schema for updating a `PatientRequirementInstruction` status (subset of fields).
|
|
70
|
+
* Useful for when a patient marks an instruction as actioned.
|
|
71
|
+
*/
|
|
72
|
+
export const updatePatientInstructionStatusSchema = z.object({
|
|
73
|
+
status: z.literal(PatientInstructionStatus.ACTION_TAKEN),
|
|
74
|
+
actionTakenAt: z.any(), // Should be a Timestamp from the server
|
|
75
|
+
});
|
|
@@ -24,6 +24,7 @@ export const createProcedureSchema = z.object({
|
|
|
24
24
|
duration: z.number().min(1).max(480), // Max 8 hours
|
|
25
25
|
practitionerId: z.string().min(1),
|
|
26
26
|
clinicBranchId: z.string().min(1),
|
|
27
|
+
photos: z.array(z.string()).optional(),
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -43,6 +44,7 @@ export const updateProcedureSchema = z.object({
|
|
|
43
44
|
technologyId: z.string().optional(),
|
|
44
45
|
productId: z.string().optional(),
|
|
45
46
|
clinicBranchId: z.string().optional(),
|
|
47
|
+
photos: z.array(z.string()).optional(),
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
/**
|
|
@@ -55,6 +57,7 @@ export const procedureSchema = createProcedureSchema.extend({
|
|
|
55
57
|
technology: z.any(), // We'll validate the full technology object separately
|
|
56
58
|
product: z.any(), // We'll validate the full product object separately
|
|
57
59
|
blockingConditions: z.array(z.any()), // We'll validate blocking conditions separately
|
|
60
|
+
contraindications: z.array(z.any()), // We'll validate contraindications separately
|
|
58
61
|
treatmentBenefits: z.array(z.any()), // We'll validate treatment benefits separately
|
|
59
62
|
preRequirements: z.array(z.any()), // We'll validate requirements separately
|
|
60
63
|
postRequirements: z.array(z.any()), // We'll validate requirements separately
|