@blackcode_sa/metaestetics-api 1.12.67 → 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.
@@ -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 getCountFromServer(q);
94
- counts[categoryId] = snapshot.data().count;
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
- return querySnapshot.docs.map(
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
- return querySnapshot.docs.map(
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;
@@ -322,6 +362,8 @@ export class SubcategoryService extends BaseService {
322
362
  const querySnapshot = await getDocs(q);
323
363
  if (querySnapshot.empty) return null;
324
364
  const doc = querySnapshot.docs[0];
365
+ // Exclude free-consultation subcategory
366
+ if (doc.id === EXCLUDED_SUBCATEGORY_ID) return null;
325
367
  return {
326
368
  id: doc.id,
327
369
  ...doc.data(),
@@ -375,6 +417,8 @@ export class SubcategoryService extends BaseService {
375
417
  if (snapshot.empty) break;
376
418
 
377
419
  for (const d of snapshot.docs) {
420
+ // Exclude free-consultation subcategory from CSV export
421
+ if (d.id === EXCLUDED_SUBCATEGORY_ID) continue;
378
422
  const subcategory = ({ id: d.id, ...d.data() } as unknown) as Subcategory;
379
423
  rows.push(this.subcategoryToCsvRow(subcategory));
380
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;
@@ -324,6 +364,8 @@ export class TechnologyService extends BaseService implements ITechnologyService
324
364
  const snapshot = await getDocs(q);
325
365
  if (snapshot.empty) return null;
326
366
  const doc = snapshot.docs[0];
367
+ // Exclude free-consultation-tech
368
+ if (doc.id === EXCLUDED_TECHNOLOGY_ID) return null;
327
369
  return {
328
370
  id: doc.id,
329
371
  ...doc.data(),
@@ -780,13 +822,14 @@ export class TechnologyService extends BaseService implements ITechnologyService
780
822
  orderBy('name'),
781
823
  );
782
824
  const snapshot = await getDocs(q);
783
- return snapshot.docs.map(
825
+ const technologies = snapshot.docs.map(
784
826
  doc =>
785
827
  ({
786
828
  id: doc.id,
787
829
  ...doc.data(),
788
830
  } as Technology),
789
831
  );
832
+ return this.filterExcludedTechnologies(technologies);
790
833
  }
791
834
 
792
835
  /**
@@ -806,13 +849,14 @@ export class TechnologyService extends BaseService implements ITechnologyService
806
849
  orderBy('name'),
807
850
  );
808
851
  const snapshot = await getDocs(q);
809
- return snapshot.docs.map(
852
+ const technologies = snapshot.docs.map(
810
853
  doc =>
811
854
  ({
812
855
  id: doc.id,
813
856
  ...doc.data(),
814
857
  } as Technology),
815
858
  );
859
+ return this.filterExcludedTechnologies(technologies);
816
860
  }
817
861
 
818
862
  /**
@@ -825,13 +869,14 @@ export class TechnologyService extends BaseService implements ITechnologyService
825
869
  orderBy('name'),
826
870
  );
827
871
  const snapshot = await getDocs(q);
828
- return snapshot.docs.map(
872
+ const technologies = snapshot.docs.map(
829
873
  doc =>
830
874
  ({
831
875
  id: doc.id,
832
876
  ...doc.data(),
833
877
  } as Technology),
834
878
  );
879
+ return this.filterExcludedTechnologies(technologies);
835
880
  }
836
881
 
837
882
  // ==========================================
@@ -1040,6 +1085,8 @@ export class TechnologyService extends BaseService implements ITechnologyService
1040
1085
  if (snapshot.empty) break;
1041
1086
 
1042
1087
  for (const d of snapshot.docs) {
1088
+ // Exclude free-consultation-tech from CSV export
1089
+ if (d.id === EXCLUDED_TECHNOLOGY_ID) continue;
1043
1090
  const technology = ({ id: d.id, ...d.data() } as unknown) as Technology;
1044
1091
  // Fetch products for this technology
1045
1092
  const productNames = await this.getProductNamesForTechnology(technology.id!);
@@ -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.getById(data.categoryId),
1501
- this.subcategoryService.getById(data.categoryId, data.subcategoryId),
1502
- this.technologyService.getById(data.technologyId),
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) {