@blackcode_sa/metaestetics-api 1.11.3 → 1.12.0

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.
Files changed (48) hide show
  1. package/dist/admin/index.d.mts +329 -318
  2. package/dist/admin/index.d.ts +329 -318
  3. package/dist/backoffice/index.d.mts +1166 -430
  4. package/dist/backoffice/index.d.ts +1166 -430
  5. package/dist/backoffice/index.js +1128 -245
  6. package/dist/backoffice/index.mjs +1119 -209
  7. package/dist/index.d.mts +4428 -4035
  8. package/dist/index.d.ts +4428 -4035
  9. package/dist/index.js +1642 -665
  10. package/dist/index.mjs +1406 -401
  11. package/package.json +1 -1
  12. package/src/backoffice/expo-safe/index.ts +3 -0
  13. package/src/backoffice/services/README.md +40 -0
  14. package/src/backoffice/services/brand.service.ts +85 -6
  15. package/src/backoffice/services/category.service.ts +92 -10
  16. package/src/backoffice/services/constants.service.ts +308 -0
  17. package/src/backoffice/services/documentation-template.service.ts +56 -2
  18. package/src/backoffice/services/index.ts +1 -0
  19. package/src/backoffice/services/product.service.ts +126 -5
  20. package/src/backoffice/services/requirement.service.ts +13 -0
  21. package/src/backoffice/services/subcategory.service.ts +184 -13
  22. package/src/backoffice/services/technology.service.ts +344 -129
  23. package/src/backoffice/types/admin-constants.types.ts +69 -0
  24. package/src/backoffice/types/brand.types.ts +1 -0
  25. package/src/backoffice/types/index.ts +1 -0
  26. package/src/backoffice/types/product.types.ts +31 -4
  27. package/src/backoffice/types/static/contraindication.types.ts +1 -0
  28. package/src/backoffice/types/static/treatment-benefit.types.ts +1 -0
  29. package/src/backoffice/types/technology.types.ts +113 -4
  30. package/src/backoffice/validations/schemas.ts +35 -9
  31. package/src/services/appointment/appointment.service.ts +0 -5
  32. package/src/services/appointment/utils/appointment.utils.ts +124 -113
  33. package/src/services/base.service.ts +10 -3
  34. package/src/services/documentation-templates/documentation-template.service.ts +116 -0
  35. package/src/services/media/media.service.ts +2 -2
  36. package/src/services/procedure/procedure.service.ts +436 -234
  37. package/src/types/appointment/index.ts +2 -3
  38. package/src/types/clinic/index.ts +1 -6
  39. package/src/types/patient/medical-info.types.ts +3 -3
  40. package/src/types/procedure/index.ts +20 -17
  41. package/src/validations/clinic.schema.ts +1 -6
  42. package/src/validations/patient/medical-info.schema.ts +7 -2
  43. package/src/backoffice/services/__tests__/brand.service.test.ts +0 -196
  44. package/src/backoffice/services/__tests__/category.service.test.ts +0 -201
  45. package/src/backoffice/services/__tests__/product.service.test.ts +0 -358
  46. package/src/backoffice/services/__tests__/requirement.service.test.ts +0 -226
  47. package/src/backoffice/services/__tests__/subcategory.service.test.ts +0 -181
  48. package/src/backoffice/services/__tests__/technology.service.test.ts +0 -1097
@@ -33,7 +33,6 @@ import {
33
33
  PractitionerProfileInfo,
34
34
  } from "../../../types/profile";
35
35
  import { BlockingCondition } from "../../../backoffice/types/static/blocking-condition.types";
36
- import { Contraindication } from "../../../backoffice/types/static/contraindication.types";
37
36
  import { Requirement } from "../../../backoffice/types/requirement.types";
38
37
  import { PRACTITIONERS_COLLECTION } from "../../../types/practitioner";
39
38
  import { CLINICS_COLLECTION } from "../../../types/clinic";
