@blackcode_sa/metaestetics-api 1.5.3 → 1.5.6

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 (32) hide show
  1. package/dist/backoffice/index.d.mts +1306 -63
  2. package/dist/backoffice/index.d.ts +1306 -63
  3. package/dist/backoffice/index.js +35 -26
  4. package/dist/backoffice/index.mjs +35 -26
  5. package/dist/index.d.mts +165 -8
  6. package/dist/index.d.ts +165 -8
  7. package/dist/index.js +726 -414
  8. package/dist/index.mjs +772 -451
  9. package/package.json +1 -1
  10. package/src/backoffice/services/brand.service.ts +2 -4
  11. package/src/backoffice/services/category.service.ts +2 -7
  12. package/src/backoffice/services/product.service.ts +5 -7
  13. package/src/backoffice/services/requirement.service.ts +6 -7
  14. package/src/backoffice/services/subcategory.service.ts +11 -8
  15. package/src/backoffice/services/technology.service.ts +6 -11
  16. package/src/backoffice/types/brand.types.ts +5 -0
  17. package/src/backoffice/types/category.types.ts +5 -0
  18. package/src/backoffice/types/documentation-templates.types.ts +4 -0
  19. package/src/backoffice/types/product.types.ts +5 -0
  20. package/src/backoffice/types/requirement.types.ts +5 -0
  21. package/src/backoffice/types/subcategory.types.ts +5 -0
  22. package/src/backoffice/types/technology.types.ts +10 -0
  23. package/src/backoffice/validations/schemas.ts +2 -0
  24. package/src/errors/auth.errors.ts +7 -0
  25. package/src/index.ts +59 -70
  26. package/src/services/auth.service.ts +94 -0
  27. package/src/services/clinic/clinic.service.ts +6 -0
  28. package/src/services/documentation-templates/documentation-template.service.ts +4 -1
  29. package/src/services/procedure/procedure.service.ts +238 -0
  30. package/src/types/documentation-templates/index.ts +5 -0
  31. package/src/types/procedure/index.ts +104 -0
  32. package/src/validations/procedure.schema.ts +58 -0
@@ -471,6 +471,100 @@ export class AuthService extends BaseService {
471
471
  return this.userService.getOrCreateUser(firebaseUser);
472
472
  }
473
473
 
474
+ /**
475
+ * Prijavljuje korisnika sa email-om i lozinkom samo za clinic_admin role
476
+ * @param email - Email korisnika
477
+ * @param password - Lozinka korisnika
478
+ * @returns Objekat koji sadrži korisnika, admin profil i grupu klinika
479
+ * @throws {AUTH_ERRORS.INVALID_ROLE} Ako korisnik nema clinic_admin rolu
480
+ * @throws {AUTH_ERRORS.NOT_FOUND} Ako admin profil nije pronađen
481
+ */
482
+ async signInClinicAdmin(
483
+ email: string,
484
+ password: string
485
+ ): Promise<{
486
+ user: User;
487
+ clinicAdmin: ClinicAdmin;
488
+ clinicGroup: ClinicGroup;
489
+ }> {
490
+ try {
491
+ // Initialize required services
492
+ const clinicAdminService = new ClinicAdminService(
493
+ this.db,
494
+ this.auth,
495
+ this.app
496
+ );
497
+ const clinicGroupService = new ClinicGroupService(
498
+ this.db,
499
+ this.auth,
500
+ this.app,
501
+ clinicAdminService
502
+ );
503
+ const clinicService = new ClinicService(
504
+ this.db,
505
+ this.auth,
506
+ this.app,
507
+ clinicGroupService,
508
+ clinicAdminService
509
+ );
510
+
511
+ // Set services to resolve circular dependencies
512
+ clinicAdminService.setServices(clinicGroupService, clinicService);
513
+
514
+ // Sign in with email/password
515
+ const { user: firebaseUser } = await signInWithEmailAndPassword(
516
+ this.auth,
517
+ email,
518
+ password
519
+ );
520
+
521
+ // Get or create user
522
+ const user = await this.userService.getOrCreateUser(firebaseUser);
523
+
524
+ // Check if user has clinic_admin role
525
+ if (!user.roles?.includes(UserRole.CLINIC_ADMIN)) {
526
+ console.error("[AUTH] User is not a clinic admin:", user.uid);
527
+ throw AUTH_ERRORS.INVALID_ROLE;
528
+ }
529
+
530
+ // Check and get admin profile
531
+ if (!user.adminProfile) {
532
+ console.error("[AUTH] User has no admin profile:", user.uid);
533
+ throw AUTH_ERRORS.NOT_FOUND;
534
+ }
535
+
536
+ // Get clinic admin profile
537
+ const adminProfile = await clinicAdminService.getClinicAdmin(
538
+ user.adminProfile
539
+ );
540
+ if (!adminProfile) {
541
+ console.error("[AUTH] Admin profile not found:", user.adminProfile);
542
+ throw AUTH_ERRORS.NOT_FOUND;
543
+ }
544
+
545
+ // Get clinic group
546
+ const clinicGroup = await clinicGroupService.getClinicGroup(
547
+ adminProfile.clinicGroupId
548
+ );
549
+ if (!clinicGroup) {
550
+ console.error(
551
+ "[AUTH] Clinic group not found:",
552
+ adminProfile.clinicGroupId
553
+ );
554
+ throw AUTH_ERRORS.NOT_FOUND;
555
+ }
556
+
557
+ return {
558
+ user,
559
+ clinicAdmin: adminProfile,
560
+ clinicGroup,
561
+ };
562
+ } catch (error) {
563
+ console.error("[AUTH] Error in signInClinicAdmin:", error);
564
+ throw error;
565
+ }
566
+ }
567
+
474
568
  /**
475
569
  * Prijavljuje korisnika sa Facebook-om
476
570
  */
