@blackcode_sa/metaestetics-api 1.12.66 → 1.12.68
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 +73 -0
- package/dist/backoffice/index.d.ts +73 -0
- package/dist/backoffice/index.js +181 -18
- package/dist/backoffice/index.mjs +181 -20
- package/dist/index.d.mts +73 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +184 -21
- package/dist/index.mjs +184 -23
- package/package.json +1 -1
- package/src/backoffice/services/category.service.ts +72 -6
- package/src/backoffice/services/subcategory.service.ts +72 -6
- package/src/backoffice/services/technology.service.ts +74 -6
- package/src/backoffice/types/category.types.ts +5 -0
- package/src/backoffice/types/technology.types.ts +5 -0
- package/src/services/procedure/procedure.service.ts +3 -3
|
@@ -23,6 +23,12 @@ import {
|
|
|
23
23
|
import { BaseService } from "../../services/base.service";
|
|
24
24
|
import { CATEGORIES_COLLECTION } from "../types/category.types";
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* ID of the free-consultation subcategory that should be hidden from admin backoffice.
|
|
28
|
+
* This subcategory is used internally for free consultation procedures.
|
|
29
|
+
*/
|
|
30
|
+
const EXCLUDED_SUBCATEGORY_ID = 'free-consultation';
|
|
31
|
+
|
|
26
32
|
/**
|
|
27
33
|
* Servis za upravljanje podkategorijama procedura.
|
|
28
34
|
* Podkategorije su drugi nivo organizacije i pripadaju određenoj kategoriji.
|
|
@@ -37,6 +43,14 @@ import { CATEGORIES_COLLECTION } from "../types/category.types";
|
|
|
37
43
|
* });
|
|
38
44
|
*/
|
|
39
45
|
export class SubcategoryService extends BaseService {
|
|
46
|
+
/**
|
|
47
|
+
* Filters out excluded subcategories from a list.
|
|
48
|
+
* @param subcategories - List of subcategories to filter
|
|
49
|
+
* @returns Filtered list without excluded subcategories
|
|
50
|
+
*/
|
|
51
|
+
private filterExcludedSubcategories(subcategories: Subcategory[]): Subcategory[] {
|
|
52
|
+
return subcategories.filter(sub => sub.id !== EXCLUDED_SUBCATEGORY_ID);
|
|
53
|
+
}
|
|
40
54
|
/**
|
|
41
55
|
* Vraća referencu na Firestore kolekciju podkategorija za određenu kategoriju
|
|
42
56
|
* @param categoryId - ID roditeljske kategorije
|
|
@@ -90,8 +104,10 @@ export class SubcategoryService extends BaseService {
|
|
|
90
104
|
const categoryId = categoryDoc.id;
|
|
91
105
|
const subcategoriesRef = this.getSubcategoriesRef(categoryId);
|
|
92
106
|
const q = query(subcategoriesRef, where("isActive", "==", active));
|
|
93
|
-
const snapshot = await
|
|
94
|
-
|
|
107
|
+
const snapshot = await getDocs(q);
|
|
108
|
+
// Filter out excluded subcategory and count
|
|
109
|
+
const filteredDocs = snapshot.docs.filter(doc => doc.id !== EXCLUDED_SUBCATEGORY_ID);
|
|
110
|
+
counts[categoryId] = filteredDocs.length;
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
return counts;
|
|
@@ -129,8 +145,9 @@ export class SubcategoryService extends BaseService {
|
|
|
129
145
|
...doc.data(),
|
|
130
146
|
} as Subcategory)
|
|
131
147
|
);
|
|
148
|
+
const filteredSubcategories = this.filterExcludedSubcategories(subcategories);
|
|
132
149
|
const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
|
|
133
|
-
return { subcategories, lastVisible: newLastVisible };
|
|
150
|
+
return { subcategories: filteredSubcategories, lastVisible: newLastVisible };
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
/**
|
|
@@ -169,8 +186,9 @@ export class SubcategoryService extends BaseService {
|
|
|
169
186
|
...doc.data(),
|
|
170
187
|
} as Subcategory)
|
|
171
188
|
);
|
|
189
|
+
const filteredSubcategories = this.filterExcludedSubcategories(subcategories);
|
|
172
190
|
const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
|
|
173
|
-
return { subcategories, lastVisible: newLastVisible };
|
|
191
|
+
return { subcategories: filteredSubcategories, lastVisible: newLastVisible };
|
|
174
192
|
}
|
|
175
193
|
|
|
176
194
|
/**
|
|
@@ -184,13 +202,14 @@ export class SubcategoryService extends BaseService {
|
|
|
184
202
|
where("isActive", "==", true)
|
|
185
203
|
);
|
|
186
204
|
const querySnapshot = await getDocs(q);
|
|
187
|
-
|
|
205
|
+
const subcategories = querySnapshot.docs.map(
|
|
188
206
|
(doc) =>
|
|
189
207
|
({
|
|
190
208
|
id: doc.id,
|
|
191
209
|
...doc.data(),
|
|
192
210
|
} as Subcategory)
|
|
193
211
|
);
|
|
212
|
+
return this.filterExcludedSubcategories(subcategories);
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
/**
|
|
@@ -203,13 +222,14 @@ export class SubcategoryService extends BaseService {
|
|
|
203
222
|
where("isActive", "==", true)
|
|
204
223
|
);
|
|
205
224
|
const querySnapshot = await getDocs(q);
|
|
206
|
-
|
|
225
|
+
const subcategories = querySnapshot.docs.map(
|
|
207
226
|
(doc) =>
|
|
208
227
|
({
|
|
209
228
|
id: doc.id,
|
|
210
229
|
...doc.data(),
|
|
211
230
|
} as Subcategory)
|
|
212
231
|
);
|
|
232
|
+
return this.filterExcludedSubcategories(subcategories);
|
|
213
233
|
}
|
|
214
234
|
|
|
215
235
|
/**
|
|
@@ -297,6 +317,26 @@ export class SubcategoryService extends BaseService {
|
|
|
297
317
|
* @returns Podkategorija ili null ako ne postoji
|
|
298
318
|
*/
|
|
299
319
|
async getById(categoryId: string, subcategoryId: string) {
|
|
320
|
+
// Prevent access to excluded subcategory
|
|
321
|
+
if (subcategoryId === EXCLUDED_SUBCATEGORY_ID) return null;
|
|
322
|
+
|
|
323
|
+
const docRef = doc(this.getSubcategoriesRef(categoryId), subcategoryId);
|
|
324
|
+
const docSnap = await getDoc(docRef);
|
|
325
|
+
if (!docSnap.exists()) return null;
|
|
326
|
+
return {
|
|
327
|
+
id: docSnap.id,
|
|
328
|
+
...docSnap.data(),
|
|
329
|
+
} as Subcategory;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Internal method to get subcategory by ID without filtering.
|
|
334
|
+
* Used internally for consultation procedures.
|
|
335
|
+
* @param categoryId - ID of the category
|
|
336
|
+
* @param subcategoryId - ID of the subcategory to get
|
|
337
|
+
* @returns Subcategory or null if not found
|
|
338
|
+
*/
|
|
339
|
+
async getByIdInternal(categoryId: string, subcategoryId: string): Promise<Subcategory | null> {
|
|
300
340
|
const docRef = doc(this.getSubcategoriesRef(categoryId), subcategoryId);
|
|
301
341
|
const docSnap = await getDoc(docRef);
|
|
302
342
|
if (!docSnap.exists()) return null;
|
|
@@ -306,6 +346,30 @@ export class SubcategoryService extends BaseService {
|
|
|
306
346
|
} as Subcategory;
|
|
307
347
|
}
|
|
308
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Finds a subcategory by exact name match within a specific category.
|
|
351
|
+
* Used for CSV import matching.
|
|
352
|
+
* @param name - Exact name of the subcategory to find
|
|
353
|
+
* @param categoryId - ID of the category to search within
|
|
354
|
+
* @returns Subcategory if found, null otherwise
|
|
355
|
+
*/
|
|
356
|
+
async findByNameAndCategory(name: string, categoryId: string): Promise<Subcategory | null> {
|
|
357
|
+
const q = query(
|
|
358
|
+
this.getSubcategoriesRef(categoryId),
|
|
359
|
+
where('name', '==', name),
|
|
360
|
+
where('isActive', '==', true),
|
|
361
|
+
);
|
|
362
|
+
const querySnapshot = await getDocs(q);
|
|
363
|
+
if (querySnapshot.empty) return null;
|
|
364
|
+
const doc = querySnapshot.docs[0];
|
|
365
|
+
// Exclude free-consultation subcategory
|
|
366
|
+
if (doc.id === EXCLUDED_SUBCATEGORY_ID) return null;
|
|
367
|
+
return {
|
|
368
|
+
id: doc.id,
|
|
369
|
+
...doc.data(),
|
|
370
|
+
} as Subcategory;
|
|
371
|
+
}
|
|
372
|
+
|
|
309
373
|
/**
|
|
310
374
|
* Exports subcategories to CSV string, suitable for Excel/Sheets.
|
|
311
375
|
* Includes headers and optional UTF-8 BOM.
|
|
@@ -353,6 +417,8 @@ export class SubcategoryService extends BaseService {
|
|
|
353
417
|
if (snapshot.empty) break;
|
|
354
418
|
|
|
355
419
|
for (const d of snapshot.docs) {
|
|
420
|
+
// Exclude free-consultation subcategory from CSV export
|
|
421
|
+
if (d.id === EXCLUDED_SUBCATEGORY_ID) continue;
|
|
356
422
|
const subcategory = ({ id: d.id, ...d.data() } as unknown) as Subcategory;
|
|
357
423
|
rows.push(this.subcategoryToCsvRow(subcategory));
|
|
358
424
|
}
|
|
@@ -33,6 +33,12 @@ import { ProcedureFamily } from '../types/static/procedure-family.types';
|
|
|
33
33
|
import { Practitioner, PractitionerCertification } from '../../types/practitioner';
|
|
34
34
|
import { Product, PRODUCTS_COLLECTION } from '../types/product.types';
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* ID of the free-consultation-tech technology that should be hidden from admin backoffice.
|
|
38
|
+
* This technology is used internally for free consultation procedures.
|
|
39
|
+
*/
|
|
40
|
+
const EXCLUDED_TECHNOLOGY_ID = 'free-consultation-tech';
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* Default vrednosti za sertifikaciju
|
|
38
44
|
*/
|
|
@@ -45,6 +51,14 @@ const DEFAULT_CERTIFICATION_REQUIREMENT: CertificationRequirement = {
|
|
|
45
51
|
* Service for managing technologies.
|
|
46
52
|
*/
|
|
47
53
|
export class TechnologyService extends BaseService implements ITechnologyService {
|
|
54
|
+
/**
|
|
55
|
+
* Filters out excluded technologies from a list.
|
|
56
|
+
* @param technologies - List of technologies to filter
|
|
57
|
+
* @returns Filtered list without excluded technologies
|
|
58
|
+
*/
|
|
59
|
+
private filterExcludedTechnologies(technologies: Technology[]): Technology[] {
|
|
60
|
+
return technologies.filter(tech => tech.id !== EXCLUDED_TECHNOLOGY_ID);
|
|
61
|
+
}
|
|
48
62
|
/**
|
|
49
63
|
* Reference to the Firestore collection of technologies.
|
|
50
64
|
*/
|
|
@@ -100,6 +114,8 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
100
114
|
const snapshot = await getDocs(q);
|
|
101
115
|
const counts: Record<string, number> = {};
|
|
102
116
|
snapshot.docs.forEach(doc => {
|
|
117
|
+
// Exclude free-consultation-tech from counts
|
|
118
|
+
if (doc.id === EXCLUDED_TECHNOLOGY_ID) return;
|
|
103
119
|
const tech = doc.data() as Technology;
|
|
104
120
|
counts[tech.subcategoryId] = (counts[tech.subcategoryId] || 0) + 1;
|
|
105
121
|
});
|
|
@@ -116,6 +132,8 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
116
132
|
const snapshot = await getDocs(q);
|
|
117
133
|
const counts: Record<string, number> = {};
|
|
118
134
|
snapshot.docs.forEach(doc => {
|
|
135
|
+
// Exclude free-consultation-tech from counts
|
|
136
|
+
if (doc.id === EXCLUDED_TECHNOLOGY_ID) return;
|
|
119
137
|
const tech = doc.data() as Technology;
|
|
120
138
|
counts[tech.categoryId] = (counts[tech.categoryId] || 0) + 1;
|
|
121
139
|
});
|
|
@@ -151,8 +169,9 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
151
169
|
...doc.data(),
|
|
152
170
|
} as Technology),
|
|
153
171
|
);
|
|
172
|
+
const filteredTechnologies = this.filterExcludedTechnologies(technologies);
|
|
154
173
|
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
155
|
-
return { technologies, lastVisible: newLastVisible };
|
|
174
|
+
return { technologies: filteredTechnologies, lastVisible: newLastVisible };
|
|
156
175
|
}
|
|
157
176
|
|
|
158
177
|
/**
|
|
@@ -187,8 +206,9 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
187
206
|
...doc.data(),
|
|
188
207
|
} as Technology),
|
|
189
208
|
);
|
|
209
|
+
const filteredTechnologies = this.filterExcludedTechnologies(technologies);
|
|
190
210
|
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
191
|
-
return { technologies, lastVisible: newLastVisible };
|
|
211
|
+
return { technologies: filteredTechnologies, lastVisible: newLastVisible };
|
|
192
212
|
}
|
|
193
213
|
|
|
194
214
|
/**
|
|
@@ -223,8 +243,9 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
223
243
|
...doc.data(),
|
|
224
244
|
} as Technology),
|
|
225
245
|
);
|
|
246
|
+
const filteredTechnologies = this.filterExcludedTechnologies(technologies);
|
|
226
247
|
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
227
|
-
return { technologies, lastVisible: newLastVisible };
|
|
248
|
+
return { technologies: filteredTechnologies, lastVisible: newLastVisible };
|
|
228
249
|
}
|
|
229
250
|
|
|
230
251
|
/**
|
|
@@ -300,6 +321,25 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
300
321
|
* @returns The technology or null if it doesn't exist.
|
|
301
322
|
*/
|
|
302
323
|
async getById(id: string): Promise<Technology | null> {
|
|
324
|
+
// Prevent access to excluded technology
|
|
325
|
+
if (id === EXCLUDED_TECHNOLOGY_ID) return null;
|
|
326
|
+
|
|
327
|
+
const docRef = doc(this.technologiesRef, id);
|
|
328
|
+
const docSnap = await getDoc(docRef);
|
|
329
|
+
if (!docSnap.exists()) return null;
|
|
330
|
+
return {
|
|
331
|
+
id: docSnap.id,
|
|
332
|
+
...docSnap.data(),
|
|
333
|
+
} as Technology;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Internal method to get technology by ID without filtering.
|
|
338
|
+
* Used internally for consultation procedures.
|
|
339
|
+
* @param id - The ID of the requested technology
|
|
340
|
+
* @returns The technology or null if it doesn't exist
|
|
341
|
+
*/
|
|
342
|
+
async getByIdInternal(id: string): Promise<Technology | null> {
|
|
303
343
|
const docRef = doc(this.technologiesRef, id);
|
|
304
344
|
const docSnap = await getDoc(docRef);
|
|
305
345
|
if (!docSnap.exists()) return null;
|
|
@@ -309,6 +349,29 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
309
349
|
} as Technology;
|
|
310
350
|
}
|
|
311
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Finds a technology by exact name match.
|
|
354
|
+
* Used for CSV import duplicate detection.
|
|
355
|
+
* @param name - Exact name of the technology to find
|
|
356
|
+
* @returns Technology if found, null otherwise
|
|
357
|
+
*/
|
|
358
|
+
async findByName(name: string): Promise<Technology | null> {
|
|
359
|
+
const q = query(
|
|
360
|
+
this.technologiesRef,
|
|
361
|
+
where('name', '==', name),
|
|
362
|
+
where('isActive', '==', true),
|
|
363
|
+
);
|
|
364
|
+
const snapshot = await getDocs(q);
|
|
365
|
+
if (snapshot.empty) return null;
|
|
366
|
+
const doc = snapshot.docs[0];
|
|
367
|
+
// Exclude free-consultation-tech
|
|
368
|
+
if (doc.id === EXCLUDED_TECHNOLOGY_ID) return null;
|
|
369
|
+
return {
|
|
370
|
+
id: doc.id,
|
|
371
|
+
...doc.data(),
|
|
372
|
+
} as Technology;
|
|
373
|
+
}
|
|
374
|
+
|
|
312
375
|
/**
|
|
313
376
|
* Dodaje novi zahtev tehnologiji
|
|
314
377
|
* @param technologyId - ID tehnologije
|
|
@@ -759,13 +822,14 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
759
822
|
orderBy('name'),
|
|
760
823
|
);
|
|
761
824
|
const snapshot = await getDocs(q);
|
|
762
|
-
|
|
825
|
+
const technologies = snapshot.docs.map(
|
|
763
826
|
doc =>
|
|
764
827
|
({
|
|
765
828
|
id: doc.id,
|
|
766
829
|
...doc.data(),
|
|
767
830
|
} as Technology),
|
|
768
831
|
);
|
|
832
|
+
return this.filterExcludedTechnologies(technologies);
|
|
769
833
|
}
|
|
770
834
|
|
|
771
835
|
/**
|
|
@@ -785,13 +849,14 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
785
849
|
orderBy('name'),
|
|
786
850
|
);
|
|
787
851
|
const snapshot = await getDocs(q);
|
|
788
|
-
|
|
852
|
+
const technologies = snapshot.docs.map(
|
|
789
853
|
doc =>
|
|
790
854
|
({
|
|
791
855
|
id: doc.id,
|
|
792
856
|
...doc.data(),
|
|
793
857
|
} as Technology),
|
|
794
858
|
);
|
|
859
|
+
return this.filterExcludedTechnologies(technologies);
|
|
795
860
|
}
|
|
796
861
|
|
|
797
862
|
/**
|
|
@@ -804,13 +869,14 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
804
869
|
orderBy('name'),
|
|
805
870
|
);
|
|
806
871
|
const snapshot = await getDocs(q);
|
|
807
|
-
|
|
872
|
+
const technologies = snapshot.docs.map(
|
|
808
873
|
doc =>
|
|
809
874
|
({
|
|
810
875
|
id: doc.id,
|
|
811
876
|
...doc.data(),
|
|
812
877
|
} as Technology),
|
|
813
878
|
);
|
|
879
|
+
return this.filterExcludedTechnologies(technologies);
|
|
814
880
|
}
|
|
815
881
|
|
|
816
882
|
// ==========================================
|
|
@@ -1019,6 +1085,8 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
1019
1085
|
if (snapshot.empty) break;
|
|
1020
1086
|
|
|
1021
1087
|
for (const d of snapshot.docs) {
|
|
1088
|
+
// Exclude free-consultation-tech from CSV export
|
|
1089
|
+
if (d.id === EXCLUDED_TECHNOLOGY_ID) continue;
|
|
1022
1090
|
const technology = ({ id: d.id, ...d.data() } as unknown) as Technology;
|
|
1023
1091
|
// Fetch products for this technology
|
|
1024
1092
|
const productNames = await this.getProductNamesForTechnology(technology.id!);
|
|
@@ -59,4 +59,9 @@ export interface ICategoryService {
|
|
|
59
59
|
delete(id: string): Promise<void>;
|
|
60
60
|
reactivate(id: string): Promise<void>;
|
|
61
61
|
getById(id: string): Promise<Category | null>;
|
|
62
|
+
findByNameAndFamily(name: string, family: ProcedureFamily): Promise<Category | null>;
|
|
63
|
+
exportToCsv(options?: {
|
|
64
|
+
includeInactive?: boolean;
|
|
65
|
+
includeBom?: boolean;
|
|
66
|
+
}): Promise<string>;
|
|
62
67
|
}
|
|
@@ -160,4 +160,9 @@ export interface ITechnologyService {
|
|
|
160
160
|
getAllForFilterBySubcategory(subcategoryId: string): Promise<Technology[]>;
|
|
161
161
|
getAllForFilterBySubcategoryId(categoryId: string, subcategoryId: string): Promise<Technology[]>;
|
|
162
162
|
getAllForFilter(): Promise<Technology[]>;
|
|
163
|
+
findByName(name: string): Promise<Technology | null>;
|
|
164
|
+
exportToCsv(options?: {
|
|
165
|
+
includeInactive?: boolean;
|
|
166
|
+
includeBom?: boolean;
|
|
167
|
+
}): Promise<string>;
|
|
163
168
|
}
|
|
@@ -1497,9 +1497,9 @@ export class ProcedureService extends BaseService {
|
|
|
1497
1497
|
// Get references to related entities (Category, Subcategory, Technology)
|
|
1498
1498
|
// For consultation, we don't need a product
|
|
1499
1499
|
const [category, subcategory, technology] = await Promise.all([
|
|
1500
|
-
this.categoryService.
|
|
1501
|
-
this.subcategoryService.
|
|
1502
|
-
this.technologyService.
|
|
1500
|
+
this.categoryService.getByIdInternal(data.categoryId),
|
|
1501
|
+
this.subcategoryService.getByIdInternal(data.categoryId, data.subcategoryId),
|
|
1502
|
+
this.technologyService.getByIdInternal(data.technologyId),
|
|
1503
1503
|
]);
|
|
1504
1504
|
|
|
1505
1505
|
if (!category || !subcategory || !technology) {
|