@@ -43,6 +42,7 @@ import {
43
42
  Technology,
44
43
  TECHNOLOGIES_COLLECTION,
45
44
  } from "../../../backoffice/types/technology.types";
45
+ import type { ContraindicationDynamic } from "../../../backoffice";
46
46
 
47
47
  /**
48
48
  * Fetches all the necessary information for an appointment by IDs.
@@ -66,7 +66,7 @@ export async function fetchAggregatedInfoUtil(
66
66
  patientInfo: PatientProfileInfo;
67
67
  procedureInfo: ProcedureSummaryInfo;
68
68
  blockingConditions: BlockingCondition[];
69
- contraindications: Contraindication[];
69
+ contraindications: ContraindicationDynamic[];
70
70
  preProcedureRequirements: Requirement[];
71
71
  postProcedureRequirements: Requirement[];
72
72
  }> {
@@ -162,7 +162,7 @@ export async function fetchAggregatedInfoUtil(
162
162
  }
163
163
 
164
164
  let blockingConditions: BlockingCondition[] = [];
165
- let contraindications: Contraindication[] = [];
165
+ let contraindications: ContraindicationDynamic[] = [];
166
166
  let preProcedureRequirements: Requirement[] = [];
167
167
  let postProcedureRequirements: Requirement[] = [];
168
168
 
@@ -213,85 +213,85 @@ export async function fetchAggregatedInfoUtil(
213
213
  * @param generateId Function to generate a unique ID
214
214
  * @returns The created Appointment
215
215
  */
