@blackcode_sa/metaestetics-api 1.11.1 → 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 +324 -330
- package/dist/admin/index.d.ts +324 -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 +3037 -3100
- package/dist/index.d.ts +3037 -3100
- package/dist/index.js +129 -379
- package/dist/index.mjs +130 -379
- 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 +37 -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 +118 -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
|
});
|
|
@@ -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
|
});
|
|
@@ -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.
|