@blackcode_sa/metaestetics-api 1.7.43 → 1.7.45

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/index.d.mts CHANGED
@@ -5936,9 +5936,9 @@ interface User {
5936
5936
  email: string | null;
5937
5937
  roles: UserRole[];
5938
5938
  isAnonymous: boolean;
5939
- createdAt: Timestamp | FieldValue | Date;
5940
- updatedAt: Timestamp | FieldValue | Date;
5941
- lastLoginAt: Timestamp | FieldValue | Date;
5939
+ createdAt: any;
5940
+ updatedAt: any;
5941
+ lastLoginAt: any;
5942
5942
  patientProfile?: string;
5943
5943
  practitionerProfile?: string;
5944
5944
  adminProfile?: string;
@@ -6391,6 +6391,15 @@ declare class ProcedureService extends BaseService {
6391
6391
  * @returns The created procedure
6392
6392
  */
6393
6393
  createProcedure(data: CreateProcedureData): Promise<Procedure>;
6394
+ /**
6395
+ * Creates multiple procedures for a list of practitioners based on common data.
6396
+ * This method is optimized for bulk creation to reduce database reads and writes.
6397
+ *
6398
+ * @param baseData - The base data for the procedures to be created, omitting the practitionerId.
6399
+ * @param practitionerIds - An array of practitioner IDs for whom the procedures will be created.
6400
+ * @returns A promise that resolves to an array of the newly created procedures.
6401
+ */
6402
+ bulkCreateProcedures(baseData: Omit<CreateProcedureData, "practitionerId">, practitionerIds: string[]): Promise<Procedure[]>;
6394
6403
  /**
6395
6404
  * Gets a procedure by ID
6396
6405
  * @param id - The ID of the procedure to get
@@ -8627,31 +8636,31 @@ declare const userSchema: z.ZodObject<{
8627
8636
  email: z.ZodNullable<z.ZodString>;
8628
8637
  roles: z.ZodArray<z.ZodNativeEnum<typeof UserRole>, "many">;
8629
8638
  isAnonymous: z.ZodBoolean;
8630
- createdAt: z.ZodType<Date | Timestamp | FieldValue, z.ZodTypeDef, Date | Timestamp | FieldValue>;
8631
- updatedAt: z.ZodType<Date | Timestamp | FieldValue, z.ZodTypeDef, Date | Timestamp | FieldValue>;
8632
- lastLoginAt: z.ZodType<Date | Timestamp | FieldValue, z.ZodTypeDef, Date | Timestamp | FieldValue>;
8639
+ createdAt: z.ZodAny;
8640
+ updatedAt: z.ZodAny;
8641
+ lastLoginAt: z.ZodAny;
8633
8642
  patientProfile: z.ZodOptional<z.ZodString>;
8634
8643
  practitionerProfile: z.ZodOptional<z.ZodString>;
8635
8644
  adminProfile: z.ZodOptional<z.ZodString>;
8636
8645
  }, "strip", z.ZodTypeAny, {
8637
- createdAt: Date | Timestamp | FieldValue;
8638
- updatedAt: Date | Timestamp | FieldValue;
8639
8646
  email: string | null;
8640
8647
  uid: string;
8641
8648
  roles: UserRole[];
8642
8649
  isAnonymous: boolean;
8643
- lastLoginAt: Date | Timestamp | FieldValue;
8650
+ createdAt?: any;
8651
+ updatedAt?: any;
8652
+ lastLoginAt?: any;
8644
8653
  patientProfile?: string | undefined;
8645
8654
  practitionerProfile?: string | undefined;
8646
8655
  adminProfile?: string | undefined;
8647
8656
  }, {
8648
- createdAt: Date | Timestamp | FieldValue;
8649
- updatedAt: Date | Timestamp | FieldValue;
8650
8657
  email: string | null;
8651
8658
  uid: string;
8652
8659
  roles: UserRole[];
8653
8660
  isAnonymous: boolean;
8654
- lastLoginAt: Date | Timestamp | FieldValue;
8661
+ createdAt?: any;
8662
+ updatedAt?: any;
8663
+ lastLoginAt?: any;
8655
8664
  patientProfile?: string | undefined;
8656
8665
  practitionerProfile?: string | undefined;
8657
8666
  adminProfile?: string | undefined;
package/dist/index.d.ts CHANGED
@@ -5936,9 +5936,9 @@ interface User {
5936
5936
  email: string | null;
5937
5937
  roles: UserRole[];
5938
5938
  isAnonymous: boolean;
5939
- createdAt: Timestamp | FieldValue | Date;
5940
- updatedAt: Timestamp | FieldValue | Date;
5941
- lastLoginAt: Timestamp | FieldValue | Date;
5939
+ createdAt: any;
5940
+ updatedAt: any;
5941
+ lastLoginAt: any;
5942
5942
  patientProfile?: string;
5943
5943
  practitionerProfile?: string;
5944
5944
  adminProfile?: string;
@@ -6391,6 +6391,15 @@ declare class ProcedureService extends BaseService {
6391
6391
  * @returns The created procedure
6392
6392
  */
6393
6393
  createProcedure(data: CreateProcedureData): Promise<Procedure>;
6394
+ /**
6395
+ * Creates multiple procedures for a list of practitioners based on common data.
6396
+ * This method is optimized for bulk creation to reduce database reads and writes.
6397
+ *
6398
+ * @param baseData - The base data for the procedures to be created, omitting the practitionerId.
6399
+ * @param practitionerIds - An array of practitioner IDs for whom the procedures will be created.
6400
+ * @returns A promise that resolves to an array of the newly created procedures.
6401
+ */
6402
+ bulkCreateProcedures(baseData: Omit<CreateProcedureData, "practitionerId">, practitionerIds: string[]): Promise<Procedure[]>;
6394
6403
  /**
6395
6404
  * Gets a procedure by ID
6396
6405
  * @param id - The ID of the procedure to get
@@ -8627,31 +8636,31 @@ declare const userSchema: z.ZodObject<{
8627
8636
  email: z.ZodNullable<z.ZodString>;
8628
8637
  roles: z.ZodArray<z.ZodNativeEnum<typeof UserRole>, "many">;
8629
8638
  isAnonymous: z.ZodBoolean;
8630
- createdAt: z.ZodType<Date | Timestamp | FieldValue, z.ZodTypeDef, Date | Timestamp | FieldValue>;
8631
- updatedAt: z.ZodType<Date | Timestamp | FieldValue, z.ZodTypeDef, Date | Timestamp | FieldValue>;
8632
- lastLoginAt: z.ZodType<Date | Timestamp | FieldValue, z.ZodTypeDef, Date | Timestamp | FieldValue>;
8639
+ createdAt: z.ZodAny;
8640
+ updatedAt: z.ZodAny;
8641
+ lastLoginAt: z.ZodAny;
8633
8642
  patientProfile: z.ZodOptional<z.ZodString>;
8634
8643
  practitionerProfile: z.ZodOptional<z.ZodString>;
8635
8644
  adminProfile: z.ZodOptional<z.ZodString>;
8636
8645
  }, "strip", z.ZodTypeAny, {
8637
- createdAt: Date | Timestamp | FieldValue;
8638
- updatedAt: Date | Timestamp | FieldValue;
8639
8646
  email: string | null;
8640
8647
  uid: string;
8641
8648
  roles: UserRole[];
8642
8649
  isAnonymous: boolean;
8643
- lastLoginAt: Date | Timestamp | FieldValue;
8650
+ createdAt?: any;
8651
+ updatedAt?: any;
8652
+ lastLoginAt?: any;
8644
8653
  patientProfile?: string | undefined;
8645
8654
  practitionerProfile?: string | undefined;
8646
8655
  adminProfile?: string | undefined;
8647
8656
  }, {
8648
- createdAt: Date | Timestamp | FieldValue;
8649
- updatedAt: Date | Timestamp | FieldValue;
8650
8657
  email: string | null;
8651
8658
  uid: string;
8652
8659
  roles: UserRole[];
8653
8660
  isAnonymous: boolean;
8654
- lastLoginAt: Date | Timestamp | FieldValue;
8661
+ createdAt?: any;
8662
+ updatedAt?: any;
8663
+ lastLoginAt?: any;
8655
8664
  patientProfile?: string | undefined;
8656
8665
  practitionerProfile?: string | undefined;
8657
8666
  adminProfile?: string | undefined;
package/dist/index.js CHANGED
@@ -923,9 +923,9 @@ var userSchema = import_zod4.z.object({
923
923
  email: import_zod4.z.string().email().nullable(),
924
924
  roles: import_zod4.z.array(userRoleSchema),
925
925
  isAnonymous: import_zod4.z.boolean(),
926
- createdAt: timestampSchema,
927
- updatedAt: timestampSchema,
928
- lastLoginAt: timestampSchema,
926
+ createdAt: import_zod4.z.any(),
927
+ updatedAt: import_zod4.z.any(),
928
+ lastLoginAt: import_zod4.z.any(),
929
929
  patientProfile: import_zod4.z.string().optional(),
930
930
  practitionerProfile: import_zod4.z.string().optional(),
931
931
  adminProfile: import_zod4.z.string().optional()
@@ -5594,9 +5594,6 @@ var PractitionerService = class extends BaseService {
5594
5594
  if (!practitioner) {
5595
5595
  throw new Error(`Practitioner ${practitionerId} not found`);
5596
5596
  }
5597
- if (!practitioner.isActive) {
5598
- throw new Error(`Practitioner ${practitionerId} is not active`);
5599
- }
5600
5597
  const clinic = await this.getClinicService().getClinic(clinicId);
5601
5598
  if (!clinic) {
5602
5599
  throw new Error(`Clinic ${clinicId} not found`);
@@ -5955,7 +5952,7 @@ var UserService = class extends BaseService {
5955
5952
  const q = (0, import_firestore19.query)((0, import_firestore19.collection)(this.db, USERS_COLLECTION), ...constraints);
5956
5953
  const querySnapshot = await (0, import_firestore19.getDocs)(q);
5957
5954
  const users = querySnapshot.docs.map((doc36) => doc36.data());
5958
- return Promise.all(users.map((userData) => userSchema.parse(userData)));
5955
+ return users.map((userData) => userSchema.parse(userData));
5959
5956
  }
5960
5957
  /**
5961
5958
  * Ažurira timestamp poslednjeg logovanja
@@ -9170,6 +9167,149 @@ var ProcedureService = class extends BaseService {
9170
9167
  const savedDoc = await (0, import_firestore27.getDoc)(procedureRef);
9171
9168
  return savedDoc.data();
9172
9169
  }
9170
+ /**
9171
+ * Creates multiple procedures for a list of practitioners based on common data.
9172
+ * This method is optimized for bulk creation to reduce database reads and writes.
9173
+ *
9174
+ * @param baseData - The base data for the procedures to be created, omitting the practitionerId.
9175
+ * @param practitionerIds - An array of practitioner IDs for whom the procedures will be created.
9176
+ * @returns A promise that resolves to an array of the newly created procedures.
9177
+ */
9178
+ async bulkCreateProcedures(baseData, practitionerIds) {
9179
+ var _a;
9180
+ if (!practitionerIds || practitionerIds.length === 0) {
9181
+ throw new Error("Practitioner IDs array cannot be empty.");
9182
+ }
9183
+ const validationData = { ...baseData, practitionerId: practitionerIds[0] };
9184
+ const validatedData = createProcedureSchema.parse(validationData);
9185
+ const [category, subcategory, technology, product, clinicSnapshot] = await Promise.all([
9186
+ this.categoryService.getById(validatedData.categoryId),
9187
+ this.subcategoryService.getById(
9188
+ validatedData.categoryId,
9189
+ validatedData.subcategoryId
9190
+ ),
9191
+ this.technologyService.getById(validatedData.technologyId),
9192
+ this.productService.getById(
9193
+ validatedData.technologyId,
9194
+ validatedData.productId
9195
+ ),
9196
+ (0, import_firestore27.getDoc)((0, import_firestore27.doc)(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId))
9197
+ ]);
9198
+ if (!category || !subcategory || !technology || !product) {
9199
+ throw new Error("One or more required base entities not found");
9200
+ }
9201
+ if (!clinicSnapshot.exists()) {
9202
+ throw new Error(
9203
+ `Clinic with ID ${validatedData.clinicBranchId} not found`
9204
+ );
9205
+ }
9206
+ const clinic = clinicSnapshot.data();
9207
+ let processedPhotos = [];
9208
+ if (validatedData.photos && validatedData.photos.length > 0) {
9209
+ const batchId = this.generateId();
9210
+ processedPhotos = await this.processMediaArray(
9211
+ validatedData.photos,
9212
+ batchId,
9213
+ "procedure-photos-batch"
9214
+ );
9215
+ }
9216
+ const practitionersMap = /* @__PURE__ */ new Map();
9217
+ for (let i = 0; i < practitionerIds.length; i += 30) {
9218
+ const chunk = practitionerIds.slice(i, i + 30);
9219
+ const practitionersQuery = (0, import_firestore27.query)(
9220
+ (0, import_firestore27.collection)(this.db, PRACTITIONERS_COLLECTION),
9221
+ (0, import_firestore27.where)((0, import_firestore27.documentId)(), "in", chunk)
9222
+ );
9223
+ const practitionersSnapshot = await (0, import_firestore27.getDocs)(practitionersQuery);
9224
+ practitionersSnapshot.docs.forEach((doc36) => {
9225
+ practitionersMap.set(doc36.id, doc36.data());
9226
+ });
9227
+ }
9228
+ if (practitionersMap.size !== practitionerIds.length) {
9229
+ const foundIds = Array.from(practitionersMap.keys());
9230
+ const notFoundIds = practitionerIds.filter(
9231
+ (id) => !foundIds.includes(id)
9232
+ );
9233
+ throw new Error(
9234
+ `The following practitioners were not found: ${notFoundIds.join(", ")}`
9235
+ );
9236
+ }
9237
+ const batch = (0, import_firestore27.writeBatch)(this.db);
9238
+ const createdProcedureIds = [];
9239
+ const clinicInfo = {
9240
+ id: clinicSnapshot.id,
9241
+ name: clinic.name,
9242
+ description: clinic.description || "",
9243
+ featuredPhoto: clinic.featuredPhotos && clinic.featuredPhotos.length > 0 ? typeof clinic.featuredPhotos[0] === "string" ? clinic.featuredPhotos[0] : "" : typeof clinic.coverPhoto === "string" ? clinic.coverPhoto : "",
9244
+ location: clinic.location,
9245
+ contactInfo: clinic.contactInfo
9246
+ };
9247
+ for (const practitionerId of practitionerIds) {
9248
+ const practitioner = practitionersMap.get(practitionerId);
9249
+ const doctorInfo = {
9250
+ id: practitioner.id,
9251
+ name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
9252
+ description: practitioner.basicInfo.bio || "",
9253
+ photo: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : "",
9254
+ rating: ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
9255
+ services: practitioner.procedures || []
9256
+ };
9257
+ const procedureId = this.generateId();
9258
+ createdProcedureIds.push(procedureId);
9259
+ const procedureRef = (0, import_firestore27.doc)(this.db, PROCEDURES_COLLECTION, procedureId);
9260
+ const newProcedure = {
9261
+ id: procedureId,
9262
+ ...validatedData,
9263
+ practitionerId,
9264
+ // Override practitionerId with the correct one
9265
+ photos: processedPhotos,
9266
+ category,
9267
+ subcategory,
9268
+ technology,
9269
+ product,
9270
+ blockingConditions: technology.blockingConditions,
9271
+ contraindications: technology.contraindications || [],
9272
+ treatmentBenefits: technology.benefits,
9273
+ preRequirements: technology.requirements.pre,
9274
+ postRequirements: technology.requirements.post,
9275
+ certificationRequirement: technology.certificationRequirement,
9276
+ documentationTemplates: (technology == null ? void 0 : technology.documentationTemplates) || [],
9277
+ clinicInfo,
9278
+ doctorInfo,
9279
+ // Set specific doctor info
9280
+ reviewInfo: {
9281
+ totalReviews: 0,
9282
+ averageRating: 0,
9283
+ effectivenessOfTreatment: 0,
9284
+ outcomeExplanation: 0,
9285
+ painManagement: 0,
9286
+ followUpCare: 0,
9287
+ valueForMoney: 0,
9288
+ recommendationPercentage: 0
9289
+ },
9290
+ isActive: true
9291
+ };
9292
+ batch.set(procedureRef, {
9293
+ ...newProcedure,
9294
+ createdAt: (0, import_firestore27.serverTimestamp)(),
9295
+ updatedAt: (0, import_firestore27.serverTimestamp)()
9296
+ });
9297
+ }
9298
+ await batch.commit();
9299
+ const fetchedProcedures = [];
9300
+ for (let i = 0; i < createdProcedureIds.length; i += 30) {
9301
+ const chunk = createdProcedureIds.slice(i, i + 30);
9302
+ const q = (0, import_firestore27.query)(
9303
+ (0, import_firestore27.collection)(this.db, PROCEDURES_COLLECTION),
9304
+ (0, import_firestore27.where)((0, import_firestore27.documentId)(), "in", chunk)
9305
+ );
9306
+ const snapshot = await (0, import_firestore27.getDocs)(q);
9307
+ snapshot.forEach((doc36) => {
9308
+ fetchedProcedures.push(doc36.data());
9309
+ });
9310
+ }
9311
+ return fetchedProcedures;
9312
+ }
9173
9313
  /**
9174
9314
  * Gets a procedure by ID
9175
9315
  * @param id - The ID of the procedure to get
package/dist/index.mjs CHANGED
@@ -698,9 +698,9 @@ var userSchema = z4.object({
698
698
  email: z4.string().email().nullable(),
699
699
  roles: z4.array(userRoleSchema),
700
700
  isAnonymous: z4.boolean(),
701
- createdAt: timestampSchema,
702
- updatedAt: timestampSchema,
703
- lastLoginAt: timestampSchema,
701
+ createdAt: z4.any(),
702
+ updatedAt: z4.any(),
703
+ lastLoginAt: z4.any(),
704
704
  patientProfile: z4.string().optional(),
705
705
  practitionerProfile: z4.string().optional(),
706
706
  adminProfile: z4.string().optional()
@@ -5499,9 +5499,6 @@ var PractitionerService = class extends BaseService {
5499
5499
  if (!practitioner) {
5500
5500
  throw new Error(`Practitioner ${practitionerId} not found`);
5501
5501
  }
5502
- if (!practitioner.isActive) {
5503
- throw new Error(`Practitioner ${practitionerId} is not active`);
5504
- }
5505
5502
  const clinic = await this.getClinicService().getClinic(clinicId);
5506
5503
  if (!clinic) {
5507
5504
  throw new Error(`Clinic ${clinicId} not found`);
@@ -5860,7 +5857,7 @@ var UserService = class extends BaseService {
5860
5857
  const q = query8(collection8(this.db, USERS_COLLECTION), ...constraints);
5861
5858
  const querySnapshot = await getDocs8(q);
5862
5859
  const users = querySnapshot.docs.map((doc36) => doc36.data());
5863
- return Promise.all(users.map((userData) => userSchema.parse(userData)));
5860
+ return users.map((userData) => userSchema.parse(userData));
5864
5861
  }
5865
5862
  /**
5866
5863
  * Ažurira timestamp poslednjeg logovanja
@@ -8903,9 +8900,11 @@ import {
8903
8900
  setDoc as setDoc14,
8904
8901
  deleteDoc as deleteDoc10,
8905
8902
  serverTimestamp as serverTimestamp13,
8903
+ writeBatch as writeBatch6,
8906
8904
  orderBy as orderBy6,
8907
8905
  limit as limit9,
8908
- startAfter as startAfter8
8906
+ startAfter as startAfter8,
8907
+ documentId as documentId2
8909
8908
  } from "firebase/firestore";
8910
8909
 
8911
8910
  // src/types/procedure/index.ts
@@ -9156,6 +9155,149 @@ var ProcedureService = class extends BaseService {
9156
9155
  const savedDoc = await getDoc19(procedureRef);
9157
9156
  return savedDoc.data();
9158
9157
  }
9158
+ /**
9159
+ * Creates multiple procedures for a list of practitioners based on common data.
9160
+ * This method is optimized for bulk creation to reduce database reads and writes.
9161
+ *
9162
+ * @param baseData - The base data for the procedures to be created, omitting the practitionerId.
9163
+ * @param practitionerIds - An array of practitioner IDs for whom the procedures will be created.
9164
+ * @returns A promise that resolves to an array of the newly created procedures.
9165
+ */
9166
+ async bulkCreateProcedures(baseData, practitionerIds) {
9167
+ var _a;
9168
+ if (!practitionerIds || practitionerIds.length === 0) {
9169
+ throw new Error("Practitioner IDs array cannot be empty.");
9170
+ }
9171
+ const validationData = { ...baseData, practitionerId: practitionerIds[0] };
9172
+ const validatedData = createProcedureSchema.parse(validationData);
9173
+ const [category, subcategory, technology, product, clinicSnapshot] = await Promise.all([
9174
+ this.categoryService.getById(validatedData.categoryId),
9175
+ this.subcategoryService.getById(
9176
+ validatedData.categoryId,
9177
+ validatedData.subcategoryId
9178
+ ),
9179
+ this.technologyService.getById(validatedData.technologyId),
9180
+ this.productService.getById(
9181
+ validatedData.technologyId,
9182
+ validatedData.productId
9183
+ ),
9184
+ getDoc19(doc16(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId))
9185
+ ]);
9186
+ if (!category || !subcategory || !technology || !product) {
9187
+ throw new Error("One or more required base entities not found");
9188
+ }
9189
+ if (!clinicSnapshot.exists()) {
9190
+ throw new Error(
9191
+ `Clinic with ID ${validatedData.clinicBranchId} not found`
9192
+ );
9193
+ }
9194
+ const clinic = clinicSnapshot.data();
9195
+ let processedPhotos = [];
9196
+ if (validatedData.photos && validatedData.photos.length > 0) {
9197
+ const batchId = this.generateId();
9198
+ processedPhotos = await this.processMediaArray(
9199
+ validatedData.photos,
9200
+ batchId,
9201
+ "procedure-photos-batch"
9202
+ );
9203
+ }
9204
+ const practitionersMap = /* @__PURE__ */ new Map();
9205
+ for (let i = 0; i < practitionerIds.length; i += 30) {
9206
+ const chunk = practitionerIds.slice(i, i + 30);
9207
+ const practitionersQuery = query16(
9208
+ collection16(this.db, PRACTITIONERS_COLLECTION),
9209
+ where16(documentId2(), "in", chunk)
9210
+ );
9211
+ const practitionersSnapshot = await getDocs16(practitionersQuery);
9212
+ practitionersSnapshot.docs.forEach((doc36) => {
9213
+ practitionersMap.set(doc36.id, doc36.data());
9214
+ });
9215
+ }
9216
+ if (practitionersMap.size !== practitionerIds.length) {
9217
+ const foundIds = Array.from(practitionersMap.keys());
9218
+ const notFoundIds = practitionerIds.filter(
9219
+ (id) => !foundIds.includes(id)
9220
+ );
9221
+ throw new Error(
9222
+ `The following practitioners were not found: ${notFoundIds.join(", ")}`
9223
+ );
9224
+ }
9225
+ const batch = writeBatch6(this.db);
9226
+ const createdProcedureIds = [];
9227
+ const clinicInfo = {
9228
+ id: clinicSnapshot.id,
9229
+ name: clinic.name,
9230
+ description: clinic.description || "",
9231
+ featuredPhoto: clinic.featuredPhotos && clinic.featuredPhotos.length > 0 ? typeof clinic.featuredPhotos[0] === "string" ? clinic.featuredPhotos[0] : "" : typeof clinic.coverPhoto === "string" ? clinic.coverPhoto : "",
9232
+ location: clinic.location,
9233
+ contactInfo: clinic.contactInfo
9234
+ };
9235
+ for (const practitionerId of practitionerIds) {
9236
+ const practitioner = practitionersMap.get(practitionerId);
9237
+ const doctorInfo = {
9238
+ id: practitioner.id,
9239
+ name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
9240
+ description: practitioner.basicInfo.bio || "",
9241
+ photo: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : "",
9242
+ rating: ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
9243
+ services: practitioner.procedures || []
9244
+ };
9245
+ const procedureId = this.generateId();
9246
+ createdProcedureIds.push(procedureId);
9247
+ const procedureRef = doc16(this.db, PROCEDURES_COLLECTION, procedureId);
9248
+ const newProcedure = {
9249
+ id: procedureId,
9250
+ ...validatedData,
9251
+ practitionerId,
9252
+ // Override practitionerId with the correct one
9253
+ photos: processedPhotos,
9254
+ category,
9255
+ subcategory,
9256
+ technology,
9257
+ product,
9258
+ blockingConditions: technology.blockingConditions,
9259
+ contraindications: technology.contraindications || [],
9260
+ treatmentBenefits: technology.benefits,
9261
+ preRequirements: technology.requirements.pre,
9262
+ postRequirements: technology.requirements.post,
9263
+ certificationRequirement: technology.certificationRequirement,
9264
+ documentationTemplates: (technology == null ? void 0 : technology.documentationTemplates) || [],
9265
+ clinicInfo,
9266
+ doctorInfo,
9267
+ // Set specific doctor info
9268
+ reviewInfo: {
9269
+ totalReviews: 0,
9270
+ averageRating: 0,
9271
+ effectivenessOfTreatment: 0,
9272
+ outcomeExplanation: 0,
9273
+ painManagement: 0,
9274
+ followUpCare: 0,
9275
+ valueForMoney: 0,
9276
+ recommendationPercentage: 0
9277
+ },
9278
+ isActive: true
9279
+ };
9280
+ batch.set(procedureRef, {
9281
+ ...newProcedure,
9282
+ createdAt: serverTimestamp13(),
9283
+ updatedAt: serverTimestamp13()
9284
+ });
9285
+ }
9286
+ await batch.commit();
9287
+ const fetchedProcedures = [];
9288
+ for (let i = 0; i < createdProcedureIds.length; i += 30) {
9289
+ const chunk = createdProcedureIds.slice(i, i + 30);
9290
+ const q = query16(
9291
+ collection16(this.db, PROCEDURES_COLLECTION),
9292
+ where16(documentId2(), "in", chunk)
9293
+ );
9294
+ const snapshot = await getDocs16(q);
9295
+ snapshot.forEach((doc36) => {
9296
+ fetchedProcedures.push(doc36.data());
9297
+ });
9298
+ }
9299
+ return fetchedProcedures;
9300
+ }
9159
9301
  /**
9160
9302
  * Gets a procedure by ID
9161
9303
  * @param id - The ID of the procedure to get
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.7.43",
4
+ "version": "1.7.45",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -1228,9 +1228,10 @@ export class PractitionerService extends BaseService {
1228
1228
  throw new Error(`Practitioner ${practitionerId} not found`);
1229
1229
  }
1230
1230
 
1231
- if (!practitioner.isActive) {
1232
- throw new Error(`Practitioner ${practitionerId} is not active`);
1233
- }
1231
+ // No need to check for is practitioner active
1232
+ // if (!practitioner.isActive) {
1233
+ // throw new Error(`Practitioner ${practitionerId} is not active`);
1234
+ // }
1234
1235
 
1235
1236
  // Validate that clinic exists
1236
1237
  const clinic = await this.getClinicService().getClinic(clinicId);
@@ -304,6 +304,187 @@ export class ProcedureService extends BaseService {
304
304
  return savedDoc.data() as Procedure;
305
305
  }
306
306
 
307
+ /**
308
+ * Creates multiple procedures for a list of practitioners based on common data.
309
+ * This method is optimized for bulk creation to reduce database reads and writes.
310
+ *
311
+ * @param baseData - The base data for the procedures to be created, omitting the practitionerId.
312
+ * @param practitionerIds - An array of practitioner IDs for whom the procedures will be created.
313
+ * @returns A promise that resolves to an array of the newly created procedures.
314
+ */
315
+ async bulkCreateProcedures(
316
+ baseData: Omit<CreateProcedureData, "practitionerId">,
317
+ practitionerIds: string[]
318
+ ): Promise<Procedure[]> {
319
+ // 1. Validation
320
+ if (!practitionerIds || practitionerIds.length === 0) {
321
+ throw new Error("Practitioner IDs array cannot be empty.");
322
+ }
323
+
324
+ // Add a dummy practitionerId for the validation schema to pass
325
+ const validationData = { ...baseData, practitionerId: practitionerIds[0] };
326
+ const validatedData = createProcedureSchema.parse(validationData);
327
+
328
+ // 2. Fetch common data once to avoid redundant reads
329
+ const [category, subcategory, technology, product, clinicSnapshot] =
330
+ await Promise.all([
331
+ this.categoryService.getById(validatedData.categoryId),
332
+ this.subcategoryService.getById(
333
+ validatedData.categoryId,
334
+ validatedData.subcategoryId
335
+ ),
336
+ this.technologyService.getById(validatedData.technologyId),
337
+ this.productService.getById(
338
+ validatedData.technologyId,
339
+ validatedData.productId
340
+ ),
341
+ getDoc(doc(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId)),
342
+ ]);
343
+
344
+ if (!category || !subcategory || !technology || !product) {
345
+ throw new Error("One or more required base entities not found");
346
+ }
347
+ if (!clinicSnapshot.exists()) {
348
+ throw new Error(
349
+ `Clinic with ID ${validatedData.clinicBranchId} not found`
350
+ );
351
+ }
352
+ const clinic = clinicSnapshot.data() as Clinic;
353
+
354
+ // 3. Handle media uploads once for efficiency
355
+ let processedPhotos: string[] = [];
356
+ if (validatedData.photos && validatedData.photos.length > 0) {
357
+ const batchId = this.generateId(); // Use a single ID for all media in this batch
358
+ processedPhotos = await this.processMediaArray(
359
+ validatedData.photos,
360
+ batchId,
361
+ "procedure-photos-batch"
362
+ );
363
+ }
364
+
365
+ // 4. Fetch all practitioner data efficiently
366
+ const practitionersMap = new Map<string, Practitioner>();
367
+ // Use 'in' query in chunks of 30, as this is the Firestore limit
368
+ for (let i = 0; i < practitionerIds.length; i += 30) {
369
+ const chunk = practitionerIds.slice(i, i + 30);
370
+ const practitionersQuery = query(
371
+ collection(this.db, PRACTITIONERS_COLLECTION),
372
+ where(documentId(), "in", chunk)
373
+ );
374
+ const practitionersSnapshot = await getDocs(practitionersQuery);
375
+ practitionersSnapshot.docs.forEach((doc) => {
376
+ practitionersMap.set(doc.id, doc.data() as Practitioner);
377
+ });
378
+ }
379
+
380
+ // Verify all practitioners were found
381
+ if (practitionersMap.size !== practitionerIds.length) {
382
+ const foundIds = Array.from(practitionersMap.keys());
383
+ const notFoundIds = practitionerIds.filter(
384
+ (id) => !foundIds.includes(id)
385
+ );
386
+ throw new Error(
387
+ `The following practitioners were not found: ${notFoundIds.join(", ")}`
388
+ );
389
+ }
390
+
391
+ // 5. Use a Firestore batch for atomic creation
392
+ const batch = writeBatch(this.db);
393
+ const createdProcedureIds: string[] = [];
394
+ const clinicInfo = {
395
+ id: clinicSnapshot.id,
396
+ name: clinic.name,
397
+ description: clinic.description || "",
398
+ featuredPhoto:
399
+ clinic.featuredPhotos && clinic.featuredPhotos.length > 0
400
+ ? typeof clinic.featuredPhotos[0] === "string"
401
+ ? clinic.featuredPhotos[0]
402
+ : ""
403
+ : typeof clinic.coverPhoto === "string"
404
+ ? clinic.coverPhoto
405
+ : "",
406
+ location: clinic.location,
407
+ contactInfo: clinic.contactInfo,
408
+ };
409
+
410
+ for (const practitionerId of practitionerIds) {
411
+ const practitioner = practitionersMap.get(practitionerId)!;
412
+
413
+ const doctorInfo = {
414
+ id: practitioner.id,
415
+ name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
416
+ description: practitioner.basicInfo.bio || "",
417
+ photo:
418
+ typeof practitioner.basicInfo.profileImageUrl === "string"
419
+ ? practitioner.basicInfo.profileImageUrl
420
+ : "",
421
+ rating: practitioner.reviewInfo?.averageRating || 0,
422
+ services: practitioner.procedures || [],
423
+ };
424
+
425
+ const procedureId = this.generateId();
426
+ createdProcedureIds.push(procedureId);
427
+ const procedureRef = doc(this.db, PROCEDURES_COLLECTION, procedureId);
428
+
429
+ // Construct the new procedure, reusing common data
430
+ const newProcedure: Omit<Procedure, "createdAt" | "updatedAt"> = {
431
+ id: procedureId,
432
+ ...validatedData,
433
+ practitionerId: practitionerId, // Override practitionerId with the correct one
434
+ photos: processedPhotos,
435
+ category,
436
+ subcategory,
437
+ technology,
438
+ product,
439
+ blockingConditions: technology.blockingConditions,
440
+ contraindications: technology.contraindications || [],
441
+ treatmentBenefits: technology.benefits,
442
+ preRequirements: technology.requirements.pre,
443
+ postRequirements: technology.requirements.post,
444
+ certificationRequirement: technology.certificationRequirement,
445
+ documentationTemplates: technology?.documentationTemplates || [],
446
+ clinicInfo,
447
+ doctorInfo, // Set specific doctor info
448
+ reviewInfo: {
449
+ totalReviews: 0,
450
+ averageRating: 0,
451
+ effectivenessOfTreatment: 0,
452
+ outcomeExplanation: 0,
453
+ painManagement: 0,
454
+ followUpCare: 0,
455
+ valueForMoney: 0,
456
+ recommendationPercentage: 0,
457
+ },
458
+ isActive: true,
459
+ };
460
+
461
+ batch.set(procedureRef, {
462
+ ...newProcedure,
463
+ createdAt: serverTimestamp(),
464
+ updatedAt: serverTimestamp(),
465
+ });
466
+ }
467
+
468
+ // 6. Commit the atomic batch write
469
+ await batch.commit();
470
+
471
+ // 7. Fetch and return the newly created procedures
472
+ const fetchedProcedures: Procedure[] = [];
473
+ for (let i = 0; i < createdProcedureIds.length; i += 30) {
474
+ const chunk = createdProcedureIds.slice(i, i + 30);
475
+ const q = query(
476
+ collection(this.db, PROCEDURES_COLLECTION),
477
+ where(documentId(), "in", chunk)
478
+ );
479
+ const snapshot = await getDocs(q);
480
+ snapshot.forEach((doc) => {
481
+ fetchedProcedures.push(doc.data() as Procedure);
482
+ });
483
+ }
484
+
485
+ return fetchedProcedures;
486
+ }
487
+
307
488
  /**
308
489
  * Gets a procedure by ID
309
490
  * @param id - The ID of the procedure to get
@@ -264,7 +264,7 @@ export class UserService extends BaseService {
264
264
  }
265
265
 
266
266
  const userData = userDoc.data();
267
- return userSchema.parse(userData);
267
+ return userSchema.parse(userData) as User;
268
268
  }
269
269
 
270
270
  /**
@@ -278,7 +278,7 @@ export class UserService extends BaseService {
278
278
  if (querySnapshot.empty) return null;
279
279
 
280
280
  const userData = querySnapshot.docs[0].data();
281
- return userSchema.parse(userData);
281
+ return userSchema.parse(userData) as User;
282
282
  }
283
283
 
284
284
  async getUsersByRole(role: UserRole): Promise<User[]> {
@@ -289,7 +289,7 @@ export class UserService extends BaseService {
289
289
  const querySnapshot = await getDocs(q);
290
290
 
291
291
  const users = querySnapshot.docs.map((doc) => doc.data());
292
- return Promise.all(users.map((userData) => userSchema.parse(userData)));
292
+ return users.map((userData) => userSchema.parse(userData) as User);
293
293
  }
294
294
 
295
295
  /**
@@ -14,9 +14,9 @@ export interface User {
14
14
  email: string | null;
15
15
  roles: UserRole[];
16
16
  isAnonymous: boolean;
17
- createdAt: Timestamp | FieldValue | Date;
18
- updatedAt: Timestamp | FieldValue | Date;
19
- lastLoginAt: Timestamp | FieldValue | Date;
17
+ createdAt: any;
18
+ updatedAt: any;
19
+ lastLoginAt: any;
20
20
  patientProfile?: string;
21
21
  practitionerProfile?: string;
22
22
  adminProfile?: string;
@@ -90,9 +90,9 @@ export const userSchema = z.object({
90
90
  email: z.string().email().nullable(),
91
91
  roles: z.array(userRoleSchema),
92
92
  isAnonymous: z.boolean(),
93
- createdAt: timestampSchema,
94
- updatedAt: timestampSchema,
95
- lastLoginAt: timestampSchema,
93
+ createdAt: z.any(),
94
+ updatedAt: z.any(),
95
+ lastLoginAt: z.any(),
96
96
  patientProfile: z.string().optional(),
97
97
  practitionerProfile: z.string().optional(),
98
98
  adminProfile: z.string().optional(),