@blackcode_sa/metaestetics-api 1.11.2 → 1.12.0

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.
Files changed (49) hide show
  1. package/dist/admin/index.d.mts +331 -318
  2. package/dist/admin/index.d.ts +331 -318
  3. package/dist/backoffice/index.d.mts +1166 -430
  4. package/dist/backoffice/index.d.ts +1166 -430
  5. package/dist/backoffice/index.js +1128 -245
  6. package/dist/backoffice/index.mjs +1119 -209
  7. package/dist/index.d.mts +4429 -4034
  8. package/dist/index.d.ts +4429 -4034
  9. package/dist/index.js +1644 -666
  10. package/dist/index.mjs +1408 -402
  11. package/package.json +1 -1
  12. package/src/backoffice/expo-safe/index.ts +3 -0
  13. package/src/backoffice/services/README.md +40 -0
  14. package/src/backoffice/services/brand.service.ts +85 -6
  15. package/src/backoffice/services/category.service.ts +92 -10
  16. package/src/backoffice/services/constants.service.ts +308 -0
  17. package/src/backoffice/services/documentation-template.service.ts +56 -2
  18. package/src/backoffice/services/index.ts +1 -0
  19. package/src/backoffice/services/product.service.ts +126 -5
  20. package/src/backoffice/services/requirement.service.ts +13 -0
  21. package/src/backoffice/services/subcategory.service.ts +184 -13
  22. package/src/backoffice/services/technology.service.ts +344 -129
  23. package/src/backoffice/types/admin-constants.types.ts +69 -0
  24. package/src/backoffice/types/brand.types.ts +1 -0
  25. package/src/backoffice/types/index.ts +1 -0
  26. package/src/backoffice/types/product.types.ts +31 -4
  27. package/src/backoffice/types/static/contraindication.types.ts +1 -0
  28. package/src/backoffice/types/static/treatment-benefit.types.ts +1 -0
  29. package/src/backoffice/types/technology.types.ts +113 -4
  30. package/src/backoffice/validations/schemas.ts +35 -9
  31. package/src/services/appointment/appointment.service.ts +0 -5
  32. package/src/services/appointment/utils/appointment.utils.ts +124 -113
  33. package/src/services/base.service.ts +10 -3
  34. package/src/services/documentation-templates/documentation-template.service.ts +116 -0
  35. package/src/services/media/media.service.ts +2 -2
  36. package/src/services/procedure/procedure.service.ts +436 -234
  37. package/src/types/appointment/index.ts +4 -3
  38. package/src/types/clinic/index.ts +1 -6
  39. package/src/types/patient/medical-info.types.ts +3 -3
  40. package/src/types/procedure/index.ts +20 -17
  41. package/src/validations/appointment.schema.ts +1 -0
  42. package/src/validations/clinic.schema.ts +1 -6
  43. package/src/validations/patient/medical-info.schema.ts +7 -2
  44. package/src/backoffice/services/__tests__/brand.service.test.ts +0 -196
  45. package/src/backoffice/services/__tests__/category.service.test.ts +0 -201
  46. package/src/backoffice/services/__tests__/product.service.test.ts +0 -358
  47. package/src/backoffice/services/__tests__/requirement.service.test.ts +0 -226
  48. package/src/backoffice/services/__tests__/subcategory.service.test.ts +0 -181
  49. package/src/backoffice/services/__tests__/technology.service.test.ts +0 -1097
@@ -7,7 +7,11 @@ import {
7
7
  getDocs,
8
8
  query,
9
9
  updateDoc,
10
- where
10
+ where,
11
+ limit,
12
+ orderBy,
13
+ startAfter,
14
+ getCountFromServer
11
15
  } from "firebase/firestore";
12
16
 
13
17
  // src/backoffice/types/brand.types.ts
@@ -16,11 +20,13 @@ var BRANDS_COLLECTION = "brands";
16
20
  // src/services/base.service.ts
17
21
  import { getStorage } from "firebase/storage";
