@blackcode_sa/metaestetics-api 1.11.1 → 1.11.3
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 +326 -330
- package/dist/admin/index.d.ts +326 -330
- package/dist/backoffice/index.d.mts +67 -283
- package/dist/backoffice/index.d.ts +67 -283
- package/dist/backoffice/index.js +6 -114
- package/dist/backoffice/index.mjs +6 -112
- package/dist/index.d.mts +3039 -3100
- package/dist/index.d.ts +3039 -3100
- package/dist/index.js +131 -380
- package/dist/index.mjs +132 -380
- package/package.json +1 -1
- package/src/backoffice/expo-safe/index.ts +0 -2
- package/src/backoffice/services/__tests__/brand.service.test.ts +196 -0
- package/src/backoffice/services/__tests__/category.service.test.ts +201 -0
- package/src/backoffice/services/__tests__/product.service.test.ts +358 -0
- package/src/backoffice/services/__tests__/requirement.service.test.ts +226 -0
- package/src/backoffice/services/__tests__/subcategory.service.test.ts +181 -0
- package/src/backoffice/services/__tests__/technology.service.test.ts +1097 -0
- package/src/backoffice/services/technology.service.ts +10 -122
- package/src/backoffice/types/index.ts +0 -1
- package/src/backoffice/types/product.types.ts +1 -3
- package/src/backoffice/types/technology.types.ts +4 -4
- package/src/backoffice/validations/schemas.ts +9 -35
- package/src/services/appointment/appointment.service.ts +5 -0
- package/src/services/appointment/utils/appointment.utils.ts +113 -124
- package/src/services/procedure/procedure.service.ts +234 -434
- package/src/types/appointment/index.ts +39 -43
- package/src/types/clinic/index.ts +6 -1
- package/src/types/patient/medical-info.types.ts +3 -3
- package/src/types/procedure/index.ts +17 -20
- package/src/validations/appointment.schema.ts +119 -170
- package/src/validations/clinic.schema.ts +6 -1
- package/src/validations/patient/medical-info.schema.ts +2 -7
- package/src/backoffice/services/README.md +0 -40
- package/src/backoffice/services/constants.service.ts +0 -268
- package/src/backoffice/types/admin-constants.types.ts +0 -69
|
@@ -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
|
});
|
|
@@ -196,6 +177,7 @@ export const appointmentMetadataSchema = z.object({
|
|
|
196
177
|
zonePhotos: z.record(z.string(), beforeAfterPerZoneSchema).nullable(),
|
|
197
178
|
zoneBilling: z.record(z.string(), billingPerZoneSchema).nullable(),
|
|
198
179
|
finalbilling: finalBillingSchema.nullable(),
|
|
180
|
+
finalizationNotes: z.string().nullable(),
|
|
199
181
|
});
|
|
200
182
|
|
|
201
183
|
// --- Main Appointment Schemas ---
|
|
@@ -205,14 +187,10 @@ export const appointmentMetadataSchema = z.object({
|
|
|
205
187
|
*/
|
|
206
188
|
export const createAppointmentSchema = z
|
|
207
189
|
.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"),
|
|
190
|
+
clinicBranchId: z.string().min(MIN_STRING_LENGTH, 'Clinic branch ID is required'),
|
|
191
|
+
practitionerId: z.string().min(MIN_STRING_LENGTH, 'Practitioner ID is required'),
|
|
192
|
+
patientId: z.string().min(MIN_STRING_LENGTH, 'Patient ID is required'),
|
|
193
|
+
procedureId: z.string().min(MIN_STRING_LENGTH, 'Procedure ID is required'),
|
|
216
194
|
appointmentStartTime: z
|
|
217
195
|
.any()
|
|
218
196
|
.refine(
|
|
@@ -220,10 +198,10 @@ export const createAppointmentSchema = z
|
|
|
220
198
|
val instanceof Date ||
|
|
221
199
|
val?._seconds !== undefined ||
|
|
222
200
|
val?.seconds !== undefined ||
|
|
223
|
-
typeof val ===
|
|
224
|
-
typeof val ===
|
|
225
|
-
(val && typeof val.toMillis ===
|
|
226
|
-
|
|
201
|
+
typeof val === 'number' ||
|
|
202
|
+
typeof val === 'string' ||
|
|
203
|
+
(val && typeof val.toMillis === 'function'),
|
|
204
|
+
'Appointment start time must be a valid timestamp or Date object',
|
|
227
205
|
),
|
|
228
206
|
appointmentEndTime: z
|
|
229
207
|
.any()
|
|
@@ -232,27 +210,21 @@ export const createAppointmentSchema = z
|
|
|
232
210
|
val instanceof Date ||
|
|
233
211
|
val?._seconds !== undefined ||
|
|
234
212
|
val?.seconds !== undefined ||
|
|
235
|
-
typeof val ===
|
|
236
|
-
typeof val ===
|
|
237
|
-
(val && typeof val.toMillis ===
|
|
238
|
-
|
|
213
|
+
typeof val === 'number' ||
|
|
214
|
+
typeof val === 'string' ||
|
|
215
|
+
(val && typeof val.toMillis === 'function'),
|
|
216
|
+
'Appointment end time must be a valid timestamp or Date object',
|
|
239
217
|
),
|
|
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(),
|
|
218
|
+
cost: z.number().min(0, 'Cost must be a non-negative number'),
|
|
219
|
+
currency: z.string().min(1, 'Currency is required'),
|
|
220
|
+
patientNotes: z.string().max(MAX_STRING_LENGTH, 'Patient notes too long').nullable().optional(),
|
|
247
221
|
initialStatus: appointmentStatusSchema,
|
|
248
|
-
initialPaymentStatus: paymentStatusSchema
|
|
249
|
-
|
|
250
|
-
.default(PaymentStatus.UNPAID),
|
|
251
|
-
clinic_tz: z.string().min(1, "Timezone is required"),
|
|
222
|
+
initialPaymentStatus: paymentStatusSchema.optional().default(PaymentStatus.UNPAID),
|
|
223
|
+
clinic_tz: z.string().min(1, 'Timezone is required'),
|
|
252
224
|
})
|
|
253
225
|
.refine((data) => data.appointmentEndTime > data.appointmentStartTime, {
|
|
254
|
-
message:
|
|
255
|
-
path: [
|
|
226
|
+
message: 'Appointment end time must be after start time',
|
|
227
|
+
path: ['appointmentEndTime'],
|
|
256
228
|
});
|
|
257
229
|
|
|
258
230
|
/**
|
|
@@ -268,30 +240,24 @@ export const updateAppointmentSchema = z
|
|
|
268
240
|
actualDurationMinutes: z
|
|
269
241
|
.number()
|
|
270
242
|
.int()
|
|
271
|
-
.positive(
|
|
243
|
+
.positive('Duration must be a positive integer')
|
|
272
244
|
.optional(),
|
|
273
245
|
cancellationReason: z
|
|
274
246
|
.string()
|
|
275
|
-
.max(MAX_STRING_LENGTH,
|
|
247
|
+
.max(MAX_STRING_LENGTH, 'Cancellation reason too long')
|
|
276
248
|
.nullable()
|
|
277
249
|
.optional(),
|
|
278
|
-
canceledBy: z
|
|
279
|
-
.enum(["patient", "clinic", "practitioner", "system"])
|
|
280
|
-
.optional(),
|
|
250
|
+
canceledBy: z.enum(['patient', 'clinic', 'practitioner', 'system']).optional(),
|
|
281
251
|
internalNotes: z
|
|
282
252
|
.string()
|
|
283
|
-
.max(MAX_STRING_LENGTH_LONG,
|
|
253
|
+
.max(MAX_STRING_LENGTH_LONG, 'Internal notes too long')
|
|
284
254
|
.nullable()
|
|
285
255
|
.optional(),
|
|
286
256
|
patientNotes: z.any().optional().nullable(),
|
|
287
257
|
paymentStatus: paymentStatusSchema.optional(),
|
|
288
258
|
paymentTransactionId: z.any().optional().nullable(),
|
|
289
|
-
completedPreRequirements: z
|
|
290
|
-
|
|
291
|
-
.optional(),
|
|
292
|
-
completedPostRequirements: z
|
|
293
|
-
.union([z.array(z.string()), z.any()])
|
|
294
|
-
.optional(),
|
|
259
|
+
completedPreRequirements: z.union([z.array(z.string()), z.any()]).optional(),
|
|
260
|
+
completedPostRequirements: z.union([z.array(z.string()), z.any()]).optional(),
|
|
295
261
|
linkedFormIds: z.union([z.array(z.string()), z.any()]).optional(),
|
|
296
262
|
pendingUserFormsIds: z.union([z.array(z.string()), z.any()]).optional(),
|
|
297
263
|
appointmentStartTime: z
|
|
@@ -302,10 +268,10 @@ export const updateAppointmentSchema = z
|
|
|
302
268
|
val instanceof Date ||
|
|
303
269
|
val?._seconds !== undefined ||
|
|
304
270
|
val?.seconds !== undefined ||
|
|
305
|
-
typeof val ===
|
|
306
|
-
typeof val ===
|
|
307
|
-
(val && typeof val.toMillis ===
|
|
308
|
-
|
|
271
|
+
typeof val === 'number' ||
|
|
272
|
+
typeof val === 'string' ||
|
|
273
|
+
(val && typeof val.toMillis === 'function'),
|
|
274
|
+
'Appointment start time must be a valid timestamp or Date object',
|
|
309
275
|
)
|
|
310
276
|
.optional(),
|
|
311
277
|
appointmentEndTime: z
|
|
@@ -316,10 +282,10 @@ export const updateAppointmentSchema = z
|
|
|
316
282
|
val instanceof Date ||
|
|
317
283
|
val?._seconds !== undefined ||
|
|
318
284
|
val?.seconds !== undefined ||
|
|
319
|
-
typeof val ===
|
|
320
|
-
typeof val ===
|
|
321
|
-
(val && typeof val.toMillis ===
|
|
322
|
-
|
|
285
|
+
typeof val === 'number' ||
|
|
286
|
+
typeof val === 'string' ||
|
|
287
|
+
(val && typeof val.toMillis === 'function'),
|
|
288
|
+
'Appointment end time must be a valid timestamp or Date object',
|
|
323
289
|
)
|
|
324
290
|
.optional(),
|
|
325
291
|
calendarEventId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
@@ -327,21 +293,10 @@ export const updateAppointmentSchema = z
|
|
|
327
293
|
clinicBranchId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
328
294
|
practitionerId: z.string().min(MIN_STRING_LENGTH).optional(),
|
|
329
295
|
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(),
|
|
296
|
+
linkedForms: z.union([z.array(linkedFormInfoSchema).max(MAX_ARRAY_LENGTH), z.any()]).optional(),
|
|
297
|
+
media: z.union([z.array(appointmentMediaItemSchema).max(MAX_ARRAY_LENGTH), z.any()]).optional(),
|
|
298
|
+
reviewInfo: z.union([patientReviewInfoSchema.nullable(), z.any()]).optional(),
|
|
299
|
+
finalizedDetails: z.union([finalizedDetailsSchema.nullable(), z.any()]).optional(),
|
|
345
300
|
isArchived: z.boolean().optional(),
|
|
346
301
|
updatedAt: z.any().optional(),
|
|
347
302
|
metadata: appointmentMetadataSchema.optional(),
|
|
@@ -359,9 +314,9 @@ export const updateAppointmentSchema = z
|
|
|
359
314
|
},
|
|
360
315
|
{
|
|
361
316
|
message:
|
|
362
|
-
|
|
363
|
-
path: [
|
|
364
|
-
}
|
|
317
|
+
'Cancellation reason and canceled by must be provided when canceling an appointment with patient or clinic origin.',
|
|
318
|
+
path: ['status'],
|
|
319
|
+
},
|
|
365
320
|
)
|
|
366
321
|
.refine(
|
|
367
322
|
(data) => {
|
|
@@ -371,10 +326,9 @@ export const updateAppointmentSchema = z
|
|
|
371
326
|
return true;
|
|
372
327
|
},
|
|
373
328
|
{
|
|
374
|
-
message:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
329
|
+
message: 'Appointment end time must be after start time if both are provided',
|
|
330
|
+
path: ['appointmentEndTime'],
|
|
331
|
+
},
|
|
378
332
|
);
|
|
379
333
|
|
|
380
334
|
/**
|
|
@@ -393,10 +347,10 @@ export const searchAppointmentsSchema = z
|
|
|
393
347
|
val instanceof Date ||
|
|
394
348
|
val?._seconds !== undefined ||
|
|
395
349
|
val?.seconds !== undefined ||
|
|
396
|
-
typeof val ===
|
|
397
|
-
typeof val ===
|
|
398
|
-
(val && typeof val.toMillis ===
|
|
399
|
-
|
|
350
|
+
typeof val === 'number' ||
|
|
351
|
+
typeof val === 'string' ||
|
|
352
|
+
(val && typeof val.toMillis === 'function'),
|
|
353
|
+
'Start date must be a valid timestamp or Date object',
|
|
400
354
|
)
|
|
401
355
|
.optional(),
|
|
402
356
|
endDate: z
|
|
@@ -407,17 +361,14 @@ export const searchAppointmentsSchema = z
|
|
|
407
361
|
val instanceof Date ||
|
|
408
362
|
val?._seconds !== undefined ||
|
|
409
363
|
val?.seconds !== undefined ||
|
|
410
|
-
typeof val ===
|
|
411
|
-
typeof val ===
|
|
412
|
-
(val && typeof val.toMillis ===
|
|
413
|
-
|
|
364
|
+
typeof val === 'number' ||
|
|
365
|
+
typeof val === 'string' ||
|
|
366
|
+
(val && typeof val.toMillis === 'function'),
|
|
367
|
+
'End date must be a valid timestamp or Date object',
|
|
414
368
|
)
|
|
415
369
|
.optional(),
|
|
416
370
|
status: z
|
|
417
|
-
.union([
|
|
418
|
-
appointmentStatusSchema,
|
|
419
|
-
z.array(appointmentStatusSchema).nonempty(),
|
|
420
|
-
])
|
|
371
|
+
.union([appointmentStatusSchema, z.array(appointmentStatusSchema).nonempty()])
|
|
421
372
|
.optional(),
|
|
422
373
|
limit: z.number().positive().int().optional().default(20),
|
|
423
374
|
startAfter: z.any().optional(),
|
|
@@ -431,9 +382,9 @@ export const searchAppointmentsSchema = z
|
|
|
431
382
|
},
|
|
432
383
|
{
|
|
433
384
|
message:
|
|
434
|
-
|
|
435
|
-
path: [
|
|
436
|
-
}
|
|
385
|
+
'At least one of patientId, practitionerId, or clinicBranchId must be provided if no date or status filters are set.',
|
|
386
|
+
path: ['patientId'],
|
|
387
|
+
},
|
|
437
388
|
)
|
|
438
389
|
.refine(
|
|
439
390
|
(data) => {
|
|
@@ -443,18 +394,16 @@ export const searchAppointmentsSchema = z
|
|
|
443
394
|
return true;
|
|
444
395
|
},
|
|
445
396
|
{
|
|
446
|
-
message:
|
|
447
|
-
path: [
|
|
448
|
-
}
|
|
397
|
+
message: 'End date must be after or the same as start date',
|
|
398
|
+
path: ['endDate'],
|
|
399
|
+
},
|
|
449
400
|
);
|
|
450
401
|
|
|
451
402
|
/**
|
|
452
403
|
* Schema for validating appointment reschedule data
|
|
453
404
|
*/
|
|
454
405
|
export const rescheduleAppointmentSchema = z.object({
|
|
455
|
-
appointmentId: z
|
|
456
|
-
.string()
|
|
457
|
-
.min(MIN_STRING_LENGTH, "Appointment ID is required"),
|
|
406
|
+
appointmentId: z.string().min(MIN_STRING_LENGTH, 'Appointment ID is required'),
|
|
458
407
|
newStartTime: z
|
|
459
408
|
.any()
|
|
460
409
|
.refine(
|
|
@@ -462,10 +411,10 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
462
411
|
val instanceof Date ||
|
|
463
412
|
val?._seconds !== undefined ||
|
|
464
413
|
val?.seconds !== undefined ||
|
|
465
|
-
typeof val ===
|
|
466
|
-
typeof val ===
|
|
467
|
-
(val && typeof val.toMillis ===
|
|
468
|
-
|
|
414
|
+
typeof val === 'number' ||
|
|
415
|
+
typeof val === 'string' ||
|
|
416
|
+
(val && typeof val.toMillis === 'function'),
|
|
417
|
+
'New start time must be a valid timestamp, Date object, number, or string',
|
|
469
418
|
),
|
|
470
419
|
newEndTime: z
|
|
471
420
|
.any()
|
|
@@ -474,9 +423,9 @@ export const rescheduleAppointmentSchema = z.object({
|
|
|
474
423
|
val instanceof Date ||
|
|
475
424
|
val?._seconds !== undefined ||
|
|
476
425
|
val?.seconds !== undefined ||
|
|
477
|
-
typeof val ===
|
|
478
|
-
typeof val ===
|
|
479
|
-
(val && typeof val.toMillis ===
|
|
480
|
-
|
|
426
|
+
typeof val === 'number' ||
|
|
427
|
+
typeof val === 'string' ||
|
|
428
|
+
(val && typeof val.toMillis === 'function'),
|
|
429
|
+
'New end time must be a valid timestamp, Date object, number, or string',
|
|
481
430
|
),
|
|
482
431
|
});
|
|
@@ -7,7 +7,12 @@ import {
|
|
|
7
7
|
PracticeType,
|
|
8
8
|
Language,
|
|
9
9
|
} from "../types/clinic";
|
|
10
|
-
|
|
10
|
+
import { ProcedureFamily } from "../backoffice/types/static/procedure-family.types";
|
|
11
|
+
import { TreatmentBenefit } from "../backoffice/types/static/treatment-benefit.types";
|
|
12
|
+
import {
|
|
13
|
+
Currency,
|
|
14
|
+
PricingMeasure,
|
|
15
|
+
} from "../backoffice/types/static/pricing.types";
|
|
11
16
|
import { clinicReviewInfoSchema } from "./reviews.schema";
|
|
12
17
|
import {
|
|
13
18
|
procedureSummaryInfoSchema,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
CosmeticAllergySubtype,
|
|
8
8
|
} from "../../types/patient/allergies";
|
|
9
9
|
import { BlockingCondition } from "../../backoffice/types/static/blocking-condition.types";
|
|
10
|
+
import { Contraindication } from "../../backoffice/types/static/contraindication.types";
|
|
10
11
|
import { timestampSchema } from "../common.schema";
|
|
11
12
|
|
|
12
13
|
export const allergySubtypeSchema = z.union([
|
|
@@ -49,14 +50,8 @@ export const blockingConditionSchema = z.object({
|
|
|
49
50
|
isActive: z.boolean(),
|
|
50
51
|
});
|
|
51
52
|
|
|
52
|
-
export const contraindicationDynamicSchema = z.object({
|
|
53
|
-
id: z.string(),
|
|
54
|
-
name: z.string(),
|
|
55
|
-
description: z.string().optional(),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
53
|
export const contraindicationSchema = z.object({
|
|
59
|
-
condition:
|
|
54
|
+
condition: z.nativeEnum(Contraindication),
|
|
60
55
|
lastOccurrence: timestampSchema,
|
|
61
56
|
frequency: z.enum(["rare", "occasional", "frequent"]),
|
|
62
57
|
notes: z.string().optional().nullable(),
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Backoffice Services
|
|
2
|
-
|
|
3
|
-
This directory contains services used by the backoffice application.
|
|
4
|
-
|
|
5
|
-
## Services
|
|
6
|
-
|
|
7
|
-
### `CategoryService`
|
|
8
|
-
|
|
9
|
-
Manages procedure categories. Categories are the first level of organization after procedure family (aesthetics/surgery).
|
|
10
|
-
|
|
11
|
-
- **`create(category)`**: Creates a new category.
|
|
12
|
-
- **`getAll()`**: Retrieves all active categories.
|
|
13
|
-
- **`getAllByFamily(family)`**: Retrieves all active categories for a specific procedure family.
|
|
14
|
-
- **`update(id, category)`**: Updates an existing category.
|
|
15
|
-
- **`delete(id)`**: Soft deletes a category.
|
|
16
|
-
- **`getById(id)`**: Retrieves a category by its ID.
|
|
17
|
-
|
|
18
|
-
### `ProductService`
|
|
19
|
-
|
|
20
|
-
Manages products, which are sub-items of a `Technology`.
|
|
21
|
-
|
|
22
|
-
- **`create(technologyId, brandId, product)`**: Creates a new product under a technology.
|
|
23
|
-
- **`getAllByTechnology(technologyId)`**: Retrieves all products for a technology.
|
|
24
|
-
- **`getAllByBrand(brandId)`**: Retrieves all products for a brand.
|
|
25
|
-
- **`update(technologyId, productId, product)`**: Updates a product.
|
|
26
|
-
- **`delete(technologyId, productId)`**: Soft deletes a product.
|
|
27
|
-
- **`getById(technologyId, productId)`**: Retrieves a product by its ID.
|
|
28
|
-
|
|
29
|
-
### `ConstantsService`
|
|
30
|
-
|
|
31
|
-
Manages administrative constants like treatment benefits and contraindications.
|
|
32
|
-
|
|
33
|
-
- **`getTreatmentBenefits()`**: Retrieves all treatment benefits.
|
|
34
|
-
- **`addTreatmentBenefit(benefit)`**: Adds a new treatment benefit.
|
|
35
|
-
- **`updateTreatmentBenefit(benefit)`**: Updates an existing treatment benefit.
|
|
36
|
-
- **`deleteTreatmentBenefit(benefitId)`**: Deletes a treatment benefit.
|
|
37
|
-
- **`getContraindications()`**: Retrieves all contraindications.
|
|
38
|
-
- **`addContraindication(contraindication)`**: Adds a new contraindication.
|
|
39
|
-
- **`updateContraindication(contraindication)`**: Updates an existing contraindication.
|
|
40
|
-
- **`deleteContraindication(contraindicationId)`**: Deletes a contraindication.
|