216
- export async function createAppointmentUtil(
217
- db: Firestore,
218
- data: CreateAppointmentData,
219
- aggregatedInfo: {
220
- clinicInfo: ClinicInfo;
221
- practitionerInfo: PractitionerProfileInfo;
222
- patientInfo: PatientProfileInfo;
223
- procedureInfo: ProcedureSummaryInfo;
224
- blockingConditions: BlockingCondition[];
225
- contraindications: Contraindication[];
226
- preProcedureRequirements: Requirement[];
227
- postProcedureRequirements: Requirement[];
228
- },
229
- generateId: () => string
230
- ): Promise<Appointment> {
231
- try {
232
- const appointmentId = generateId();
233
-
234
- // Create appointment object
235
- const appointment: Omit<Appointment, "createdAt" | "updatedAt"> & {
236
- createdAt: any;
237
- updatedAt: any;
238
- } = {
239
- id: appointmentId,
240
- calendarEventId: data.calendarEventId,
241
- clinicBranchId: data.clinicBranchId,
242
- clinicInfo: aggregatedInfo.clinicInfo,
243
- practitionerId: data.practitionerId,
244
- practitionerInfo: aggregatedInfo.practitionerInfo,
245
- patientId: data.patientId,
246
- patientInfo: aggregatedInfo.patientInfo,
247
- procedureId: data.procedureId,
248
- procedureInfo: aggregatedInfo.procedureInfo,
249
- status: data.initialStatus,
250
- bookingTime: Timestamp.now(),
251
- appointmentStartTime: data.appointmentStartTime,
252
- appointmentEndTime: data.appointmentEndTime,
253
- patientNotes: data.patientNotes || null,
254
- cost: data.cost,
255
- currency: data.currency,
256
- paymentStatus: data.initialPaymentStatus || PaymentStatus.UNPAID,
257
- blockingConditions: aggregatedInfo.blockingConditions,
258
- contraindications: aggregatedInfo.contraindications,
259
- preProcedureRequirements: aggregatedInfo.preProcedureRequirements,
260
- postProcedureRequirements: aggregatedInfo.postProcedureRequirements,
261
- completedPreRequirements: [],
262
- completedPostRequirements: [],
263
- createdAt: serverTimestamp(),
264
- updatedAt: serverTimestamp(),
265
- };
266
-
267
- // Add additional fields for confirmation if appointment is already confirmed
268
- if (data.initialStatus === AppointmentStatus.CONFIRMED) {
269
- appointment.confirmationTime = Timestamp.now();
270
- }
271
-
272
- // Save to Firestore
273
- await setDoc(doc(db, APPOINTMENTS_COLLECTION, appointmentId), appointment);
274
-
275
- // Update the calendar event with the appointment ID
276
- const calendarEventRef = doc(db, CALENDAR_COLLECTION, data.calendarEventId);
277
- await updateDoc(calendarEventRef, {
278
- appointmentId: appointmentId,
279
- updatedAt: serverTimestamp(),
280
- });
281
-
282
- // Return the created appointment
283
- // Convert serverTimestamp to regular Timestamp for immediate use
284
- const now = Timestamp.now();
285
- return {
286
- ...appointment,
287
- createdAt: now,
288
- updatedAt: now,
289
- } as Appointment;
290
- } catch (error) {
291
- console.error("Error creating appointment:", error);
292
- throw error;
293
- }
294
- }
216
+ // export async function createAppointmentUtil(
217
+ // db: Firestore,
218
+ // data: CreateAppointmentData,
219
+ // aggregatedInfo: {
220
+ // clinicInfo: ClinicInfo;
221
+ // practitionerInfo: PractitionerProfileInfo;
222
+ // patientInfo: PatientProfileInfo;
223
+ // procedureInfo: ProcedureSummaryInfo;
224
+ // blockingConditions: BlockingCondition[];
225
+ // contraindications: ContraindicationDynamic[];
226
+ // preProcedureRequirements: Requirement[];
227
+ // postProcedureRequirements: Requirement[];
228
+ // },
229
+ // generateId: () => string
230
+ // ): Promise<Appointment> {
231
+ // try {
232
+ // const appointmentId = generateId();
233
+
234
+ // // Create appointment object
235
+ // const appointment: Omit<Appointment, "createdAt" | "updatedAt"> & {
236
+ // createdAt: any;
237
+ // updatedAt: any;
238
+ // } = {
239
+ // id: appointmentId,
240
+ // calendarEventId: data.calendarEventId,
241
+ // clinicBranchId: data.clinicBranchId,
242
+ // clinicInfo: aggregatedInfo.clinicInfo,
243
+ // practitionerId: data.practitionerId,
244
+ // practitionerInfo: aggregatedInfo.practitionerInfo,
245
+ // patientId: data.patientId,
246
+ // patientInfo: aggregatedInfo.patientInfo,
247
+ // procedureId: data.procedureId,
248
+ // procedureInfo: aggregatedInfo.procedureInfo,
249
+ // status: data.initialStatus,
250
+ // bookingTime: Timestamp.now(),
251
+ // appointmentStartTime: data.appointmentStartTime,
252
+ // appointmentEndTime: data.appointmentEndTime,
253
+ // patientNotes: data.patientNotes || null,
254
+ // cost: data.cost,
255
+ // currency: data.currency,
256
+ // paymentStatus: data.initialPaymentStatus || PaymentStatus.UNPAID,
257
+ // blockingConditions: aggregatedInfo.blockingConditions,
258
+ // contraindications: aggregatedInfo.contraindications,
259
+ // preProcedureRequirements: aggregatedInfo.preProcedureRequirements,
260
+ // postProcedureRequirements: aggregatedInfo.postProcedureRequirements,
261
+ // completedPreRequirements: [],
262
+ // completedPostRequirements: [],
263
+ // createdAt: serverTimestamp(),
264
+ // updatedAt: serverTimestamp(),
265
+ // };
266
+
267
+ // // Add additional fields for confirmation if appointment is already confirmed
268
+ // if (data.initialStatus === AppointmentStatus.CONFIRMED) {
269
+ // appointment.confirmationTime = Timestamp.now();
270
+ // }
271
+
272
+ // // Save to Firestore
273
+ // await setDoc(doc(db, APPOINTMENTS_COLLECTION, appointmentId), appointment);
274
+
275
+ // // Update the calendar event with the appointment ID
276
+ // const calendarEventRef = doc(db, CALENDAR_COLLECTION, data.calendarEventId);
277
+ // await updateDoc(calendarEventRef, {
278
+ // appointmentId: appointmentId,
279
+ // updatedAt: serverTimestamp(),
280
+ // });
281
+
282
+ // // Return the created appointment
283
+ // // Convert serverTimestamp to regular Timestamp for immediate use
284
+ // const now = Timestamp.now();
285
+ // return {
286
+ // ...appointment,
287
+ // createdAt: now,
288
+ // updatedAt: now,
289
+ // } as Appointment;
290
+ // } catch (error) {
291
+ // console.error("Error creating appointment:", error);
292
+ // throw error;
293
+ // }
294
+ // }
295
295
 
