@blackcode_sa/metaestetics-api 1.7.13 → 1.7.15
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 +12 -6
- package/dist/admin/index.d.ts +12 -6
- package/dist/admin/index.js +127 -14
- package/dist/admin/index.mjs +127 -14
- package/dist/index.d.mts +130 -113
- package/dist/index.d.ts +130 -113
- package/dist/index.js +69 -4
- package/dist/index.mjs +69 -4
- package/package.json +1 -1
- package/src/admin/booking/booking.admin.ts +160 -14
- package/src/services/procedure/procedure.service.ts +94 -2
- package/src/types/procedure/index.ts +4 -3
- package/src/validations/procedure.schema.ts +3 -3
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
import { DocumentManagerAdminService } from "../documentation-templates/document-manager.admin";
|
|
65
65
|
import { LinkedFormInfo } from "../../types/appointment";
|
|
66
66
|
import { TimestampUtils } from "../../utils/TimestampUtils";
|
|
67
|
+
import { Logger } from "../logger";
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Interface for the data required by orchestrateAppointmentCreation
|
|
@@ -112,9 +113,19 @@ export class BookingAdmin {
|
|
|
112
113
|
}
|
|
113
114
|
): Promise<{ availableSlots: { start: admin.firestore.Timestamp }[] }> {
|
|
114
115
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
Logger.info("[BookingAdmin] Starting availability calculation", {
|
|
117
|
+
clinicId,
|
|
118
|
+
practitionerId,
|
|
119
|
+
procedureId,
|
|
120
|
+
timeframeStart:
|
|
121
|
+
timeframe.start instanceof Date
|
|
122
|
+
? timeframe.start.toISOString()
|
|
123
|
+
: timeframe.start.toDate().toISOString(),
|
|
124
|
+
timeframeEnd:
|
|
125
|
+
timeframe.end instanceof Date
|
|
126
|
+
? timeframe.end.toISOString()
|
|
127
|
+
: timeframe.end.toDate().toISOString(),
|
|
128
|
+
});
|
|
118
129
|
|
|
119
130
|
// Convert timeframe dates to Firestore Timestamps if needed
|
|
120
131
|
const start =
|
|
@@ -128,42 +139,80 @@ export class BookingAdmin {
|
|
|
128
139
|
: timeframe.end;
|
|
129
140
|
|
|
130
141
|
// 1. Fetch clinic data
|
|
142
|
+
Logger.debug("[BookingAdmin] Fetching clinic data", { clinicId });
|
|
131
143
|
const clinicDoc = await this.db.collection("clinics").doc(clinicId).get();
|
|
132
144
|
if (!clinicDoc.exists) {
|
|
145
|
+
Logger.error("[BookingAdmin] Clinic not found", { clinicId });
|
|
133
146
|
throw new Error(`Clinic ${clinicId} not found`);
|
|
134
147
|
}
|
|
135
148
|
const clinic = clinicDoc.data() as unknown as Clinic;
|
|
149
|
+
Logger.debug("[BookingAdmin] Retrieved clinic data", {
|
|
150
|
+
clinicName: clinic.name,
|
|
151
|
+
clinicHasWorkingHours: !!clinic.workingHours,
|
|
152
|
+
});
|
|
136
153
|
|
|
137
154
|
// 2. Fetch practitioner data
|
|
155
|
+
Logger.debug("[BookingAdmin] Fetching practitioner data", {
|
|
156
|
+
practitionerId,
|
|
157
|
+
});
|
|
138
158
|
const practitionerDoc = await this.db
|
|
139
159
|
.collection("practitioners")
|
|
140
160
|
.doc(practitionerId)
|
|
141
161
|
.get();
|
|
142
162
|
if (!practitionerDoc.exists) {
|
|
163
|
+
Logger.error("[BookingAdmin] Practitioner not found", {
|
|
164
|
+
practitionerId,
|
|
165
|
+
});
|
|
143
166
|
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
144
167
|
}
|
|
145
168
|
const practitioner = practitionerDoc.data() as unknown as Practitioner;
|
|
169
|
+
Logger.debug("[BookingAdmin] Retrieved practitioner data", {
|
|
170
|
+
practitionerName: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
|
|
171
|
+
pracWorkingHoursCount: practitioner.clinicWorkingHours?.length || 0,
|
|
172
|
+
});
|
|
146
173
|
|
|
147
174
|
// 3. Fetch procedure data
|
|
175
|
+
Logger.debug("[BookingAdmin] Fetching procedure data", { procedureId });
|
|
148
176
|
const procedureDoc = await this.db
|
|
149
177
|
.collection("procedures")
|
|
150
178
|
.doc(procedureId)
|
|
151
179
|
.get();
|
|
152
180
|
if (!procedureDoc.exists) {
|
|
181
|
+
Logger.error("[BookingAdmin] Procedure not found", { procedureId });
|
|
153
182
|
throw new Error(`Procedure ${procedureId} not found`);
|
|
154
183
|
}
|
|
155
184
|
const procedure = procedureDoc.data() as unknown as Procedure;
|
|
185
|
+
Logger.debug("[BookingAdmin] Retrieved procedure data", {
|
|
186
|
+
procedureName: procedure.name,
|
|
187
|
+
procedureDuration: procedure.duration,
|
|
188
|
+
});
|
|
156
189
|
|
|
157
190
|
// 4. Fetch clinic calendar events
|
|
191
|
+
Logger.debug("[BookingAdmin] Fetching clinic calendar events", {
|
|
192
|
+
clinicId,
|
|
193
|
+
startTime: start.toDate().toISOString(),
|
|
194
|
+
endTime: end.toDate().toISOString(),
|
|
195
|
+
});
|
|
158
196
|
const clinicCalendarEvents = await this.getClinicCalendarEvents(
|
|
159
197
|
clinicId,
|
|
160
198
|
start,
|
|
161
199
|
end
|
|
162
200
|
);
|
|
201
|
+
Logger.debug("[BookingAdmin] Retrieved clinic calendar events", {
|
|
202
|
+
count: clinicCalendarEvents.length,
|
|
203
|
+
});
|
|
163
204
|
|
|
164
205
|
// 5. Fetch practitioner calendar events
|
|
206
|
+
Logger.debug("[BookingAdmin] Fetching practitioner calendar events", {
|
|
207
|
+
practitionerId,
|
|
208
|
+
startTime: start.toDate().toISOString(),
|
|
209
|
+
endTime: end.toDate().toISOString(),
|
|
210
|
+
});
|
|
165
211
|
const practitionerCalendarEvents =
|
|
166
212
|
await this.getPractitionerCalendarEvents(practitionerId, start, end);
|
|
213
|
+
Logger.debug("[BookingAdmin] Retrieved practitioner calendar events", {
|
|
214
|
+
count: practitionerCalendarEvents.length,
|
|
215
|
+
});
|
|
167
216
|
|
|
168
217
|
// Since we're working with two different Timestamp implementations (admin vs client),
|
|
169
218
|
// we need to convert our timestamps to the client-side format expected by the calculator
|
|
@@ -186,17 +235,57 @@ export class BookingAdmin {
|
|
|
186
235
|
),
|
|
187
236
|
};
|
|
188
237
|
|
|
238
|
+
Logger.info("[BookingAdmin] Calling availability calculator", {
|
|
239
|
+
calculatorInputReady: true,
|
|
240
|
+
timeframeDurationHours: Math.round(
|
|
241
|
+
(end.toMillis() - start.toMillis()) / (1000 * 60 * 60)
|
|
242
|
+
),
|
|
243
|
+
clinicEventsCount: clinicCalendarEvents.length,
|
|
244
|
+
practitionerEventsCount: practitionerCalendarEvents.length,
|
|
245
|
+
});
|
|
246
|
+
|
|
189
247
|
// Use the calculator to compute available slots
|
|
190
248
|
const result = BookingAvailabilityCalculator.calculateSlots(request);
|
|
191
249
|
|
|
192
250
|
// Convert the client Timestamps to admin Timestamps before returning
|
|
193
|
-
|
|
251
|
+
const availableSlotsResult = {
|
|
194
252
|
availableSlots: result.availableSlots.map((slot) => ({
|
|
195
253
|
start: admin.firestore.Timestamp.fromMillis(slot.start.toMillis()),
|
|
196
254
|
})),
|
|
197
255
|
};
|
|
256
|
+
|
|
257
|
+
Logger.info(
|
|
258
|
+
"[BookingAdmin] Availability calculation completed successfully",
|
|
259
|
+
{
|
|
260
|
+
availableSlotsCount: availableSlotsResult.availableSlots.length,
|
|
261
|
+
firstSlotTime:
|
|
262
|
+
availableSlotsResult.availableSlots.length > 0
|
|
263
|
+
? availableSlotsResult.availableSlots[0].start
|
|
264
|
+
.toDate()
|
|
265
|
+
.toISOString()
|
|
266
|
+
: "none",
|
|
267
|
+
lastSlotTime:
|
|
268
|
+
availableSlotsResult.availableSlots.length > 0
|
|
269
|
+
? availableSlotsResult.availableSlots[
|
|
270
|
+
availableSlotsResult.availableSlots.length - 1
|
|
271
|
+
].start
|
|
272
|
+
.toDate()
|
|
273
|
+
.toISOString()
|
|
274
|
+
: "none",
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return availableSlotsResult;
|
|
198
279
|
} catch (error) {
|
|
199
|
-
|
|
280
|
+
const errorMessage =
|
|
281
|
+
error instanceof Error ? error.message : String(error);
|
|
282
|
+
Logger.error("[BookingAdmin] Error getting available slots", {
|
|
283
|
+
errorMessage,
|
|
284
|
+
clinicId,
|
|
285
|
+
practitionerId,
|
|
286
|
+
procedureId,
|
|
287
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
288
|
+
});
|
|
200
289
|
throw error;
|
|
201
290
|
}
|
|
202
291
|
}
|
|
@@ -243,6 +332,12 @@ export class BookingAdmin {
|
|
|
243
332
|
end: admin.firestore.Timestamp
|
|
244
333
|
): Promise<any[]> {
|
|
245
334
|
try {
|
|
335
|
+
Logger.debug("[BookingAdmin] Querying clinic calendar events", {
|
|
336
|
+
clinicId,
|
|
337
|
+
startTime: start.toDate().toISOString(),
|
|
338
|
+
endTime: end.toDate().toISOString(),
|
|
339
|
+
});
|
|
340
|
+
|
|
246
341
|
const eventsRef = this.db
|
|
247
342
|
.collection(`clinics/${clinicId}/calendar`)
|
|
248
343
|
.where("eventTime.start", ">=", start)
|
|
@@ -250,15 +345,28 @@ export class BookingAdmin {
|
|
|
250
345
|
|
|
251
346
|
const snapshot = await eventsRef.get();
|
|
252
347
|
|
|
253
|
-
|
|
348
|
+
const events = snapshot.docs.map((doc) => ({
|
|
254
349
|
...doc.data(),
|
|
255
350
|
id: doc.id,
|
|
256
351
|
}));
|
|
352
|
+
|
|
353
|
+
Logger.debug("[BookingAdmin] Retrieved clinic calendar events", {
|
|
354
|
+
clinicId,
|
|
355
|
+
eventsCount: events.length,
|
|
356
|
+
eventsTypes: this.summarizeEventTypes(events),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return events;
|
|
257
360
|
} catch (error) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
361
|
+
const errorMessage =
|
|
362
|
+
error instanceof Error ? error.message : String(error);
|
|
363
|
+
Logger.error("[BookingAdmin] Error fetching clinic calendar events", {
|
|
364
|
+
errorMessage,
|
|
365
|
+
clinicId,
|
|
366
|
+
startTime: start.toDate().toISOString(),
|
|
367
|
+
endTime: end.toDate().toISOString(),
|
|
368
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
369
|
+
});
|
|
262
370
|
return [];
|
|
263
371
|
}
|
|
264
372
|
}
|
|
@@ -277,6 +385,12 @@ export class BookingAdmin {
|
|
|
277
385
|
end: admin.firestore.Timestamp
|
|
278
386
|
): Promise<any[]> {
|
|
279
387
|
try {
|
|
388
|
+
Logger.debug("[BookingAdmin] Querying practitioner calendar events", {
|
|
389
|
+
practitionerId,
|
|
390
|
+
startTime: start.toDate().toISOString(),
|
|
391
|
+
endTime: end.toDate().toISOString(),
|
|
392
|
+
});
|
|
393
|
+
|
|
280
394
|
const eventsRef = this.db
|
|
281
395
|
.collection(`practitioners/${practitionerId}/calendar`)
|
|
282
396
|
.where("eventTime.start", ">=", start)
|
|
@@ -284,19 +398,51 @@ export class BookingAdmin {
|
|
|
284
398
|
|
|
285
399
|
const snapshot = await eventsRef.get();
|
|
286
400
|
|
|
287
|
-
|
|
401
|
+
const events = snapshot.docs.map((doc) => ({
|
|
288
402
|
...doc.data(),
|
|
289
403
|
id: doc.id,
|
|
290
404
|
}));
|
|
405
|
+
|
|
406
|
+
Logger.debug("[BookingAdmin] Retrieved practitioner calendar events", {
|
|
407
|
+
practitionerId,
|
|
408
|
+
eventsCount: events.length,
|
|
409
|
+
eventsTypes: this.summarizeEventTypes(events),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
return events;
|
|
291
413
|
} catch (error) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
414
|
+
const errorMessage =
|
|
415
|
+
error instanceof Error ? error.message : String(error);
|
|
416
|
+
Logger.error(
|
|
417
|
+
"[BookingAdmin] Error fetching practitioner calendar events",
|
|
418
|
+
{
|
|
419
|
+
errorMessage,
|
|
420
|
+
practitionerId,
|
|
421
|
+
startTime: start.toDate().toISOString(),
|
|
422
|
+
endTime: end.toDate().toISOString(),
|
|
423
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
424
|
+
}
|
|
295
425
|
);
|
|
296
426
|
return [];
|
|
297
427
|
}
|
|
298
428
|
}
|
|
299
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Summarizes event types for logging purposes
|
|
432
|
+
* @param events Array of calendar events
|
|
433
|
+
* @returns Object with counts of each event type
|
|
434
|
+
*/
|
|
435
|
+
private summarizeEventTypes(events: any[]): Record<string, number> {
|
|
436
|
+
const typeCounts: Record<string, number> = {};
|
|
437
|
+
|
|
438
|
+
events.forEach((event) => {
|
|
439
|
+
const eventType = event.eventType || "unknown";
|
|
440
|
+
typeCounts[eventType] = (typeCounts[eventType] || 0) + 1;
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
return typeCounts;
|
|
444
|
+
}
|
|
445
|
+
|
|
300
446
|
private _generateCalendarProcedureInfo(
|
|
301
447
|
procedure: Procedure
|
|
302
448
|
): CalendarProcedureInfo {
|
|
@@ -70,12 +70,14 @@ import { Clinic, CLINICS_COLLECTION } from "../../types/clinic";
|
|
|
70
70
|
import { ProcedureReviewInfo } from "../../types/reviews";
|
|
71
71
|
import { distanceBetween, geohashQueryBounds } from "geofire-common";
|
|
72
72
|
import { TreatmentBenefit } from "../../backoffice/types/static/treatment-benefit.types";
|
|
73
|
+
import { MediaService, MediaAccessLevel } from "../media/media.service";
|
|
73
74
|
|
|
74
75
|
export class ProcedureService extends BaseService {
|
|
75
76
|
private categoryService: CategoryService;
|
|
76
77
|
private subcategoryService: SubcategoryService;
|
|
77
78
|
private technologyService: TechnologyService;
|
|
78
79
|
private productService: ProductService;
|
|
80
|
+
private mediaService: MediaService;
|
|
79
81
|
|
|
80
82
|
constructor(
|
|
81
83
|
db: Firestore,
|
|
@@ -84,13 +86,81 @@ export class ProcedureService extends BaseService {
|
|
|
84
86
|
categoryService: CategoryService,
|
|
85
87
|
subcategoryService: SubcategoryService,
|
|
86
88
|
technologyService: TechnologyService,
|
|
87
|
-
productService: ProductService
|
|
89
|
+
productService: ProductService,
|
|
90
|
+
mediaService: MediaService
|
|
88
91
|
) {
|
|
89
92
|
super(db, auth, app);
|
|
90
93
|
this.categoryService = categoryService;
|
|
91
94
|
this.subcategoryService = subcategoryService;
|
|
92
95
|
this.technologyService = technologyService;
|
|
93
96
|
this.productService = productService;
|
|
97
|
+
this.mediaService = mediaService;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Process media resource (string URL or File object)
|
|
102
|
+
* @param media String URL or File object
|
|
103
|
+
* @param ownerId Owner ID for the media (usually procedureId)
|
|
104
|
+
* @param collectionName Collection name for organizing files
|
|
105
|
+
* @returns URL string after processing
|
|
106
|
+
*/
|
|
107
|
+
private async processMedia(
|
|
108
|
+
media: string | File | Blob | null | undefined,
|
|
109
|
+
ownerId: string,
|
|
110
|
+
collectionName: string
|
|
111
|
+
): Promise<string | null> {
|
|
112
|
+
if (!media) return null;
|
|
113
|
+
|
|
114
|
+
// If already a string URL, return it directly
|
|
115
|
+
if (typeof media === "string") {
|
|
116
|
+
return media;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// If it's a File, upload it using MediaService
|
|
120
|
+
if (media instanceof File || media instanceof Blob) {
|
|
121
|
+
console.log(
|
|
122
|
+
`[ProcedureService] Uploading ${collectionName} media for ${ownerId}`
|
|
123
|
+
);
|
|
124
|
+
const metadata = await this.mediaService.uploadMedia(
|
|
125
|
+
media,
|
|
126
|
+
ownerId,
|
|
127
|
+
MediaAccessLevel.PUBLIC,
|
|
128
|
+
collectionName
|
|
129
|
+
);
|
|
130
|
+
return metadata.url;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Process array of media resources (strings or Files)
|
|
138
|
+
* @param mediaArray Array of string URLs or File objects
|
|
139
|
+
* @param ownerId Owner ID for the media
|
|
140
|
+
* @param collectionName Collection name for organizing files
|
|
141
|
+
* @returns Array of URL strings after processing
|
|
142
|
+
*/
|
|
143
|
+
private async processMediaArray(
|
|
144
|
+
mediaArray: (string | File | Blob)[] | undefined,
|
|
145
|
+
ownerId: string,
|
|
146
|
+
collectionName: string
|
|
147
|
+
): Promise<string[]> {
|
|
148
|
+
if (!mediaArray || mediaArray.length === 0) return [];
|
|
149
|
+
|
|
150
|
+
const result: string[] = [];
|
|
151
|
+
|
|
152
|
+
for (const media of mediaArray) {
|
|
153
|
+
const processedUrl = await this.processMedia(
|
|
154
|
+
media,
|
|
155
|
+
ownerId,
|
|
156
|
+
collectionName
|
|
157
|
+
);
|
|
158
|
+
if (processedUrl) {
|
|
159
|
+
result.push(processedUrl);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
94
164
|
}
|
|
95
165
|
|
|
96
166
|
/**
|
|
@@ -101,6 +171,9 @@ export class ProcedureService extends BaseService {
|
|
|
101
171
|
async createProcedure(data: CreateProcedureData): Promise<Procedure> {
|
|
102
172
|
const validatedData = createProcedureSchema.parse(data);
|
|
103
173
|
|
|
174
|
+
// Generate procedure ID first so we can use it for media uploads
|
|
175
|
+
const procedureId = this.generateId();
|
|
176
|
+
|
|
104
177
|
// Get references to related entities (Category, Subcategory, Technology, Product)
|
|
105
178
|
const [category, subcategory, technology, product] = await Promise.all([
|
|
106
179
|
this.categoryService.getById(validatedData.categoryId),
|
|
@@ -146,6 +219,16 @@ export class ProcedureService extends BaseService {
|
|
|
146
219
|
}
|
|
147
220
|
const practitioner = practitionerSnapshot.data() as Practitioner; // Assert type
|
|
148
221
|
|
|
222
|
+
// Process photos if provided
|
|
223
|
+
let processedPhotos: string[] = [];
|
|
224
|
+
if (validatedData.photos && validatedData.photos.length > 0) {
|
|
225
|
+
processedPhotos = await this.processMediaArray(
|
|
226
|
+
validatedData.photos,
|
|
227
|
+
procedureId,
|
|
228
|
+
"procedure-photos"
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
149
232
|
// Create aggregated clinic info for the procedure document
|
|
150
233
|
const clinicInfo = {
|
|
151
234
|
id: clinicSnapshot.id,
|
|
@@ -174,10 +257,10 @@ export class ProcedureService extends BaseService {
|
|
|
174
257
|
};
|
|
175
258
|
|
|
176
259
|
// Create the procedure object
|
|
177
|
-
const procedureId = this.generateId();
|
|
178
260
|
const newProcedure: Omit<Procedure, "createdAt" | "updatedAt"> = {
|
|
179
261
|
id: procedureId,
|
|
180
262
|
...validatedData,
|
|
263
|
+
photos: processedPhotos,
|
|
181
264
|
category, // Embed full objects
|
|
182
265
|
subcategory,
|
|
183
266
|
technology,
|
|
@@ -296,6 +379,15 @@ export class ProcedureService extends BaseService {
|
|
|
296
379
|
let newPractitioner: Practitioner | null = null;
|
|
297
380
|
let newClinic: Clinic | null = null;
|
|
298
381
|
|
|
382
|
+
// Process photos if provided
|
|
383
|
+
if (validatedData.photos !== undefined) {
|
|
384
|
+
updatedProcedureData.photos = await this.processMediaArray(
|
|
385
|
+
validatedData.photos,
|
|
386
|
+
id,
|
|
387
|
+
"procedure-photos"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
299
391
|
// --- Prepare updates and fetch new related data if IDs change ---
|
|
300
392
|
|
|
301
393
|
// Handle Practitioner Change
|
|
@@ -20,6 +20,7 @@ import { DoctorInfo } from "../clinic";
|
|
|
20
20
|
import { PRACTITIONERS_COLLECTION } from "../practitioner";
|
|
21
21
|
import { ProcedureReviewInfo } from "../reviews";
|
|
22
22
|
import type { Contraindication } from "../../backoffice/types/static/contraindication.types";
|
|
23
|
+
import { MediaResource } from "../../services/media/media.service";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Procedure represents a specific medical procedure that can be performed by a practitioner in a clinic
|
|
@@ -31,7 +32,7 @@ export interface Procedure {
|
|
|
31
32
|
/** Name of the procedure */
|
|
32
33
|
name: string;
|
|
33
34
|
/** Photos of the procedure */
|
|
34
|
-
photos?:
|
|
35
|
+
photos?: MediaResource[];
|
|
35
36
|
/** Detailed description of the procedure */
|
|
36
37
|
description: string;
|
|
37
38
|
/** Family of procedures this belongs to (aesthetics/surgery) */
|
|
@@ -101,7 +102,7 @@ export interface CreateProcedureData {
|
|
|
101
102
|
duration: number;
|
|
102
103
|
practitionerId: string;
|
|
103
104
|
clinicBranchId: string;
|
|
104
|
-
photos?:
|
|
105
|
+
photos?: MediaResource[];
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
/**
|
|
@@ -121,7 +122,7 @@ export interface UpdateProcedureData {
|
|
|
121
122
|
technologyId?: string;
|
|
122
123
|
productId?: string;
|
|
123
124
|
clinicBranchId?: string;
|
|
124
|
-
photos?:
|
|
125
|
+
photos?: MediaResource[];
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
/**
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "../backoffice/types/static/pricing.types";
|
|
7
7
|
import { clinicInfoSchema, doctorInfoSchema } from "./shared.schema";
|
|
8
8
|
import { procedureReviewInfoSchema } from "./reviews.schema";
|
|
9
|
-
|
|
9
|
+
import { mediaResourceSchema } from "./media.schema";
|
|
10
10
|
/**
|
|
11
11
|
* Schema for creating a new procedure
|
|
12
12
|
*/
|
|
@@ -24,7 +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(
|
|
27
|
+
photos: z.array(mediaResourceSchema).optional(),
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -44,7 +44,7 @@ export const updateProcedureSchema = z.object({
|
|
|
44
44
|
technologyId: z.string().optional(),
|
|
45
45
|
productId: z.string().optional(),
|
|
46
46
|
clinicBranchId: z.string().optional(),
|
|
47
|
-
photos: z.array(
|
|
47
|
+
photos: z.array(mediaResourceSchema).optional(),
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
/**
|