@blackcode_sa/metaestetics-api 1.13.6 → 1.13.8

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
@@ -6693,7 +6693,15 @@ declare class ProcedureService extends BaseService {
6693
6693
  private technologyService;
6694
6694
  private productService;
6695
6695
  private mediaService;
6696
+ private practitionerService?;
6696
6697
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, categoryService: CategoryService, subcategoryService: SubcategoryService, technologyService: TechnologyService, productService: ProductService, mediaService: MediaService);
6698
+ setPractitionerService(practitionerService: PractitionerService): void;
6699
+ /**
6700
+ * Filters out procedures from draft practitioners
6701
+ * @param procedures - Array of procedures to filter
6702
+ * @returns Filtered array of procedures (excluding those from draft practitioners)
6703
+ */
6704
+ private filterDraftPractitionerProcedures;
6697
6705
  /**
6698
6706
  * Process media resource (string URL or File object)
6699
6707
  * @param media String URL or File object
@@ -6782,9 +6790,10 @@ declare class ProcedureService extends BaseService {
6782
6790
  * Gets all procedures for a practitioner
6783
6791
  * @param practitionerId - The ID of the practitioner
6784
6792
  * @param clinicBranchId - Optional clinic branch ID to filter by
6793
+ * @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
6785
6794
  * @returns List of procedures
6786
6795
  */
6787
- getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string): Promise<Procedure[]>;
6796
+ getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string, excludeDraftPractitioners?: boolean): Promise<Procedure[]>;
6788
6797
  /**
6789
6798
  * Gets all inactive procedures for a practitioner
6790
6799
  * @param practitionerId - The ID of the practitioner
@@ -6825,9 +6834,10 @@ declare class ProcedureService extends BaseService {
6825
6834
  *
6826
6835
  * @param pagination - Optional number of procedures per page (0 or undefined returns all)
6827
6836
  * @param lastDoc - Optional last document for pagination (if continuing from a previous page)
6837
+ * @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
6828
6838
  * @returns Object containing procedures array and the last document for pagination
6829
6839
  */
6830
- getAllProcedures(pagination?: number, lastDoc?: any): Promise<{
6840
+ getAllProcedures(pagination?: number, lastDoc?: any, excludeDraftPractitioners?: boolean): Promise<{
6831
6841
  procedures: Procedure[];
6832
6842
  lastDoc: any;
6833
6843
  }>;
@@ -6924,7 +6934,7 @@ declare class PractitionerService extends BaseService {
6924
6934
  private mediaService;
6925
6935
  private procedureService?;
6926
6936
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, clinicService?: ClinicService, procedureService?: ProcedureService);
6927
- private getClinicService;
6937
+ getClinicService(): ClinicService;
6928
6938
  private getProcedureService;
6929
6939
  setClinicService(clinicService: ClinicService): void;
6930
6940
  setProcedureService(procedureService: ProcedureService): void;
@@ -6992,6 +7002,19 @@ declare class PractitionerService extends BaseService {
6992
7002
  * Dohvata zdravstvenog radnika po User ID-u
6993
7003
  */
6994
7004
  getPractitionerByUserRef(userRef: string): Promise<Practitioner | null>;
7005
+ /**
7006
+ * Finds a draft practitioner profile by email address
7007
+ * Used to detect if a draft profile exists when a doctor registers without a token
7008
+ *
7009
+ * @param email - Email address to search for
7010
+ * @returns Draft practitioner profile if found, null otherwise
7011
+ *
7012
+ * @remarks
7013
+ * Requires Firestore composite index on:
7014
+ * - Collection: practitioners
7015
+ * - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
7016
+ */
7017
+ findDraftPractitionerByEmail(email: string): Promise<Practitioner | null>;
6995
7018
  /**
6996
7019
  * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
6997
7020
  */
package/dist/index.d.ts CHANGED
@@ -6693,7 +6693,15 @@ declare class ProcedureService extends BaseService {
6693
6693
  private technologyService;
6694
6694
  private productService;
6695
6695
  private mediaService;
6696
+ private practitionerService?;
6696
6697
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, categoryService: CategoryService, subcategoryService: SubcategoryService, technologyService: TechnologyService, productService: ProductService, mediaService: MediaService);
6698
+ setPractitionerService(practitionerService: PractitionerService): void;
6699
+ /**
6700
+ * Filters out procedures from draft practitioners
6701
+ * @param procedures - Array of procedures to filter
6702
+ * @returns Filtered array of procedures (excluding those from draft practitioners)
6703
+ */
6704
+ private filterDraftPractitionerProcedures;
6697
6705
  /**
6698
6706
  * Process media resource (string URL or File object)
6699
6707
  * @param media String URL or File object
@@ -6782,9 +6790,10 @@ declare class ProcedureService extends BaseService {
6782
6790
  * Gets all procedures for a practitioner
6783
6791
  * @param practitionerId - The ID of the practitioner
6784
6792
  * @param clinicBranchId - Optional clinic branch ID to filter by
6793
+ * @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
6785
6794
  * @returns List of procedures
6786
6795
  */
6787
- getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string): Promise<Procedure[]>;
6796
+ getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string, excludeDraftPractitioners?: boolean): Promise<Procedure[]>;
6788
6797
  /**
6789
6798
  * Gets all inactive procedures for a practitioner
6790
6799
  * @param practitionerId - The ID of the practitioner
@@ -6825,9 +6834,10 @@ declare class ProcedureService extends BaseService {
6825
6834
  *
6826
6835
  * @param pagination - Optional number of procedures per page (0 or undefined returns all)
6827
6836
  * @param lastDoc - Optional last document for pagination (if continuing from a previous page)
6837
+ * @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
6828
6838
  * @returns Object containing procedures array and the last document for pagination
6829
6839
  */