296
296
  /**
297
297
  * Updates an existing appointment in Firestore.
@@ -327,23 +327,27 @@ export async function updateAppointmentUtil(
327
327
  const validPreReqIds = currentAppointment.preProcedureRequirements.map(
328
328
  (req) => req.id
329
329
  );
330
- const invalidPreReqIds = data.completedPreRequirements.filter(
331
- (id) => !validPreReqIds.includes(id)
332
- );
333
330
 
334
- if (invalidPreReqIds.length > 0) {
335
- throw new Error(
336
- `Invalid pre-requirement IDs: ${invalidPreReqIds.join(", ")}`
331
+ // Only perform validation and merging if the input is an array
332
+ if (Array.isArray(data.completedPreRequirements)) {
333
+ const invalidPreReqIds = data.completedPreRequirements.filter(
334
+ (id) => !validPreReqIds.includes(id)
337
335
  );
338
- }
339
336
 
340
- // Update the completed pre-requirements
341
- completedPreRequirements = [
342
- ...new Set([
343
- ...completedPreRequirements,
344
- ...data.completedPreRequirements,
345
- ]),
346
- ];
337
+ if (invalidPreReqIds.length > 0) {
338
+ throw new Error(
339
+ `Invalid pre-requirement IDs: ${invalidPreReqIds.join(", ")}`
340
+ );
341
+ }
342
+
343
+ // Update the completed pre-requirements
344
+ completedPreRequirements = [
345
+ ...new Set([
346
+ ...completedPreRequirements,
347
+ ...data.completedPreRequirements,
348
+ ]),
349
+ ];
350
+ }
347
351
  }
348
352
 
349
353
  if (data.completedPostRequirements) {
@@ -351,30 +355,37 @@ export async function updateAppointmentUtil(
351
355
  const validPostReqIds = currentAppointment.postProcedureRequirements.map(
352
356
  (req) => req.id
353
357
  );
354
- const invalidPostReqIds = data.completedPostRequirements.filter(
355
- (id) => !validPostReqIds.includes(id)
356
- );
357
358
 
358
- if (invalidPostReqIds.length > 0) {
359
- throw new Error(
360
- `Invalid post-requirement IDs: ${invalidPostReqIds.join(", ")}`
359
+ if (Array.isArray(data.completedPostRequirements)) {
360
+ const invalidPostReqIds = data.completedPostRequirements.filter(
361
+ (id) => !validPostReqIds.includes(id)
361
362
  );
362
- }
363
363
 
364
- // Update the completed post-requirements
365
- completedPostRequirements = [
366
- ...new Set([
367
- ...completedPostRequirements,
368
- ...data.completedPostRequirements,
369
- ]),
370
- ];
364
+ if (invalidPostReqIds.length > 0) {
365
+ throw new Error(
366
+ `Invalid post-requirement IDs: ${invalidPostReqIds.join(", ")}`
367
+ );
368
+ }
369
+
370
+ // Update the completed post-requirements
371
+ completedPostRequirements = [
372
+ ...new Set([
373
+ ...completedPostRequirements,
374
+ ...data.completedPostRequirements,
375
+ ]),
376
+ ];
377
+ }
371
378
  }
372
379
 
373
380
  // Prepare update data
374
381
  const updateData: any = {
375
382
  ...data,
376
- completedPreRequirements,
377
- completedPostRequirements,
383
+ completedPreRequirements: Array.isArray(data.completedPreRequirements)
384
+ ? completedPreRequirements
385
+ : data.completedPreRequirements,
386
+ completedPostRequirements: Array.isArray(data.completedPostRequirements)
387
+ ? completedPostRequirements
388
+ : data.completedPostRequirements,
378
389
  updatedAt: serverTimestamp(),
379
390
  };
380
391
 
@@ -454,7 +465,7 @@ async function updateCalendarEventStatus(
454
465
  case AppointmentStatus.CANCELED_CLINIC:
455
466
  calendarStatus = "canceled";
456
467
  break;
457
- case AppointmentStatus.RESCHEDULED:
468
+ case AppointmentStatus.RESCHEDULED_BY_CLINIC:
458
469
  calendarStatus = "rescheduled";
459
470
  break;
460
471
  case AppointmentStatus.COMPLETED:
@@ -7,13 +7,20 @@ export class BaseService {
7
7
  protected db: Firestore;
8
8
  protected auth: Auth;
9
9
  protected app: FirebaseApp;
10
- protected storage: FirebaseStorage;
10
+ protected storage!: FirebaseStorage;
11
11
 
12
- constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
12
+ constructor(
13
+ db: Firestore,
14
+ auth: Auth,
15
+ app: FirebaseApp,
16
+ storage?: FirebaseStorage
17
+ ) {
13
18
  this.db = db;
14
19
  this.auth = auth;
15
20
  this.app = app;
16
- this.storage = getStorage(app);
21
+ if (app) {
22
+ this.storage = storage || getStorage(app);
23
+ }
17
24
  }
18
25
 
19
26
  /**
@@ -15,6 +15,7 @@ import {
15
15
  QueryDocumentSnapshot,
16
16
  serverTimestamp,
17
17
  Timestamp,
18
+ QueryConstraint,
18
19
  } from "firebase/firestore";
19
20
  import { BaseService } from "../base.service";
20
21
  import {
@@ -28,6 +29,7 @@ import {
28
29
  createDocumentTemplateSchema,
29
30
  updateDocumentTemplateSchema,
30
31
  } from "../../validations/documentation-templates.schema";
32
+ import { getCountFromServer } from "firebase/firestore";
31
33
 
32
34
  /**
33
35
  * Service for managing documentation templates
@@ -38,6 +40,10 @@ export class DocumentationTemplateService extends BaseService {
38
40
  DOCUMENTATION_TEMPLATES_COLLECTION
39
41
  );
40
42
 
43
+ constructor(...args: ConstructorParameters<typeof BaseService>) {
44
+ super(...args);
45
+ }
46
+
41
47
  /**
42
48
  * Create a new document template
43
49
  * @param data - Template data
@@ -303,6 +309,116 @@ export class DocumentationTemplateService extends BaseService {
303
309
  };
304
310
  }
305
311
 
312
+ /**
313
+ * Get all active templates with optional filters and pagination.
314
+ * @param options - Options for filtering and pagination.
315
+ * @returns A promise that resolves to the templates and the last visible document.
316
+ */
317
+ async getTemplates(options: {
318
+ pageSize?: number;
319
+ lastDoc?: QueryDocumentSnapshot<DocumentTemplate>;
320
+ isUserForm?: boolean;
321
+ isRequired?: boolean;
322
+ sortingOrder?: number;
323
+ }): Promise<{
324
+ templates: DocumentTemplate[];
325
+ lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
326
+ }> {
327
+ const {
328
+ pageSize = 20,
329
+ lastDoc,
330
+ isUserForm,
331
+ isRequired,
332
+ sortingOrder,
333
+ } = options;
334
+ const constraints: QueryConstraint[] = [
335
+ where("isActive", "==", true),
336
+ orderBy("sortingOrder", "asc"),
337
+ orderBy("title", "asc"),
338
+ limit(pageSize),
339
+ ];
340
+
341
+ if (isUserForm !== undefined) {
342
+ constraints.push(where("isUserForm", "==", isUserForm));
343
+ }
344
+ if (isRequired !== undefined) {
345
+ constraints.push(where("isRequired", "==", isRequired));
346
+ }
347
+ if (sortingOrder !== undefined) {
348
+ constraints.push(where("sortingOrder", "==", sortingOrder));
349
+ }
350
+ if (lastDoc) {
351
+ constraints.push(startAfter(lastDoc));
352
+ }
353
+
354
+ const q = query(this.collectionRef, ...constraints.filter((c) => c));
355
+
356
+ const querySnapshot = await getDocs(q);
357
+ const templates: DocumentTemplate[] = [];
358
+ let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
359
+
360
+ querySnapshot.forEach((doc) => {
361
+ templates.push(doc.data() as DocumentTemplate);
362
+ lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
363
+ });
364
+
365
+ return {
366
+ templates,
367
+ lastDoc: lastVisible,
368
+ };
369
+ }
370
+
371
+ /**
372
+ * Get the total count of active templates with optional filters.
373
+ * @param options - Options for filtering.
374
+ * @returns A promise that resolves to the total count of templates.
375
+ */
376
+ async getTemplatesCount(options: {
377
+ isUserForm?: boolean;
378
+ isRequired?: boolean;
379
+ sortingOrder?: number;
380
+ search?: string; // Search will be applied in-memory for now
381
+ }): Promise<number> {
382
+ const { isUserForm, isRequired, sortingOrder } = options;
383
+ const constraints = [where("isActive", "==", true)];
384
+
385
+ if (isUserForm !== undefined) {
386
+ constraints.push(where("isUserForm", "==", isUserForm));
387
+ }
388
+ if (isRequired !== undefined) {
389
+ constraints.push(where("isRequired", "==", isRequired));
390
+ }
391
+ if (sortingOrder !== undefined) {
392
+ constraints.push(where("sortingOrder", "==", sortingOrder));
393
+ }
394
+
395
+ const q = query(this.collectionRef, ...constraints.filter((c) => c));
396
+ const snapshot = await getCountFromServer(q);
397
+
398
+ return snapshot.data().count;
399
+ }
400
+
401
+ /**
402
+ * Get all active templates without pagination for filtering purposes.
403
+ * @returns A promise that resolves to an array of all active templates.
404
+ */
405
+ async getAllActiveTemplates(): Promise<DocumentTemplate[]> {
406
+ const q = query(
407
+ this.collectionRef,
408
+ where("isActive", "==", true),
409
+ orderBy("title", "asc")
410
+ );
411
+
412
+ const querySnapshot = await getDocs(q);
413
+ const templates: DocumentTemplate[] = [];
414
+
415
+ querySnapshot.forEach((doc) => {
416
+ templates.push(doc.data() as DocumentTemplate);
417
+ });
418
+
419
+ return templates;
420
+ }
421
+
306
422
  /**
307
423
  * Get templates by tags
308
424
  * @param tags - Tags to filter by
@@ -57,8 +57,8 @@ export interface MediaMetadata {
57
57
  export const MEDIA_METADATA_COLLECTION = "media_metadata";
58
58
 
59
59
  export class MediaService extends BaseService {
60
- constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
61
- super(db, auth, app);
60
+ constructor(...args: ConstructorParameters<typeof BaseService>) {
61
+ super(...args);
62
62
  }
63
63
 
64
64
  /**