18
22
  var BaseService = class {
19
- constructor(db, auth, app) {
23
+ constructor(db, auth, app, storage) {
20
24
  this.db = db;
21
25
  this.auth = auth;
22
26
  this.app = app;
23
- this.storage = getStorage(app);
27
+ if (app) {
28
+ this.storage = storage || getStorage(app);
29
+ }
24
30
  }
25
31
  /**
26
32
  * Generiše jedinstveni ID za dokumente
@@ -53,6 +59,7 @@ var BrandService = class extends BaseService {
53
59
  const now = /* @__PURE__ */ new Date();
54
60
  const newBrand = {
55
61
  ...brand,
62
+ name_lowercase: brand.name.toLowerCase(),
56
63
  createdAt: now,
57
64
  updatedAt: now,
58
65
  isActive: true
@@ -61,15 +68,69 @@ var BrandService = class extends BaseService {
61
68
  return { id: docRef.id, ...newBrand };
62
69
  }
63
70
  /**
64
- * Gets all active brands
71
+ * Gets a paginated list of active brands, optionally filtered by name.
72
+ * @param rowsPerPage - The number of brands to fetch.
73
+ * @param searchTerm - An optional string to filter brand names by (starts-with search).
74
+ * @param lastVisible - An optional document snapshot to use as a cursor for pagination.
65
75
  */
66
- async getAll() {
67
- const q = query(this.getBrandsRef(), where("isActive", "==", true));
76
+ async getAll(rowsPerPage, searchTerm, lastVisible) {
77
+ const constraints = [
78
+ where("isActive", "==", true),
79
+ orderBy("name_lowercase")
80
+ ];
81
+ if (searchTerm) {
82
+ const lowercasedSearchTerm = searchTerm.toLowerCase();
83
+ constraints.push(where("name_lowercase", ">=", lowercasedSearchTerm));
84
+ constraints.push(
85
+ where("name_lowercase", "<=", lowercasedSearchTerm + "\uF8FF")
86
+ );
87
+ }
88
+ if (lastVisible) {
89
+ constraints.push(startAfter(lastVisible));
90
+ }
91
+ constraints.push(limit(rowsPerPage));
92
+ const q = query(this.getBrandsRef(), ...constraints);
93
+ const snapshot = await getDocs(q);
94
+ const brands = snapshot.docs.map(
95
+ (doc11) => ({
96
+ id: doc11.id,
97
+ ...doc11.data()
98
+ })
99
+ );
100
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
101
+ return { brands, lastVisible: newLastVisible };
102
+ }
103
+ /**
104
+ * Gets the total count of active brands, optionally filtered by name.
105
+ * @param searchTerm - An optional string to filter brand names by (starts-with search).
106
+ */
107
+ async getBrandsCount(searchTerm) {
108
+ const constraints = [where("isActive", "==", true)];
109
+ if (searchTerm) {
110
+ const lowercasedSearchTerm = searchTerm.toLowerCase();
111
+ constraints.push(where("name_lowercase", ">=", lowercasedSearchTerm));
112
+ constraints.push(
113
+ where("name_lowercase", "<=", lowercasedSearchTerm + "\uF8FF")
114
+ );
115
+ }
116
+ const q = query(this.getBrandsRef(), ...constraints);
117
+ const snapshot = await getCountFromServer(q);
118
+ return snapshot.data().count;
119
+ }
120
+ /**
121
+ * Gets all active brands for filter dropdowns (not paginated).
122
+ */
123
+ async getAllForFilter() {
124
+ const q = query(
125
+ this.getBrandsRef(),
126
+ where("isActive", "==", true),
127
+ orderBy("name")
128
+ );
68
129
  const snapshot = await getDocs(q);
69
130
  return snapshot.docs.map(
70
- (doc10) => ({
71
- id: doc10.id,
72
- ...doc10.data()
131
+ (doc11) => ({
132
+ id: doc11.id,
133
+ ...doc11.data()
73
134
  })
74
135
  );
75
136
  }
@@ -81,6 +142,9 @@ var BrandService = class extends BaseService {
81
142
  ...brand,
82
143
  updatedAt: /* @__PURE__ */ new Date()
83
144
  };
145
+ if (brand.name) {
146
+ updateData.name_lowercase = brand.name.toLowerCase();
147
+ }
84
148
  const docRef = doc(this.getBrandsRef(), brandId);
85
149
  await updateDoc(docRef, updateData);
86
150
  return this.getById(brandId);
@@ -112,9 +176,13 @@ import {
112
176
  addDoc as addDoc2,
113
177
  collection as collection2,
114
178
  doc as doc2,
179
+ getCountFromServer as getCountFromServer2,
115
180
  getDoc as getDoc2,
116
181
  getDocs as getDocs2,
182
+ limit as limit2,
183
+ orderBy as orderBy2,
117
184
  query as query2,
185
+ startAfter as startAfter2,
118
186
  updateDoc as updateDoc2,
119
187
  where as where2
120
188
  } from "firebase/firestore";
@@ -122,6 +190,13 @@ import {
122
190
  // src/backoffice/types/category.types.ts
123
191
  var CATEGORIES_COLLECTION = "backoffice_categories";
124
192
 
193
+ // src/backoffice/types/static/procedure-family.types.ts
194
+ var ProcedureFamily = /* @__PURE__ */ ((ProcedureFamily2) => {
195
+ ProcedureFamily2["AESTHETICS"] = "aesthetics";
196
+ ProcedureFamily2["SURGERY"] = "surgery";
197
+ return ProcedureFamily2;
198
+ })(ProcedureFamily || {});
199
+
125
200
  // src/backoffice/services/category.service.ts
126
201
  var CategoryService = class extends BaseService {
127
202
  /**
@@ -147,37 +222,87 @@ var CategoryService = class extends BaseService {
147
222
  return { id: docRef.id, ...newCategory };
148
223
  }
149
224
  /**
150
- * Vraća sve aktivne kategorije
151
- * @returns Lista aktivnih kategorija
225
+ * Returns counts of categories for each family.
226
+ * @param active - Whether to count active or inactive categories.
227
+ * @returns A record mapping family to category count.
152
228
  */
153
- async getAll() {
229
+ async getCategoryCounts(active = true) {
230
+ const counts = {};
231
+ const families = Object.values(ProcedureFamily);
232
+ for (const family of families) {
233
+ const q = query2(
234
+ this.categoriesRef,
235
+ where2("family", "==", family),
236
+ where2("isActive", "==", active)
237
+ );
238
+ const snapshot = await getCountFromServer2(q);
239
+ counts[family] = snapshot.data().count;
240
+ }
241
+ return counts;
242
+ }
243
+ /**
244
+ * Vraća sve kategorije za potrebe filtera (bez paginacije)
245
+ * @returns Lista svih aktivnih kategorija
246
+ */
247
+ async getAllForFilter() {
154
248
  const q = query2(this.categoriesRef, where2("isActive", "==", true));
155
249
  const snapshot = await getDocs2(q);
156
250
  return snapshot.docs.map(
157
- (doc10) => ({
158
- id: doc10.id,
159
- ...doc10.data()
251
+ (doc11) => ({
252
+ id: doc11.id,
253
+ ...doc11.data()
160
254
  })
161
255
  );
162
256
  }
163
257
  /**
164
- * Vraća sve aktivne kategorije za određenu familiju procedura
258
+ * Vraća sve kategorije sa paginacijom
259
+ * @param options - Pagination and filter options
260
+ * @returns Lista kategorija i poslednji vidljiv dokument
261
+ */
262
+ async getAll(options = {}) {
263
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
264
+ const constraints = [
265
+ where2("isActive", "==", active),
266
+ orderBy2("name"),
267
+ queryLimit ? limit2(queryLimit) : void 0,
268
+ lastVisible ? startAfter2(lastVisible) : void 0
269
+ ].filter((c) => !!c);
270
+ const q = query2(this.categoriesRef, ...constraints);
271
+ const snapshot = await getDocs2(q);
272
+ const categories = snapshot.docs.map(
273
+ (doc11) => ({
274
+ id: doc11.id,
275
+ ...doc11.data()
276
+ })
277
+ );
278
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
279
+ return { categories, lastVisible: newLastVisible };
280
+ }
281
+ /**
282
+ * Vraća sve aktivne kategorije za određenu familiju procedura sa paginacijom
165
283
  * @param family - Familija procedura (aesthetics/surgery)
284
+ * @param options - Pagination options
166
285
  * @returns Lista kategorija koje pripadaju traženoj familiji
167
286
  */
168
- async getAllByFamily(family) {
169
- const q = query2(
170
- this.categoriesRef,
287
+ async getAllByFamily(family, options = {}) {
288
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
289
+ const constraints = [
171
290
  where2("family", "==", family),
172
- where2("isActive", "==", true)
173
- );
291
+ where2("isActive", "==", active),
292
+ orderBy2("name"),
293
+ queryLimit ? limit2(queryLimit) : void 0,
294
+ lastVisible ? startAfter2(lastVisible) : void 0
295
+ ].filter((c) => !!c);
296
+ const q = query2(this.categoriesRef, ...constraints);
174
297
  const snapshot = await getDocs2(q);
175
- return snapshot.docs.map(
176
- (doc10) => ({
177
- id: doc10.id,
178
- ...doc10.data()
298
+ const categories = snapshot.docs.map(
299
+ (doc11) => ({
300
+ id: doc11.id,
301
+ ...doc11.data()
179
302
  })
180
303
  );
304
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
305
+ return { categories, lastVisible: newLastVisible };
181
306
  }
182
307
  /**
183
308
  * Ažurira postojeću kategoriju
@@ -201,6 +326,13 @@ var CategoryService = class extends BaseService {
201
326
  async delete(id) {
202
327
  await this.update(id, { isActive: false });
203
328
  }
329
+ /**
330
+ * Reactivates a category by setting its isActive flag to true.
331
+ * @param id - The ID of the category to reactivate.
332
+ */
333
+ async reactivate(id) {
334
+ await this.update(id, { isActive: true });
335
+ }
204
336
  /**
205
337
  * Vraća kategoriju po ID-u
206
338
  * @param id - ID tražene kategorije
@@ -228,9 +360,9 @@ import {
228
360
  deleteDoc,
229
361
  query as query3,
230
362
  where as where3,
231
- orderBy,
232
- limit,
233
- startAfter
363
+ orderBy as orderBy3,
364
+ limit as limit3,
365
+ startAfter as startAfter3
234
366
  } from "firebase/firestore";
235
367
 
236
368
  // src/types/documentation-templates/index.ts
@@ -465,9 +597,10 @@ var updateFilledDocumentDataSchema = z.object({
465
597
  });
466
598
 
467
599
  // src/services/documentation-templates/documentation-template.service.ts
600
+ import { getCountFromServer as getCountFromServer3 } from "firebase/firestore";
468
601
  var DocumentationTemplateService = class extends BaseService {
469
- constructor() {
470
- super(...arguments);
602
+ constructor(...args) {
603
+ super(...args);
471
604
  this.collectionRef = collection3(
472
605
  this.db,
473
606
  DOCUMENTATION_TEMPLATES_COLLECTION
@@ -616,11 +749,11 @@ var DocumentationTemplateService = class extends BaseService {
616
749
  this.db,
617
750
  `${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
618
751
  );
619
- const q = query3(versionsCollectionRef, orderBy("version", "desc"));
752
+ const q = query3(versionsCollectionRef, orderBy3("version", "desc"));
620
753
  const querySnapshot = await getDocs3(q);
621
754
  const versions = [];
622
- querySnapshot.forEach((doc10) => {
623
- versions.push(doc10.data());
755
+ querySnapshot.forEach((doc11) => {
756
+ versions.push(doc11.data());
624
757
  });
625
758
  return versions;
626
759
  }
@@ -642,24 +775,106 @@ var DocumentationTemplateService = class extends BaseService {
642
775
  let q = query3(
643
776
  this.collectionRef,
644
777
  where3("isActive", "==", true),
645
- orderBy("updatedAt", "desc"),
646
- limit(pageSize)
778
+ orderBy3("updatedAt", "desc"),
779
+ limit3(pageSize)
647
780
  );
648
781
  if (lastDoc) {
649
- q = query3(q, startAfter(lastDoc));
782
+ q = query3(q, startAfter3(lastDoc));
783
+ }
784
+ const querySnapshot = await getDocs3(q);
785
+ const templates = [];
786
+ let lastVisible = null;
787
+ querySnapshot.forEach((doc11) => {
788
+ templates.push(doc11.data());
789
+ lastVisible = doc11;
790
+ });
791
+ return {
792
+ templates,
793
+ lastDoc: lastVisible
794
+ };
795
+ }
796
+ /**
797
+ * Get all active templates with optional filters and pagination.
798
+ * @param options - Options for filtering and pagination.
799
+ * @returns A promise that resolves to the templates and the last visible document.
800
+ */
801
+ async getTemplates(options) {
802
+ const {
803
+ pageSize = 20,
804
+ lastDoc,
805
+ isUserForm,
806
+ isRequired,
807
+ sortingOrder
808
+ } = options;
809
+ const constraints = [
810
+ where3("isActive", "==", true),
811
+ orderBy3("sortingOrder", "asc"),
812
+ orderBy3("title", "asc"),
813
+ limit3(pageSize)
814
+ ];
815
+ if (isUserForm !== void 0) {
816
+ constraints.push(where3("isUserForm", "==", isUserForm));
817
+ }
818
+ if (isRequired !== void 0) {
819
+ constraints.push(where3("isRequired", "==", isRequired));
820
+ }
821
+ if (sortingOrder !== void 0) {
822
+ constraints.push(where3("sortingOrder", "==", sortingOrder));
823
+ }
824
+ if (lastDoc) {
825
+ constraints.push(startAfter3(lastDoc));
650
826
  }
827
+ const q = query3(this.collectionRef, ...constraints.filter((c) => c));
651
828
  const querySnapshot = await getDocs3(q);
652
829
  const templates = [];
653
830
  let lastVisible = null;
654
- querySnapshot.forEach((doc10) => {
655
- templates.push(doc10.data());
656
- lastVisible = doc10;
831
+ querySnapshot.forEach((doc11) => {
832
+ templates.push(doc11.data());
833
+ lastVisible = doc11;
657
834
  });
658
835
  return {
659
836
  templates,
660
837
  lastDoc: lastVisible
661
838
  };
662
839
  }
840
+ /**
841
+ * Get the total count of active templates with optional filters.
842
+ * @param options - Options for filtering.
843
+ * @returns A promise that resolves to the total count of templates.
844
+ */
845
+ async getTemplatesCount(options) {
846
+ const { isUserForm, isRequired, sortingOrder } = options;
847
+ const constraints = [where3("isActive", "==", true)];
848
+ if (isUserForm !== void 0) {
849
+ constraints.push(where3("isUserForm", "==", isUserForm));
850
+ }
851
+ if (isRequired !== void 0) {
852
+ constraints.push(where3("isRequired", "==", isRequired));
853
+ }
854
+ if (sortingOrder !== void 0) {
855
+ constraints.push(where3("sortingOrder", "==", sortingOrder));
856
+ }
857
+ const q = query3(this.collectionRef, ...constraints.filter((c) => c));
858
+ const snapshot = await getCountFromServer3(q);
859
+ return snapshot.data().count;
860
+ }
861
+ /**
862
+ * Get all active templates without pagination for filtering purposes.
863
+ * @returns A promise that resolves to an array of all active templates.
864
+ */
865
+ async getAllActiveTemplates() {
866
+ const q = query3(
867
+ this.collectionRef,
868
+ where3("isActive", "==", true),
869
+ orderBy3("title", "asc")
870
+ );
871
+ const querySnapshot = await getDocs3(q);
872
+ const templates = [];
873
+ querySnapshot.forEach((doc11) => {
874
+ templates.push(doc11.data());
875
+ });
876
+ return templates;
877
+ }
663
878
  /**
664
879
  * Get templates by tags
665
880
  * @param tags - Tags to filter by
@@ -672,18 +887,18 @@ var DocumentationTemplateService = class extends BaseService {
672
887
  this.collectionRef,
673
888
  where3("isActive", "==", true),
674
889
  where3("tags", "array-contains-any", tags),
675
- orderBy("updatedAt", "desc"),
676
- limit(pageSize)
890
+ orderBy3("updatedAt", "desc"),
891
+ limit3(pageSize)
677
892
  );
678
893
  if (lastDoc) {
679
- q = query3(q, startAfter(lastDoc));
894
+ q = query3(q, startAfter3(lastDoc));
680
895
  }
681
896
  const querySnapshot = await getDocs3(q);
682
897
  const templates = [];
683
898
  let lastVisible = null;
684
- querySnapshot.forEach((doc10) => {
685
- templates.push(doc10.data());
686
- lastVisible = doc10;
899
+ querySnapshot.forEach((doc11) => {
900
+ templates.push(doc11.data());
901
+ lastVisible = doc11;
687
902
  });
688
903
  return {
689
904
  templates,
@@ -701,18 +916,18 @@ var DocumentationTemplateService = class extends BaseService {
701
916
  let q = query3(
702
917
  this.collectionRef,
703
918
  where3("createdBy", "==", userId),
704
- orderBy("updatedAt", "desc"),
705
- limit(pageSize)
919
+ orderBy3("updatedAt", "desc"),
920
+ limit3(pageSize)
706
921
  );
707
922
  if (lastDoc) {
708
- q = query3(q, startAfter(lastDoc));
923
+ q = query3(q, startAfter3(lastDoc));
709
924
  }
710
925
  const querySnapshot = await getDocs3(q);
711
926
  const templates = [];
712
927
  let lastVisible = null;
713
- querySnapshot.forEach((doc10) => {
714
- templates.push(doc10.data());
715
- lastVisible = doc10;
928
+ querySnapshot.forEach((doc11) => {
929
+ templates.push(doc11.data());
930
+ lastVisible = doc11;
716
931
  });
717
932
  return {
718
933
  templates,
@@ -728,7 +943,7 @@ var DocumentationTemplateService = class extends BaseService {
728
943
  let q = query3(
729
944
  this.collectionRef,
730
945
  where3("isActive", "==", true),
731
- orderBy("updatedAt", "desc")
946
+ orderBy3("updatedAt", "desc")
732
947
  );
733
948
  if ((options == null ? void 0 : options.isUserForm) !== void 0) {
734
949
  q = query3(q, where3("isUserForm", "==", options.isUserForm));
@@ -738,8 +953,8 @@ var DocumentationTemplateService = class extends BaseService {
738
953
  }
739
954
  const querySnapshot = await getDocs3(q);
740
955
  const templates = [];
741
- querySnapshot.forEach((doc10) => {
742
- templates.push(doc10.data());
956
+ querySnapshot.forEach((doc11) => {
957
+ templates.push(doc11.data());
743
958
  });
744
959
  return templates;
745
960
  }
@@ -754,9 +969,9 @@ import {
754
969
  setDoc as setDoc3,
755
970
  updateDoc as updateDoc5,
756
971
  query as query5,
757
- orderBy as orderBy3,
758
- limit as limit3,
759
- startAfter as startAfter2
972
+ orderBy as orderBy5,
973
+ limit as limit5,
974
+ startAfter as startAfter4
760
975
  } from "firebase/firestore";
761
976
 
762
977
  // src/services/media/media.service.ts
@@ -776,10 +991,10 @@ import {
776
991
  collection as collection4,
777
992
  query as query4,
778
993
  where as where4,
779
- limit as limit2,
994
+ limit as limit4,
780
995
  getDocs as getDocs4,
781
996
  deleteDoc as deleteDoc2,
782
- orderBy as orderBy2
997
+ orderBy as orderBy4
783
998
  } from "firebase/firestore";
784
999
 
785
1000
  // src/backoffice/services/documentation-template.service.ts
@@ -790,8 +1005,8 @@ var DocumentationTemplateServiceBackoffice = class {
790
1005
  * @param auth - Firebase Auth instance
791
1006
  * @param app - Firebase App instance
792
1007
  */
793
- constructor(db, auth, app) {
794
- this.apiService = new DocumentationTemplateService(db, auth, app);
1008
+ constructor(...args) {
1009
+ this.apiService = new DocumentationTemplateService(...args);
795
1010
  }
796
1011
  /**
797
1012
  * Create a new document template
@@ -836,6 +1051,40 @@ var DocumentationTemplateServiceBackoffice = class {
836
1051
  async getActiveTemplates(pageSize = 20, lastDoc) {
837
1052
  return this.apiService.getActiveTemplates(pageSize, lastDoc);
838
1053
  }
1054
+ /**
1055
+ * Get all active templates with optional filters and pagination.
1056
+ * @param options - Options for filtering and pagination.
1057
+ * @returns A promise that resolves to the templates and the last visible document.
1058
+ */
1059
+ async getTemplates(options) {
1060
+ return this.apiService.getTemplates(options);
1061
+ }
1062
+ /**
1063
+ * Get the total count of active templates with optional filters.
1064
+ * @param options - Options for filtering.
1065
+ * @returns A promise that resolves to the total count of templates.
1066
+ */
1067
+ async getTemplatesCount(options) {
1068
+ return this.apiService.getTemplatesCount(options);
1069
+ }
1070
+ /**
1071
+ * Get all active templates without pagination for filtering purposes.
1072
+ * @returns A promise that resolves to an array of all active templates.
1073
+ */
1074
+ async getAllActiveTemplates() {
1075
+ return this.apiService.getAllActiveTemplates();
1076
+ }
1077
+ /**
1078
+ * Searches for active templates by title.
1079
+ * @param title - The title to search for.
1080
+ * @returns A list of templates that match the search criteria.
1081
+ */
1082
+ async search(title) {
1083
+ const { templates } = await this.apiService.getActiveTemplates(1e3);
1084
+ return templates.filter(
1085
+ (t) => t.title.toLowerCase().includes(title.toLowerCase())
1086
+ );
1087
+ }
839
1088
  /**
840
1089
  * Get templates by tags
841
1090
  * @param tags - Tags to filter by
@@ -879,12 +1128,17 @@ var DocumentationTemplateServiceBackoffice = class {
879
1128
  import {
880
1129
  addDoc as addDoc3,
881
1130
  collection as collection6,
1131
+ collectionGroup,
882
1132
  doc as doc6,
883
1133
  getDoc as getDoc6,
884
1134
  getDocs as getDocs6,
885
1135
  query as query6,
886
1136
  updateDoc as updateDoc6,
887
- where as where6
1137
+ where as where6,
1138
+ limit as limit6,
1139
+ orderBy as orderBy6,
1140
+ startAfter as startAfter5,
1141
+ getCountFromServer as getCountFromServer4
888
1142
  } from "firebase/firestore";
889
1143
 
890
1144
  // src/backoffice/types/product.types.ts
@@ -928,20 +1182,102 @@ var ProductService = class extends BaseService {
928
1182
  return { id: productRef.id, ...newProduct };
929
1183
  }
930
1184
  /**
931
- * Gets all products for a technology
1185
+ * Gets a paginated list of all products, with optional filters.
1186
+ * This uses a collectionGroup query to search across all technologies.
932
1187
  */
933
- async getAllByTechnology(technologyId) {
1188
+ async getAll(options) {
1189
+ const {
1190
+ rowsPerPage,
1191
+ lastVisible,
1192
+ categoryId,
1193
+ subcategoryId,
1194
+ technologyId
1195
+ } = options;
1196
+ const constraints = [
1197
+ where6("isActive", "==", true),
1198
+ orderBy6("name")
1199
+ ];
1200
+ if (categoryId) {
1201
+ constraints.push(where6("categoryId", "==", categoryId));
1202
+ }
1203
+ if (subcategoryId) {
1204
+ constraints.push(where6("subcategoryId", "==", subcategoryId));
1205
+ }
1206
+ if (technologyId) {
1207
+ constraints.push(where6("technologyId", "==", technologyId));
1208
+ }
1209
+ if (lastVisible) {
1210
+ constraints.push(startAfter5(lastVisible));
1211
+ }
1212
+ constraints.push(limit6(rowsPerPage));
934
1213
  const q = query6(
935
- this.getProductsRef(technologyId),
936
- where6("isActive", "==", true)
1214
+ collectionGroup(this.db, PRODUCTS_COLLECTION),
1215
+ ...constraints
937
1216
  );
938
1217
  const snapshot = await getDocs6(q);
939
- return snapshot.docs.map(
940
- (doc10) => ({
941
- id: doc10.id,
942
- ...doc10.data()
1218
+ const products = snapshot.docs.map(
1219
+ (doc11) => ({
1220
+ id: doc11.id,
1221
+ ...doc11.data()
943
1222
  })
944
1223
  );
1224
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1225
+ return { products, lastVisible: newLastVisible };
1226
+ }
1227
+ /**
1228
+ * Gets the total count of active products, with optional filters.
1229
+ */
1230
+ async getProductsCount(options) {
1231
+ const { categoryId, subcategoryId, technologyId } = options;
1232
+ const constraints = [where6("isActive", "==", true)];
1233
+ if (categoryId) {
1234
+ constraints.push(where6("categoryId", "==", categoryId));
1235
+ }
1236
+ if (subcategoryId) {
1237
+ constraints.push(where6("subcategoryId", "==", subcategoryId));
1238
+ }
1239
+ if (technologyId) {
1240
+ constraints.push(where6("technologyId", "==", technologyId));
1241
+ }
1242
+ const q = query6(
1243
+ collectionGroup(this.db, PRODUCTS_COLLECTION),
1244
+ ...constraints
1245
+ );
1246
+ const snapshot = await getCountFromServer4(q);
1247
+ return snapshot.data().count;
1248
+ }
1249
+ /**
1250
+ * Gets counts of active products grouped by category, subcategory, and technology.
1251
+ * This uses a single collectionGroup query for efficiency.
1252
+ */
1253
+ async getProductCounts() {
1254
+ const q = query6(
1255
+ collectionGroup(this.db, PRODUCTS_COLLECTION),
1256
+ where6("isActive", "==", true)
1257
+ );
1258
+ const snapshot = await getDocs6(q);
1259
+ const counts = {
1260
+ byCategory: {},
1261
+ bySubcategory: {},
1262
+ byTechnology: {}
1263
+ };
1264
+ if (snapshot.empty) {
1265
+ return counts;
1266
+ }
1267
+ snapshot.docs.forEach((doc11) => {
1268
+ const product = doc11.data();
1269
+ const { categoryId, subcategoryId, technologyId } = product;
1270
+ if (categoryId) {
1271
+ counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
1272
+ }
1273
+ if (subcategoryId) {
1274
+ counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
1275
+ }
1276
+ if (technologyId) {
1277
+ counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
1278
+ }
1279
+ });
1280
+ return counts;
945
1281
  }
946
1282
  /**
947
1283
  * Gets all products for a brand by filtering through all technologies
@@ -959,9 +1295,9 @@ var ProductService = class extends BaseService {
959
1295
  const snapshot = await getDocs6(q);
960
1296
  products.push(
961
1297
  ...snapshot.docs.map(
962
- (doc10) => ({
963
- id: doc10.id,
964
- ...doc10.data()
1298
+ (doc11) => ({
1299
+ id: doc11.id,
1300
+ ...doc11.data()
965
1301
  })
966
1302
  )
967
1303
  );
@@ -1059,9 +1395,9 @@ var RequirementService = class extends BaseService {
1059
1395
  const q = query7(this.requirementsRef, where7("isActive", "==", true));
1060
1396
  const snapshot = await getDocs7(q);
1061
1397
  return snapshot.docs.map(
1062
- (doc10) => ({
1063
- id: doc10.id,
1064
- ...doc10.data()
1398
+ (doc11) => ({
1399
+ id: doc11.id,
1400
+ ...doc11.data()
1065
1401
  })
1066
1402
  );
1067
1403
  }
@@ -1078,12 +1414,24 @@ var RequirementService = class extends BaseService {
1078
1414
  );
1079
1415
  const snapshot = await getDocs7(q);
1080
1416
  return snapshot.docs.map(
1081
- (doc10) => ({
1082
- id: doc10.id,
1083
- ...doc10.data()
1417
+ (doc11) => ({
1418
+ id: doc11.id,
1419
+ ...doc11.data()
1084
1420
  })
1085
1421
  );
1086
1422
  }
1423
+ /**
1424
+ * Searches for requirements by name.
1425
+ * @param name - The name to search for.
1426
+ * @param type - The type of requirement (pre/post).
1427
+ * @returns A list of requirements that match the search criteria.
1428
+ */
1429
+ async search(name, type) {
1430
+ const requirements = await this.getAllByType(type);
1431
+ return requirements.filter(
1432
+ (r) => r.name.toLowerCase().includes(name.toLowerCase())
1433
+ );
1434
+ }
1087
1435
  /**
1088
1436
  * Ažurira postojeći zahtev
1089
1437
  * @param id - ID zahteva koji se ažurira
@@ -1126,10 +1474,17 @@ var RequirementService = class extends BaseService {
1126
1474
  import {
1127
1475
  addDoc as addDoc5,
1128
1476
  collection as collection8,
1477
+ collectionGroup as collectionGroup2,
1478
+ deleteDoc as deleteDoc3,
1129
1479
  doc as doc8,
1480
+ getCountFromServer as getCountFromServer5,
1130
1481
  getDoc as getDoc8,
1131
1482
  getDocs as getDocs8,
1483
+ limit as limit7,
1484
+ orderBy as orderBy7,
1132
1485
  query as query8,
1486
+ setDoc as setDoc4,
1487
+ startAfter as startAfter6,
1133
1488
  updateDoc as updateDoc8,
1134
1489
  where as where8
1135
1490
  } from "firebase/firestore";
@@ -1173,20 +1528,110 @@ var SubcategoryService = class extends BaseService {
1173
1528
  return { id: docRef.id, ...newSubcategory };
1174
1529
  }
1175
1530
  /**
1176
- * Vraća sve aktivne podkategorije za određenu kategoriju
1531
+ * Returns counts of subcategories for all categories.
1532
+ * @param active - Whether to count active or inactive subcategories.
1533
+ * @returns A record mapping category ID to subcategory count.
1534
+ */
1535
+ async getSubcategoryCounts(active = true) {
1536
+ const categoriesRef = collection8(this.db, CATEGORIES_COLLECTION);
1537
+ const categoriesSnapshot = await getDocs8(categoriesRef);
1538
+ const counts = {};
1539
+ for (const categoryDoc of categoriesSnapshot.docs) {
1540
+ const categoryId = categoryDoc.id;
1541
+ const subcategoriesRef = this.getSubcategoriesRef(categoryId);
1542
+ const q = query8(subcategoriesRef, where8("isActive", "==", active));
1543
+ const snapshot = await getCountFromServer5(q);
1544
+ counts[categoryId] = snapshot.data().count;
1545
+ }
1546
+ return counts;
1547
+ }
1548
+ /**
1549
+ * Vraća sve aktivne podkategorije za određenu kategoriju sa paginacijom
1177
1550
  * @param categoryId - ID kategorije čije podkategorije tražimo
1178
- * @returns Lista aktivnih podkategorija
1551
+ * @param options - Pagination options
1552
+ * @returns Lista aktivnih podkategorija i poslednji vidljiv dokument
1553
+ */
1554
+ async getAllByCategoryId(categoryId, options = {}) {
1555
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1556
+ const constraints = [
1557
+ where8("isActive", "==", active),
1558
+ orderBy7("name"),
1559
+ queryLimit ? limit7(queryLimit) : void 0,
1560
+ lastVisible ? startAfter6(lastVisible) : void 0
1561
+ ].filter((c) => !!c);
1562
+ const q = query8(this.getSubcategoriesRef(categoryId), ...constraints);
1563
+ const querySnapshot = await getDocs8(q);
1564
+ const subcategories = querySnapshot.docs.map(
1565
+ (doc11) => ({
1566
+ id: doc11.id,
1567
+ ...doc11.data()
1568
+ })
1569
+ );
1570
+ const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
1571
+ return { subcategories, lastVisible: newLastVisible };
1572
+ }
1573
+ /**
1574
+ * Vraća sve podkategorije sa paginacijom koristeći collection group query.
1575
+ * NOTE: This query requires a composite index in Firestore on the 'subcategories' collection group.
1576
+ * The index should be on 'isActive' (ascending) and 'name' (ascending).
1577
+ * Firestore will provide a link to create this index in the console error if it's missing.
1578
+ * @param options - Pagination options
1579
+ * @returns Lista podkategorija i poslednji vidljiv dokument
1580
+ */
1581
+ async getAll(options = {}) {
1582
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1583
+ const constraints = [
1584
+ where8("isActive", "==", active),
1585
+ orderBy7("name"),
1586
+ queryLimit ? limit7(queryLimit) : void 0,
1587
+ lastVisible ? startAfter6(lastVisible) : void 0
1588
+ ].filter((c) => !!c);
1589
+ const q = query8(
1590
+ collectionGroup2(this.db, SUBCATEGORIES_COLLECTION),
1591
+ ...constraints
1592
+ );
1593
+ const querySnapshot = await getDocs8(q);
1594
+ const subcategories = querySnapshot.docs.map(
1595
+ (doc11) => ({
1596
+ id: doc11.id,
1597
+ ...doc11.data()
1598
+ })
1599
+ );
1600
+ const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
1601
+ return { subcategories, lastVisible: newLastVisible };
1602
+ }
1603
+ /**
1604
+ * Vraća sve subkategorije za određenu kategoriju za potrebe filtera (bez paginacije)
1605
+ * @param categoryId - ID kategorije čije subkategorije tražimo
1606
+ * @returns Lista svih aktivnih subkategorija
1179
1607
  */
1180
- async getAllByCategoryId(categoryId) {
1608
+ async getAllForFilterByCategoryId(categoryId) {
1181
1609
  const q = query8(
1182
1610
  this.getSubcategoriesRef(categoryId),
1183
1611
  where8("isActive", "==", true)
1184
1612
  );
1185
- const snapshot = await getDocs8(q);
1186
- return snapshot.docs.map(
1187
- (doc10) => ({
1188
- id: doc10.id,
1189
- ...doc10.data()
1613
+ const querySnapshot = await getDocs8(q);
1614
+ return querySnapshot.docs.map(
1615
+ (doc11) => ({
1616
+ id: doc11.id,
1617
+ ...doc11.data()
1618
+ })
1619
+ );
1620
+ }
1621
+ /**
1622
+ * Vraća sve subkategorije za potrebe filtera (bez paginacije)
1623
+ * @returns Lista svih aktivnih subkategorija
1624
+ */
1625
+ async getAllForFilter() {
1626
+ const q = query8(
1627
+ collectionGroup2(this.db, SUBCATEGORIES_COLLECTION),
1628
+ where8("isActive", "==", true)
1629
+ );
1630
+ const querySnapshot = await getDocs8(q);
1631
+ return querySnapshot.docs.map(
1632
+ (doc11) => ({
1633
+ id: doc11.id,
1634
+ ...doc11.data()
1190
1635
  })
1191
1636
  );
1192
1637
  }
@@ -1198,13 +1643,42 @@ var SubcategoryService = class extends BaseService {
1198
1643
  * @returns Ažurirana podkategorija
1199
1644
  */
1200
1645
  async update(categoryId, subcategoryId, subcategory) {
1201
- const updateData = {
1202
- ...subcategory,
1203
- updatedAt: /* @__PURE__ */ new Date()
1204
- };
1205
- const docRef = doc8(this.getSubcategoriesRef(categoryId), subcategoryId);
1206
- await updateDoc8(docRef, updateData);
1207
- return this.getById(categoryId, subcategoryId);
1646
+ const newCategoryId = subcategory.categoryId;
1647
+ if (newCategoryId && newCategoryId !== categoryId) {
1648
+ const oldDocRef = doc8(
1649
+ this.getSubcategoriesRef(categoryId),
1650
+ subcategoryId
1651
+ );
1652
+ const docSnap = await getDoc8(oldDocRef);
1653
+ if (!docSnap.exists()) {
1654
+ throw new Error("Subcategory to update does not exist.");
1655
+ }
1656
+ const existingData = docSnap.data();
1657
+ const newData = {
1658
+ ...existingData,
1659
+ ...subcategory,
1660
+ categoryId: newCategoryId,
1661
+ // Ensure categoryId is updated
1662
+ createdAt: existingData.createdAt,
1663
+ // Preserve original creation date
1664
+ updatedAt: /* @__PURE__ */ new Date()
1665
+ };
1666
+ const newDocRef = doc8(
1667
+ this.getSubcategoriesRef(newCategoryId),
1668
+ subcategoryId
1669
+ );
1670
+ await setDoc4(newDocRef, newData);
1671
+ await deleteDoc3(oldDocRef);
1672
+ return { id: subcategoryId, ...newData };
1673
+ } else {
1674
+ const updateData = {
1675
+ ...subcategory,
1676
+ updatedAt: /* @__PURE__ */ new Date()
1677
+ };
1678
+ const docRef = doc8(this.getSubcategoriesRef(categoryId), subcategoryId);
1679
+ await updateDoc8(docRef, updateData);
1680
+ return this.getById(categoryId, subcategoryId);
1681
+ }
1208
1682
  }
1209
1683
  /**
1210
1684
  * Soft delete podkategorije (postavlja isActive na false)
@@ -1214,6 +1688,14 @@ var SubcategoryService = class extends BaseService {
1214
1688
  async delete(categoryId, subcategoryId) {
1215
1689
  await this.update(categoryId, subcategoryId, { isActive: false });
1216
1690
  }
1691
+ /**
1692
+ * Reactivates a subcategory by setting its isActive flag to true.
1693
+ * @param categoryId - The ID of the category to which the subcategory belongs.
1694
+ * @param subcategoryId - The ID of the subcategory to reactivate.
1695
+ */
1696
+ async reactivate(categoryId, subcategoryId) {
1697
+ await this.update(categoryId, subcategoryId, { isActive: true });
1698
+ }
1217
1699
  /**
1218
1700
  * Vraća podkategoriju po ID-u
1219
1701
  * @param categoryId - ID kategorije kojoj pripada podkategorija
@@ -1238,7 +1720,10 @@ import {
1238
1720
  doc as doc9,
1239
1721
  getDoc as getDoc9,
1240
1722
  getDocs as getDocs9,
1723
+ limit as limit8,
1724
+ orderBy as orderBy8,
1241
1725
  query as query9,
1726
+ startAfter as startAfter7,
1242
1727
  updateDoc as updateDoc9,
1243
1728
  where as where9,
1244
1729
  arrayUnion,
@@ -1276,137 +1761,185 @@ var DEFAULT_CERTIFICATION_REQUIREMENT = {
1276
1761
  };
1277
1762
  var TechnologyService = class extends BaseService {
1278
1763
  /**
1279
- * Vraća referencu na Firestore kolekciju tehnologija
1764
+ * Reference to the Firestore collection of technologies.
1280
1765
  */
1281
- getTechnologiesRef() {
1766
+ get technologiesRef() {
1282
1767
  return collection9(this.db, TECHNOLOGIES_COLLECTION);
1283
1768
  }
1284
1769
  /**
1285
- * Kreira novu tehnologiju
1286
- * @param technology - Podaci za novu tehnologiju
1287
- * @returns Kreirana tehnologija sa generisanim ID-em
1770
+ * Creates a new technology.
1771
+ * @param technology - Data for the new technology.
1772
+ * @returns The created technology with its generated ID.
1288
1773
  */
1289
1774
  async create(technology) {
1290
1775
  const now = /* @__PURE__ */ new Date();
1291
1776
  const newTechnology = {
1292
- ...technology,
1293
- createdAt: now,
1294
- updatedAt: now,
1295
- isActive: true,
1296
- requirements: technology.requirements || {
1297
- pre: [],
1298
- post: []
1299
- },
1777
+ name: technology.name,
1778
+ description: technology.description,
1779
+ family: technology.family,
1780
+ categoryId: technology.categoryId,
1781
+ subcategoryId: technology.subcategoryId,
1782
+ requirements: technology.requirements || { pre: [], post: [] },
1300
1783
  blockingConditions: technology.blockingConditions || [],
1301
1784
  contraindications: technology.contraindications || [],
1302
1785
  benefits: technology.benefits || [],
1303
- certificationRequirement: technology.certificationRequirement || DEFAULT_CERTIFICATION_REQUIREMENT
1786
+ certificationRequirement: technology.certificationRequirement || DEFAULT_CERTIFICATION_REQUIREMENT,
1787
+ documentationTemplates: technology.documentationTemplates || [],
1788
+ isActive: true,
1789
+ createdAt: now,
1790
+ updatedAt: now
1304
1791
  };
1305
- const docRef = await addDoc6(this.getTechnologiesRef(), newTechnology);
1792
+ if (technology.technicalDetails) {
1793
+ newTechnology.technicalDetails = technology.technicalDetails;
1794
+ }
1795
+ const docRef = await addDoc6(this.technologiesRef, newTechnology);
1306
1796
  return { id: docRef.id, ...newTechnology };
1307
1797
  }
1308
1798
  /**
1309
- * Vraća sve aktivne tehnologije
1310
- * @returns Lista aktivnih tehnologija
1799
+ * Returns counts of technologies for each subcategory.
1800
+ * @param active - Whether to count active or inactive technologies.
1801
+ * @returns A record mapping subcategory ID to technology count.
1311
1802
  */
1312
- async getAll() {
1313
- const q = query9(this.getTechnologiesRef(), where9("isActive", "==", true));
1803
+ async getTechnologyCounts(active = true) {
1804
+ const q = query9(this.technologiesRef, where9("isActive", "==", active));
1314
1805
  const snapshot = await getDocs9(q);
1315
- return snapshot.docs.map(
1316
- (doc10) => ({
1317
- id: doc10.id,
1318
- ...doc10.data()
1319
- })
1320
- );
1806
+ const counts = {};
1807
+ snapshot.docs.forEach((doc11) => {
1808
+ const tech = doc11.data();
1809
+ counts[tech.subcategoryId] = (counts[tech.subcategoryId] || 0) + 1;
1810
+ });
1811
+ return counts;
1321
1812
  }
1322
1813
  /**
1323
- * Vraća sve aktivne tehnologije za određenu familiju
1324
- * @param family - Familija procedura
1325
- * @returns Lista aktivnih tehnologija
1814
+ * Returns counts of technologies for each category.
1815
+ * @param active - Whether to count active or inactive technologies.
1816
+ * @returns A record mapping category ID to technology count.
1326
1817
  */
1327
- async getAllByFamily(family) {
1328
- const q = query9(
1329
- this.getTechnologiesRef(),
1330
- where9("isActive", "==", true),
1331
- where9("family", "==", family)
1332
- );
1818
+ async getTechnologyCountsByCategory(active = true) {
1819
+ const q = query9(this.technologiesRef, where9("isActive", "==", active));
1333
1820
  const snapshot = await getDocs9(q);
1334
- return snapshot.docs.map(
1335
- (doc10) => ({
1336
- id: doc10.id,
1337
- ...doc10.data()
1821
+ const counts = {};
1822
+ snapshot.docs.forEach((doc11) => {
1823
+ const tech = doc11.data();
1824
+ counts[tech.categoryId] = (counts[tech.categoryId] || 0) + 1;
1825
+ });
1826
+ return counts;
1827
+ }
1828
+ /**
1829
+ * Returns all technologies with pagination.
1830
+ * @param options - Pagination and filter options.
1831
+ * @returns A list of technologies and the last visible document.
1832
+ */
1833
+ async getAll(options = {}) {
1834
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1835
+ const constraints = [
1836
+ where9("isActive", "==", active),
1837
+ orderBy8("name"),
1838
+ queryLimit ? limit8(queryLimit) : void 0,
1839
+ lastVisible ? startAfter7(lastVisible) : void 0
1840
+ ].filter((c) => !!c);
1841
+ const q = query9(this.technologiesRef, ...constraints);
1842
+ const snapshot = await getDocs9(q);
1843
+ const technologies = snapshot.docs.map(
1844
+ (doc11) => ({
1845
+ id: doc11.id,
1846
+ ...doc11.data()
1338
1847
  })
1339
1848
  );
1340
- }
1341
- /**
1342
- * Vraća sve aktivne tehnologije za određenu kategoriju
1343
- * @param categoryId - ID kategorije
1344
- * @returns Lista aktivnih tehnologija
1345
- */
1346
- async getAllByCategoryId(categoryId) {
1347
- const q = query9(
1348
- this.getTechnologiesRef(),
1349
- where9("isActive", "==", true),
1350
- where9("categoryId", "==", categoryId)
1351
- );
1849
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1850
+ return { technologies, lastVisible: newLastVisible };
1851
+ }
1852
+ /**
1853
+ * Returns all technologies for a specific category with pagination.
1854
+ * @param categoryId - The ID of the category.
1855
+ * @param options - Pagination options.
1856
+ * @returns A list of technologies for the specified category.
1857
+ */
1858
+ async getAllByCategoryId(categoryId, options = {}) {
1859
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1860
+ const constraints = [
1861
+ where9("categoryId", "==", categoryId),
1862
+ where9("isActive", "==", active),
1863
+ orderBy8("name"),
1864
+ queryLimit ? limit8(queryLimit) : void 0,
1865
+ lastVisible ? startAfter7(lastVisible) : void 0
1866
+ ].filter((c) => !!c);
1867
+ const q = query9(this.technologiesRef, ...constraints);
1352
1868
  const snapshot = await getDocs9(q);
1353
- return snapshot.docs.map(
1354
- (doc10) => ({
1355
- id: doc10.id,
1356
- ...doc10.data()
1869
+ const technologies = snapshot.docs.map(
1870
+ (doc11) => ({
1871
+ id: doc11.id,
1872
+ ...doc11.data()
1357
1873
  })
1358
1874
  );
1359
- }
1360
- /**
1361
- * Vraća sve aktivne tehnologije za određenu podkategoriju
1362
- * @param subcategoryId - ID podkategorije
1363
- * @returns Lista aktivnih tehnologija
1364
- */
1365
- async getAllBySubcategoryId(subcategoryId) {
1366
- const q = query9(
1367
- this.getTechnologiesRef(),
1368
- where9("isActive", "==", true),
1369
- where9("subcategoryId", "==", subcategoryId)
1370
- );
1875
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1876
+ return { technologies, lastVisible: newLastVisible };
1877
+ }
1878
+ /**
1879
+ * Returns all technologies for a specific subcategory with pagination.
1880
+ * @param subcategoryId - The ID of the subcategory.
1881
+ * @param options - Pagination options.
1882
+ * @returns A list of technologies for the specified subcategory.
1883
+ */
1884
+ async getAllBySubcategoryId(subcategoryId, options = {}) {
1885
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1886
+ const constraints = [
1887
+ where9("subcategoryId", "==", subcategoryId),
1888
+ where9("isActive", "==", active),
1889
+ orderBy8("name"),
1890
+ queryLimit ? limit8(queryLimit) : void 0,
1891
+ lastVisible ? startAfter7(lastVisible) : void 0
1892
+ ].filter((c) => !!c);
1893
+ const q = query9(this.technologiesRef, ...constraints);
1371
1894
  const snapshot = await getDocs9(q);
1372
- return snapshot.docs.map(
1373
- (doc10) => ({
1374
- id: doc10.id,
1375
- ...doc10.data()
1895
+ const technologies = snapshot.docs.map(
1896
+ (doc11) => ({
1897
+ id: doc11.id,
1898
+ ...doc11.data()
1376
1899
  })
1377
1900
  );
1901
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1902
+ return { technologies, lastVisible: newLastVisible };
1378
1903
  }
1379
1904
  /**
1380
- * Ažurira postojeću tehnologiju
1381
- * @param technologyId - ID tehnologije
1382
- * @param technology - Novi podaci za tehnologiju
1383
- * @returns Ažurirana tehnologija
1905
+ * Updates an existing technology.
1906
+ * @param id - The ID of the technology to update.
1907
+ * @param technology - New data for the technology.
1908
+ * @returns The updated technology.
1384
1909
  */
1385
- async update(technologyId, technology) {
1386
- const updateData = {
1387
- ...technology,
1388
- updatedAt: /* @__PURE__ */ new Date()
1389
- };
1390
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
1910
+ async update(id, technology) {
1911
+ const updateData = { ...technology };
1912
+ Object.keys(updateData).forEach((key) => {
1913
+ if (updateData[key] === void 0) {
1914
+ delete updateData[key];
1915
+ }
1916
+ });
1917
+ updateData.updatedAt = /* @__PURE__ */ new Date();
1918
+ const docRef = doc9(this.technologiesRef, id);
1391
1919
  await updateDoc9(docRef, updateData);
1392
- return this.getById(technologyId);
1920
+ return this.getById(id);
1393
1921
  }
1394
1922
  /**
1395
- * Soft delete tehnologije (postavlja isActive na false)
1396
- * @param technologyId - ID tehnologije koja se briše
1923
+ * Soft deletes a technology.
1924
+ * @param id - The ID of the technology to delete.
1397
1925
  */
1398
- async delete(technologyId) {
1399
- await this.update(technologyId, {
1400
- isActive: false
1401
- });
1926
+ async delete(id) {
1927
+ await this.update(id, { isActive: false });
1402
1928
  }
1403
1929
  /**
1404
- * Vraća tehnologiju po ID-u
1405
- * @param technologyId - ID tražene tehnologije
1406
- * @returns Tehnologija ili null ako ne postoji
1930
+ * Reactivates a technology.
1931
+ * @param id - The ID of the technology to reactivate.
1407
1932
  */
1408
- async getById(technologyId) {
1409
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
1933
+ async reactivate(id) {
1934
+ await this.update(id, { isActive: true });
1935
+ }
1936
+ /**
1937
+ * Returns a technology by its ID.
1938
+ * @param id - The ID of the requested technology.
1939
+ * @returns The technology or null if it doesn't exist.
1940
+ */
1941
+ async getById(id) {
1942
+ const docRef = doc9(this.technologiesRef, id);
1410
1943
  const docSnap = await getDoc9(docRef);
1411
1944
  if (!docSnap.exists()) return null;
1412
1945
  return {
@@ -1421,7 +1954,7 @@ var TechnologyService = class extends BaseService {
1421
1954
  * @returns Ažurirana tehnologija sa novim zahtevom
1422
1955
  */
1423
1956
  async addRequirement(technologyId, requirement) {
1424
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
1957
+ const docRef = doc9(this.technologiesRef, technologyId);
1425
1958
  const requirementType = requirement.type === "pre" ? "requirements.pre" : "requirements.post";
1426
1959
  await updateDoc9(docRef, {
1427
1960
  [requirementType]: arrayUnion(requirement),
@@ -1436,7 +1969,7 @@ var TechnologyService = class extends BaseService {
1436
1969
  * @returns Ažurirana tehnologija bez uklonjenog zahteva
1437
1970
  */
1438
1971
  async removeRequirement(technologyId, requirement) {
1439
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
1972
+ const docRef = doc9(this.technologiesRef, technologyId);
1440
1973
  const requirementType = requirement.type === "pre" ? "requirements.pre" : "requirements.post";
1441
1974
  await updateDoc9(docRef, {
1442
1975
  [requirementType]: arrayRemove(requirement),
@@ -1476,7 +2009,7 @@ var TechnologyService = class extends BaseService {
1476
2009
  * @returns Ažurirana tehnologija
1477
2010
  */
1478
2011
  async addBlockingCondition(technologyId, condition) {
1479
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2012
+ const docRef = doc9(this.technologiesRef, technologyId);
1480
2013
  await updateDoc9(docRef, {
1481
2014
  blockingConditions: arrayUnion(condition),
1482
2015
  updatedAt: /* @__PURE__ */ new Date()
@@ -1490,7 +2023,7 @@ var TechnologyService = class extends BaseService {
1490
2023
  * @returns Ažurirana tehnologija
1491
2024
  */
1492
2025
  async removeBlockingCondition(technologyId, condition) {
1493
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2026
+ const docRef = doc9(this.technologiesRef, technologyId);
1494
2027
  await updateDoc9(docRef, {
1495
2028
  blockingConditions: arrayRemove(condition),
1496
2029
  updatedAt: /* @__PURE__ */ new Date()
@@ -1504,9 +2037,17 @@ var TechnologyService = class extends BaseService {
1504
2037
  * @returns Ažurirana tehnologija
1505
2038
  */
1506
2039
  async addContraindication(technologyId, contraindication) {
1507
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2040
+ const docRef = doc9(this.technologiesRef, technologyId);
2041
+ const technology = await this.getById(technologyId);
2042
+ if (!technology) {
2043
+ throw new Error(`Technology with id ${technologyId} not found`);
2044
+ }
2045
+ const existingContraindications = technology.contraindications || [];
2046
+ if (existingContraindications.some((c) => c.id === contraindication.id)) {
2047
+ return technology;
2048
+ }
1508
2049
  await updateDoc9(docRef, {
1509
- contraindications: arrayUnion(contraindication),
2050
+ contraindications: [...existingContraindications, contraindication],
1510
2051
  updatedAt: /* @__PURE__ */ new Date()
1511
2052
  });
1512
2053
  return this.getById(technologyId);
@@ -1518,9 +2059,45 @@ var TechnologyService = class extends BaseService {
1518
2059
  * @returns Ažurirana tehnologija
1519
2060
  */
1520
2061
  async removeContraindication(technologyId, contraindication) {
1521
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2062
+ const docRef = doc9(this.technologiesRef, technologyId);
2063
+ const technology = await this.getById(technologyId);
2064
+ if (!technology) {
2065
+ throw new Error(`Technology with id ${technologyId} not found`);
2066
+ }
2067
+ const updatedContraindications = (technology.contraindications || []).filter((c) => c.id !== contraindication.id);
2068
+ await updateDoc9(docRef, {
2069
+ contraindications: updatedContraindications,
2070
+ updatedAt: /* @__PURE__ */ new Date()
2071
+ });
2072
+ return this.getById(technologyId);
2073
+ }
2074
+ /**
2075
+ * Updates an existing contraindication in a technology's list.
2076
+ * If the contraindication does not exist, it will not be added.
2077
+ * @param technologyId - ID of the technology
2078
+ * @param contraindication - The updated contraindication object
2079
+ * @returns The updated technology
2080
+ */
2081
+ async updateContraindication(technologyId, contraindication) {
2082
+ const docRef = doc9(this.technologiesRef, technologyId);
2083
+ const technology = await this.getById(technologyId);
2084
+ if (!technology) {
2085
+ throw new Error(`Technology with id ${technologyId} not found`);
2086
+ }
2087
+ const contraindications = technology.contraindications || [];
2088
+ const index = contraindications.findIndex(
2089
+ (c) => c.id === contraindication.id
2090
+ );
2091
+ if (index === -1) {
2092
+ console.warn(
2093
+ `Contraindication with id ${contraindication.id} not found for technology ${technologyId}. No update performed.`
2094
+ );
2095
+ return technology;
2096
+ }
2097
+ const updatedContraindications = [...contraindications];
2098
+ updatedContraindications[index] = contraindication;
1522
2099
  await updateDoc9(docRef, {
1523
- contraindications: arrayRemove(contraindication),
2100
+ contraindications: updatedContraindications,
1524
2101
  updatedAt: /* @__PURE__ */ new Date()
1525
2102
  });
1526
2103
  return this.getById(technologyId);
@@ -1532,9 +2109,17 @@ var TechnologyService = class extends BaseService {
1532
2109
  * @returns Ažurirana tehnologija
1533
2110
  */
1534
2111
  async addBenefit(technologyId, benefit) {
1535
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2112
+ const docRef = doc9(this.technologiesRef, technologyId);
2113
+ const technology = await this.getById(technologyId);
2114
+ if (!technology) {
2115
+ throw new Error(`Technology with id ${technologyId} not found`);
2116
+ }
2117
+ const existingBenefits = technology.benefits || [];
2118
+ if (existingBenefits.some((b) => b.id === benefit.id)) {
2119
+ return technology;
2120
+ }
1536
2121
  await updateDoc9(docRef, {
1537
- benefits: arrayUnion(benefit),
2122
+ benefits: [...existingBenefits, benefit],
1538
2123
  updatedAt: /* @__PURE__ */ new Date()
1539
2124
  });
1540
2125
  return this.getById(technologyId);
@@ -1546,9 +2131,45 @@ var TechnologyService = class extends BaseService {
1546
2131
  * @returns Ažurirana tehnologija
1547
2132
  */
1548
2133
  async removeBenefit(technologyId, benefit) {
1549
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2134
+ const docRef = doc9(this.technologiesRef, technologyId);
2135
+ const technology = await this.getById(technologyId);
2136
+ if (!technology) {
2137
+ throw new Error(`Technology with id ${technologyId} not found`);
2138
+ }
2139
+ const updatedBenefits = (technology.benefits || []).filter(
2140
+ (b) => b.id !== benefit.id
2141
+ );
2142
+ await updateDoc9(docRef, {
2143
+ benefits: updatedBenefits,
2144
+ updatedAt: /* @__PURE__ */ new Date()
2145
+ });
2146
+ return this.getById(technologyId);
2147
+ }
2148
+ /**
2149
+ * Updates an existing benefit in a technology's list.
2150
+ * If the benefit does not exist, it will not be added.
2151
+ * @param technologyId - ID of the technology
2152
+ * @param benefit - The updated benefit object
2153
+ * @returns The updated technology
2154
+ */
2155
+ async updateBenefit(technologyId, benefit) {
2156
+ const docRef = doc9(this.technologiesRef, technologyId);
2157
+ const technology = await this.getById(technologyId);
2158
+ if (!technology) {
2159
+ throw new Error(`Technology with id ${technologyId} not found`);
2160
+ }
2161
+ const benefits = technology.benefits || [];
2162
+ const index = benefits.findIndex((b) => b.id === benefit.id);
2163
+ if (index === -1) {
2164
+ console.warn(
2165
+ `Benefit with id ${benefit.id} not found for technology ${technologyId}. No update performed.`
2166
+ );
2167
+ return technology;
2168
+ }
2169
+ const updatedBenefits = [...benefits];
2170
+ updatedBenefits[index] = benefit;
1550
2171
  await updateDoc9(docRef, {
1551
- benefits: arrayRemove(benefit),
2172
+ benefits: updatedBenefits,
1552
2173
  updatedAt: /* @__PURE__ */ new Date()
1553
2174
  });
1554
2175
  return this.getById(technologyId);
@@ -1587,7 +2208,7 @@ var TechnologyService = class extends BaseService {
1587
2208
  * @returns Ažurirana tehnologija
1588
2209
  */
1589
2210
  async updateCertificationRequirement(technologyId, certificationRequirement) {
1590
- const docRef = doc9(this.getTechnologiesRef(), technologyId);
2211
+ const docRef = doc9(this.technologiesRef, technologyId);
1591
2212
  await updateDoc9(docRef, {
1592
2213
  certificationRequirement,
1593
2214
  updatedAt: /* @__PURE__ */ new Date()
@@ -1665,7 +2286,7 @@ var TechnologyService = class extends BaseService {
1665
2286
  */
1666
2287
  async getAllowedTechnologies(practitioner) {
1667
2288
  const allTechnologies = await this.getAll();
1668
- const allowedTechnologies = allTechnologies.filter(
2289
+ const allowedTechnologies = allTechnologies.technologies.filter(
1669
2290
  (technology) => this.validateCertification(
1670
2291
  technology.certificationRequirement,
1671
2292
  practitioner.certification
@@ -1685,6 +2306,283 @@ var TechnologyService = class extends BaseService {
1685
2306
  subcategories
1686
2307
  };
1687
2308
  }
2309
+ /**
2310
+ * Gets all active technologies for a subcategory for filter dropdowns.
2311
+ * @param categoryId - The ID of the parent category.
2312
+ * @param subcategoryId - The ID of the subcategory.
2313
+ */
2314
+ async getAllForFilterBySubcategoryId(categoryId, subcategoryId) {
2315
+ const q = query9(
2316
+ collection9(this.db, TECHNOLOGIES_COLLECTION),
2317
+ where9("isActive", "==", true),
2318
+ where9("categoryId", "==", categoryId),
2319
+ where9("subcategoryId", "==", subcategoryId),
2320
+ orderBy8("name")
2321
+ );
2322
+ const snapshot = await getDocs9(q);
2323
+ return snapshot.docs.map(
2324
+ (doc11) => ({
2325
+ id: doc11.id,
2326
+ ...doc11.data()
2327
+ })
2328
+ );
2329
+ }
2330
+ /**
2331
+ * Gets all active technologies for filter dropdowns.
2332
+ */
2333
+ async getAllForFilter() {
2334
+ const q = query9(
2335
+ collection9(this.db, TECHNOLOGIES_COLLECTION),
2336
+ where9("isActive", "==", true),
2337
+ orderBy8("name")
2338
+ );
2339
+ const snapshot = await getDocs9(q);
2340
+ return snapshot.docs.map(
2341
+ (doc11) => ({
2342
+ id: doc11.id,
2343
+ ...doc11.data()
2344
+ })
2345
+ );
2346
+ }
2347
+ };
2348
+
2349
+ // src/backoffice/services/constants.service.ts
2350
+ import {
2351
+ arrayRemove as arrayRemove2,
2352
+ arrayUnion as arrayUnion2,
2353
+ doc as doc10,
2354
+ getDoc as getDoc10,
2355
+ setDoc as setDoc5,
2356
+ updateDoc as updateDoc10
2357
+ } from "firebase/firestore";
2358
+ var ADMIN_CONSTANTS_COLLECTION = "admin-constants";
2359
+ var TREATMENT_BENEFITS_DOC = "treatment-benefits";
2360
+ var CONTRAINDICATIONS_DOC = "contraindications";
2361
+ var ConstantsService = class extends BaseService {
2362
+ /**
2363
+ * @description Gets the reference to the document holding treatment benefits.
2364
+ * @private
2365
+ * @type {DocumentReference}
2366
+ */
2367
+ get treatmentBenefitsDocRef() {
2368
+ return doc10(this.db, ADMIN_CONSTANTS_COLLECTION, TREATMENT_BENEFITS_DOC);
2369
+ }
2370
+ /**
2371
+ * @description Gets the reference to the document holding contraindications.
2372
+ * @private
2373
+ * @type {DocumentReference}
2374
+ */
2375
+ get contraindicationsDocRef() {
2376
+ return doc10(this.db, ADMIN_CONSTANTS_COLLECTION, CONTRAINDICATIONS_DOC);
2377
+ }
2378
+ // =================================================================
2379
+ // Treatment Benefits
2380
+ // =================================================================
2381
+ /**
2382
+ * @description Retrieves all treatment benefits without pagination.
2383
+ * @returns {Promise<TreatmentBenefitDynamic[]>} An array of all treatment benefits.
2384
+ */
2385
+ async getAllBenefitsForFilter() {
2386
+ const docSnap = await getDoc10(this.treatmentBenefitsDocRef);
2387
+ if (!docSnap.exists()) {
2388
+ return [];
2389
+ }
2390
+ return docSnap.data().benefits;
2391
+ }
2392
+ /**
2393
+ * @description Retrieves a paginated list of treatment benefits.
2394
+ * @param {{ page: number; limit: number }} options - Pagination options.
2395
+ * @returns {Promise<{ benefits: TreatmentBenefitDynamic[]; total: number }>} A paginated list of benefits and the total count.
2396
+ */
2397
+ async getAllBenefits(options) {
2398
+ const allBenefits = await this.getAllBenefitsForFilter();
2399
+ const { page, limit: limit9 } = options;
2400
+ const startIndex = page * limit9;
2401
+ const endIndex = startIndex + limit9;
2402
+ const paginatedBenefits = allBenefits.slice(startIndex, endIndex);
2403
+ return { benefits: paginatedBenefits, total: allBenefits.length };
2404
+ }
2405
+ /**
2406
+ * @description Adds a new treatment benefit.
2407
+ * @param {Omit<TreatmentBenefitDynamic, "id">} benefit - The treatment benefit to add, without an ID.
2408
+ * @returns {Promise<TreatmentBenefitDynamic>} The newly created treatment benefit with its generated ID.
2409
+ */
2410
+ async addTreatmentBenefit(benefit) {
2411
+ const newBenefit = {
2412
+ id: this.generateId(),
2413
+ ...benefit
2414
+ };
2415
+ const docSnap = await getDoc10(this.treatmentBenefitsDocRef);
2416
+ if (!docSnap.exists()) {
2417
+ await setDoc5(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
2418
+ } else {
2419
+ await updateDoc10(this.treatmentBenefitsDocRef, {
2420
+ benefits: arrayUnion2(newBenefit)
2421
+ });
2422
+ }
2423
+ return newBenefit;
2424
+ }
2425
+ /**
2426
+ * @description Retrieves a single treatment benefit by its ID.
2427
+ * @param {string} benefitId - The ID of the treatment benefit to retrieve.
2428
+ * @returns {Promise<TreatmentBenefitDynamic | undefined>} The found treatment benefit or undefined.
2429
+ */
2430
+ async getBenefitById(benefitId) {
2431
+ const benefits = await this.getAllBenefitsForFilter();
2432
+ return benefits.find((b) => b.id === benefitId);
2433
+ }
2434
+ /**
2435
+ * @description Searches for treatment benefits by name (case-insensitive).
2436
+ * @param {string} searchTerm - The term to search for in the benefit names.
2437
+ * @returns {Promise<TreatmentBenefitDynamic[]>} An array of matching treatment benefits.
2438
+ */
2439
+ async searchBenefitsByName(searchTerm) {
2440
+ const benefits = await this.getAllBenefitsForFilter();
2441
+ const normalizedSearchTerm = searchTerm.toLowerCase();
2442
+ return benefits.filter(
2443
+ (b) => b.name.toLowerCase().includes(normalizedSearchTerm)
2444
+ );
2445
+ }
2446
+ /**
2447
+ * @description Updates an existing treatment benefit.
2448
+ * @param {TreatmentBenefitDynamic} benefit - The treatment benefit with updated data. Its ID must match an existing benefit.
2449
+ * @returns {Promise<TreatmentBenefitDynamic>} The updated treatment benefit.
2450
+ * @throws {Error} If the treatment benefit is not found.
2451
+ */
2452
+ async updateTreatmentBenefit(benefit) {
2453
+ const benefits = await this.getAllBenefitsForFilter();
2454
+ const benefitIndex = benefits.findIndex((b) => b.id === benefit.id);
2455
+ if (benefitIndex === -1) {
2456
+ throw new Error("Treatment benefit not found.");
2457
+ }
2458
+ benefits[benefitIndex] = benefit;
2459
+ await updateDoc10(this.treatmentBenefitsDocRef, { benefits });
2460
+ return benefit;
2461
+ }
2462
+ /**
2463
+ * @description Deletes a treatment benefit by its ID.
2464
+ * @param {string} benefitId - The ID of the treatment benefit to delete.
2465
+ * @returns {Promise<void>}
2466
+ */
2467
+ async deleteTreatmentBenefit(benefitId) {
2468
+ const benefits = await this.getAllBenefitsForFilter();
2469
+ const benefitToRemove = benefits.find((b) => b.id === benefitId);
2470
+ if (!benefitToRemove) {
2471
+ return;
2472
+ }
2473
+ await updateDoc10(this.treatmentBenefitsDocRef, {
2474
+ benefits: arrayRemove2(benefitToRemove)
2475
+ });
2476
+ }
2477
+ // =================================================================
2478
+ // Contraindications
2479
+ // =================================================================
2480
+ /**
2481
+ * @description Retrieves all contraindications without pagination.
2482
+ * @returns {Promise<ContraindicationDynamic[]>} An array of all contraindications.
2483
+ */
2484
+ async getAllContraindicationsForFilter() {
2485
+ const docSnap = await getDoc10(this.contraindicationsDocRef);
2486
+ if (!docSnap.exists()) {
2487
+ return [];
2488
+ }
2489
+ return docSnap.data().contraindications;
2490
+ }
2491
+ /**
2492
+ * @description Retrieves a paginated list of contraindications.
2493
+ * @param {{ page: number; limit: number }} options - Pagination options.
2494
+ * @returns {Promise<{ contraindications: ContraindicationDynamic[]; total: number }>} A paginated list and the total count.
2495
+ */
2496
+ async getAllContraindications(options) {
2497
+ const allContraindications = await this.getAllContraindicationsForFilter();
2498
+ const { page, limit: limit9 } = options;
2499
+ const startIndex = page * limit9;
2500
+ const endIndex = startIndex + limit9;
2501
+ const paginatedContraindications = allContraindications.slice(
2502
+ startIndex,
2503
+ endIndex
2504
+ );
2505
+ return {
2506
+ contraindications: paginatedContraindications,
2507
+ total: allContraindications.length
2508
+ };
2509
+ }
2510
+ /**
2511
+ * @description Adds a new contraindication.
2512
+ * @param {Omit<ContraindicationDynamic, "id">} contraindication - The contraindication to add, without an ID.
2513
+ * @returns {Promise<ContraindicationDynamic>} The newly created contraindication with its generated ID.
2514
+ */
2515
+ async addContraindication(contraindication) {
2516
+ const newContraindication = {
2517
+ id: this.generateId(),
2518
+ ...contraindication
2519
+ };
2520
+ const docSnap = await getDoc10(this.contraindicationsDocRef);
2521
+ if (!docSnap.exists()) {
2522
+ await setDoc5(this.contraindicationsDocRef, {
2523
+ contraindications: [newContraindication]
2524
+ });
2525
+ } else {
2526
+ await updateDoc10(this.contraindicationsDocRef, {
2527
+ contraindications: arrayUnion2(newContraindication)
2528
+ });
2529
+ }
2530
+ return newContraindication;
2531
+ }
2532
+ /**
2533
+ * @description Retrieves a single contraindication by its ID.
2534
+ * @param {string} contraindicationId - The ID of the contraindication to retrieve.
2535
+ * @returns {Promise<ContraindicationDynamic | undefined>} The found contraindication or undefined.
2536
+ */
2537
+ async getContraindicationById(contraindicationId) {
2538
+ const contraindications = await this.getAllContraindicationsForFilter();
2539
+ return contraindications.find((c) => c.id === contraindicationId);
2540
+ }
2541
+ /**
2542
+ * @description Searches for contraindications by name (case-insensitive).
2543
+ * @param {string} searchTerm - The term to search for in the contraindication names.
2544
+ * @returns {Promise<ContraindicationDynamic[]>} An array of matching contraindications.
2545
+ */
2546
+ async searchContraindicationsByName(searchTerm) {
2547
+ const contraindications = await this.getAllContraindicationsForFilter();
2548
+ const normalizedSearchTerm = searchTerm.toLowerCase();
2549
+ return contraindications.filter(
2550
+ (c) => c.name.toLowerCase().includes(normalizedSearchTerm)
2551
+ );
2552
+ }
2553
+ /**
2554
+ * @description Updates an existing contraindication.
2555
+ * @param {ContraindicationDynamic} contraindication - The contraindication with updated data. Its ID must match an existing one.
2556
+ * @returns {Promise<ContraindicationDynamic>} The updated contraindication.
2557
+ * @throws {Error} If the contraindication is not found.
2558
+ */
2559
+ async updateContraindication(contraindication) {
2560
+ const contraindications = await this.getAllContraindicationsForFilter();
2561
+ const index = contraindications.findIndex(
2562
+ (c) => c.id === contraindication.id
2563
+ );
2564
+ if (index === -1) {
2565
+ throw new Error("Contraindication not found.");
2566
+ }
2567
+ contraindications[index] = contraindication;
2568
+ await updateDoc10(this.contraindicationsDocRef, { contraindications });
2569
+ return contraindication;
2570
+ }
2571
+ /**
2572
+ * @description Deletes a contraindication by its ID.
2573
+ * @param {string} contraindicationId - The ID of the contraindication to delete.
2574
+ * @returns {Promise<void>}
2575
+ */
2576
+ async deleteContraindication(contraindicationId) {
2577
+ const contraindications = await this.getAllContraindicationsForFilter();
2578
+ const toRemove = contraindications.find((c) => c.id === contraindicationId);
2579
+ if (!toRemove) {
2580
+ return;
2581
+ }
2582
+ await updateDoc10(this.contraindicationsDocRef, {
2583
+ contraindications: arrayRemove2(toRemove)
2584
+ });
2585
+ }
1688
2586
  };
1689
2587
 
1690
2588
  // src/backoffice/types/static/blocking-condition.types.ts
@@ -1743,13 +2641,6 @@ var Currency = /* @__PURE__ */ ((Currency2) => {
1743
2641
  return Currency2;
1744
2642
  })(Currency || {});
1745
2643
 
1746
- // src/backoffice/types/static/procedure-family.types.ts
1747
- var ProcedureFamily = /* @__PURE__ */ ((ProcedureFamily2) => {
1748
- ProcedureFamily2["AESTHETICS"] = "aesthetics";
1749
- ProcedureFamily2["SURGERY"] = "surgery";
1750
- return ProcedureFamily2;
1751
- })(ProcedureFamily || {});
1752
-
1753
2644
  // src/backoffice/types/static/treatment-benefit.types.ts
1754
2645
  var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
1755
2646
  TreatmentBenefit2["WRINKLE_REDUCTION"] = "wrinkle_reduction";
@@ -1772,9 +2663,25 @@ var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
1772
2663
 
1773
2664
  // src/backoffice/validations/schemas.ts
1774
2665
  import { z as z2 } from "zod";
2666
+ var contraindicationDynamicSchema = z2.object({
2667
+ id: z2.string().min(1, "Contraindication ID is required").regex(
2668
+ /^[a-z0-9_]+$/,
2669
+ "ID must be in snake_case (lowercase, numbers, and underscores only)"
2670
+ ),
2671
+ name: z2.string().min(1, "Contraindication name is required"),
2672
+ description: z2.string().optional()
2673
+ });
2674
+ var treatmentBenefitDynamicSchema = z2.object({
2675
+ id: z2.string().min(1, "Benefit ID is required").regex(
2676
+ /^[a-z0-9_]+$/,
2677
+ "ID must be in snake_case (lowercase, numbers, and underscores only)"
2678
+ ),
2679
+ name: z2.string().min(1, "Benefit name is required"),
2680
+ description: z2.string().optional()
2681
+ });
1775
2682
  var blockingConditionSchemaBackoffice = z2.nativeEnum(BlockingCondition);
1776
- var contraindicationSchemaBackoffice = z2.nativeEnum(Contraindication);
1777
- var treatmentBenefitSchemaBackoffice = z2.nativeEnum(TreatmentBenefit);
2683
+ var contraindicationSchemaBackoffice = contraindicationDynamicSchema;
2684
+ var treatmentBenefitSchemaBackoffice = treatmentBenefitDynamicSchema;
1778
2685
  var procedureFamilySchemaBackoffice = z2.nativeEnum(ProcedureFamily);
1779
2686
  var timeUnitSchemaBackoffice = z2.nativeEnum(TimeUnit);
1780
2687
  var requirementTypeSchema = z2.nativeEnum(RequirementType);
@@ -1996,6 +2903,7 @@ export {
1996
2903
  CertificationLevel,
1997
2904
  CertificationSpecialty,
1998
2905
  CircularReferenceError,
2906
+ ConstantsService,
1999
2907
  Contraindication,
2000
2908
  ContraindicationError,
2001
2909
  Currency,
@@ -2043,6 +2951,7 @@ export {
2043
2951
  certificationLevelSchema,
2044
2952
  certificationRequirementSchema,
2045
2953
  certificationSpecialtySchema,
2954
+ contraindicationDynamicSchema,
2046
2955
  contraindicationSchemaBackoffice,
2047
2956
  createDocumentTemplateSchema,
2048
2957
  documentElementSchema,
@@ -2059,6 +2968,7 @@ export {
2059
2968
  technologyUpdateSchema,
2060
2969
  timeUnitSchemaBackoffice,
2061
2970
  timeframeSchema,
2971
+ treatmentBenefitDynamicSchema,
2062
2972
  treatmentBenefitSchemaBackoffice,
2063
2973
  updateDocumentTemplateSchema
2064
2974
  };