@blackcode_sa/metaestetics-api 1.13.6 → 1.13.10
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 +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +209 -7
- package/dist/index.mjs +209 -7
- package/package.json +1 -1
- package/src/errors/auth.errors.ts +12 -1
- package/src/services/auth/auth.service.ts +55 -1
- package/src/services/practitioner/practitioner.service.ts +58 -1
- package/src/services/procedure/procedure.service.ts +169 -4
package/dist/index.d.mts
CHANGED
|
@@ -6693,7 +6693,23 @@ 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 that should not be visible to patients:
|
|
6701
|
+
* 1. Procedures with no practitioner (missing practitionerId)
|
|
6702
|
+
* 2. Procedures where practitioner doesn't exist
|
|
6703
|
+
* 3. Procedures from draft practitioners
|
|
6704
|
+
* 4. Procedures from inactive practitioners (isActive === false)
|
|
6705
|
+
*
|
|
6706
|
+
* Note: Each procedure has ONE practitionerId. If that practitioner is inactive/draft/missing,
|
|
6707
|
+
* the procedure is filtered out.
|
|
6708
|
+
*
|
|
6709
|
+
* @param procedures - Array of procedures to filter
|
|
6710
|
+
* @returns Filtered array of procedures (excluding invalid/inactive/draft practitioners)
|
|
6711
|
+
*/
|
|
6712
|
+
private filterDraftPractitionerProcedures;
|
|
6697
6713
|
/**
|
|
6698
6714
|
* Process media resource (string URL or File object)
|
|
6699
6715
|
* @param media String URL or File object
|
|
@@ -6782,9 +6798,10 @@ declare class ProcedureService extends BaseService {
|
|
|
6782
6798
|
* Gets all procedures for a practitioner
|
|
6783
6799
|
* @param practitionerId - The ID of the practitioner
|
|
6784
6800
|
* @param clinicBranchId - Optional clinic branch ID to filter by
|
|
6801
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
|
|
6785
6802
|
* @returns List of procedures
|
|
6786
6803
|
*/
|
|
6787
|
-
getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string): Promise<Procedure[]>;
|
|
6804
|
+
getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string, excludeDraftPractitioners?: boolean): Promise<Procedure[]>;
|
|
6788
6805
|
/**
|
|
6789
6806
|
* Gets all inactive procedures for a practitioner
|
|
6790
6807
|
* @param practitionerId - The ID of the practitioner
|
|
@@ -6825,9 +6842,10 @@ declare class ProcedureService extends BaseService {
|
|
|
6825
6842
|
*
|
|
6826
6843
|
* @param pagination - Optional number of procedures per page (0 or undefined returns all)
|
|
6827
6844
|
* @param lastDoc - Optional last document for pagination (if continuing from a previous page)
|
|
6845
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
|
|
6828
6846
|
* @returns Object containing procedures array and the last document for pagination
|
|
6829
6847
|
*/
|
|
6830
|
-
getAllProcedures(pagination?: number, lastDoc?: any): Promise<{
|
|
6848
|
+
getAllProcedures(pagination?: number, lastDoc?: any, excludeDraftPractitioners?: boolean): Promise<{
|
|
6831
6849
|
procedures: Procedure[];
|
|
6832
6850
|
lastDoc: any;
|
|
6833
6851
|
}>;
|
|
@@ -6924,7 +6942,7 @@ declare class PractitionerService extends BaseService {
|
|
|
6924
6942
|
private mediaService;
|
|
6925
6943
|
private procedureService?;
|
|
6926
6944
|
constructor(db: Firestore, auth: Auth, app: FirebaseApp, clinicService?: ClinicService, procedureService?: ProcedureService);
|
|
6927
|
-
|
|
6945
|
+
getClinicService(): ClinicService;
|
|
6928
6946
|
private getProcedureService;
|
|
6929
6947
|
setClinicService(clinicService: ClinicService): void;
|
|
6930
6948
|
setProcedureService(procedureService: ProcedureService): void;
|
|
@@ -6992,6 +7010,19 @@ declare class PractitionerService extends BaseService {
|
|
|
6992
7010
|
* Dohvata zdravstvenog radnika po User ID-u
|
|
6993
7011
|
*/
|
|
6994
7012
|
getPractitionerByUserRef(userRef: string): Promise<Practitioner | null>;
|
|
7013
|
+
/**
|
|
7014
|
+
* Finds a draft practitioner profile by email address
|
|
7015
|
+
* Used to detect if a draft profile exists when a doctor registers without a token
|
|
7016
|
+
*
|
|
7017
|
+
* @param email - Email address to search for
|
|
7018
|
+
* @returns Draft practitioner profile if found, null otherwise
|
|
7019
|
+
*
|
|
7020
|
+
* @remarks
|
|
7021
|
+
* Requires Firestore composite index on:
|
|
7022
|
+
* - Collection: practitioners
|
|
7023
|
+
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
7024
|
+
*/
|
|
7025
|
+
findDraftPractitionerByEmail(email: string): Promise<Practitioner | null>;
|
|
6995
7026
|
/**
|
|
6996
7027
|
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
6997
7028
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -6693,7 +6693,23 @@ 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 that should not be visible to patients:
|
|
6701
|
+
* 1. Procedures with no practitioner (missing practitionerId)
|
|
6702
|
+
* 2. Procedures where practitioner doesn't exist
|
|
6703
|
+
* 3. Procedures from draft practitioners
|
|
6704
|
+
* 4. Procedures from inactive practitioners (isActive === false)
|
|
6705
|
+
*
|
|
6706
|
+
* Note: Each procedure has ONE practitionerId. If that practitioner is inactive/draft/missing,
|
|
6707
|
+
* the procedure is filtered out.
|
|
6708
|
+
*
|
|
6709
|
+
* @param procedures - Array of procedures to filter
|
|
6710
|
+
* @returns Filtered array of procedures (excluding invalid/inactive/draft practitioners)
|
|
6711
|
+
*/
|
|
6712
|
+
private filterDraftPractitionerProcedures;
|
|
6697
6713
|
/**
|
|
6698
6714
|
* Process media resource (string URL or File object)
|
|
6699
6715
|
* @param media String URL or File object
|
|
@@ -6782,9 +6798,10 @@ declare class ProcedureService extends BaseService {
|
|
|
6782
6798
|
* Gets all procedures for a practitioner
|
|
6783
6799
|
* @param practitionerId - The ID of the practitioner
|
|
6784
6800
|
* @param clinicBranchId - Optional clinic branch ID to filter by
|
|
6801
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
|
|
6785
6802
|
* @returns List of procedures
|
|
6786
6803
|
*/
|
|
6787
|
-
getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string): Promise<Procedure[]>;
|
|
6804
|
+
getProceduresByPractitioner(practitionerId: string, clinicBranchId?: string, excludeDraftPractitioners?: boolean): Promise<Procedure[]>;
|
|
6788
6805
|
/**
|
|
6789
6806
|
* Gets all inactive procedures for a practitioner
|
|
6790
6807
|
* @param practitionerId - The ID of the practitioner
|
|
@@ -6825,9 +6842,10 @@ declare class ProcedureService extends BaseService {
|
|
|
6825
6842
|
*
|
|
6826
6843
|
* @param pagination - Optional number of procedures per page (0 or undefined returns all)
|
|
6827
6844
|
* @param lastDoc - Optional last document for pagination (if continuing from a previous page)
|
|
6845
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
|
|
6828
6846
|
* @returns Object containing procedures array and the last document for pagination
|
|
6829
6847
|
*/
|
|
6830
|
-
getAllProcedures(pagination?: number, lastDoc?: any): Promise<{
|
|
6848
|
+
getAllProcedures(pagination?: number, lastDoc?: any, excludeDraftPractitioners?: boolean): Promise<{
|
|
6831
6849
|
procedures: Procedure[];
|
|
6832
6850
|
lastDoc: any;
|
|
6833
6851
|
}>;
|
|
@@ -6924,7 +6942,7 @@ declare class PractitionerService extends BaseService {
|
|
|
6924
6942
|
private mediaService;
|
|
6925
6943
|
private procedureService?;
|
|
6926
6944
|
constructor(db: Firestore, auth: Auth, app: FirebaseApp, clinicService?: ClinicService, procedureService?: ProcedureService);
|
|
6927
|
-
|
|
6945
|
+
getClinicService(): ClinicService;
|
|
6928
6946
|
private getProcedureService;
|
|
6929
6947
|
setClinicService(clinicService: ClinicService): void;
|
|
6930
6948
|
setProcedureService(procedureService: ProcedureService): void;
|
|
@@ -6992,6 +7010,19 @@ declare class PractitionerService extends BaseService {
|
|
|
6992
7010
|
* Dohvata zdravstvenog radnika po User ID-u
|
|
6993
7011
|
*/
|
|
6994
7012
|
getPractitionerByUserRef(userRef: string): Promise<Practitioner | null>;
|
|
7013
|
+
/**
|
|
7014
|
+
* Finds a draft practitioner profile by email address
|
|
7015
|
+
* Used to detect if a draft profile exists when a doctor registers without a token
|
|
7016
|
+
*
|
|
7017
|
+
* @param email - Email address to search for
|
|
7018
|
+
* @returns Draft practitioner profile if found, null otherwise
|
|
7019
|
+
*
|
|
7020
|
+
* @remarks
|
|
7021
|
+
* Requires Firestore composite index on:
|
|
7022
|
+
* - Collection: practitioners
|
|
7023
|
+
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
7024
|
+
*/
|
|
7025
|
+
findDraftPractitionerByEmail(email: string): Promise<Practitioner | null>;
|
|
6995
7026
|
/**
|
|
6996
7027
|
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
6997
7028
|
*/
|
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]
|
|
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,91 @@ 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 that should not be visible to patients:
|
|
20026
|
+
* 1. Procedures with no practitioner (missing practitionerId)
|
|
20027
|
+
* 2. Procedures where practitioner doesn't exist
|
|
20028
|
+
* 3. Procedures from draft practitioners
|
|
20029
|
+
* 4. Procedures from inactive practitioners (isActive === false)
|
|
20030
|
+
*
|
|
20031
|
+
* Note: Each procedure has ONE practitionerId. If that practitioner is inactive/draft/missing,
|
|
20032
|
+
* the procedure is filtered out.
|
|
20033
|
+
*
|
|
20034
|
+
* @param procedures - Array of procedures to filter
|
|
20035
|
+
* @returns Filtered array of procedures (excluding invalid/inactive/draft practitioners)
|
|
20036
|
+
*/
|
|
20037
|
+
async filterDraftPractitionerProcedures(procedures) {
|
|
20038
|
+
if (!this.practitionerService || procedures.length === 0) {
|
|
20039
|
+
return procedures;
|
|
20040
|
+
}
|
|
20041
|
+
try {
|
|
20042
|
+
const proceduresWithPractitioner = procedures.filter(
|
|
20043
|
+
(p) => p.practitionerId && p.practitionerId.trim() !== ""
|
|
20044
|
+
);
|
|
20045
|
+
if (proceduresWithPractitioner.length === 0) {
|
|
20046
|
+
console.log(
|
|
20047
|
+
`[ProcedureService] All ${procedures.length} procedures have no practitionerId - filtering out`
|
|
20048
|
+
);
|
|
20049
|
+
return [];
|
|
20050
|
+
}
|
|
20051
|
+
const practitionerIds = Array.from(
|
|
20052
|
+
new Set(proceduresWithPractitioner.map((p) => p.practitionerId).filter(Boolean))
|
|
20053
|
+
);
|
|
20054
|
+
if (practitionerIds.length === 0) {
|
|
20055
|
+
return [];
|
|
20056
|
+
}
|
|
20057
|
+
const practitionerPromises = practitionerIds.map(
|
|
20058
|
+
(id) => this.practitionerService.getPractitioner(id).catch(() => null)
|
|
20059
|
+
);
|
|
20060
|
+
const practitioners = await Promise.all(practitionerPromises);
|
|
20061
|
+
const practitionerMap = /* @__PURE__ */ new Map();
|
|
20062
|
+
practitioners.forEach((practitioner, index) => {
|
|
20063
|
+
if (practitioner) {
|
|
20064
|
+
practitionerMap.set(practitionerIds[index], practitioner);
|
|
20065
|
+
}
|
|
20066
|
+
});
|
|
20067
|
+
const filteredProcedures = proceduresWithPractitioner.filter((procedure) => {
|
|
20068
|
+
const practitioner = practitionerMap.get(procedure.practitionerId);
|
|
20069
|
+
if (!practitioner) {
|
|
20070
|
+
console.log(
|
|
20071
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} not found`
|
|
20072
|
+
);
|
|
20073
|
+
return false;
|
|
20074
|
+
}
|
|
20075
|
+
if (practitioner.status === "draft" /* DRAFT */) {
|
|
20076
|
+
console.log(
|
|
20077
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} is DRAFT`
|
|
20078
|
+
);
|
|
20079
|
+
return false;
|
|
20080
|
+
}
|
|
20081
|
+
if (!practitioner.isActive) {
|
|
20082
|
+
console.log(
|
|
20083
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} is not active`
|
|
20084
|
+
);
|
|
20085
|
+
return false;
|
|
20086
|
+
}
|
|
20087
|
+
return true;
|
|
20088
|
+
});
|
|
20089
|
+
const filteredCount = procedures.length - filteredProcedures.length;
|
|
20090
|
+
if (filteredCount > 0) {
|
|
20091
|
+
const noPractitionerCount = procedures.length - proceduresWithPractitioner.length;
|
|
20092
|
+
const invalidPractitionerCount = proceduresWithPractitioner.length - filteredProcedures.length;
|
|
20093
|
+
console.log(
|
|
20094
|
+
`[ProcedureService] Filtered out ${filteredCount} procedures: ${noPractitionerCount} with no practitionerId, ${invalidPractitionerCount} with missing/draft/inactive practitioners`
|
|
20095
|
+
);
|
|
20096
|
+
}
|
|
20097
|
+
return filteredProcedures;
|
|
20098
|
+
} catch (error) {
|
|
20099
|
+
console.error(
|
|
20100
|
+
"[ProcedureService] Error filtering practitioner procedures:",
|
|
20101
|
+
error
|
|
20102
|
+
);
|
|
20103
|
+
return procedures;
|
|
20104
|
+
}
|
|
20105
|
+
}
|
|
19926
20106
|
/**
|
|
19927
20107
|
* Process media resource (string URL or File object)
|
|
19928
20108
|
* @param media String URL or File object
|
|
@@ -20627,15 +20807,17 @@ var ProcedureService = class extends BaseService {
|
|
|
20627
20807
|
(0, import_firestore58.where)("isActive", "==", true)
|
|
20628
20808
|
);
|
|
20629
20809
|
const snapshot = await (0, import_firestore58.getDocs)(q);
|
|
20630
|
-
|
|
20810
|
+
const procedures = snapshot.docs.map((doc47) => doc47.data());
|
|
20811
|
+
return await this.filterDraftPractitionerProcedures(procedures);
|
|
20631
20812
|
}
|
|
20632
20813
|
/**
|
|
20633
20814
|
* Gets all procedures for a practitioner
|
|
20634
20815
|
* @param practitionerId - The ID of the practitioner
|
|
20635
20816
|
* @param clinicBranchId - Optional clinic branch ID to filter by
|
|
20817
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
|
|
20636
20818
|
* @returns List of procedures
|
|
20637
20819
|
*/
|
|
20638
|
-
async getProceduresByPractitioner(practitionerId, clinicBranchId) {
|
|
20820
|
+
async getProceduresByPractitioner(practitionerId, clinicBranchId, excludeDraftPractitioners = true) {
|
|
20639
20821
|
const constraints = [
|
|
20640
20822
|
(0, import_firestore58.where)("practitionerId", "==", practitionerId),
|
|
20641
20823
|
(0, import_firestore58.where)("isActive", "==", true)
|
|
@@ -20648,7 +20830,19 @@ var ProcedureService = class extends BaseService {
|
|
|
20648
20830
|
...constraints
|
|
20649
20831
|
);
|
|
20650
20832
|
const snapshot = await (0, import_firestore58.getDocs)(q);
|
|
20651
|
-
|
|
20833
|
+
const procedures = snapshot.docs.map((doc47) => doc47.data());
|
|
20834
|
+
if (excludeDraftPractitioners && this.practitionerService) {
|
|
20835
|
+
try {
|
|
20836
|
+
const practitioner = await this.practitionerService.getPractitioner(practitionerId);
|
|
20837
|
+
if (practitioner && practitioner.status === "draft" /* DRAFT */) {
|
|
20838
|
+
console.log(`[ProcedureService] Excluding procedures for draft practitioner ${practitionerId}`);
|
|
20839
|
+
return [];
|
|
20840
|
+
}
|
|
20841
|
+
} catch (error) {
|
|
20842
|
+
console.error(`[ProcedureService] Error checking practitioner status for ${practitionerId}:`, error);
|
|
20843
|
+
}
|
|
20844
|
+
}
|
|
20845
|
+
return procedures;
|
|
20652
20846
|
}
|
|
20653
20847
|
/**
|
|
20654
20848
|
* Gets all inactive procedures for a practitioner
|
|
@@ -20857,9 +21051,10 @@ var ProcedureService = class extends BaseService {
|
|
|
20857
21051
|
*
|
|
20858
21052
|
* @param pagination - Optional number of procedures per page (0 or undefined returns all)
|
|
20859
21053
|
* @param lastDoc - Optional last document for pagination (if continuing from a previous page)
|
|
21054
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
|
|
20860
21055
|
* @returns Object containing procedures array and the last document for pagination
|
|
20861
21056
|
*/
|
|
20862
|
-
async getAllProcedures(pagination, lastDoc) {
|
|
21057
|
+
async getAllProcedures(pagination, lastDoc, excludeDraftPractitioners = true) {
|
|
20863
21058
|
try {
|
|
20864
21059
|
const proceduresCollection = (0, import_firestore58.collection)(this.db, PROCEDURES_COLLECTION);
|
|
20865
21060
|
let proceduresQuery = (0, import_firestore58.query)(proceduresCollection);
|
|
@@ -20881,7 +21076,7 @@ var ProcedureService = class extends BaseService {
|
|
|
20881
21076
|
}
|
|
20882
21077
|
const proceduresSnapshot = await (0, import_firestore58.getDocs)(proceduresQuery);
|
|
20883
21078
|
const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
|
|
20884
|
-
|
|
21079
|
+
let procedures = proceduresSnapshot.docs.map((doc47) => {
|
|
20885
21080
|
const data = doc47.data();
|
|
20886
21081
|
return {
|
|
20887
21082
|
...data,
|
|
@@ -20889,6 +21084,9 @@ var ProcedureService = class extends BaseService {
|
|
|
20889
21084
|
// Ensure ID is present
|
|
20890
21085
|
};
|
|
20891
21086
|
});
|
|
21087
|
+
if (excludeDraftPractitioners) {
|
|
21088
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21089
|
+
}
|
|
20892
21090
|
return {
|
|
20893
21091
|
procedures,
|
|
20894
21092
|
lastDoc: lastVisible
|
|
@@ -21013,6 +21211,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21013
21211
|
if (hasNestedFilters) {
|
|
21014
21212
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21015
21213
|
}
|
|
21214
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21016
21215
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
21017
21216
|
console.log(`[PROCEDURE_SERVICE] Strategy 1 success: ${procedures.length} procedures`);
|
|
21018
21217
|
if (procedures.length < (filters.pagination || 10)) {
|
|
@@ -21053,6 +21252,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21053
21252
|
if (hasNestedFilters) {
|
|
21054
21253
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21055
21254
|
}
|
|
21255
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21056
21256
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
21057
21257
|
console.log(`[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`);
|
|
21058
21258
|
if (procedures.length < (filters.pagination || 10)) {
|
|
@@ -21133,6 +21333,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21133
21333
|
}
|
|
21134
21334
|
});
|
|
21135
21335
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21336
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21136
21337
|
console.log("[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):", {
|
|
21137
21338
|
procedureCount: procedures.length
|
|
21138
21339
|
});
|
|
@@ -21164,6 +21365,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21164
21365
|
(doc47) => ({ ...doc47.data(), id: doc47.id })
|
|
21165
21366
|
);
|
|
21166
21367
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21368
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21167
21369
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
21168
21370
|
console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
|
|
21169
21371
|
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]
|
|
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,91 @@ 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 that should not be visible to patients:
|
|
20262
|
+
* 1. Procedures with no practitioner (missing practitionerId)
|
|
20263
|
+
* 2. Procedures where practitioner doesn't exist
|
|
20264
|
+
* 3. Procedures from draft practitioners
|
|
20265
|
+
* 4. Procedures from inactive practitioners (isActive === false)
|
|
20266
|
+
*
|
|
20267
|
+
* Note: Each procedure has ONE practitionerId. If that practitioner is inactive/draft/missing,
|
|
20268
|
+
* the procedure is filtered out.
|
|
20269
|
+
*
|
|
20270
|
+
* @param procedures - Array of procedures to filter
|
|
20271
|
+
* @returns Filtered array of procedures (excluding invalid/inactive/draft practitioners)
|
|
20272
|
+
*/
|
|
20273
|
+
async filterDraftPractitionerProcedures(procedures) {
|
|
20274
|
+
if (!this.practitionerService || procedures.length === 0) {
|
|
20275
|
+
return procedures;
|
|
20276
|
+
}
|
|
20277
|
+
try {
|
|
20278
|
+
const proceduresWithPractitioner = procedures.filter(
|
|
20279
|
+
(p) => p.practitionerId && p.practitionerId.trim() !== ""
|
|
20280
|
+
);
|
|
20281
|
+
if (proceduresWithPractitioner.length === 0) {
|
|
20282
|
+
console.log(
|
|
20283
|
+
`[ProcedureService] All ${procedures.length} procedures have no practitionerId - filtering out`
|
|
20284
|
+
);
|
|
20285
|
+
return [];
|
|
20286
|
+
}
|
|
20287
|
+
const practitionerIds = Array.from(
|
|
20288
|
+
new Set(proceduresWithPractitioner.map((p) => p.practitionerId).filter(Boolean))
|
|
20289
|
+
);
|
|
20290
|
+
if (practitionerIds.length === 0) {
|
|
20291
|
+
return [];
|
|
20292
|
+
}
|
|
20293
|
+
const practitionerPromises = practitionerIds.map(
|
|
20294
|
+
(id) => this.practitionerService.getPractitioner(id).catch(() => null)
|
|
20295
|
+
);
|
|
20296
|
+
const practitioners = await Promise.all(practitionerPromises);
|
|
20297
|
+
const practitionerMap = /* @__PURE__ */ new Map();
|
|
20298
|
+
practitioners.forEach((practitioner, index) => {
|
|
20299
|
+
if (practitioner) {
|
|
20300
|
+
practitionerMap.set(practitionerIds[index], practitioner);
|
|
20301
|
+
}
|
|
20302
|
+
});
|
|
20303
|
+
const filteredProcedures = proceduresWithPractitioner.filter((procedure) => {
|
|
20304
|
+
const practitioner = practitionerMap.get(procedure.practitionerId);
|
|
20305
|
+
if (!practitioner) {
|
|
20306
|
+
console.log(
|
|
20307
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} not found`
|
|
20308
|
+
);
|
|
20309
|
+
return false;
|
|
20310
|
+
}
|
|
20311
|
+
if (practitioner.status === "draft" /* DRAFT */) {
|
|
20312
|
+
console.log(
|
|
20313
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} is DRAFT`
|
|
20314
|
+
);
|
|
20315
|
+
return false;
|
|
20316
|
+
}
|
|
20317
|
+
if (!practitioner.isActive) {
|
|
20318
|
+
console.log(
|
|
20319
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} is not active`
|
|
20320
|
+
);
|
|
20321
|
+
return false;
|
|
20322
|
+
}
|
|
20323
|
+
return true;
|
|
20324
|
+
});
|
|
20325
|
+
const filteredCount = procedures.length - filteredProcedures.length;
|
|
20326
|
+
if (filteredCount > 0) {
|
|
20327
|
+
const noPractitionerCount = procedures.length - proceduresWithPractitioner.length;
|
|
20328
|
+
const invalidPractitionerCount = proceduresWithPractitioner.length - filteredProcedures.length;
|
|
20329
|
+
console.log(
|
|
20330
|
+
`[ProcedureService] Filtered out ${filteredCount} procedures: ${noPractitionerCount} with no practitionerId, ${invalidPractitionerCount} with missing/draft/inactive practitioners`
|
|
20331
|
+
);
|
|
20332
|
+
}
|
|
20333
|
+
return filteredProcedures;
|
|
20334
|
+
} catch (error) {
|
|
20335
|
+
console.error(
|
|
20336
|
+
"[ProcedureService] Error filtering practitioner procedures:",
|
|
20337
|
+
error
|
|
20338
|
+
);
|
|
20339
|
+
return procedures;
|
|
20340
|
+
}
|
|
20341
|
+
}
|
|
20162
20342
|
/**
|
|
20163
20343
|
* Process media resource (string URL or File object)
|
|
20164
20344
|
* @param media String URL or File object
|
|
@@ -20863,15 +21043,17 @@ var ProcedureService = class extends BaseService {
|
|
|
20863
21043
|
where33("isActive", "==", true)
|
|
20864
21044
|
);
|
|
20865
21045
|
const snapshot = await getDocs33(q);
|
|
20866
|
-
|
|
21046
|
+
const procedures = snapshot.docs.map((doc47) => doc47.data());
|
|
21047
|
+
return await this.filterDraftPractitionerProcedures(procedures);
|
|
20867
21048
|
}
|
|
20868
21049
|
/**
|
|
20869
21050
|
* Gets all procedures for a practitioner
|
|
20870
21051
|
* @param practitionerId - The ID of the practitioner
|
|
20871
21052
|
* @param clinicBranchId - Optional clinic branch ID to filter by
|
|
21053
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
|
|
20872
21054
|
* @returns List of procedures
|
|
20873
21055
|
*/
|
|
20874
|
-
async getProceduresByPractitioner(practitionerId, clinicBranchId) {
|
|
21056
|
+
async getProceduresByPractitioner(practitionerId, clinicBranchId, excludeDraftPractitioners = true) {
|
|
20875
21057
|
const constraints = [
|
|
20876
21058
|
where33("practitionerId", "==", practitionerId),
|
|
20877
21059
|
where33("isActive", "==", true)
|
|
@@ -20884,7 +21066,19 @@ var ProcedureService = class extends BaseService {
|
|
|
20884
21066
|
...constraints
|
|
20885
21067
|
);
|
|
20886
21068
|
const snapshot = await getDocs33(q);
|
|
20887
|
-
|
|
21069
|
+
const procedures = snapshot.docs.map((doc47) => doc47.data());
|
|
21070
|
+
if (excludeDraftPractitioners && this.practitionerService) {
|
|
21071
|
+
try {
|
|
21072
|
+
const practitioner = await this.practitionerService.getPractitioner(practitionerId);
|
|
21073
|
+
if (practitioner && practitioner.status === "draft" /* DRAFT */) {
|
|
21074
|
+
console.log(`[ProcedureService] Excluding procedures for draft practitioner ${practitionerId}`);
|
|
21075
|
+
return [];
|
|
21076
|
+
}
|
|
21077
|
+
} catch (error) {
|
|
21078
|
+
console.error(`[ProcedureService] Error checking practitioner status for ${practitionerId}:`, error);
|
|
21079
|
+
}
|
|
21080
|
+
}
|
|
21081
|
+
return procedures;
|
|
20888
21082
|
}
|
|
20889
21083
|
/**
|
|
20890
21084
|
* Gets all inactive procedures for a practitioner
|
|
@@ -21093,9 +21287,10 @@ var ProcedureService = class extends BaseService {
|
|
|
21093
21287
|
*
|
|
21094
21288
|
* @param pagination - Optional number of procedures per page (0 or undefined returns all)
|
|
21095
21289
|
* @param lastDoc - Optional last document for pagination (if continuing from a previous page)
|
|
21290
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
|
|
21096
21291
|
* @returns Object containing procedures array and the last document for pagination
|
|
21097
21292
|
*/
|
|
21098
|
-
async getAllProcedures(pagination, lastDoc) {
|
|
21293
|
+
async getAllProcedures(pagination, lastDoc, excludeDraftPractitioners = true) {
|
|
21099
21294
|
try {
|
|
21100
21295
|
const proceduresCollection = collection33(this.db, PROCEDURES_COLLECTION);
|
|
21101
21296
|
let proceduresQuery = query33(proceduresCollection);
|
|
@@ -21117,7 +21312,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21117
21312
|
}
|
|
21118
21313
|
const proceduresSnapshot = await getDocs33(proceduresQuery);
|
|
21119
21314
|
const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
|
|
21120
|
-
|
|
21315
|
+
let procedures = proceduresSnapshot.docs.map((doc47) => {
|
|
21121
21316
|
const data = doc47.data();
|
|
21122
21317
|
return {
|
|
21123
21318
|
...data,
|
|
@@ -21125,6 +21320,9 @@ var ProcedureService = class extends BaseService {
|
|
|
21125
21320
|
// Ensure ID is present
|
|
21126
21321
|
};
|
|
21127
21322
|
});
|
|
21323
|
+
if (excludeDraftPractitioners) {
|
|
21324
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21325
|
+
}
|
|
21128
21326
|
return {
|
|
21129
21327
|
procedures,
|
|
21130
21328
|
lastDoc: lastVisible
|
|
@@ -21249,6 +21447,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21249
21447
|
if (hasNestedFilters) {
|
|
21250
21448
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21251
21449
|
}
|
|
21450
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21252
21451
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
21253
21452
|
console.log(`[PROCEDURE_SERVICE] Strategy 1 success: ${procedures.length} procedures`);
|
|
21254
21453
|
if (procedures.length < (filters.pagination || 10)) {
|
|
@@ -21289,6 +21488,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21289
21488
|
if (hasNestedFilters) {
|
|
21290
21489
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21291
21490
|
}
|
|
21491
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21292
21492
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
21293
21493
|
console.log(`[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`);
|
|
21294
21494
|
if (procedures.length < (filters.pagination || 10)) {
|
|
@@ -21369,6 +21569,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21369
21569
|
}
|
|
21370
21570
|
});
|
|
21371
21571
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21572
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21372
21573
|
console.log("[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):", {
|
|
21373
21574
|
procedureCount: procedures.length
|
|
21374
21575
|
});
|
|
@@ -21400,6 +21601,7 @@ var ProcedureService = class extends BaseService {
|
|
|
21400
21601
|
(doc47) => ({ ...doc47.data(), id: doc47.id })
|
|
21401
21602
|
);
|
|
21402
21603
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
21604
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
21403
21605
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
21404
21606
|
console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
|
|
21405
21607
|
if (procedures.length < (filters.pagination || 10)) {
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,123 @@ 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 that should not be visible to patients:
|
|
94
|
+
* 1. Procedures with no practitioner (missing practitionerId)
|
|
95
|
+
* 2. Procedures where practitioner doesn't exist
|
|
96
|
+
* 3. Procedures from draft practitioners
|
|
97
|
+
* 4. Procedures from inactive practitioners (isActive === false)
|
|
98
|
+
*
|
|
99
|
+
* Note: Each procedure has ONE practitionerId. If that practitioner is inactive/draft/missing,
|
|
100
|
+
* the procedure is filtered out.
|
|
101
|
+
*
|
|
102
|
+
* @param procedures - Array of procedures to filter
|
|
103
|
+
* @returns Filtered array of procedures (excluding invalid/inactive/draft practitioners)
|
|
104
|
+
*/
|
|
105
|
+
private async filterDraftPractitionerProcedures(
|
|
106
|
+
procedures: Procedure[]
|
|
107
|
+
): Promise<Procedure[]> {
|
|
108
|
+
if (!this.practitionerService || procedures.length === 0) {
|
|
109
|
+
return procedures;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// First, filter out procedures with no practitionerId
|
|
114
|
+
const proceduresWithPractitioner = procedures.filter((p) =>
|
|
115
|
+
p.practitionerId && p.practitionerId.trim() !== ''
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (proceduresWithPractitioner.length === 0) {
|
|
119
|
+
console.log(
|
|
120
|
+
`[ProcedureService] All ${procedures.length} procedures have no practitionerId - filtering out`
|
|
121
|
+
);
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Get unique practitioner IDs from procedures
|
|
126
|
+
const practitionerIds = Array.from(
|
|
127
|
+
new Set(proceduresWithPractitioner.map((p) => p.practitionerId).filter(Boolean))
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (practitionerIds.length === 0) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Fetch all practitioners in parallel
|
|
135
|
+
const practitionerPromises = practitionerIds.map((id) =>
|
|
136
|
+
this.practitionerService!.getPractitioner(id).catch(() => null)
|
|
137
|
+
);
|
|
138
|
+
const practitioners = await Promise.all(practitionerPromises);
|
|
139
|
+
|
|
140
|
+
// Create a map of practitioner ID to practitioner data
|
|
141
|
+
const practitionerMap = new Map<string, Practitioner>();
|
|
142
|
+
practitioners.forEach((practitioner, index) => {
|
|
143
|
+
if (practitioner) {
|
|
144
|
+
practitionerMap.set(practitionerIds[index], practitioner);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Filter out procedures that:
|
|
149
|
+
// 1. Have no practitionerId (already filtered above, but double-check)
|
|
150
|
+
// 2. Have a practitioner that doesn't exist
|
|
151
|
+
// 3. Have a practitioner with DRAFT status
|
|
152
|
+
// 4. Have a practitioner that is not active (isActive === false)
|
|
153
|
+
const filteredProcedures = proceduresWithPractitioner.filter((procedure) => {
|
|
154
|
+
// Check if practitioner exists
|
|
155
|
+
const practitioner = practitionerMap.get(procedure.practitionerId);
|
|
156
|
+
|
|
157
|
+
if (!practitioner) {
|
|
158
|
+
console.log(
|
|
159
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} not found`
|
|
160
|
+
);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if practitioner is DRAFT
|
|
165
|
+
if (practitioner.status === PractitionerStatus.DRAFT) {
|
|
166
|
+
console.log(
|
|
167
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} is DRAFT`
|
|
168
|
+
);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check if practitioner is active (must be true to show procedure)
|
|
173
|
+
if (!practitioner.isActive) {
|
|
174
|
+
console.log(
|
|
175
|
+
`[ProcedureService] Filtering out procedure ${procedure.id} - practitioner ${procedure.practitionerId} is not active`
|
|
176
|
+
);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const filteredCount = procedures.length - filteredProcedures.length;
|
|
184
|
+
if (filteredCount > 0) {
|
|
185
|
+
const noPractitionerCount = procedures.length - proceduresWithPractitioner.length;
|
|
186
|
+
const invalidPractitionerCount = proceduresWithPractitioner.length - filteredProcedures.length;
|
|
187
|
+
console.log(
|
|
188
|
+
`[ProcedureService] Filtered out ${filteredCount} procedures: ` +
|
|
189
|
+
`${noPractitionerCount} with no practitionerId, ` +
|
|
190
|
+
`${invalidPractitionerCount} with missing/draft/inactive practitioners`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return filteredProcedures;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(
|
|
197
|
+
'[ProcedureService] Error filtering practitioner procedures:',
|
|
198
|
+
error
|
|
199
|
+
);
|
|
200
|
+
// On error, return original procedures to avoid breaking functionality
|
|
201
|
+
return procedures;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
84
205
|
/**
|
|
85
206
|
* Process media resource (string URL or File object)
|
|
86
207
|
* @param media String URL or File object
|
|
@@ -993,16 +1114,24 @@ export class ProcedureService extends BaseService {
|
|
|
993
1114
|
where('isActive', '==', true),
|
|
994
1115
|
);
|
|
995
1116
|
const snapshot = await getDocs(q);
|
|
996
|
-
|
|
1117
|
+
const procedures = snapshot.docs.map(doc => doc.data() as Procedure);
|
|
1118
|
+
|
|
1119
|
+
// Filter out procedures from draft practitioners
|
|
1120
|
+
return await this.filterDraftPractitionerProcedures(procedures);
|
|
997
1121
|
}
|
|
998
1122
|
|
|
999
1123
|
/**
|
|
1000
1124
|
* Gets all procedures for a practitioner
|
|
1001
1125
|
* @param practitionerId - The ID of the practitioner
|
|
1002
1126
|
* @param clinicBranchId - Optional clinic branch ID to filter by
|
|
1127
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures if the practitioner is in DRAFT status
|
|
1003
1128
|
* @returns List of procedures
|
|
1004
1129
|
*/
|
|
1005
|
-
async getProceduresByPractitioner(
|
|
1130
|
+
async getProceduresByPractitioner(
|
|
1131
|
+
practitionerId: string,
|
|
1132
|
+
clinicBranchId?: string,
|
|
1133
|
+
excludeDraftPractitioners: boolean = true
|
|
1134
|
+
): Promise<Procedure[]> {
|
|
1006
1135
|
const constraints: QueryConstraint[] = [
|
|
1007
1136
|
where('practitionerId', '==', practitionerId),
|
|
1008
1137
|
where('isActive', '==', true),
|
|
@@ -1017,7 +1146,23 @@ export class ProcedureService extends BaseService {
|
|
|
1017
1146
|
...constraints
|
|
1018
1147
|
);
|
|
1019
1148
|
const snapshot = await getDocs(q);
|
|
1020
|
-
|
|
1149
|
+
const procedures = snapshot.docs.map(doc => doc.data() as Procedure);
|
|
1150
|
+
|
|
1151
|
+
// If we need to exclude draft practitioners and have the service available
|
|
1152
|
+
if (excludeDraftPractitioners && this.practitionerService) {
|
|
1153
|
+
try {
|
|
1154
|
+
const practitioner = await this.practitionerService.getPractitioner(practitionerId);
|
|
1155
|
+
if (practitioner && practitioner.status === PractitionerStatus.DRAFT) {
|
|
1156
|
+
console.log(`[ProcedureService] Excluding procedures for draft practitioner ${practitionerId}`);
|
|
1157
|
+
return [];
|
|
1158
|
+
}
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
console.error(`[ProcedureService] Error checking practitioner status for ${practitionerId}:`, error);
|
|
1161
|
+
// On error, default to returning procedures to avoid breaking UI
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
return procedures;
|
|
1021
1166
|
}
|
|
1022
1167
|
|
|
1023
1168
|
/**
|
|
@@ -1285,11 +1430,13 @@ export class ProcedureService extends BaseService {
|
|
|
1285
1430
|
*
|
|
1286
1431
|
* @param pagination - Optional number of procedures per page (0 or undefined returns all)
|
|
1287
1432
|
* @param lastDoc - Optional last document for pagination (if continuing from a previous page)
|
|
1433
|
+
* @param excludeDraftPractitioners - Whether to exclude procedures from draft practitioners (default: true)
|
|
1288
1434
|
* @returns Object containing procedures array and the last document for pagination
|
|
1289
1435
|
*/
|
|
1290
1436
|
async getAllProcedures(
|
|
1291
1437
|
pagination?: number,
|
|
1292
1438
|
lastDoc?: any,
|
|
1439
|
+
excludeDraftPractitioners: boolean = true,
|
|
1293
1440
|
): Promise<{ procedures: Procedure[]; lastDoc: any }> {
|
|
1294
1441
|
try {
|
|
1295
1442
|
const proceduresCollection = collection(this.db, PROCEDURES_COLLECTION);
|
|
@@ -1316,7 +1463,7 @@ export class ProcedureService extends BaseService {
|
|
|
1316
1463
|
const proceduresSnapshot = await getDocs(proceduresQuery);
|
|
1317
1464
|
const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
|
|
1318
1465
|
|
|
1319
|
-
|
|
1466
|
+
let procedures = proceduresSnapshot.docs.map(doc => {
|
|
1320
1467
|
const data = doc.data() as Procedure;
|
|
1321
1468
|
return {
|
|
1322
1469
|
...data,
|
|
@@ -1324,6 +1471,11 @@ export class ProcedureService extends BaseService {
|
|
|
1324
1471
|
};
|
|
1325
1472
|
});
|
|
1326
1473
|
|
|
1474
|
+
// Filter out procedures from draft practitioners if requested
|
|
1475
|
+
if (excludeDraftPractitioners) {
|
|
1476
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1327
1479
|
return {
|
|
1328
1480
|
procedures,
|
|
1329
1481
|
lastDoc: lastVisible,
|
|
@@ -1494,6 +1646,9 @@ export class ProcedureService extends BaseService {
|
|
|
1494
1646
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
1495
1647
|
}
|
|
1496
1648
|
|
|
1649
|
+
// Filter out procedures from draft practitioners
|
|
1650
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
1651
|
+
|
|
1497
1652
|
const lastDoc =
|
|
1498
1653
|
querySnapshot.docs.length > 0
|
|
1499
1654
|
? querySnapshot.docs[querySnapshot.docs.length - 1]
|
|
@@ -1551,6 +1706,9 @@ export class ProcedureService extends BaseService {
|
|
|
1551
1706
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
1552
1707
|
}
|
|
1553
1708
|
|
|
1709
|
+
// Filter out procedures from draft practitioners
|
|
1710
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
1711
|
+
|
|
1554
1712
|
const lastDoc =
|
|
1555
1713
|
querySnapshot.docs.length > 0
|
|
1556
1714
|
? querySnapshot.docs[querySnapshot.docs.length - 1]
|
|
@@ -1660,6 +1818,10 @@ export class ProcedureService extends BaseService {
|
|
|
1660
1818
|
},
|
|
1661
1819
|
});
|
|
1662
1820
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
1821
|
+
|
|
1822
|
+
// Filter out procedures from draft practitioners
|
|
1823
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
1824
|
+
|
|
1663
1825
|
console.log('[PROCEDURE_SERVICE] After applyInMemoryFilters (Strategy 3):', {
|
|
1664
1826
|
procedureCount: procedures.length,
|
|
1665
1827
|
});
|
|
@@ -1700,6 +1862,9 @@ export class ProcedureService extends BaseService {
|
|
|
1700
1862
|
|
|
1701
1863
|
// Apply all client-side filters using centralized function
|
|
1702
1864
|
procedures = this.applyInMemoryFilters(procedures, filters);
|
|
1865
|
+
|
|
1866
|
+
// Filter out procedures from draft practitioners
|
|
1867
|
+
procedures = await this.filterDraftPractitionerProcedures(procedures);
|
|
1703
1868
|
|
|
1704
1869
|
const lastDoc =
|
|
1705
1870
|
querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|