@blackcode_sa/metaestetics-api 1.14.23 → 1.14.27
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/backoffice/index.d.mts +9 -4
- package/dist/backoffice/index.d.ts +9 -4
- package/dist/backoffice/index.js +18 -8
- package/dist/backoffice/index.mjs +18 -8
- package/dist/index.d.mts +12 -6
- package/dist/index.d.ts +12 -6
- package/dist/index.js +218 -151
- package/dist/index.mjs +197 -130
- package/package.json +4 -1
- package/src/backoffice/services/brand.service.ts +21 -4
- package/src/backoffice/types/brand.types.ts +2 -0
- package/src/services/auth/auth.service.ts +16 -5
- package/src/services/patient/patient.service.ts +6 -4
- package/src/services/patient/utils/clinic.utils.ts +78 -1
|
@@ -46,12 +46,18 @@ export class BrandService extends BaseService {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Gets a paginated list of active brands, optionally filtered by name.
|
|
49
|
+
* Gets a paginated list of active brands, optionally filtered by name and category.
|
|
50
50
|
* @param rowsPerPage - The number of brands to fetch.
|
|
51
51
|
* @param searchTerm - An optional string to filter brand names by (starts-with search).
|
|
52
52
|
* @param lastVisible - An optional document snapshot to use as a cursor for pagination.
|
|
53
|
+
* @param category - An optional category to filter brands by.
|
|
53
54
|
*/
|
|
54
|
-
async getAll(
|
|
55
|
+
async getAll(
|
|
56
|
+
rowsPerPage: number,
|
|
57
|
+
searchTerm?: string,
|
|
58
|
+
lastVisible?: any,
|
|
59
|
+
category?: string
|
|
60
|
+
) {
|
|
55
61
|
const constraints: QueryConstraint[] = [
|
|
56
62
|
where("isActive", "==", true),
|
|
57
63
|
orderBy("name_lowercase"),
|
|
@@ -65,6 +71,10 @@ export class BrandService extends BaseService {
|
|
|
65
71
|
);
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
if (category) {
|
|
75
|
+
constraints.push(where("category", "==", category));
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
if (lastVisible) {
|
|
69
79
|
constraints.push(startAfter(lastVisible));
|
|
70
80
|
}
|
|
@@ -87,10 +97,11 @@ export class BrandService extends BaseService {
|
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
/**
|
|
90
|
-
* Gets the total count of active brands, optionally filtered by name.
|
|
100
|
+
* Gets the total count of active brands, optionally filtered by name and category.
|
|
91
101
|
* @param searchTerm - An optional string to filter brand names by (starts-with search).
|
|
102
|
+
* @param category - An optional category to filter brands by.
|
|
92
103
|
*/
|
|
93
|
-
async getBrandsCount(searchTerm?: string) {
|
|
104
|
+
async getBrandsCount(searchTerm?: string, category?: string) {
|
|
94
105
|
const constraints: QueryConstraint[] = [where("isActive", "==", true)];
|
|
95
106
|
|
|
96
107
|
if (searchTerm) {
|
|
@@ -101,6 +112,10 @@ export class BrandService extends BaseService {
|
|
|
101
112
|
);
|
|
102
113
|
}
|
|
103
114
|
|
|
115
|
+
if (category) {
|
|
116
|
+
constraints.push(where("category", "==", category));
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
const q = query(this.getBrandsRef(), ...constraints);
|
|
105
120
|
const snapshot = await getCountFromServer(q);
|
|
106
121
|
return snapshot.data().count;
|
|
@@ -184,6 +199,7 @@ export class BrandService extends BaseService {
|
|
|
184
199
|
"id",
|
|
185
200
|
"name",
|
|
186
201
|
"manufacturer",
|
|
202
|
+
"category",
|
|
187
203
|
"website",
|
|
188
204
|
"description",
|
|
189
205
|
"isActive",
|
|
@@ -230,6 +246,7 @@ export class BrandService extends BaseService {
|
|
|
230
246
|
brand.id ?? "",
|
|
231
247
|
brand.name ?? "",
|
|
232
248
|
brand.manufacturer ?? "",
|
|
249
|
+
brand.category ?? "",
|
|
233
250
|
brand.website ?? "",
|
|
234
251
|
brand.description ?? "",
|
|
235
252
|
String(brand.isActive ?? ""),
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @property manufacturer - Naziv proizvođača
|
|
8
8
|
* @property description - Detaljan opis brenda i njegovih proizvoda
|
|
9
9
|
* @property website - Web stranica brenda
|
|
10
|
+
* @property category - Kategorija brenda (npr. "laser", "peeling", "injectables") - za filtriranje
|
|
10
11
|
* @property isActive - Da li je brend aktivan u sistemu
|
|
11
12
|
* @property createdAt - Datum kreiranja
|
|
12
13
|
* @property updatedAt - Datum poslednjeg ažuriranja
|
|
@@ -21,6 +22,7 @@ export interface Brand {
|
|
|
21
22
|
isActive: boolean;
|
|
22
23
|
website?: string;
|
|
23
24
|
description?: string;
|
|
25
|
+
category?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -45,7 +45,6 @@ import { FirebaseErrorCode } from '../../errors/firebase.errors';
|
|
|
45
45
|
import { FirebaseError } from '../../errors/firebase.errors';
|
|
46
46
|
import { BaseService } from '../base.service';
|
|
47
47
|
import { UserService } from '../user/user.service';
|
|
48
|
-
import { throws } from 'assert';
|
|
49
48
|
import {
|
|
50
49
|
ClinicGroup,
|
|
51
50
|
AdminToken,
|
|
@@ -639,11 +638,23 @@ export class AuthService extends BaseService {
|
|
|
639
638
|
}
|
|
640
639
|
|
|
641
640
|
const firebaseError = error as FirebaseError;
|
|
642
|
-
|
|
643
|
-
|
|
641
|
+
|
|
642
|
+
// Handle specific Firebase errors
|
|
643
|
+
switch (firebaseError.code) {
|
|
644
|
+
case FirebaseErrorCode.USER_NOT_FOUND:
|
|
645
|
+
throw AUTH_ERRORS.USER_NOT_FOUND;
|
|
646
|
+
case FirebaseErrorCode.INVALID_EMAIL:
|
|
647
|
+
throw AUTH_ERRORS.INVALID_EMAIL;
|
|
648
|
+
case FirebaseErrorCode.TOO_MANY_REQUESTS:
|
|
649
|
+
throw AUTH_ERRORS.TOO_MANY_REQUESTS;
|
|
650
|
+
case FirebaseErrorCode.NETWORK_ERROR:
|
|
651
|
+
throw AUTH_ERRORS.NETWORK_ERROR;
|
|
652
|
+
case FirebaseErrorCode.OPERATION_NOT_ALLOWED:
|
|
653
|
+
throw AUTH_ERRORS.OPERATION_NOT_ALLOWED;
|
|
654
|
+
default:
|
|
655
|
+
// Re-throw unknown errors as-is
|
|
656
|
+
throw error;
|
|
644
657
|
}
|
|
645
|
-
|
|
646
|
-
throw error;
|
|
647
658
|
}
|
|
648
659
|
}
|
|
649
660
|
|
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
getPatientsByPractitionerUtil,
|
|
87
87
|
getPatientsByPractitionerWithDetailsUtil,
|
|
88
88
|
getPatientsByClinicUtil,
|
|
89
|
+
getPatientsByClinicWithDetailsUtil,
|
|
89
90
|
createPatientTokenUtil,
|
|
90
91
|
validatePatientTokenUtil,
|
|
91
92
|
markPatientTokenAsUsedUtil,
|
|
@@ -763,13 +764,14 @@ export class PatientService extends BaseService {
|
|
|
763
764
|
}
|
|
764
765
|
|
|
765
766
|
/**
|
|
766
|
-
* Gets all patients associated with a specific clinic.
|
|
767
|
+
* Gets all patients associated with a specific clinic, including sensitive info.
|
|
768
|
+
* This merges data from PatientProfile and PatientSensitiveInfo subcollection.
|
|
767
769
|
*
|
|
768
770
|
* @param {string} clinicId - ID of the clinic whose patients to retrieve
|
|
769
771
|
* @param {Object} options - Optional parameters for pagination
|
|
770
772
|
* @param {number} options.limit - Maximum number of profiles to return
|
|
771
773
|
* @param {string} options.startAfter - The ID of the document to start after (for pagination)
|
|
772
|
-
* @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles
|
|
774
|
+
* @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles with merged sensitive info
|
|
773
775
|
*/
|
|
774
776
|
async getPatientsByClinic(
|
|
775
777
|
clinicId: string,
|
|
@@ -778,8 +780,8 @@ export class PatientService extends BaseService {
|
|
|
778
780
|
startAfter?: string;
|
|
779
781
|
},
|
|
780
782
|
): Promise<PatientProfile[]> {
|
|
781
|
-
console.log(`[PatientService.getPatientsByClinic] Fetching patients for clinic: ${clinicId}`);
|
|
782
|
-
return
|
|
783
|
+
console.log(`[PatientService.getPatientsByClinic] Fetching patients with details for clinic: ${clinicId}`);
|
|
784
|
+
return getPatientsByClinicWithDetailsUtil(this.db, clinicId, options);
|
|
783
785
|
}
|
|
784
786
|
|
|
785
787
|
/**
|
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
getDoc,
|
|
11
11
|
QueryConstraint,
|
|
12
12
|
} from "firebase/firestore";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
PatientProfile,
|
|
15
|
+
PATIENTS_COLLECTION,
|
|
16
|
+
PatientSensitiveInfo,
|
|
17
|
+
} from "../../../types/patient";
|
|
18
|
+
import { getSensitiveInfoDocRef } from "./docs.utils";
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Retrieves all patients associated with a specific clinic with pagination support.
|
|
@@ -78,3 +83,75 @@ export const getPatientsByClinicUtil = async (
|
|
|
78
83
|
);
|
|
79
84
|
}
|
|
80
85
|
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Retrieves all patients associated with a specific clinic, including their sensitive info.
|
|
89
|
+
* This merges data from PatientProfile and PatientSensitiveInfo subcollection.
|
|
90
|
+
*
|
|
91
|
+
* @param {Firestore} db - Firestore instance
|
|
92
|
+
* @param {string} clinicId - ID of the clinic whose patients to retrieve
|
|
93
|
+
* @param {Object} options - Optional parameters for pagination
|
|
94
|
+
* @param {number} options.limit - Maximum number of profiles to return
|
|
95
|
+
* @param {string} options.startAfter - The ID of the document to start after (for pagination)
|
|
96
|
+
* @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles with merged sensitive info
|
|
97
|
+
*/
|
|
98
|
+
export const getPatientsByClinicWithDetailsUtil = async (
|
|
99
|
+
db: Firestore,
|
|
100
|
+
clinicId: string,
|
|
101
|
+
options?: { limit?: number; startAfter?: string }
|
|
102
|
+
): Promise<PatientProfile[]> => {
|
|
103
|
+
try {
|
|
104
|
+
console.log(
|
|
105
|
+
`[getPatientsByClinicWithDetailsUtil] Fetching patients with details for clinic ID: ${clinicId} with options:`,
|
|
106
|
+
options
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// First, get all patient profiles for this clinic
|
|
110
|
+
const patientProfiles = await getPatientsByClinicUtil(db, clinicId, options);
|
|
111
|
+
|
|
112
|
+
// Then, fetch sensitive info for each patient and merge it
|
|
113
|
+
const patientsWithDetails: PatientProfile[] = await Promise.all(
|
|
114
|
+
patientProfiles.map(async (profile) => {
|
|
115
|
+
try {
|
|
116
|
+
const sensitiveInfoDoc = await getDoc(
|
|
117
|
+
getSensitiveInfoDocRef(db, profile.id)
|
|
118
|
+
);
|
|
119
|
+
const sensitiveInfo = sensitiveInfoDoc.exists()
|
|
120
|
+
? (sensitiveInfoDoc.data() as PatientSensitiveInfo)
|
|
121
|
+
: null;
|
|
122
|
+
|
|
123
|
+
// Merge sensitive info into profile (sensitive info takes precedence)
|
|
124
|
+
return {
|
|
125
|
+
...profile,
|
|
126
|
+
// Merge phoneNumber from sensitive info if not in profile
|
|
127
|
+
phoneNumber: profile.phoneNumber || sensitiveInfo?.phoneNumber || null,
|
|
128
|
+
// Merge dateOfBirth from sensitive info if not in profile
|
|
129
|
+
dateOfBirth: profile.dateOfBirth || sensitiveInfo?.dateOfBirth || null,
|
|
130
|
+
} as PatientProfile;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(
|
|
133
|
+
`[getPatientsByClinicWithDetailsUtil] Error fetching sensitive info for patient ${profile.id}:`,
|
|
134
|
+
error
|
|
135
|
+
);
|
|
136
|
+
// Return profile without sensitive info in case of error
|
|
137
|
+
return profile;
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
console.log(
|
|
143
|
+
`[getPatientsByClinicWithDetailsUtil] Found ${patientsWithDetails.length} patients with details for clinic ID: ${clinicId}`
|
|
144
|
+
);
|
|
145
|
+
return patientsWithDetails;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(
|
|
148
|
+
`[getPatientsByClinicWithDetailsUtil] Error fetching patients with details:`,
|
|
149
|
+
error
|
|
150
|
+
);
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Failed to retrieve patients with details: ${
|
|
153
|
+
error instanceof Error ? error.message : String(error)
|
|
154
|
+
}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
};
|