6830
- getAllProcedures(pagination?: number, lastDoc?: any): Promise<{
6840
+ getAllProcedures(pagination?: number, lastDoc?: any, excludeDraftPractitioners?: boolean): Promise<{
6831
6841
  procedures: Procedure[];
6832
6842
  lastDoc: any;
6833
6843
  }>;
@@ -6924,7 +6934,7 @@ declare class PractitionerService extends BaseService {
6924
6934
  private mediaService;
6925
6935
  private procedureService?;
6926
6936
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, clinicService?: ClinicService, procedureService?: ProcedureService);
6927
- private getClinicService;
6937
+ getClinicService(): ClinicService;
6928
6938
  private getProcedureService;
6929
6939
  setClinicService(clinicService: ClinicService): void;
6930
6940
  setProcedureService(procedureService: ProcedureService): void;
@@ -6992,6 +7002,19 @@ declare class PractitionerService extends BaseService {
6992
7002
  * Dohvata zdravstvenog radnika po User ID-u
6993
7003
  */
6994
7004
  getPractitionerByUserRef(userRef: string): Promise<Practitioner | null>;
7005
+ /**
7006
+ * Finds a draft practitioner profile by email address
7007
+ * Used to detect if a draft profile exists when a doctor registers without a token
7008
+ *
7009
+ * @param email - Email address to search for
7010
+ * @returns Draft practitioner profile if found, null otherwise
7011
+ *
7012
+ * @remarks
7013
+ * Requires Firestore composite index on:
7014
+ * - Collection: practitioners
7015
+ * - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
7016
+ */
7017
+ findDraftPractitionerByEmail(email: string): Promise<Practitioner | null>;
6995
7018
  /**
6996
7019
  * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
6997
7020
  */
package/dist/index.js CHANGED
@@ -7319,11 +7319,12 @@ var userSchema = import_zod4.z.object({
7319
7319
 
7320
7320
  // src/errors/auth.errors.ts
7321
7321
  var AuthError = class extends Error {
7322
- constructor(message, code, status = 400) {
7322
+ constructor(message, code, status = 400, metadata) {
7323
7323
  super(message);
7324
7324
  this.code = code;
7325
7325
  this.status = status;
7326
7326
  this.name = "AuthError";
7327
+ this.metadata = metadata;
7327
7328
  }
7328
7329
  };
7329
7330
  var AUTH_ERRORS = {
@@ -7502,6 +7503,12 @@ var AUTH_ERRORS = {
7502
7503
  "Lozinka je previ\u0161e slaba. Molimo koristite ja\u010Du lozinku.",
7503
7504
  "AUTH/WEAK_PASSWORD",
7504
7505
  400
7506
+ ),
7507
+ // Draft profile exists error
7508
+ DRAFT_PROFILE_EXISTS: new AuthError(
7509
+ "A draft practitioner profile exists for this email. Please use your invitation code to claim it, or contact support if you don't have one.",
7510
+ "AUTH/DRAFT_PROFILE_EXISTS",
7511
+ 409
7505
7512
  )
7506
7513
  };
7507
7514
 
@@ -11546,6 +11553,52 @@ var PractitionerService = class extends BaseService {
11546
11553
  }
11547
11554
  return querySnapshot.docs[0].data();
11548
11555
  }
11556
+ /**
11557
+ * Finds a draft practitioner profile by email address
11558
+ * Used to detect if a draft profile exists when a doctor registers without a token
11559
+ *
11560
+ * @param email - Email address to search for
11561
+ * @returns Draft practitioner profile if found, null otherwise
11562
+ *
11563
+ * @remarks
11564
+ * Requires Firestore composite index on:
11565
+ * - Collection: practitioners
11566
+ * - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
11567
+ */
11568
+ async findDraftPractitionerByEmail(email) {
11569
+ try {
11570
+ const normalizedEmail = email.toLowerCase().trim();
11571
+ console.log("[PRACTITIONER] Searching for draft practitioner by email", {
11572
+ email: normalizedEmail
11573
+ });
11574
+ const q = (0, import_firestore32.query)(
11575
+ (0, import_firestore32.collection)(this.db, PRACTITIONERS_COLLECTION),
11576
+ (0, import_firestore32.where)("basicInfo.email", "==", normalizedEmail),
11577
+ (0, import_firestore32.where)("status", "==", "draft" /* DRAFT */),
11578
+ (0, import_firestore32.where)("userRef", "==", ""),
11579
+ (0, import_firestore32.limit)(1)
11580
+ );
11581
+ const querySnapshot = await (0, import_firestore32.getDocs)(q);
11582
+ if (querySnapshot.empty) {
11583
+ console.log("[PRACTITIONER] No draft practitioner found for email", {
11584
+ email: normalizedEmail
11585
+ });
11586
+ return null;
11587
+ }
11588
+ const draftPractitioner = querySnapshot.docs[0].data();
11589
+ console.log("[PRACTITIONER] Draft practitioner found", {
11590
+ email: normalizedEmail,
11591
+ practitionerId: draftPractitioner.id
11592
+ });
11593
+ return draftPractitioner;
11594
+ } catch (error) {
11595
+ console.error(
11596
+ "[PRACTITIONER] Error finding draft practitioner by email:",
11597
+ error
11598
+ );
11599
+ return null;
11600
+ }
11601
+ }
11549
11602
  /**
11550
11603
  * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
11551
11604
  */
@@ -15290,7 +15343,49 @@ var AuthService = class extends BaseService {
15290
15343
  }
15291
15344
  practitioner = claimedPractitioner;
15292
15345
  } else {
15293
- console.log("[AUTH] Creating new practitioner profile");
15346
+ console.log("[AUTH] Checking for existing draft practitioner profile", {
15347
+ email: data.email
15348
+ });
15349
+ const draftPractitioner = await practitionerService.findDraftPractitionerByEmail(
15350
+ data.email
15351
+ );
15352
+ if (draftPractitioner) {
15353
+ console.log("[AUTH] Draft practitioner profile found", {
15354
+ practitionerId: draftPractitioner.id,
15355
+ email: data.email,
15356
+ clinics: draftPractitioner.clinics
15357
+ });
15358
+ let clinicNames = [];
15359
+ if (draftPractitioner.clinicsInfo && draftPractitioner.clinicsInfo.length > 0) {
15360
+ clinicNames = draftPractitioner.clinicsInfo.map((clinic) => clinic.name).filter((name) => !!name);
15361
+ } else if (draftPractitioner.clinics && draftPractitioner.clinics.length > 0) {
15362
+ console.log("[AUTH] clinicsInfo missing, fetching clinic names from clinic IDs");
15363
+ const clinicService = practitionerService.getClinicService();
15364
+ const clinicNamePromises = draftPractitioner.clinics.map(async (clinicId) => {
15365
+ try {
15366
+ const clinic = await clinicService.getClinic(clinicId);
15367
+ return (clinic == null ? void 0 : clinic.name) || null;
15368
+ } catch (error) {
15369
+ console.error(`[AUTH] Error fetching clinic ${clinicId}:`, error);
15370
+ return null;
15371
+ }
15372
+ });
15373
+ const names = await Promise.all(clinicNamePromises);
15374
+ clinicNames = names.filter((name) => !!name);
15375
+ }
15376
+ await cleanupFirebaseUser(firebaseUser);
15377
+ throw new AuthError(
15378
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.message,
15379
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.code,
15380
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.status,
15381
+ {
15382
+ clinicNames,
15383
+ clinics: draftPractitioner.clinics,
15384
+ clinicsInfo: draftPractitioner.clinicsInfo
15385
+ }
15386
+ );
15387
+ }
15388
+ console.log("[AUTH] No draft profile found, creating new practitioner profile");
15294
15389
  const practitionerData = buildPractitionerData(data, firebaseUser.uid);
15295
15390
  practitioner = await practitionerService.createPractitioner(practitionerData);
15296
15391
  }
@@ -19923,6 +20018,53 @@ var ProcedureService = class extends BaseService {
19923
20018
  this.productService = productService;
19924
20019
  this.mediaService = mediaService;
19925
20020
  }
20021
+ setPractitionerService(practitionerService) {
20022
+ this.practitionerService = practitionerService;
20023
+ }
20024
+ /**
20025
+ * Filters out procedures from draft practitioners
20026
+ * @param procedures - Array of procedures to filter
20027
+ * @returns Filtered array of procedures (excluding those from draft practitioners)
20028
+ */
20029
+ async filterDraftPractitionerProcedures(procedures) {
20030
+ if (!this.practitionerService || procedures.length === 0) {
20031
+ return procedures;
20032
+ }
20033
+ try {
20034
+ const practitionerIds = Array.from(
20035
+ new Set(procedures.map((p) => p.practitionerId).filter(Boolean))
20036
+ );
20037
+ if (practitionerIds.length === 0) {
20038
+ return procedures;
20039
+ }
20040
+ const practitionerPromises = practitionerIds.map(
20041
+ (id) => this.practitionerService.getPractitioner(id).catch(() => null)
20042
+ );
20043
+ const practitioners = await Promise.all(practitionerPromises);
20044
+ const practitionerStatusMap = /* @__PURE__ */ new Map();
20045
+ practitioners.forEach((practitioner, index) => {
20046
+ if (practitioner) {
20047
+ practitionerStatusMap.set(practitionerIds[index], practitioner.status);
20048
+ }
20049
+ });
20050
+ const filteredProcedures = procedures.filter((procedure) => {
20051
+ const practitionerStatus = practitionerStatusMap.get(procedure.practitionerId);
20052
+ return practitionerStatus !== "draft" /* DRAFT */;
20053
+ });
20054
+ if (filteredProcedures.length !== procedures.length) {
20055
+ console.log(
20056
+ `[ProcedureService] Filtered out ${procedures.length - filteredProcedures.length} procedures from draft practitioners`
20057
+ );
20058
+ }
20059
+ return filteredProcedures;
20060
+ } catch (error) {
20061
+ console.error(
20062
+ "[ProcedureService] Error filtering draft practitioner procedures:",
20063
+ error
20064
+ );
20065
+ return procedures;
20066
+ }
20067
+ }
19926
20068
  /**
19927
20069
  * Process media resource (string URL or File object)
19928
20070
  * @param media String URL or File object
@@ -20633,9 +20775,10 @@ var ProcedureService = class extends BaseService {
20633
20775
  * Gets all procedures for a practitioner
20634
20776
  * @param practitionerId - The ID of the practitioner
20635
20777
  * @param clinicBranchId - Optional clinic branch ID to filter by
20778
+ * @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
20636
20779
  * @returns List of procedures
20637
20780
  */
20638
- async getProceduresByPractitioner(practitionerId, clinicBranchId) {
20781
+ async getProceduresByPractitioner(practitionerId, clinicBranchId, excludeDraftPractitioners = true) {
20639
20782
  const constraints = [
20640
20783
  (0, import_firestore58.where)("practitionerId", "==", practitionerId),
20641
20784
  (0, import_firestore58.where)("isActive", "==", true)
@@ -20648,7 +20791,19 @@ var ProcedureService = class extends BaseService {
20648
20791
  ...constraints
20649
20792
  );
20650
20793
  const snapshot = await (0, import_firestore58.getDocs)(q);
20651
- return snapshot.docs.map((doc47) => doc47.data());
20794
+ const procedures = snapshot.docs.map((doc47) => doc47.data());
20795
+ if (excludeDraftPractitioners && this.practitionerService) {
20796
+ try {
20797
+ const practitioner = await this.practitionerService.getPractitioner(practitionerId);
20798
+ if (practitioner && practitioner.status === "draft" /* DRAFT */) {
20799
+ console.log(`[ProcedureService] Excluding procedures for draft practitioner ${practitionerId}`);
20800
+ return [];
20801
+ }
20802
+ } catch (error) {
20803
+ console.error(`[ProcedureService] Error checking practitioner status for ${practitionerId}:`, error);
20804
+ }
20805
+ }
20806
+ return procedures;
20652
20807
  }
20653
20808
  /**
20654
20809
  * Gets all inactive procedures for a practitioner
@@ -20857,9 +21012,10 @@ var ProcedureService = class extends BaseService {
20857
21012
  *
20858
21013
  * @param pagination - Optional number of procedures per page (0 or undefined returns all)
20859
21014
  * @param lastDoc - Optional last document for pagination (if continuing from a previous page)
21015
+ * @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
20860
21016
  * @returns Object containing procedures array and the last document for pagination
20861
21017
  */
20862
- async getAllProcedures(pagination, lastDoc) {
21018
+ async getAllProcedures(pagination, lastDoc, excludeDraftPractitioners = true) {
20863
21019
  try {
20864
21020
  const proceduresCollection = (0, import_firestore58.collection)(this.db, PROCEDURES_COLLECTION);
20865
21021
  let proceduresQuery = (0, import_firestore58.query)(proceduresCollection);
@@ -20881,7 +21037,7 @@ var ProcedureService = class extends BaseService {
20881
21037
  }
20882
21038
  const proceduresSnapshot = await (0, import_firestore58.getDocs)(proceduresQuery);
20883
21039
  const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
20884
- const procedures = proceduresSnapshot.docs.map((doc47) => {
21040
+ let procedures = proceduresSnapshot.docs.map((doc47) => {
20885
21041
  const data = doc47.data();
20886
21042
  return {
20887
21043
  ...data,
@@ -20889,6 +21045,9 @@ var ProcedureService = class extends BaseService {
20889
21045
  // Ensure ID is present
20890
21046
  };
20891
21047
  });
21048
+ if (excludeDraftPractitioners) {
21049
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21050
+ }
20892
21051
  return {
20893
21052
  procedures,
20894
21053
  lastDoc: lastVisible
@@ -21053,6 +21212,7 @@ var ProcedureService = class extends BaseService {
21053
21212
  if (hasNestedFilters) {
21054
21213
  procedures = this.applyInMemoryFilters(procedures, filters);
21055
21214
  }
21215
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21056
21216
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
21057
21217
  console.log(`[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`);
21058
21218
  if (procedures.length < (filters.pagination || 10)) {
@@ -21133,6 +21293,7 @@ var ProcedureService = class extends BaseService {
21133
21293
  }
21134
21294
  });
21135
21295
  procedures = this.applyInMemoryFilters(procedures, filters);
21296
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21136
21297
  console.log("[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):", {
21137
21298
  procedureCount: procedures.length
21138
21299
  });
@@ -21164,6 +21325,7 @@ var ProcedureService = class extends BaseService {
21164
21325
  (doc47) => ({ ...doc47.data(), id: doc47.id })
21165
21326
  );
21166
21327
  procedures = this.applyInMemoryFilters(procedures, filters);
21328
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21167
21329
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
21168
21330
  console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
21169
21331
  if (procedures.length < (filters.pagination || 10)) {
package/dist/index.mjs CHANGED
@@ -7225,11 +7225,12 @@ var userSchema = z4.object({
7225
7225
 
7226
7226
  // src/errors/auth.errors.ts
7227
7227
  var AuthError = class extends Error {
7228
- constructor(message, code, status = 400) {
7228
+ constructor(message, code, status = 400, metadata) {
7229
7229
  super(message);
7230
7230
  this.code = code;
7231
7231
  this.status = status;
7232
7232
  this.name = "AuthError";
7233
+ this.metadata = metadata;
7233
7234
  }
7234
7235
  };
7235
7236
  var AUTH_ERRORS = {
@@ -7408,6 +7409,12 @@ var AUTH_ERRORS = {
7408
7409
  "Lozinka je previ\u0161e slaba. Molimo koristite ja\u010Du lozinku.",
7409
7410
  "AUTH/WEAK_PASSWORD",
7410
7411
  400
7412
+ ),
7413
+ // Draft profile exists error
7414
+ DRAFT_PROFILE_EXISTS: new AuthError(
7415
+ "A draft practitioner profile exists for this email. Please use your invitation code to claim it, or contact support if you don't have one.",
7416
+ "AUTH/DRAFT_PROFILE_EXISTS",
7417
+ 409
7411
7418
  )
7412
7419
  };
7413
7420
 
@@ -11569,6 +11576,52 @@ var PractitionerService = class extends BaseService {
11569
11576
  }
11570
11577
  return querySnapshot.docs[0].data();
11571
11578
  }
11579
+ /**
11580
+ * Finds a draft practitioner profile by email address
11581
+ * Used to detect if a draft profile exists when a doctor registers without a token
11582
+ *
11583
+ * @param email - Email address to search for
11584
+ * @returns Draft practitioner profile if found, null otherwise
11585
+ *
11586
+ * @remarks
11587
+ * Requires Firestore composite index on:
11588
+ * - Collection: practitioners
11589
+ * - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
11590
+ */
11591
+ async findDraftPractitionerByEmail(email) {
11592
+ try {
11593
+ const normalizedEmail = email.toLowerCase().trim();
11594
+ console.log("[PRACTITIONER] Searching for draft practitioner by email", {
11595
+ email: normalizedEmail
11596
+ });
11597
+ const q = query13(
11598
+ collection13(this.db, PRACTITIONERS_COLLECTION),
11599
+ where13("basicInfo.email", "==", normalizedEmail),
11600
+ where13("status", "==", "draft" /* DRAFT */),
11601
+ where13("userRef", "==", ""),
11602
+ limit7(1)
11603
+ );
11604
+ const querySnapshot = await getDocs13(q);
11605
+ if (querySnapshot.empty) {
11606
+ console.log("[PRACTITIONER] No draft practitioner found for email", {
11607
+ email: normalizedEmail
11608
+ });
11609
+ return null;
11610
+ }
11611
+ const draftPractitioner = querySnapshot.docs[0].data();
11612
+ console.log("[PRACTITIONER] Draft practitioner found", {
11613
+ email: normalizedEmail,
11614
+ practitionerId: draftPractitioner.id
11615
+ });
11616
+ return draftPractitioner;
11617
+ } catch (error) {
11618
+ console.error(
11619
+ "[PRACTITIONER] Error finding draft practitioner by email:",
11620
+ error
11621
+ );
11622
+ return null;
11623
+ }
11624
+ }
11572
11625
  /**
11573
11626
  * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
11574
11627
  */
@@ -15377,7 +15430,49 @@ var AuthService = class extends BaseService {
15377
15430
  }
15378
15431
  practitioner = claimedPractitioner;
15379
15432
  } else {
15380
- console.log("[AUTH] Creating new practitioner profile");
15433
+ console.log("[AUTH] Checking for existing draft practitioner profile", {
15434
+ email: data.email
15435
+ });
15436
+ const draftPractitioner = await practitionerService.findDraftPractitionerByEmail(
15437
+ data.email
15438
+ );
15439
+ if (draftPractitioner) {
15440
+ console.log("[AUTH] Draft practitioner profile found", {
15441
+ practitionerId: draftPractitioner.id,
15442
+ email: data.email,
15443
+ clinics: draftPractitioner.clinics
15444
+ });
15445
+ let clinicNames = [];
15446
+ if (draftPractitioner.clinicsInfo && draftPractitioner.clinicsInfo.length > 0) {
15447
+ clinicNames = draftPractitioner.clinicsInfo.map((clinic) => clinic.name).filter((name) => !!name);
15448
+ } else if (draftPractitioner.clinics && draftPractitioner.clinics.length > 0) {
15449
+ console.log("[AUTH] clinicsInfo missing, fetching clinic names from clinic IDs");
15450
+ const clinicService = practitionerService.getClinicService();
15451
+ const clinicNamePromises = draftPractitioner.clinics.map(async (clinicId) => {
15452
+ try {
15453
+ const clinic = await clinicService.getClinic(clinicId);
15454
+ return (clinic == null ? void 0 : clinic.name) || null;
15455
+ } catch (error) {
15456
+ console.error(`[AUTH] Error fetching clinic ${clinicId}:`, error);
15457
+ return null;
15458
+ }
15459
+ });
15460
+ const names = await Promise.all(clinicNamePromises);
15461
+ clinicNames = names.filter((name) => !!name);
15462
+ }
15463
+ await cleanupFirebaseUser(firebaseUser);
15464
+ throw new AuthError(
15465
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.message,
15466
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.code,
15467
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.status,
15468
+ {
15469
+ clinicNames,
15470
+ clinics: draftPractitioner.clinics,
15471
+ clinicsInfo: draftPractitioner.clinicsInfo
15472
+ }
15473
+ );
15474
+ }
15475
+ console.log("[AUTH] No draft profile found, creating new practitioner profile");
15381
15476
  const practitionerData = buildPractitionerData(data, firebaseUser.uid);
15382
15477
  practitioner = await practitionerService.createPractitioner(practitionerData);
15383
15478
  }
@@ -20159,6 +20254,53 @@ var ProcedureService = class extends BaseService {
20159
20254
  this.productService = productService;
20160
20255
  this.mediaService = mediaService;
20161
20256
  }
20257
+ setPractitionerService(practitionerService) {
20258
+ this.practitionerService = practitionerService;
20259
+ }
20260
+ /**
20261
+ * Filters out procedures from draft practitioners
20262
+ * @param procedures - Array of procedures to filter
20263
+ * @returns Filtered array of procedures (excluding those from draft practitioners)
20264
+ */
20265
+ async filterDraftPractitionerProcedures(procedures) {
20266
+ if (!this.practitionerService || procedures.length === 0) {
20267
+ return procedures;
20268
+ }
20269
+ try {
20270
+ const practitionerIds = Array.from(
20271
+ new Set(procedures.map((p) => p.practitionerId).filter(Boolean))
20272
+ );
20273
+ if (practitionerIds.length === 0) {
20274
+ return procedures;
20275
+ }
20276
+ const practitionerPromises = practitionerIds.map(
20277
+ (id) => this.practitionerService.getPractitioner(id).catch(() => null)
20278
+ );
20279
+ const practitioners = await Promise.all(practitionerPromises);
20280
+ const practitionerStatusMap = /* @__PURE__ */ new Map();
20281
+ practitioners.forEach((practitioner, index) => {
20282
+ if (practitioner) {
20283
+ practitionerStatusMap.set(practitionerIds[index], practitioner.status);
20284
+ }
20285
+ });
20286
+ const filteredProcedures = procedures.filter((procedure) => {
20287
+ const practitionerStatus = practitionerStatusMap.get(procedure.practitionerId);
20288
+ return practitionerStatus !== "draft" /* DRAFT */;
20289
+ });
20290
+ if (filteredProcedures.length !== procedures.length) {
20291
+ console.log(
20292
+ `[ProcedureService] Filtered out ${procedures.length - filteredProcedures.length} procedures from draft practitioners`
20293
+ );
20294
+ }
20295
+ return filteredProcedures;
20296
+ } catch (error) {
20297
+ console.error(
20298
+ "[ProcedureService] Error filtering draft practitioner procedures:",
20299
+ error
20300
+ );
20301
+ return procedures;
20302
+ }
20303
+ }
20162
20304
  /**
20163
20305
  * Process media resource (string URL or File object)
20164
20306
  * @param media String URL or File object
@@ -20869,9 +21011,10 @@ var ProcedureService = class extends BaseService {
20869
21011
  * Gets all procedures for a practitioner
20870
21012
  * @param practitionerId - The ID of the practitioner
20871
21013
  * @param clinicBranchId - Optional clinic branch ID to filter by
21014
+ * @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
20872
21015
  * @returns List of procedures
20873
21016
  */
20874
- async getProceduresByPractitioner(practitionerId, clinicBranchId) {
21017
+ async getProceduresByPractitioner(practitionerId, clinicBranchId, excludeDraftPractitioners = true) {
20875
21018
  const constraints = [
20876
21019
  where33("practitionerId", "==", practitionerId),
20877
21020
  where33("isActive", "==", true)
@@ -20884,7 +21027,19 @@ var ProcedureService = class extends BaseService {
20884
21027
  ...constraints
20885
21028
  );
20886
21029
  const snapshot = await getDocs33(q);
20887
- return snapshot.docs.map((doc47) => doc47.data());
21030
+ const procedures = snapshot.docs.map((doc47) => doc47.data());
21031
+ if (excludeDraftPractitioners && this.practitionerService) {
21032
+ try {
21033
+ const practitioner = await this.practitionerService.getPractitioner(practitionerId);
21034
+ if (practitioner && practitioner.status === "draft" /* DRAFT */) {
21035
+ console.log(`[ProcedureService] Excluding procedures for draft practitioner ${practitionerId}`);
21036
+ return [];
21037
+ }
21038
+ } catch (error) {
21039
+ console.error(`[ProcedureService] Error checking practitioner status for ${practitionerId}:`, error);
21040
+ }
21041
+ }
21042
+ return procedures;
20888
21043
  }
20889
21044
  /**
20890
21045
  * Gets all inactive procedures for a practitioner
@@ -21093,9 +21248,10 @@ var ProcedureService = class extends BaseService {
21093
21248
  *
21094
21249
  * @param pagination - Optional number of procedures per page (0 or undefined returns all)
21095
21250
  * @param lastDoc - Optional last document for pagination (if continuing from a previous page)
21251
+ * @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
21096
21252
  * @returns Object containing procedures array and the last document for pagination
21097
21253
  */
21098
- async getAllProcedures(pagination, lastDoc) {
21254
+ async getAllProcedures(pagination, lastDoc, excludeDraftPractitioners = true) {
21099
21255
  try {
21100
21256
  const proceduresCollection = collection33(this.db, PROCEDURES_COLLECTION);
21101
21257
  let proceduresQuery = query33(proceduresCollection);
@@ -21117,7 +21273,7 @@ var ProcedureService = class extends BaseService {
21117
21273
  }
21118
21274
  const proceduresSnapshot = await getDocs33(proceduresQuery);
21119
21275
  const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
21120
- const procedures = proceduresSnapshot.docs.map((doc47) => {
21276
+ let procedures = proceduresSnapshot.docs.map((doc47) => {
21121
21277
  const data = doc47.data();
21122
21278
  return {
21123
21279
  ...data,
@@ -21125,6 +21281,9 @@ var ProcedureService = class extends BaseService {
21125
21281
  // Ensure ID is present
21126
21282
  };
21127
21283
  });
21284
+ if (excludeDraftPractitioners) {
21285
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21286
+ }
21128
21287
  return {
21129
21288
  procedures,
21130
21289
  lastDoc: lastVisible
@@ -21289,6 +21448,7 @@ var ProcedureService = class extends BaseService {
21289
21448
  if (hasNestedFilters) {
21290
21449
  procedures = this.applyInMemoryFilters(procedures, filters);
21291
21450
  }
21451
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21292
21452
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
21293
21453
  console.log(`[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`);
21294
21454
  if (procedures.length < (filters.pagination || 10)) {
@@ -21369,6 +21529,7 @@ var ProcedureService = class extends BaseService {
21369
21529
  }
21370
21530
  });
21371
21531
  procedures = this.applyInMemoryFilters(procedures, filters);
21532
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21372
21533
  console.log("[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):", {
21373
21534
  procedureCount: procedures.length
21374
21535
  });
@@ -21400,6 +21561,7 @@ var ProcedureService = class extends BaseService {
21400
21561
  (doc47) => ({ ...doc47.data(), id: doc47.id })
21401
21562
  );
21402
21563
  procedures = this.applyInMemoryFilters(procedures, filters);
21564
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
21403
21565
  const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
21404
21566
  console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
21405
21567
  if (procedures.length < (filters.pagination || 10)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.13.6",
4
+ "version": "1.13.8",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -1,11 +1,15 @@
1
1
  export class AuthError extends Error {
2
+ public metadata?: Record<string, any>;
3
+
2
4
  constructor(
3
5
  message: string,
4
6
  public code: string,
5
- public status: number = 400
7
+ public status: number = 400,
8
+ metadata?: Record<string, any>
6
9
  ) {
7
10
  super(message);
8
11
  this.name = "AuthError";
12
+ this.metadata = metadata;
9
13
  }
10
14
  }
11
15
 
@@ -197,4 +201,11 @@ export const AUTH_ERRORS = {
197
201
  "AUTH/WEAK_PASSWORD",
198
202
  400
199
203
  ),
204
+
205
+ // Draft profile exists error
206
+ DRAFT_PROFILE_EXISTS: new AuthError(
207
+ "A draft practitioner profile exists for this email. Please use your invitation code to claim it, or contact support if you don't have one.",
208
+ "AUTH/DRAFT_PROFILE_EXISTS",
209
+ 409
210
+ ),
200
211
  } as const;
@@ -724,7 +724,61 @@ export class AuthService extends BaseService {
724
724
  }
725
725
  practitioner = claimedPractitioner;
726
726
  } else {
727
- console.log('[AUTH] Creating new practitioner profile');
727
+ // Check if a draft profile exists for this email
728
+ console.log('[AUTH] Checking for existing draft practitioner profile', {
729
+ email: data.email,
730
+ });
731
+ const draftPractitioner = await practitionerService.findDraftPractitionerByEmail(
732
+ data.email
733
+ );
734
+
735
+ if (draftPractitioner) {
736
+ console.log('[AUTH] Draft practitioner profile found', {
737
+ practitionerId: draftPractitioner.id,
738
+ email: data.email,
739
+ clinics: draftPractitioner.clinics,
740
+ });
741
+
742
+ // Extract clinic names from clinicsInfo (should be populated when draft is created)
743
+ let clinicNames: string[] = [];
744
+ if (draftPractitioner.clinicsInfo && draftPractitioner.clinicsInfo.length > 0) {
745
+ clinicNames = draftPractitioner.clinicsInfo
746
+ .map((clinic) => clinic.name)
747
+ .filter((name): name is string => !!name);
748
+ } else if (draftPractitioner.clinics && draftPractitioner.clinics.length > 0) {
749
+ // Fallback: fetch clinic names if clinicsInfo is missing
750
+ console.log('[AUTH] clinicsInfo missing, fetching clinic names from clinic IDs');
751
+ const clinicService = practitionerService.getClinicService();
752
+ const clinicNamePromises = draftPractitioner.clinics.map(async (clinicId) => {
753
+ try {
754
+ const clinic = await clinicService.getClinic(clinicId);
755
+ return clinic?.name || null;
756
+ } catch (error) {
757
+ console.error(`[AUTH] Error fetching clinic ${clinicId}:`, error);
758
+ return null;
759
+ }
760
+ });
761
+ const names = await Promise.all(clinicNamePromises);
762
+ clinicNames = names.filter((name): name is string => !!name);
763
+ }
764
+
765
+ // Cleanup Firebase user since we're not creating a profile
766
+ await cleanupFirebaseUser(firebaseUser);
767
+
768
+ // Throw error with clinic information
769
+ throw new AuthError(
770
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.message,
771
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.code,
772
+ AUTH_ERRORS.DRAFT_PROFILE_EXISTS.status,
773
+ {
774
+ clinicNames,
775
+ clinics: draftPractitioner.clinics,
776
+ clinicsInfo: draftPractitioner.clinicsInfo,
777
+ }
778
+ );
779
+ }
780
+
781
+ console.log('[AUTH] No draft profile found, creating new practitioner profile');
728
782
  const practitionerData = buildPractitionerData(data, firebaseUser.uid);
729
783
  practitioner = await practitionerService.createPractitioner(practitionerData);
730
784
  }
@@ -81,7 +81,7 @@ export class PractitionerService extends BaseService {
81
81
  this.mediaService = new MediaService(db, auth, app);
82
82
  }
83
83
 
84
- private getClinicService(): ClinicService {
84
+ public getClinicService(): ClinicService {
85
85
  if (!this.clinicService) {
86
86
  throw new Error("Clinic service not initialized!");
87
87
  }
@@ -657,6 +657,63 @@ export class PractitionerService extends BaseService {
657
657
  return querySnapshot.docs[0].data() as Practitioner;
658
658
  }
659
659
 
660
+ /**
661
+ * Finds a draft practitioner profile by email address
662
+ * Used to detect if a draft profile exists when a doctor registers without a token
663
+ *
664
+ * @param email - Email address to search for
665
+ * @returns Draft practitioner profile if found, null otherwise
666
+ *
667
+ * @remarks
668
+ * Requires Firestore composite index on:
669
+ * - Collection: practitioners
670
+ * - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
671
+ */
672
+ async findDraftPractitionerByEmail(
673
+ email: string
674
+ ): Promise<Practitioner | null> {
675
+ try {
676
+ const normalizedEmail = email.toLowerCase().trim();
677
+
678
+ console.log("[PRACTITIONER] Searching for draft practitioner by email", {
679
+ email: normalizedEmail,
680
+ });
681
+
682
+ const q = query(
683
+ collection(this.db, PRACTITIONERS_COLLECTION),
684
+ where("basicInfo.email", "==", normalizedEmail),
685
+ where("status", "==", PractitionerStatus.DRAFT),
686
+ where("userRef", "==", ""),
687
+ limit(1)
688
+ );
689
+
690
+ const querySnapshot = await getDocs(q);
691
+
692
+ if (querySnapshot.empty) {
693
+ console.log("[PRACTITIONER] No draft practitioner found for email", {
694
+ email: normalizedEmail,
695
+ });
696
+ return null;
697
+ }
698
+
699
+ const draftPractitioner = querySnapshot.docs[0].data() as Practitioner;
700
+ console.log("[PRACTITIONER] Draft practitioner found", {
701
+ email: normalizedEmail,
702
+ practitionerId: draftPractitioner.id,
703
+ });
704
+
705
+ return draftPractitioner;
706
+ } catch (error) {
707
+ console.error(
708
+ "[PRACTITIONER] Error finding draft practitioner by email:",
709
+ error
710
+ );
711
+ // If query fails (e.g., index not created), return null to allow registration
712
+ // This prevents blocking registration if index is missing
713
+ return null;
714
+ }
715
+ }
716
+
660
717
  /**
661
718
  * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
662
719
  */
@@ -56,12 +56,16 @@ import { distanceBetween, geohashQueryBounds } from 'geofire-common';
56
56
  import { MediaService, MediaAccessLevel } from '../media/media.service';
57
57
  import type { ProcedureProduct } from '../../backoffice/types/procedure-product.types';
58
58
 
59
+ import { PractitionerService } from '../practitioner/practitioner.service';
60
+ import { PractitionerStatus } from '../../types/practitioner';
61
+
59
62
  export class ProcedureService extends BaseService {
60
63
  private categoryService: CategoryService;
61
64
  private subcategoryService: SubcategoryService;
62
65
  private technologyService: TechnologyService;
63
66
  private productService: ProductService;
64
67
  private mediaService: MediaService;
68
+ private practitionerService?: PractitionerService;
65
69
 
66
70
  constructor(
67
71
  db: Firestore,
@@ -81,6 +85,71 @@ export class ProcedureService extends BaseService {
81
85
  this.mediaService = mediaService;
82
86
  }
83
87
 
88
+ setPractitionerService(practitionerService: PractitionerService) {
89
+ this.practitionerService = practitionerService;
90
+ }
91
+
92
+ /**
93
+ * Filters out procedures from draft practitioners
94
+ * @param procedures - Array of procedures to filter
95
+ * @returns Filtered array of procedures (excluding those from draft practitioners)
96
+ */
97
+ private async filterDraftPractitionerProcedures(
98
+ procedures: Procedure[]
99
+ ): Promise<Procedure[]> {
100
+ if (!this.practitionerService || procedures.length === 0) {
101
+ return procedures;
102
+ }
103
+
104
+ try {
105
+ // Get unique practitioner IDs from procedures
106
+ const practitionerIds = Array.from(
107
+ new Set(procedures.map((p) => p.practitionerId).filter(Boolean))
108
+ );
109
+
110
+ if (practitionerIds.length === 0) {
111
+ return procedures;
112
+ }
113
+
114
+ // Fetch all practitioners in parallel
115
+ const practitionerPromises = practitionerIds.map((id) =>
116
+ this.practitionerService!.getPractitioner(id).catch(() => null)
117
+ );
118
+ const practitioners = await Promise.all(practitionerPromises);
119
+
120
+ // Create a map of practitioner ID to status
121
+ const practitionerStatusMap = new Map<string, PractitionerStatus>();
122
+ practitioners.forEach((practitioner, index) => {
123
+ if (practitioner) {
124
+ practitionerStatusMap.set(practitionerIds[index], practitioner.status);
125
+ }
126
+ });
127
+
128
+ // Filter out procedures from draft practitioners
129
+ const filteredProcedures = procedures.filter((procedure) => {
130
+ const practitionerStatus = practitionerStatusMap.get(procedure.practitionerId);
131
+ return practitionerStatus !== PractitionerStatus.DRAFT;
132
+ });
133
+
134
+ if (filteredProcedures.length !== procedures.length) {
135
+ console.log(
136
+ `[ProcedureService] Filtered out ${
137
+ procedures.length - filteredProcedures.length
138
+ } procedures from draft practitioners`
139
+ );
140
+ }
141
+
142
+ return filteredProcedures;
143
+ } catch (error) {
144
+ console.error(
145
+ '[ProcedureService] Error filtering draft practitioner procedures:',
146
+ error
147
+ );
148
+ // On error, return original procedures to avoid breaking functionality
149
+ return procedures;
150
+ }
151
+ }
152
+
84
153
  /**
85
154
  * Process media resource (string URL or File object)
86
155
  * @param media String URL or File object
@@ -1000,9 +1069,14 @@ export class ProcedureService extends BaseService {
1000
1069
  * Gets all procedures for a practitioner
1001
1070
  * @param practitionerId - The ID of the practitioner
1002
1071
  * @param clinicBranchId - Optional clinic branch ID to filter by
1072
+ * @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
1003
1073
  * @returns List of procedures
1004
1074
  */
1005
- async getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string): Promise<Procedure[]> {
1075
+ async getProceduresByPractitioner(
1076
+ practitionerId: string,
1077
+ clinicBranchId?: string,
1078
+ excludeDraftPractitioners: boolean = true
1079
+ ): Promise<Procedure[]> {
1006
1080
  const constraints: QueryConstraint[] = [
1007
1081
  where('practitionerId', '==', practitionerId),
1008
1082
  where('isActive', '==', true),
@@ -1017,7 +1091,23 @@ export class ProcedureService extends BaseService {
1017
1091
  ...constraints
1018
1092
  );
1019
1093
  const snapshot = await getDocs(q);
1020
- return snapshot.docs.map(doc => doc.data() as Procedure);
1094
+ const procedures = snapshot.docs.map(doc => doc.data() as Procedure);
1095
+
1096
+ // If we need to exclude draft practitioners and have the service available
1097
+ if (excludeDraftPractitioners && this.practitionerService) {
1098
+ try {
1099
+ const practitioner = await this.practitionerService.getPractitioner(practitionerId);
1100
+ if (practitioner && practitioner.status === PractitionerStatus.DRAFT) {
1101
+ console.log(`[ProcedureService] Excluding procedures for draft practitioner ${practitionerId}`);
1102
+ return [];
1103
+ }
1104
+ } catch (error) {
1105
+ console.error(`[ProcedureService] Error checking practitioner status for ${practitionerId}:`, error);
1106
+ // On error, default to returning procedures to avoid breaking UI
1107
+ }
1108
+ }
1109
+
1110
+ return procedures;
1021
1111
  }
1022
1112
 
1023
1113
  /**
@@ -1285,11 +1375,13 @@ export class ProcedureService extends BaseService {
1285
1375
  *
1286
1376
  * @param pagination - Optional number of procedures per page (0 or undefined returns all)
1287
1377
  * @param lastDoc - Optional last document for pagination (if continuing from a previous page)
1378
+ * @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
1288
1379
  * @returns Object containing procedures array and the last document for pagination
1289
1380
  */
1290
1381
  async getAllProcedures(
1291
1382
  pagination?: number,
1292
1383
  lastDoc?: any,
1384
+ excludeDraftPractitioners: boolean = true,
1293
1385
  ): Promise<{ procedures: Procedure[]; lastDoc: any }> {
1294
1386
  try {
1295
1387
  const proceduresCollection = collection(this.db, PROCEDURES_COLLECTION);
@@ -1316,7 +1408,7 @@ export class ProcedureService extends BaseService {
1316
1408
  const proceduresSnapshot = await getDocs(proceduresQuery);
1317
1409
  const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
1318
1410
 
1319
- const procedures = proceduresSnapshot.docs.map(doc => {
1411
+ let procedures = proceduresSnapshot.docs.map(doc => {
1320
1412
  const data = doc.data() as Procedure;
1321
1413
  return {
1322
1414
  ...data,
@@ -1324,6 +1416,11 @@ export class ProcedureService extends BaseService {
1324
1416
  };
1325
1417
  });
1326
1418
 
1419
+ // Filter out procedures from draft practitioners if requested
1420
+ if (excludeDraftPractitioners) {
1421
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
1422
+ }
1423
+
1327
1424
  return {
1328
1425
  procedures,
1329
1426
  lastDoc: lastVisible,
@@ -1551,6 +1648,9 @@ export class ProcedureService extends BaseService {
1551
1648
  procedures = this.applyInMemoryFilters(procedures, filters);
1552
1649
  }
1553
1650
 
1651
+ // Filter out procedures from draft practitioners
1652
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
1653
+
1554
1654
  const lastDoc =
1555
1655
  querySnapshot.docs.length > 0
1556
1656
  ? querySnapshot.docs[querySnapshot.docs.length - 1]
@@ -1660,6 +1760,10 @@ export class ProcedureService extends BaseService {
1660
1760
  },
1661
1761
  });
1662
1762
  procedures = this.applyInMemoryFilters(procedures, filters);
1763
+
1764
+ // Filter out procedures from draft practitioners
1765
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
1766
+
1663
1767
  console.log('[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):', {
1664
1768
  procedureCount: procedures.length,
1665
1769
  });
@@ -1700,6 +1804,9 @@ export class ProcedureService extends BaseService {
1700
1804
 
1701
1805
  // Apply all client-side filters using centralized function
1702
1806
  procedures = this.applyInMemoryFilters(procedures, filters);
1807
+
1808
+ // Filter out procedures from draft practitioners
1809
+ procedures = await this.filterDraftPractitionerProcedures(procedures);
1703
1810
 
1704
1811
  const lastDoc =
1705
1812
  querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;