@blackcode_sa/metaestetics-api 1.14.77 → 1.14.79
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/admin/index.d.mts +5 -0
- package/dist/admin/index.d.ts +5 -0
- package/dist/admin/index.js +48 -6
- package/dist/admin/index.mjs +48 -6
- package/dist/index.js +7 -5
- package/dist/index.mjs +7 -5
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +59 -6
- package/src/admin/mailing/clinicWelcome/clinicWelcome.mailing.ts +1 -1
- package/src/services/__tests__/auth/auth.mock.test.ts +2 -2
- package/src/services/__tests__/auth/auth.setup.ts +6 -1
- package/src/services/__tests__/auth.service.test.ts +8 -44
- package/src/services/__tests__/base.service.test.ts +4 -45
- package/src/services/__tests__/user.service.test.ts +6 -4
- package/src/services/appointment/utils/appointment.utils.ts +0 -3
- package/src/services/appointment/utils/extended-procedure.utils.ts +1 -0
- package/src/services/auth/auth.v2.service.ts +7 -7
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +11 -33
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +21 -151
- package/src/services/clinic/__tests__/clinic.service.test.ts +17 -69
- package/src/services/clinic/utils/clinic-group.utils.ts +2 -2
- package/src/services/clinic/utils/clinic.utils.ts +28 -22
- package/src/services/clinic/utils/index.ts +0 -1
- package/src/services/notifications/__tests__/notification.service.test.ts +5 -5
- package/src/services/patient/__tests__/patient.service.test.ts +17 -25
- package/src/services/patient/utils/docs.utils.ts +1 -1
- package/src/services/user/user.v2.service.ts +4 -3
package/dist/admin/index.d.mts
CHANGED
|
@@ -3606,6 +3606,11 @@ declare class AppointmentAggregationService {
|
|
|
3606
3606
|
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
3607
3607
|
*/
|
|
3608
3608
|
private createPreAppointmentRequirementInstances;
|
|
3609
|
+
/**
|
|
3610
|
+
* Resolves an array of requirement items that may be string IDs or full Requirement objects.
|
|
3611
|
+
* String IDs are batch-fetched from the backoffice_requirements collection.
|
|
3612
|
+
*/
|
|
3613
|
+
private resolveRequirements;
|
|
3609
3614
|
/**
|
|
3610
3615
|
* Fetches post-requirements from a procedure document
|
|
3611
3616
|
* @param procedureId - The procedure ID to fetch requirements from
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -3606,6 +3606,11 @@ declare class AppointmentAggregationService {
|
|
|
3606
3606
|
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
3607
3607
|
*/
|
|
3608
3608
|
private createPreAppointmentRequirementInstances;
|
|
3609
|
+
/**
|
|
3610
|
+
* Resolves an array of requirement items that may be string IDs or full Requirement objects.
|
|
3611
|
+
* String IDs are batch-fetched from the backoffice_requirements collection.
|
|
3612
|
+
*/
|
|
3613
|
+
private resolveRequirements;
|
|
3609
3614
|
/**
|
|
3610
3615
|
* Fetches post-requirements from a procedure document
|
|
3611
3616
|
* @param procedureId - The procedure ID to fetch requirements from
|
package/dist/admin/index.js
CHANGED
|
@@ -603,6 +603,9 @@ var PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME = "patientRequirements";
|
|
|
603
603
|
// src/admin/aggregation/appointment/appointment.aggregation.service.ts
|
|
604
604
|
var admin6 = __toESM(require("firebase-admin"));
|
|
605
605
|
|
|
606
|
+
// src/backoffice/types/requirement.types.ts
|
|
607
|
+
var REQUIREMENTS_COLLECTION = "backoffice_requirements";
|
|
608
|
+
|
|
606
609
|
// src/types/procedure/index.ts
|
|
607
610
|
var PROCEDURES_COLLECTION = "procedures";
|
|
608
611
|
|
|
@@ -3605,9 +3608,12 @@ var AppointmentAggregationService = class {
|
|
|
3605
3608
|
const batch = this.db.batch();
|
|
3606
3609
|
let instancesCreatedCount = 0;
|
|
3607
3610
|
let createdInstances = [];
|
|
3611
|
+
const resolvedPreRequirements = await this.resolveRequirements(
|
|
3612
|
+
appointment.preProcedureRequirements
|
|
3613
|
+
);
|
|
3608
3614
|
Logger.info(
|
|
3609
|
-
`[AggService] Found ${
|
|
3610
|
-
|
|
3615
|
+
`[AggService] Found ${resolvedPreRequirements.length} pre-requirements to process: ${JSON.stringify(
|
|
3616
|
+
resolvedPreRequirements.map((r) => {
|
|
3611
3617
|
var _a2, _b;
|
|
3612
3618
|
return {
|
|
3613
3619
|
id: r.id,
|
|
@@ -3620,7 +3626,7 @@ var AppointmentAggregationService = class {
|
|
|
3620
3626
|
})
|
|
3621
3627
|
)}`
|
|
3622
3628
|
);
|
|
3623
|
-
for (const template of
|
|
3629
|
+
for (const template of resolvedPreRequirements) {
|
|
3624
3630
|
if (!template) {
|
|
3625
3631
|
Logger.warn(
|
|
3626
3632
|
`[AggService] Found null/undefined template in preProcedureRequirements array`
|
|
@@ -3818,6 +3824,41 @@ var AppointmentAggregationService = class {
|
|
|
3818
3824
|
throw error;
|
|
3819
3825
|
}
|
|
3820
3826
|
}
|
|
3827
|
+
/**
|
|
3828
|
+
* Resolves an array of requirement items that may be string IDs or full Requirement objects.
|
|
3829
|
+
* String IDs are batch-fetched from the backoffice_requirements collection.
|
|
3830
|
+
*/
|
|
3831
|
+
async resolveRequirements(items) {
|
|
3832
|
+
if (items.length === 0) return [];
|
|
3833
|
+
const resolved = [];
|
|
3834
|
+
const idsToFetch = [];
|
|
3835
|
+
for (const item of items) {
|
|
3836
|
+
if (typeof item === "string") {
|
|
3837
|
+
idsToFetch.push(item);
|
|
3838
|
+
} else if (item && typeof item === "object" && item.id) {
|
|
3839
|
+
resolved.push(item);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
if (idsToFetch.length > 0) {
|
|
3843
|
+
Logger.info(
|
|
3844
|
+
`[AggService] Resolving ${idsToFetch.length} requirement ID(s) from ${REQUIREMENTS_COLLECTION}`
|
|
3845
|
+
);
|
|
3846
|
+
const refs = idsToFetch.map(
|
|
3847
|
+
(id) => this.db.collection(REQUIREMENTS_COLLECTION).doc(id)
|
|
3848
|
+
);
|
|
3849
|
+
const snapshots = await this.db.getAll(...refs);
|
|
3850
|
+
for (const snap of snapshots) {
|
|
3851
|
+
if (snap.exists) {
|
|
3852
|
+
resolved.push({ id: snap.id, ...snap.data() });
|
|
3853
|
+
} else {
|
|
3854
|
+
Logger.warn(
|
|
3855
|
+
`[AggService] Requirement template '${snap.id}' not found in ${REQUIREMENTS_COLLECTION}`
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
return resolved;
|
|
3861
|
+
}
|
|
3821
3862
|
/**
|
|
3822
3863
|
* Fetches post-requirements from a procedure document
|
|
3823
3864
|
* @param procedureId - The procedure ID to fetch requirements from
|
|
@@ -3831,10 +3872,11 @@ var AppointmentAggregationService = class {
|
|
|
3831
3872
|
return [];
|
|
3832
3873
|
}
|
|
3833
3874
|
const procedure = procedureDoc.data();
|
|
3834
|
-
const
|
|
3835
|
-
if (
|
|
3875
|
+
const rawPostRequirements = procedure.postRequirements || [];
|
|
3876
|
+
if (rawPostRequirements.length === 0) {
|
|
3836
3877
|
return [];
|
|
3837
3878
|
}
|
|
3879
|
+
const postRequirements = await this.resolveRequirements(rawPostRequirements);
|
|
3838
3880
|
return postRequirements.map((req) => ({
|
|
3839
3881
|
requirement: req,
|
|
3840
3882
|
sourceProcedures: [
|
|
@@ -14325,7 +14367,7 @@ var ClinicWelcomeMailingService = class extends BaseMailingService {
|
|
|
14325
14367
|
*/
|
|
14326
14368
|
constructor(firestore19, mailgunClient) {
|
|
14327
14369
|
super(firestore19, mailgunClient);
|
|
14328
|
-
this.DEFAULT_DASHBOARD_URL = "https://
|
|
14370
|
+
this.DEFAULT_DASHBOARD_URL = "https://app.metaesthetics.net/dashboard";
|
|
14329
14371
|
this.DEFAULT_SUPPORT_EMAIL = "support@metaesthetics.net";
|
|
14330
14372
|
this.DEFAULT_SUBJECT = "Welcome to MetaEsthetics - Your Clinic Registration is Complete";
|
|
14331
14373
|
this.DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
|
package/dist/admin/index.mjs
CHANGED
|
@@ -526,6 +526,9 @@ var PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME = "patientRequirements";
|
|
|
526
526
|
// src/admin/aggregation/appointment/appointment.aggregation.service.ts
|
|
527
527
|
import * as admin6 from "firebase-admin";
|
|
528
528
|
|
|
529
|
+
// src/backoffice/types/requirement.types.ts
|
|
530
|
+
var REQUIREMENTS_COLLECTION = "backoffice_requirements";
|
|
531
|
+
|
|
529
532
|
// src/types/procedure/index.ts
|
|
530
533
|
var PROCEDURES_COLLECTION = "procedures";
|
|
531
534
|
|
|
@@ -3528,9 +3531,12 @@ var AppointmentAggregationService = class {
|
|
|
3528
3531
|
const batch = this.db.batch();
|
|
3529
3532
|
let instancesCreatedCount = 0;
|
|
3530
3533
|
let createdInstances = [];
|
|
3534
|
+
const resolvedPreRequirements = await this.resolveRequirements(
|
|
3535
|
+
appointment.preProcedureRequirements
|
|
3536
|
+
);
|
|
3531
3537
|
Logger.info(
|
|
3532
|
-
`[AggService] Found ${
|
|
3533
|
-
|
|
3538
|
+
`[AggService] Found ${resolvedPreRequirements.length} pre-requirements to process: ${JSON.stringify(
|
|
3539
|
+
resolvedPreRequirements.map((r) => {
|
|
3534
3540
|
var _a2, _b;
|
|
3535
3541
|
return {
|
|
3536
3542
|
id: r.id,
|
|
@@ -3543,7 +3549,7 @@ var AppointmentAggregationService = class {
|
|
|
3543
3549
|
})
|
|
3544
3550
|
)}`
|
|
3545
3551
|
);
|
|
3546
|
-
for (const template of
|
|
3552
|
+
for (const template of resolvedPreRequirements) {
|
|
3547
3553
|
if (!template) {
|
|
3548
3554
|
Logger.warn(
|
|
3549
3555
|
`[AggService] Found null/undefined template in preProcedureRequirements array`
|
|
@@ -3741,6 +3747,41 @@ var AppointmentAggregationService = class {
|
|
|
3741
3747
|
throw error;
|
|
3742
3748
|
}
|
|
3743
3749
|
}
|
|
3750
|
+
/**
|
|
3751
|
+
* Resolves an array of requirement items that may be string IDs or full Requirement objects.
|
|
3752
|
+
* String IDs are batch-fetched from the backoffice_requirements collection.
|
|
3753
|
+
*/
|
|
3754
|
+
async resolveRequirements(items) {
|
|
3755
|
+
if (items.length === 0) return [];
|
|
3756
|
+
const resolved = [];
|
|
3757
|
+
const idsToFetch = [];
|
|
3758
|
+
for (const item of items) {
|
|
3759
|
+
if (typeof item === "string") {
|
|
3760
|
+
idsToFetch.push(item);
|
|
3761
|
+
} else if (item && typeof item === "object" && item.id) {
|
|
3762
|
+
resolved.push(item);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
if (idsToFetch.length > 0) {
|
|
3766
|
+
Logger.info(
|
|
3767
|
+
`[AggService] Resolving ${idsToFetch.length} requirement ID(s) from ${REQUIREMENTS_COLLECTION}`
|
|
3768
|
+
);
|
|
3769
|
+
const refs = idsToFetch.map(
|
|
3770
|
+
(id) => this.db.collection(REQUIREMENTS_COLLECTION).doc(id)
|
|
3771
|
+
);
|
|
3772
|
+
const snapshots = await this.db.getAll(...refs);
|
|
3773
|
+
for (const snap of snapshots) {
|
|
3774
|
+
if (snap.exists) {
|
|
3775
|
+
resolved.push({ id: snap.id, ...snap.data() });
|
|
3776
|
+
} else {
|
|
3777
|
+
Logger.warn(
|
|
3778
|
+
`[AggService] Requirement template '${snap.id}' not found in ${REQUIREMENTS_COLLECTION}`
|
|
3779
|
+
);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
return resolved;
|
|
3784
|
+
}
|
|
3744
3785
|
/**
|
|
3745
3786
|
* Fetches post-requirements from a procedure document
|
|
3746
3787
|
* @param procedureId - The procedure ID to fetch requirements from
|
|
@@ -3754,10 +3795,11 @@ var AppointmentAggregationService = class {
|
|
|
3754
3795
|
return [];
|
|
3755
3796
|
}
|
|
3756
3797
|
const procedure = procedureDoc.data();
|
|
3757
|
-
const
|
|
3758
|
-
if (
|
|
3798
|
+
const rawPostRequirements = procedure.postRequirements || [];
|
|
3799
|
+
if (rawPostRequirements.length === 0) {
|
|
3759
3800
|
return [];
|
|
3760
3801
|
}
|
|
3802
|
+
const postRequirements = await this.resolveRequirements(rawPostRequirements);
|
|
3761
3803
|
return postRequirements.map((req) => ({
|
|
3762
3804
|
requirement: req,
|
|
3763
3805
|
sourceProcedures: [
|
|
@@ -14248,7 +14290,7 @@ var ClinicWelcomeMailingService = class extends BaseMailingService {
|
|
|
14248
14290
|
*/
|
|
14249
14291
|
constructor(firestore19, mailgunClient) {
|
|
14250
14292
|
super(firestore19, mailgunClient);
|
|
14251
|
-
this.DEFAULT_DASHBOARD_URL = "https://
|
|
14293
|
+
this.DEFAULT_DASHBOARD_URL = "https://app.metaesthetics.net/dashboard";
|
|
14252
14294
|
this.DEFAULT_SUPPORT_EMAIL = "support@metaesthetics.net";
|
|
14253
14295
|
this.DEFAULT_SUBJECT = "Welcome to MetaEsthetics - Your Clinic Registration is Complete";
|
|
14254
14296
|
this.DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
|
package/dist/index.js
CHANGED
|
@@ -9779,7 +9779,7 @@ var initSensitiveInfoDocIfNotExists = async (db, patientId, userRef) => {
|
|
|
9779
9779
|
(key, value) => value && typeof value === "object" && value.constructor && value.constructor.name === "Object" ? "[serverTimestamp]" : value
|
|
9780
9780
|
)
|
|
9781
9781
|
);
|
|
9782
|
-
await createSensitiveInfoUtil(db, defaultSensitiveInfo, userRef);
|
|
9782
|
+
await createSensitiveInfoUtil(db, defaultSensitiveInfo, userRef, []);
|
|
9783
9783
|
const verifyDoc = await (0, import_firestore21.getDoc)(sensitiveInfoRef);
|
|
9784
9784
|
console.log(
|
|
9785
9785
|
`[initSensitiveInfoDocIfNotExists] Verification - document exists: ${verifyDoc.exists()}`
|
|
@@ -14773,10 +14773,11 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
|
|
|
14773
14773
|
if (data.featuredPhotos && data.featuredPhotos.length > 0) {
|
|
14774
14774
|
console.log("[CLINIC] Processing featured photos update");
|
|
14775
14775
|
try {
|
|
14776
|
-
const
|
|
14776
|
+
const allPhotos = data.featuredPhotos;
|
|
14777
|
+
const dataUrlPhotos = allPhotos.filter(
|
|
14777
14778
|
(photo) => typeof photo === "string" && photo.startsWith("data:")
|
|
14778
14779
|
);
|
|
14779
|
-
const existingPhotos =
|
|
14780
|
+
const existingPhotos = allPhotos.filter(
|
|
14780
14781
|
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
14781
14782
|
);
|
|
14782
14783
|
if (dataUrlPhotos.length > 0) {
|
|
@@ -14809,9 +14810,10 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
|
|
|
14809
14810
|
try {
|
|
14810
14811
|
const updatedPhotosWithTags = [];
|
|
14811
14812
|
for (const photoWithTag of data.photosWithTags) {
|
|
14812
|
-
|
|
14813
|
+
const photoUrl = photoWithTag.url;
|
|
14814
|
+
if (photoUrl && photoUrl.startsWith("data:")) {
|
|
14813
14815
|
const uploadedUrl = await uploadPhoto(
|
|
14814
|
-
|
|
14816
|
+
photoUrl,
|
|
14815
14817
|
"clinics",
|
|
14816
14818
|
clinicId,
|
|
14817
14819
|
`tagged-${photoWithTag.tag}`,
|
package/dist/index.mjs
CHANGED
|
@@ -9742,7 +9742,7 @@ var initSensitiveInfoDocIfNotExists = async (db, patientId, userRef) => {
|
|
|
9742
9742
|
(key, value) => value && typeof value === "object" && value.constructor && value.constructor.name === "Object" ? "[serverTimestamp]" : value
|
|
9743
9743
|
)
|
|
9744
9744
|
);
|
|
9745
|
-
await createSensitiveInfoUtil(db, defaultSensitiveInfo, userRef);
|
|
9745
|
+
await createSensitiveInfoUtil(db, defaultSensitiveInfo, userRef, []);
|
|
9746
9746
|
const verifyDoc = await getDoc13(sensitiveInfoRef);
|
|
9747
9747
|
console.log(
|
|
9748
9748
|
`[initSensitiveInfoDocIfNotExists] Verification - document exists: ${verifyDoc.exists()}`
|
|
@@ -14849,10 +14849,11 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
|
|
|
14849
14849
|
if (data.featuredPhotos && data.featuredPhotos.length > 0) {
|
|
14850
14850
|
console.log("[CLINIC] Processing featured photos update");
|
|
14851
14851
|
try {
|
|
14852
|
-
const
|
|
14852
|
+
const allPhotos = data.featuredPhotos;
|
|
14853
|
+
const dataUrlPhotos = allPhotos.filter(
|
|
14853
14854
|
(photo) => typeof photo === "string" && photo.startsWith("data:")
|
|
14854
14855
|
);
|
|
14855
|
-
const existingPhotos =
|
|
14856
|
+
const existingPhotos = allPhotos.filter(
|
|
14856
14857
|
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
14857
14858
|
);
|
|
14858
14859
|
if (dataUrlPhotos.length > 0) {
|
|
@@ -14885,9 +14886,10 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
|
|
|
14885
14886
|
try {
|
|
14886
14887
|
const updatedPhotosWithTags = [];
|
|
14887
14888
|
for (const photoWithTag of data.photosWithTags) {
|
|
14888
|
-
|
|
14889
|
+
const photoUrl = photoWithTag.url;
|
|
14890
|
+
if (photoUrl && photoUrl.startsWith("data:")) {
|
|
14889
14891
|
const uploadedUrl = await uploadPhoto(
|
|
14890
|
-
|
|
14892
|
+
photoUrl,
|
|
14891
14893
|
"clinics",
|
|
14892
14894
|
clinicId,
|
|
14893
14895
|
`tagged-${photoWithTag.tag}`,
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '../../../types/patient/patient-requirements';
|
|
14
14
|
import {
|
|
15
15
|
Requirement as RequirementTemplate,
|
|
16
|
-
|
|
16
|
+
REQUIREMENTS_COLLECTION,
|
|
17
17
|
RequirementType,
|
|
18
18
|
TimeUnit, // Added import
|
|
19
19
|
} from '../../../backoffice/types/requirement.types';
|
|
@@ -671,12 +671,17 @@ export class AppointmentAggregationService {
|
|
|
671
671
|
// Store created instances for fallback direct creation if needed
|
|
672
672
|
let createdInstances = [];
|
|
673
673
|
|
|
674
|
+
// Resolve string IDs to full RequirementTemplate objects
|
|
675
|
+
const resolvedPreRequirements = await this.resolveRequirements(
|
|
676
|
+
appointment.preProcedureRequirements as any,
|
|
677
|
+
);
|
|
678
|
+
|
|
674
679
|
// Log more details about the pre-requirements
|
|
675
680
|
Logger.info(
|
|
676
681
|
`[AggService] Found ${
|
|
677
|
-
|
|
682
|
+
resolvedPreRequirements.length
|
|
678
683
|
} pre-requirements to process: ${JSON.stringify(
|
|
679
|
-
|
|
684
|
+
resolvedPreRequirements.map(r => ({
|
|
680
685
|
id: r.id,
|
|
681
686
|
name: r.name,
|
|
682
687
|
type: r.type,
|
|
@@ -687,7 +692,7 @@ export class AppointmentAggregationService {
|
|
|
687
692
|
)}`,
|
|
688
693
|
);
|
|
689
694
|
|
|
690
|
-
for (const template of
|
|
695
|
+
for (const template of resolvedPreRequirements) {
|
|
691
696
|
if (!template) {
|
|
692
697
|
Logger.warn(
|
|
693
698
|
`[AggService] Found null/undefined template in preProcedureRequirements array`,
|
|
@@ -933,6 +938,51 @@ export class AppointmentAggregationService {
|
|
|
933
938
|
}
|
|
934
939
|
}
|
|
935
940
|
|
|
941
|
+
/**
|
|
942
|
+
* Resolves an array of requirement items that may be string IDs or full Requirement objects.
|
|
943
|
+
* String IDs are batch-fetched from the backoffice_requirements collection.
|
|
944
|
+
*/
|
|
945
|
+
private async resolveRequirements(
|
|
946
|
+
items: (string | RequirementTemplate)[],
|
|
947
|
+
): Promise<RequirementTemplate[]> {
|
|
948
|
+
if (items.length === 0) return [];
|
|
949
|
+
|
|
950
|
+
const resolved: RequirementTemplate[] = [];
|
|
951
|
+
const idsToFetch: string[] = [];
|
|
952
|
+
|
|
953
|
+
for (const item of items) {
|
|
954
|
+
if (typeof item === 'string') {
|
|
955
|
+
idsToFetch.push(item);
|
|
956
|
+
} else if (item && typeof item === 'object' && item.id) {
|
|
957
|
+
resolved.push(item);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (idsToFetch.length > 0) {
|
|
962
|
+
Logger.info(
|
|
963
|
+
`[AggService] Resolving ${idsToFetch.length} requirement ID(s) from ${REQUIREMENTS_COLLECTION}`,
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
// Firestore getAll supports up to 500 docs per call
|
|
967
|
+
const refs = idsToFetch.map(id =>
|
|
968
|
+
this.db.collection(REQUIREMENTS_COLLECTION).doc(id),
|
|
969
|
+
);
|
|
970
|
+
const snapshots = await this.db.getAll(...refs);
|
|
971
|
+
|
|
972
|
+
for (const snap of snapshots) {
|
|
973
|
+
if (snap.exists) {
|
|
974
|
+
resolved.push({ id: snap.id, ...snap.data() } as RequirementTemplate);
|
|
975
|
+
} else {
|
|
976
|
+
Logger.warn(
|
|
977
|
+
`[AggService] Requirement template '${snap.id}' not found in ${REQUIREMENTS_COLLECTION}`,
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return resolved;
|
|
984
|
+
}
|
|
985
|
+
|
|
936
986
|
/**
|
|
937
987
|
* Fetches post-requirements from a procedure document
|
|
938
988
|
* @param procedureId - The procedure ID to fetch requirements from
|
|
@@ -949,12 +999,15 @@ export class AppointmentAggregationService {
|
|
|
949
999
|
}
|
|
950
1000
|
|
|
951
1001
|
const procedure = procedureDoc.data() as Procedure;
|
|
952
|
-
const
|
|
1002
|
+
const rawPostRequirements = procedure.postRequirements || [];
|
|
953
1003
|
|
|
954
|
-
if (
|
|
1004
|
+
if (rawPostRequirements.length === 0) {
|
|
955
1005
|
return [];
|
|
956
1006
|
}
|
|
957
1007
|
|
|
1008
|
+
// Resolve string IDs to full Requirement objects if needed
|
|
1009
|
+
const postRequirements = await this.resolveRequirements(rawPostRequirements as any);
|
|
1010
|
+
|
|
958
1011
|
return postRequirements.map(req => ({
|
|
959
1012
|
requirement: req,
|
|
960
1013
|
sourceProcedures: [
|
|
@@ -45,7 +45,7 @@ export interface ClinicWelcomeEmailData {
|
|
|
45
45
|
*/
|
|
46
46
|
export class ClinicWelcomeMailingService extends BaseMailingService {
|
|
47
47
|
private readonly DEFAULT_DASHBOARD_URL =
|
|
48
|
-
"https://
|
|
48
|
+
"https://app.metaesthetics.net/dashboard";
|
|
49
49
|
private readonly DEFAULT_SUPPORT_EMAIL = "support@metaesthetics.net";
|
|
50
50
|
private readonly DEFAULT_SUBJECT =
|
|
51
51
|
"Welcome to MetaEsthetics - Your Clinic Registration is Complete";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setupService, mockFirebaseUser } from "./auth.setup";
|
|
2
2
|
|
|
3
3
|
describe("Firebase Mock Setup", () => {
|
|
4
4
|
beforeEach(() => {
|
|
5
|
-
|
|
5
|
+
setupService();
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
it("should properly initialize mock Firebase instance", async () => {
|
|
@@ -288,6 +288,11 @@ export const getMockUser = () => ({
|
|
|
288
288
|
|
|
289
289
|
// Setup service
|
|
290
290
|
export const setupService = () => {
|
|
291
|
-
const service = new AuthService(
|
|
291
|
+
const service = new AuthService(
|
|
292
|
+
mockDb as unknown as Firestore,
|
|
293
|
+
mockAuth as unknown as Auth,
|
|
294
|
+
{} as FirebaseApp,
|
|
295
|
+
mockUserService
|
|
296
|
+
);
|
|
292
297
|
return service;
|
|
293
298
|
};
|
|
@@ -121,6 +121,10 @@ jest.mock("../../config/firebase", () => ({
|
|
|
121
121
|
|
|
122
122
|
import { AuthService } from "../auth/auth.service";
|
|
123
123
|
import { User as FirebaseUser } from "firebase/auth";
|
|
124
|
+
import { Firestore } from "firebase/firestore";
|
|
125
|
+
import { Auth } from "firebase/auth";
|
|
126
|
+
import { FirebaseApp } from "firebase/app";
|
|
127
|
+
import { UserService } from "../user/user.service";
|
|
124
128
|
import { UserRole } from "../../types";
|
|
125
129
|
|
|
126
130
|
const mockFacebookUser = {
|
|
@@ -150,8 +154,10 @@ describe("AuthService", () => {
|
|
|
150
154
|
let service: AuthService;
|
|
151
155
|
|
|
152
156
|
beforeEach(async () => {
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
const mockDb = {} as Firestore;
|
|
158
|
+
const mockApp = {} as FirebaseApp;
|
|
159
|
+
const mockUserService = {} as UserService;
|
|
160
|
+
service = new AuthService(mockDb, mockAuth as unknown as Auth, mockApp, mockUserService);
|
|
155
161
|
});
|
|
156
162
|
|
|
157
163
|
afterEach(() => {
|
|
@@ -243,48 +249,6 @@ describe("AuthService", () => {
|
|
|
243
249
|
});
|
|
244
250
|
});
|
|
245
251
|
|
|
246
|
-
describe("signInWithFacebook", () => {
|
|
247
|
-
it("should sign in user with Facebook", async () => {
|
|
248
|
-
const expectedUser = {
|
|
249
|
-
uid: "facebook-uid",
|
|
250
|
-
email: "facebook@example.com",
|
|
251
|
-
roles: [UserRole.PATIENT],
|
|
252
|
-
isAnonymous: false,
|
|
253
|
-
createdAt: expect.any(Date),
|
|
254
|
-
updatedAt: expect.any(Date),
|
|
255
|
-
lastLoginAt: expect.any(Date),
|
|
256
|
-
patientProfile: null,
|
|
257
|
-
practitionerProfile: null,
|
|
258
|
-
adminProfile: null,
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
const result = await service.signInWithFacebook();
|
|
262
|
-
|
|
263
|
-
expect(result).toEqual(expectedUser);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe("signInWithApple", () => {
|
|
268
|
-
it("should sign in user with Apple", async () => {
|
|
269
|
-
const expectedUser = {
|
|
270
|
-
uid: "apple-uid",
|
|
271
|
-
email: "apple@example.com",
|
|
272
|
-
roles: [UserRole.PATIENT],
|
|
273
|
-
isAnonymous: false,
|
|
274
|
-
createdAt: expect.any(Date),
|
|
275
|
-
updatedAt: expect.any(Date),
|
|
276
|
-
lastLoginAt: expect.any(Date),
|
|
277
|
-
patientProfile: null,
|
|
278
|
-
practitionerProfile: null,
|
|
279
|
-
adminProfile: null,
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const result = await service.signInWithApple();
|
|
283
|
-
|
|
284
|
-
expect(result).toEqual(expectedUser);
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
252
|
describe("signInAnonymously", () => {
|
|
289
253
|
it("should sign in user anonymously", async () => {
|
|
290
254
|
const expectedUser = {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { BaseService } from "../base.service";
|
|
2
|
-
import { getFirebaseInstance } from "../../config/firebase";
|
|
3
2
|
import { Auth } from "firebase/auth";
|
|
4
3
|
import { Firestore } from "firebase/firestore";
|
|
5
|
-
|
|
6
|
-
// Mock Firebase config
|
|
7
|
-
jest.mock("../../config/firebase");
|
|
4
|
+
import { FirebaseApp } from "firebase/app";
|
|
8
5
|
|
|
9
6
|
// Test implementacija BaseService klase
|
|
10
7
|
class TestService extends BaseService {
|
|
@@ -16,62 +13,24 @@ class TestService extends BaseService {
|
|
|
16
13
|
public getAuth(): Auth {
|
|
17
14
|
return this.auth;
|
|
18
15
|
}
|
|
19
|
-
|
|
20
|
-
// Expose initialize method for testing
|
|
21
|
-
public async testEnsureInitialized() {
|
|
22
|
-
await this.ensureInitialized();
|
|
23
|
-
}
|
|
24
16
|
}
|
|
25
17
|
|
|
26
18
|
describe("BaseService", () => {
|
|
27
19
|
let service: TestService;
|
|
28
20
|
const mockDb = {} as Firestore;
|
|
29
21
|
const mockAuth = {} as Auth;
|
|
22
|
+
const mockApp = {} as FirebaseApp;
|
|
30
23
|
|
|
31
24
|
beforeEach(() => {
|
|
32
25
|
jest.clearAllMocks();
|
|
33
|
-
(getFirebaseInstance as jest.Mock).mockResolvedValue({
|
|
34
|
-
db: mockDb,
|
|
35
|
-
auth: mockAuth,
|
|
36
|
-
});
|
|
37
26
|
});
|
|
38
27
|
|
|
39
28
|
describe("initialization", () => {
|
|
40
|
-
it("treba da inicijalizuje Firebase servise",
|
|
41
|
-
service = new TestService();
|
|
42
|
-
await service.testEnsureInitialized();
|
|
29
|
+
it("treba da inicijalizuje Firebase servise", () => {
|
|
30
|
+
service = new TestService(mockDb, mockAuth, mockApp);
|
|
43
31
|
|
|
44
|
-
expect(getFirebaseInstance).toHaveBeenCalled();
|
|
45
32
|
expect(service.getDb()).toBe(mockDb);
|
|
46
33
|
expect(service.getAuth()).toBe(mockAuth);
|
|
47
34
|
});
|
|
48
|
-
|
|
49
|
-
it("treba da hendla grešku pri inicijalizaciji", async () => {
|
|
50
|
-
const mockError = new Error("Firebase init error");
|
|
51
|
-
(getFirebaseInstance as jest.Mock).mockRejectedValue(mockError);
|
|
52
|
-
|
|
53
|
-
service = new TestService();
|
|
54
|
-
|
|
55
|
-
await expect(service.testEnsureInitialized()).rejects.toThrow(mockError);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("treba da zadrži grešku inicijalizacije i baca je pri svakom pozivu", async () => {
|
|
59
|
-
const mockError = new Error("Firebase init error");
|
|
60
|
-
(getFirebaseInstance as jest.Mock).mockRejectedValue(mockError);
|
|
61
|
-
|
|
62
|
-
service = new TestService();
|
|
63
|
-
|
|
64
|
-
// Prvi pokušaj
|
|
65
|
-
await expect(service.testEnsureInitialized()).rejects.toThrow(mockError);
|
|
66
|
-
|
|
67
|
-
// Firebase je sada uspešno inicijalizovan
|
|
68
|
-
(getFirebaseInstance as jest.Mock).mockResolvedValue({
|
|
69
|
-
db: mockDb,
|
|
70
|
-
auth: mockAuth,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Ali i dalje treba da dobijemo originalnu grešku
|
|
74
|
-
await expect(service.testEnsureInitialized()).rejects.toThrow(mockError);
|
|
75
|
-
});
|
|
76
35
|
});
|
|
77
36
|
});
|
|
@@ -131,9 +131,11 @@ describe("UserService", () => {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
userService = new UserService(
|
|
134
|
+
mockDb,
|
|
134
135
|
mockAuth,
|
|
135
|
-
|
|
136
|
-
new
|
|
136
|
+
{} as any,
|
|
137
|
+
new PatientService(mockDb, mockAuth, {} as any),
|
|
138
|
+
new ClinicAdminService(mockDb, mockAuth, {} as any)
|
|
137
139
|
);
|
|
138
140
|
});
|
|
139
141
|
|
|
@@ -460,7 +462,7 @@ describe("UserService", () => {
|
|
|
460
462
|
});
|
|
461
463
|
});
|
|
462
464
|
|
|
463
|
-
describe("
|
|
465
|
+
describe("removeRoleAndProfile", () => {
|
|
464
466
|
it("treba da ukloni ulogu korisniku", async () => {
|
|
465
467
|
const userWithMultipleRoles = {
|
|
466
468
|
...mockUser,
|
|
@@ -487,7 +489,7 @@ describe("UserService", () => {
|
|
|
487
489
|
data: () => updatedUser,
|
|
488
490
|
});
|
|
489
491
|
|
|
490
|
-
await userService.
|
|
492
|
+
await userService.removeRoleAndProfile("test-uid", UserRole.PATIENT);
|
|
491
493
|
|
|
492
494
|
// Provera da li je pozvan updateDoc sa ispravnim parametrima
|
|
493
495
|
expect(updateDoc).toHaveBeenCalledWith(
|
|
@@ -33,9 +33,6 @@ import { PATIENTS_COLLECTION } from '../../../types/patient';
|
|
|
33
33
|
import { CLINICS_COLLECTION } from '../../../types/clinic';
|
|
34
34
|
import { BlockingCondition } from '../../../backoffice/types/static/blocking-condition.types';
|
|
35
35
|
import { Requirement } from '../../../backoffice/types/requirement.types';
|
|
36
|
-
import { PRACTITIONERS_COLLECTION } from '../../../types/practitioner';
|
|
37
|
-
import { CLINICS_COLLECTION } from '../../../types/clinic';
|
|
38
|
-
import { PATIENTS_COLLECTION } from '../../../types/patient';
|
|
39
36
|
import { PROCEDURES_COLLECTION } from '../../../types/procedure';
|
|
40
37
|
import { Technology, TECHNOLOGIES_COLLECTION } from '../../../backoffice/types/technology.types';
|
|
41
38
|
import type { ContraindicationDynamic } from '../../../backoffice';
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ExtendedProcedureInfo,
|
|
5
5
|
AppointmentProductMetadata,
|
|
6
6
|
APPOINTMENTS_COLLECTION,
|
|
7
|
+
LinkedFormInfo,
|
|
7
8
|
} from '../../../types/appointment';
|
|
8
9
|
import { getAppointmentOrThrow, initializeMetadata } from './zone-management.utils';
|
|
9
10
|
import { PROCEDURES_COLLECTION } from '../../../types/procedure';
|