@@ -258,6 +258,12 @@ export class ClinicService extends BaseService {
258
258
  }
259
259
  console.log("[CLINIC_SERVICE] Clinic group verified");
260
260
 
261
+ // TODO: Handle logo, coverPhoto, featuredPhotos, photosWithTags and upload them to storage
262
+ // Use storage service to upload files, and get the download URL
263
+ // Use path 'clinics/{clinicId}/{photoType}/{filename}' for storing the files
264
+ // Photo types: logo, coverPhoto, featuredPhotos, photosWithTags
265
+ // Storage can be accessed by using the storage service like app.getStorage()
266
+
261
267
  // Create the clinic data
262
268
  const createClinicData: CreateClinicData = {
263
269
  clinicGroupId,
@@ -21,9 +21,12 @@ import {
21
21
  CreateDocumentTemplateData,
22
22
  DocumentElement,
23
23
  DocumentTemplate,
24
- DOCUMENTATION_TEMPLATES_COLLECTION,
25
24
  UpdateDocumentTemplateData,
26
25
  } from "../../types";
26
+ import {
27
+ FILLED_DOCUMENTS_COLLECTION,
28
+ DOCUMENTATION_TEMPLATES_COLLECTION,
29
+ } from "../../types";
27
30
  import {
28
31
  createDocumentTemplateSchema,
29
32
  updateDocumentTemplateSchema,
@@ -0,0 +1,238 @@
1
+ import {
2
+ collection,
3
+ doc,
4
+ getDoc,
5
+ getDocs,
6
+ query,
7
+ where,
8
+ updateDoc,
9
+ setDoc,
10
+ deleteDoc,
11
+ Timestamp,
12
+ serverTimestamp,
13
+ DocumentData,
14
+ } from "firebase/firestore";
15
+ import { BaseService } from "../base.service";
16
+ import {
17
+ Procedure,
18
+ CreateProcedureData,
19
+ UpdateProcedureData,
20
+ PROCEDURES_COLLECTION,
21
+ } from "../../types/procedure";
22
+ import {
23
+ createProcedureSchema,
24
+ updateProcedureSchema,
25
+ } from "../../validations/procedure.schema";
26
+ import { z } from "zod";
27
+ import { Auth } from "firebase/auth";
28
+ import { Firestore } from "firebase/firestore";
29
+ import { FirebaseApp } from "firebase/app";
30
+ import {
31
+ Category,
32
+ CATEGORIES_COLLECTION,
33
+ } from "../../backoffice/types/category.types";
34
+ import {
35
+ Subcategory,
36
+ SUBCATEGORIES_COLLECTION,
37
+ } from "../../backoffice/types/subcategory.types";
38
+ import {
39
+ Technology,
40
+ TECHNOLOGIES_COLLECTION,
41
+ } from "../../backoffice/types/technology.types";
42
+ import {
43
+ Product,
44
+ PRODUCTS_COLLECTION,
45
+ } from "../../backoffice/types/product.types";
46
+
47
+ export class ProcedureService extends BaseService {
48
+ constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
49
+ super(db, auth, app);
50
+ }
51
+
52
+ /**
53
+ * Creates a new procedure
54
+ * @param data - The data for creating a new procedure
55
+ * @returns The created procedure
56
+ */
57
+ async createProcedure(data: CreateProcedureData): Promise<Procedure> {
58
+ // Validate input data
59
+ const validatedData = createProcedureSchema.parse(data);
60
+
61
+ // Get references to related entities
62
+ const [category, subcategory, technology, product] = await Promise.all([
63
+ this.getCategory(validatedData.categoryId),
64
+ this.getSubcategory(validatedData.subcategoryId),
65
+ this.getTechnology(validatedData.technologyId),
66
+ this.getProduct(validatedData.productId),
67
+ ]);
68
+
69
+ if (!category || !subcategory || !technology || !product) {
70
+ throw new Error("One or more required entities not found");
71
+ }
72
+
73
+ // Create the procedure object
74
+ const procedure: Omit<Procedure, "id"> = {
75
+ ...validatedData,
76
+ category,
77
+ subcategory,
78
+ technology,
79
+ product,
80
+ blockingConditions: technology.blockingConditions,
81
+ treatmentBenefits: technology.benefits,
82
+ preRequirements: technology.requirements.pre,
83
+ postRequirements: technology.requirements.post,
84
+ certificationRequirement: technology.certificationRequirement,
85
+ documentationTemplates: technology.documentationTemplates || [],
86
+ isActive: true,
87
+ createdAt: new Date(),
88
+ updatedAt: new Date(),
89
+ };
90
+
91
+ // Generate ID and create document
92
+ const id = this.generateId();
93
+ const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
94
+ await setDoc(docRef, {
95
+ ...procedure,
96
+ id,
97
+ createdAt: serverTimestamp(),
98
+ updatedAt: serverTimestamp(),
99
+ });
100
+
101
+ return { ...procedure, id } as Procedure;
102
+ }
103
+
104
+ /**
105
+ * Gets a procedure by ID
106
+ * @param id - The ID of the procedure to get
107
+ * @returns The procedure if found, null otherwise
108
+ */
109
+ async getProcedure(id: string): Promise<Procedure | null> {
110
+ const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
111
+ const docSnap = await getDoc(docRef);
112
+
113
+ if (!docSnap.exists()) {
114
+ return null;
115
+ }
116
+
117
+ return docSnap.data() as Procedure;
118
+ }
119
+
120
+ /**
121
+ * Gets all procedures for a clinic branch
122
+ * @param clinicBranchId - The ID of the clinic branch
123
+ * @returns List of procedures
124
+ */
125
+ async getProceduresByClinicBranch(
126
+ clinicBranchId: string
127
+ ): Promise<Procedure[]> {
128
+ const q = query(
129
+ collection(this.db, PROCEDURES_COLLECTION),
130
+ where("clinicBranchId", "==", clinicBranchId),
131
+ where("isActive", "==", true)
132
+ );
133
+ const snapshot = await getDocs(q);
134
+ return snapshot.docs.map((doc) => doc.data() as Procedure);
135
+ }
136
+
137
+ /**
138
+ * Gets all procedures for a practitioner
139
+ * @param practitionerId - The ID of the practitioner
140
+ * @returns List of procedures
141
+ */
142
+ async getProceduresByPractitioner(
143
+ practitionerId: string
144
+ ): Promise<Procedure[]> {
145
+ const q = query(
146
+ collection(this.db, PROCEDURES_COLLECTION),
147
+ where("practitionerId", "==", practitionerId),
148
+ where("isActive", "==", true)
149
+ );
150
+ const snapshot = await getDocs(q);
151
+ return snapshot.docs.map((doc) => doc.data() as Procedure);
152
+ }
153
+
154
+ /**
155
+ * Updates a procedure
156
+ * @param id - The ID of the procedure to update
157
+ * @param data - The data to update
158
+ * @returns The updated procedure
159
+ */
160
+ async updateProcedure(
161
+ id: string,
162
+ data: UpdateProcedureData
163
+ ): Promise<Procedure> {
164
+ // Validate input data
165
+ const validatedData = updateProcedureSchema.parse(data);
166
+
167
+ // Get the existing procedure
168
+ const existingProcedure = await this.getProcedure(id);
169
+ if (!existingProcedure) {
170
+ throw new Error(`Procedure with ID ${id} not found`);
171
+ }
172
+
173
+ // Update the procedure
174
+ const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
175
+ await updateDoc(docRef, {
176
+ ...validatedData,
177
+ updatedAt: serverTimestamp(),
178
+ });
179
+
180
+ return {
181
+ ...existingProcedure,
182
+ ...validatedData,
183
+ updatedAt: new Date(),
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Deactivates a procedure
189
+ * @param id - The ID of the procedure to deactivate
190
+ */
191
+ async deactivateProcedure(id: string): Promise<void> {
192
+ const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
193
+ await updateDoc(docRef, {
194
+ isActive: false,
195
+ updatedAt: serverTimestamp(),
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Gets a category by ID
201
+ * @private
202
+ */
203
+ private async getCategory(id: string): Promise<Category | null> {
204
+ const docRef = doc(this.db, CATEGORIES_COLLECTION, id);
205
+ const docSnap = await getDoc(docRef);
206
+ return docSnap.exists() ? (docSnap.data() as Category) : null;
207
+ }
208
+
209
+ /**
210
+ * Gets a subcategory by ID
211
+ * @private
212
+ */
213
+ private async getSubcategory(id: string): Promise<Subcategory | null> {
214
+ const docRef = doc(this.db, SUBCATEGORIES_COLLECTION, id);
215
+ const docSnap = await getDoc(docRef);
216
+ return docSnap.exists() ? (docSnap.data() as Subcategory) : null;
217
+ }
218
+
219
+ /**
220
+ * Gets a technology by ID
221
+ * @private
222
+ */
223
+ private async getTechnology(id: string): Promise<Technology | null> {
224
+ const docRef = doc(this.db, TECHNOLOGIES_COLLECTION, id);
225
+ const docSnap = await getDoc(docRef);
226
+ return docSnap.exists() ? (docSnap.data() as Technology) : null;
227
+ }
228
+
229
+ /**
230
+ * Gets a product by ID
231
+ * @private
232
+ */
233
+ private async getProduct(id: string): Promise<Product | null> {
234
+ const docRef = doc(this.db, PRODUCTS_COLLECTION, id);
235
+ const docSnap = await getDoc(docRef);
236
+ return docSnap.exists() ? (docSnap.data() as Product) : null;
237
+ }
238
+ }
@@ -2,6 +2,11 @@
2
2
  * Types for the Medical Documentation Templating System
3
3
  */
4
4
 
5
+ /**
6
+ * Kolekcija u Firestore bazi gde se čuvaju dokumentacijske šablone
7
+ */
8
+ export const DOCUMENTATION_TEMPLATES_COLLECTION = "documentation-templates";
9
+ export const FILLED_DOCUMENTS_COLLECTION = "filled-documents";
5
10
  /**
6
11
  * Enum for element types in documentation templates
7
12
  */
@@ -0,0 +1,104 @@
1
+ import { ProcedureFamily } from "../../backoffice/types/static/procedure-family.types";
2
+ import { Category } from "../../backoffice/types/category.types";
3
+ import { Subcategory } from "../../backoffice/types/subcategory.types";
4
+ import { Technology } from "../../backoffice/types/technology.types";
5
+ import { Product } from "../../backoffice/types/product.types";
6
+ import {
7
+ PricingMeasure,
8
+ Currency,
9
+ } from "../../backoffice/types/static/pricing.types";
10
+ import { Requirement } from "../../backoffice/types/requirement.types";
11
+ import { BlockingCondition } from "../../backoffice/types/static/blocking-condition.types";
12
+ import { TreatmentBenefit } from "../../backoffice/types/static/treatment-benefit.types";
13
+ import { CertificationRequirement } from "../../backoffice/types/static/certification.types";
14
+ import { DocumentTemplate } from "../documentation-templates";
15
+
16
+ /**
17
+ * Procedure represents a specific medical procedure that can be performed by a practitioner in a clinic
18
+ * It inherits properties from technology and adds clinic/practitioner specific details
19
+ */
20
+ export interface Procedure {
21
+ /** Unique identifier of the procedure */
22
+ id: string;
23
+ /** Name of the procedure */
24
+ name: string;
25
+ /** Detailed description of the procedure */
26
+ description: string;
27
+ /** Family of procedures this belongs to (aesthetics/surgery) */
28
+ family: ProcedureFamily;
29
+ /** Category this procedure belongs to */
30
+ category: Category;
31
+ /** Subcategory this procedure belongs to */
32
+ subcategory: Subcategory;
33
+ /** Technology used in this procedure */
34
+ technology: Technology;
35
+ /** Product used in this procedure */
36
+ product: Product;
37
+ /** Price of the procedure */
38
+ price: number;
39
+ /** Currency for the price */
40
+ currency: Currency;
41
+ /** How the price is measured (per ml, per zone, etc.) */
42
+ pricingMeasure: PricingMeasure;
43
+ /** Duration of the procedure in minutes */
44
+ duration: number;
45
+ /** Blocking conditions that prevent this procedure */
46
+ blockingConditions: BlockingCondition[];
47
+ /** Treatment benefits of this procedure */
48
+ treatmentBenefits: TreatmentBenefit[];
49
+ /** Pre-procedure requirements */
50
+ preRequirements: Requirement[];
51
+ /** Post-procedure requirements */
52
+ postRequirements: Requirement[];
53
+ /** Certification requirements for performing this procedure */
54
+ certificationRequirement: CertificationRequirement;
55
+ /** Documentation templates required for this procedure */
56
+ documentationTemplates: DocumentTemplate[];
57
+ /** ID of the practitioner who performs this procedure */
58
+ practitionerId: string;
59
+ /** ID of the clinic branch where this procedure is performed */
60
+ clinicBranchId: string;
61
+ /** Whether this procedure is active */
62
+ isActive: boolean;
63
+ /** When this procedure was created */
64
+ createdAt: Date;
65
+ /** When this procedure was last updated */
66
+ updatedAt: Date;
67
+ }
68
+
69
+ /**
70
+ * Data required to create a new procedure
71
+ */
72
+ export interface CreateProcedureData {
73
+ name: string;
74
+ description: string;
75
+ family: ProcedureFamily;
76
+ categoryId: string;
77
+ subcategoryId: string;
78
+ technologyId: string;
79
+ productId: string;
80
+ price: number;
81
+ currency: Currency;
82
+ pricingMeasure: PricingMeasure;
83
+ duration: number;
84
+ practitionerId: string;
85
+ clinicBranchId: string;
86
+ }
87
+
88
+ /**
89
+ * Data that can be updated for an existing procedure
90
+ */
91
+ export interface UpdateProcedureData {
92
+ name?: string;
93
+ description?: string;
94
+ price?: number;
95
+ currency?: Currency;
96
+ pricingMeasure?: PricingMeasure;
97
+ duration?: number;
98
+ isActive?: boolean;
99
+ }
100
+
101
+ /**
102
+ * Collection name for procedures in Firestore
103
+ */
104
+ export const PROCEDURES_COLLECTION = "procedures";
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import { ProcedureFamily } from "../backoffice/types/static/procedure-family.types";
3
+ import {
4
+ Currency,
5
+ PricingMeasure,
6
+ } from "../backoffice/types/static/pricing.types";
7
+
8
+ /**
9
+ * Schema for creating a new procedure
10
+ */
11
+ export const createProcedureSchema = z.object({
12
+ name: z.string().min(1).max(200),
13
+ description: z.string().min(1).max(2000),
14
+ family: z.nativeEnum(ProcedureFamily),
15
+ categoryId: z.string().min(1),
16
+ subcategoryId: z.string().min(1),
17
+ technologyId: z.string().min(1),
18
+ productId: z.string().min(1),
19
+ price: z.number().min(0),
20
+ currency: z.nativeEnum(Currency),
21
+ pricingMeasure: z.nativeEnum(PricingMeasure),
22
+ duration: z.number().min(1).max(480), // Max 8 hours
23
+ practitionerId: z.string().min(1),
24
+ clinicBranchId: z.string().min(1),
25
+ });
26
+
27
+ /**
28
+ * Schema for updating an existing procedure
29
+ */
30
+ export const updateProcedureSchema = z.object({
31
+ name: z.string().min(1).max(200).optional(),
32
+ description: z.string().min(1).max(2000).optional(),
33
+ price: z.number().min(0).optional(),
34
+ currency: z.nativeEnum(Currency).optional(),
35
+ pricingMeasure: z.nativeEnum(PricingMeasure).optional(),
36
+ duration: z.number().min(1).max(480).optional(), // Max 8 hours
37
+ isActive: z.boolean().optional(),
38
+ });
39
+
40
+ /**
41
+ * Schema for validating a complete procedure object
42
+ */
43
+ export const procedureSchema = createProcedureSchema.extend({
44
+ id: z.string().min(1),
45
+ category: z.any(), // We'll validate the full category object separately
46
+ subcategory: z.any(), // We'll validate the full subcategory object separately
47
+ technology: z.any(), // We'll validate the full technology object separately
48
+ product: z.any(), // We'll validate the full product object separately
49
+ blockingConditions: z.array(z.any()), // We'll validate blocking conditions separately
50
+ treatmentBenefits: z.array(z.any()), // We'll validate treatment benefits separately
51
+ preRequirements: z.array(z.any()), // We'll validate requirements separately
52
+ postRequirements: z.array(z.any()), // We'll validate requirements separately
53
+ certificationRequirement: z.any(), // We'll validate certification requirement separately
54
+ documentationTemplates: z.array(z.any()), // We'll validate documentation templates separately
55
+ isActive: z.boolean(),
56
+ createdAt: z.date(),
57
+ updatedAt: z.date(),
58
+ });