@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 +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +168 -6
- package/dist/index.mjs +168 -6
- 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 +110 -3
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
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,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,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(
|
|
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
|
-
|
|
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
|
-
|
|
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;
|