@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
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  CertificationLevel: () => CertificationLevel,
33
33
  CertificationSpecialty: () => CertificationSpecialty,
34
34
  CircularReferenceError: () => CircularReferenceError,
35
+ ConstantsService: () => ConstantsService,
35
36
  Contraindication: () => Contraindication,
36
37
  ContraindicationError: () => ContraindicationError,
37
38
  Currency: () => Currency,
@@ -79,6 +80,7 @@ __export(index_exports, {
79
80
  certificationLevelSchema: () => certificationLevelSchema,
80
81
  certificationRequirementSchema: () => certificationRequirementSchema,
81
82
  certificationSpecialtySchema: () => certificationSpecialtySchema,
83
+ contraindicationDynamicSchema: () => contraindicationDynamicSchema,
82
84
  contraindicationSchemaBackoffice: () => contraindicationSchemaBackoffice,
83
85
  createDocumentTemplateSchema: () => createDocumentTemplateSchema,
84
86
  documentElementSchema: () => documentElementSchema,
@@ -95,6 +97,7 @@ __export(index_exports, {
95
97
  technologyUpdateSchema: () => technologyUpdateSchema,
96
98
  timeUnitSchemaBackoffice: () => timeUnitSchemaBackoffice,
97
99
  timeframeSchema: () => timeframeSchema,
100
+ treatmentBenefitDynamicSchema: () => treatmentBenefitDynamicSchema,
98
101
  treatmentBenefitSchemaBackoffice: () => treatmentBenefitSchemaBackoffice,
99
102
  updateDocumentTemplateSchema: () => updateDocumentTemplateSchema
100
103
  });
@@ -109,11 +112,13 @@ var BRANDS_COLLECTION = "brands";
109
112
  // src/services/base.service.ts
110
113
  var import_storage = require("firebase/storage");
111
114
  var BaseService = class {
112
- constructor(db, auth, app) {
115
+ constructor(db, auth, app, storage) {
113
116
  this.db = db;
114
117
  this.auth = auth;
115
118
  this.app = app;
116
- this.storage = (0, import_storage.getStorage)(app);
119
+ if (app) {
120
+ this.storage = storage || (0, import_storage.getStorage)(app);
121
+ }
117
122
  }
118
123
  /**
119
124
  * Generiše jedinstveni ID za dokumente
@@ -146,6 +151,7 @@ var BrandService = class extends BaseService {
146
151
  const now = /* @__PURE__ */ new Date();
147
152
  const newBrand = {
148
153
  ...brand,
154
+ name_lowercase: brand.name.toLowerCase(),
149
155
  createdAt: now,
150
156
  updatedAt: now,
151
157
  isActive: true
@@ -154,15 +160,69 @@ var BrandService = class extends BaseService {
154
160
  return { id: docRef.id, ...newBrand };
155
161
  }
156
162
  /**
157
- * Gets all active brands
163
+ * Gets a paginated list of active brands, optionally filtered by name.
164
+ * @param rowsPerPage - The number of brands to fetch.
165
+ * @param searchTerm - An optional string to filter brand names by (starts-with search).
166
+ * @param lastVisible - An optional document snapshot to use as a cursor for pagination.
158
167
  */
159
- async getAll() {
160
- const q = (0, import_firestore.query)(this.getBrandsRef(), (0, import_firestore.where)("isActive", "==", true));
168
+ async getAll(rowsPerPage, searchTerm, lastVisible) {
169
+ const constraints = [
170
+ (0, import_firestore.where)("isActive", "==", true),
171
+ (0, import_firestore.orderBy)("name_lowercase")
172
+ ];
173
+ if (searchTerm) {
174
+ const lowercasedSearchTerm = searchTerm.toLowerCase();
175
+ constraints.push((0, import_firestore.where)("name_lowercase", ">=", lowercasedSearchTerm));
176
+ constraints.push(
177
+ (0, import_firestore.where)("name_lowercase", "<=", lowercasedSearchTerm + "\uF8FF")
178
+ );
179
+ }
180
+ if (lastVisible) {
181
+ constraints.push((0, import_firestore.startAfter)(lastVisible));
182
+ }
183
+ constraints.push((0, import_firestore.limit)(rowsPerPage));
184
+ const q = (0, import_firestore.query)(this.getBrandsRef(), ...constraints);
185
+ const snapshot = await (0, import_firestore.getDocs)(q);
186
+ const brands = snapshot.docs.map(
187
+ (doc11) => ({
188
+ id: doc11.id,
189
+ ...doc11.data()
190
+ })
191
+ );
192
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
193
+ return { brands, lastVisible: newLastVisible };
194
+ }
195
+ /**
196
+ * Gets the total count of active brands, optionally filtered by name.
197
+ * @param searchTerm - An optional string to filter brand names by (starts-with search).
198
+ */
199
+ async getBrandsCount(searchTerm) {
200
+ const constraints = [(0, import_firestore.where)("isActive", "==", true)];
201
+ if (searchTerm) {
202
+ const lowercasedSearchTerm = searchTerm.toLowerCase();
203
+ constraints.push((0, import_firestore.where)("name_lowercase", ">=", lowercasedSearchTerm));
204
+ constraints.push(
205
+ (0, import_firestore.where)("name_lowercase", "<=", lowercasedSearchTerm + "\uF8FF")
206
+ );
207
+ }
208
+ const q = (0, import_firestore.query)(this.getBrandsRef(), ...constraints);
209
+ const snapshot = await (0, import_firestore.getCountFromServer)(q);
210
+ return snapshot.data().count;
211
+ }
212
+ /**
213
+ * Gets all active brands for filter dropdowns (not paginated).
214
+ */
215
+ async getAllForFilter() {
216
+ const q = (0, import_firestore.query)(
217
+ this.getBrandsRef(),
218
+ (0, import_firestore.where)("isActive", "==", true),
219
+ (0, import_firestore.orderBy)("name")
220
+ );
161
221
  const snapshot = await (0, import_firestore.getDocs)(q);
162
222
  return snapshot.docs.map(
163
- (doc10) => ({
164
- id: doc10.id,
165
- ...doc10.data()
223
+ (doc11) => ({
224
+ id: doc11.id,
225
+ ...doc11.data()
166
226
  })
167
227
  );
168
228
  }
@@ -174,6 +234,9 @@ var BrandService = class extends BaseService {
174
234
  ...brand,
175
235
  updatedAt: /* @__PURE__ */ new Date()
176
236
  };
237
+ if (brand.name) {
238
+ updateData.name_lowercase = brand.name.toLowerCase();
239
+ }
177
240
  const docRef = (0, import_firestore.doc)(this.getBrandsRef(), brandId);
178
241
  await (0, import_firestore.updateDoc)(docRef, updateData);
179
242
  return this.getById(brandId);
@@ -206,6 +269,13 @@ var import_firestore2 = require("firebase/firestore");
206
269
  // src/backoffice/types/category.types.ts
207
270
  var CATEGORIES_COLLECTION = "backoffice_categories";
208
271
 
272
+ // src/backoffice/types/static/procedure-family.types.ts
273
+ var ProcedureFamily = /* @__PURE__ */ ((ProcedureFamily2) => {
274
+ ProcedureFamily2["AESTHETICS"] = "aesthetics";
275
+ ProcedureFamily2["SURGERY"] = "surgery";
276
+ return ProcedureFamily2;
277
+ })(ProcedureFamily || {});
278
+
209
279
  // src/backoffice/services/category.service.ts
210
280
  var CategoryService = class extends BaseService {
211
281
  /**
@@ -231,37 +301,87 @@ var CategoryService = class extends BaseService {
231
301
  return { id: docRef.id, ...newCategory };
232
302
  }
233
303
  /**
234
- * Vraća sve aktivne kategorije
235
- * @returns Lista aktivnih kategorija
304
+ * Returns counts of categories for each family.
305
+ * @param active - Whether to count active or inactive categories.
306
+ * @returns A record mapping family to category count.
236
307
  */
237
- async getAll() {
308
+ async getCategoryCounts(active = true) {
309
+ const counts = {};
310
+ const families = Object.values(ProcedureFamily);
311
+ for (const family of families) {
312
+ const q = (0, import_firestore2.query)(
313
+ this.categoriesRef,
314
+ (0, import_firestore2.where)("family", "==", family),
315
+ (0, import_firestore2.where)("isActive", "==", active)
316
+ );
317
+ const snapshot = await (0, import_firestore2.getCountFromServer)(q);
318
+ counts[family] = snapshot.data().count;
319
+ }
320
+ return counts;
321
+ }
322
+ /**
323
+ * Vraća sve kategorije za potrebe filtera (bez paginacije)
324
+ * @returns Lista svih aktivnih kategorija
325
+ */
326
+ async getAllForFilter() {
238
327
  const q = (0, import_firestore2.query)(this.categoriesRef, (0, import_firestore2.where)("isActive", "==", true));
239
328
  const snapshot = await (0, import_firestore2.getDocs)(q);
240
329
  return snapshot.docs.map(
241
- (doc10) => ({
242
- id: doc10.id,
243
- ...doc10.data()
330
+ (doc11) => ({
331
+ id: doc11.id,
332
+ ...doc11.data()
244
333
  })
245
334
  );
246
335
  }
247
336
  /**
248
- * Vraća sve aktivne kategorije za određenu familiju procedura
337
+ * Vraća sve kategorije sa paginacijom
338
+ * @param options - Pagination and filter options
339
+ * @returns Lista kategorija i poslednji vidljiv dokument
340
+ */
341
+ async getAll(options = {}) {
342
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
343
+ const constraints = [
344
+ (0, import_firestore2.where)("isActive", "==", active),
345
+ (0, import_firestore2.orderBy)("name"),
346
+ queryLimit ? (0, import_firestore2.limit)(queryLimit) : void 0,
347
+ lastVisible ? (0, import_firestore2.startAfter)(lastVisible) : void 0
348
+ ].filter((c) => !!c);
349
+ const q = (0, import_firestore2.query)(this.categoriesRef, ...constraints);
350
+ const snapshot = await (0, import_firestore2.getDocs)(q);
351
+ const categories = snapshot.docs.map(
352
+ (doc11) => ({
353
+ id: doc11.id,
354
+ ...doc11.data()
355
+ })
356
+ );
357
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
358
+ return { categories, lastVisible: newLastVisible };
359
+ }
360
+ /**
361
+ * Vraća sve aktivne kategorije za određenu familiju procedura sa paginacijom
249
362
  * @param family - Familija procedura (aesthetics/surgery)
363
+ * @param options - Pagination options
250
364
  * @returns Lista kategorija koje pripadaju traženoj familiji
251
365
  */
252
- async getAllByFamily(family) {
253
- const q = (0, import_firestore2.query)(
254
- this.categoriesRef,
366
+ async getAllByFamily(family, options = {}) {
367
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
368
+ const constraints = [
255
369
  (0, import_firestore2.where)("family", "==", family),
256
- (0, import_firestore2.where)("isActive", "==", true)
257
- );
370
+ (0, import_firestore2.where)("isActive", "==", active),
371
+ (0, import_firestore2.orderBy)("name"),
372
+ queryLimit ? (0, import_firestore2.limit)(queryLimit) : void 0,
373
+ lastVisible ? (0, import_firestore2.startAfter)(lastVisible) : void 0
374
+ ].filter((c) => !!c);
375
+ const q = (0, import_firestore2.query)(this.categoriesRef, ...constraints);
258
376
  const snapshot = await (0, import_firestore2.getDocs)(q);
259
- return snapshot.docs.map(
260
- (doc10) => ({
261
- id: doc10.id,
262
- ...doc10.data()
377
+ const categories = snapshot.docs.map(
378
+ (doc11) => ({
379
+ id: doc11.id,
380
+ ...doc11.data()
263
381
  })
264
382
  );
383
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
384
+ return { categories, lastVisible: newLastVisible };
265
385
  }
266
386
  /**
267
387
  * Ažurira postojeću kategoriju
@@ -285,6 +405,13 @@ var CategoryService = class extends BaseService {
285
405
  async delete(id) {
286
406
  await this.update(id, { isActive: false });
287
407
  }
408
+ /**
409
+ * Reactivates a category by setting its isActive flag to true.
410
+ * @param id - The ID of the category to reactivate.
411
+ */
412
+ async reactivate(id) {
413
+ await this.update(id, { isActive: true });
414
+ }
288
415
  /**
289
416
  * Vraća kategoriju po ID-u
290
417
  * @param id - ID tražene kategorije
@@ -536,9 +663,10 @@ var updateFilledDocumentDataSchema = import_zod.z.object({
536
663
  });
537
664
 
538
665
  // src/services/documentation-templates/documentation-template.service.ts
666
+ var import_firestore4 = require("firebase/firestore");
539
667
  var DocumentationTemplateService = class extends BaseService {
540
- constructor() {
541
- super(...arguments);
668
+ constructor(...args) {
669
+ super(...args);
542
670
  this.collectionRef = (0, import_firestore3.collection)(
543
671
  this.db,
544
672
  DOCUMENTATION_TEMPLATES_COLLECTION
@@ -690,8 +818,8 @@ var DocumentationTemplateService = class extends BaseService {
690
818
  const q = (0, import_firestore3.query)(versionsCollectionRef, (0, import_firestore3.orderBy)("version", "desc"));
691
819
  const querySnapshot = await (0, import_firestore3.getDocs)(q);
692
820
  const versions = [];
693
- querySnapshot.forEach((doc10) => {
694
- versions.push(doc10.data());
821
+ querySnapshot.forEach((doc11) => {
822
+ versions.push(doc11.data());
695
823
  });
696
824
  return versions;
697
825
  }
@@ -722,15 +850,97 @@ var DocumentationTemplateService = class extends BaseService {
722
850
  const querySnapshot = await (0, import_firestore3.getDocs)(q);
723
851
  const templates = [];
724
852
  let lastVisible = null;
725
- querySnapshot.forEach((doc10) => {
726
- templates.push(doc10.data());
727
- lastVisible = doc10;
853
+ querySnapshot.forEach((doc11) => {
854
+ templates.push(doc11.data());
855
+ lastVisible = doc11;
728
856
  });
729
857
  return {
730
858
  templates,
731
859
  lastDoc: lastVisible
732
860
  };
733
861
  }
862
+ /**
863
+ * Get all active templates with optional filters and pagination.
864
+ * @param options - Options for filtering and pagination.
865
+ * @returns A promise that resolves to the templates and the last visible document.
866
+ */
867
+ async getTemplates(options) {
868
+ const {
869
+ pageSize = 20,
870
+ lastDoc,
871
+ isUserForm,
872
+ isRequired,
873
+ sortingOrder
874
+ } = options;
875
+ const constraints = [
876
+ (0, import_firestore3.where)("isActive", "==", true),
877
+ (0, import_firestore3.orderBy)("sortingOrder", "asc"),
878
+ (0, import_firestore3.orderBy)("title", "asc"),
879
+ (0, import_firestore3.limit)(pageSize)
880
+ ];
881
+ if (isUserForm !== void 0) {
882
+ constraints.push((0, import_firestore3.where)("isUserForm", "==", isUserForm));
883
+ }
884
+ if (isRequired !== void 0) {
885
+ constraints.push((0, import_firestore3.where)("isRequired", "==", isRequired));
886
+ }
887
+ if (sortingOrder !== void 0) {
888
+ constraints.push((0, import_firestore3.where)("sortingOrder", "==", sortingOrder));
889
+ }
890
+ if (lastDoc) {
891
+ constraints.push((0, import_firestore3.startAfter)(lastDoc));
892
+ }
893
+ const q = (0, import_firestore3.query)(this.collectionRef, ...constraints.filter((c) => c));
894
+ const querySnapshot = await (0, import_firestore3.getDocs)(q);
895
+ const templates = [];
896
+ let lastVisible = null;
897
+ querySnapshot.forEach((doc11) => {
898
+ templates.push(doc11.data());
899
+ lastVisible = doc11;
900
+ });
901
+ return {
902
+ templates,
903
+ lastDoc: lastVisible
904
+ };
905
+ }
906
+ /**
907
+ * Get the total count of active templates with optional filters.
908
+ * @param options - Options for filtering.
909
+ * @returns A promise that resolves to the total count of templates.
910
+ */
911
+ async getTemplatesCount(options) {
912
+ const { isUserForm, isRequired, sortingOrder } = options;
913
+ const constraints = [(0, import_firestore3.where)("isActive", "==", true)];
914
+ if (isUserForm !== void 0) {
915
+ constraints.push((0, import_firestore3.where)("isUserForm", "==", isUserForm));
916
+ }
917
+ if (isRequired !== void 0) {
918
+ constraints.push((0, import_firestore3.where)("isRequired", "==", isRequired));
919
+ }
920
+ if (sortingOrder !== void 0) {
921
+ constraints.push((0, import_firestore3.where)("sortingOrder", "==", sortingOrder));
922
+ }
923
+ const q = (0, import_firestore3.query)(this.collectionRef, ...constraints.filter((c) => c));
924
+ const snapshot = await (0, import_firestore4.getCountFromServer)(q);
925
+ return snapshot.data().count;
926
+ }
927
+ /**
928
+ * Get all active templates without pagination for filtering purposes.
929
+ * @returns A promise that resolves to an array of all active templates.
930
+ */
931
+ async getAllActiveTemplates() {
932
+ const q = (0, import_firestore3.query)(
933
+ this.collectionRef,
934
+ (0, import_firestore3.where)("isActive", "==", true),
935
+ (0, import_firestore3.orderBy)("title", "asc")
936
+ );
937
+ const querySnapshot = await (0, import_firestore3.getDocs)(q);
938
+ const templates = [];
939
+ querySnapshot.forEach((doc11) => {
940
+ templates.push(doc11.data());
941
+ });
942
+ return templates;
943
+ }
734
944
  /**
735
945
  * Get templates by tags
736
946
  * @param tags - Tags to filter by
@@ -752,9 +962,9 @@ var DocumentationTemplateService = class extends BaseService {
752
962
  const querySnapshot = await (0, import_firestore3.getDocs)(q);
753
963
  const templates = [];
754
964
  let lastVisible = null;
755
- querySnapshot.forEach((doc10) => {
756
- templates.push(doc10.data());
757
- lastVisible = doc10;
965
+ querySnapshot.forEach((doc11) => {
966
+ templates.push(doc11.data());
967
+ lastVisible = doc11;
758
968
  });
759
969
  return {
760
970
  templates,
@@ -781,9 +991,9 @@ var DocumentationTemplateService = class extends BaseService {
781
991
  const querySnapshot = await (0, import_firestore3.getDocs)(q);
782
992
  const templates = [];
783
993
  let lastVisible = null;
784
- querySnapshot.forEach((doc10) => {
785
- templates.push(doc10.data());
786
- lastVisible = doc10;
994
+ querySnapshot.forEach((doc11) => {
995
+ templates.push(doc11.data());
996
+ lastVisible = doc11;
787
997
  });
788
998
  return {
789
999
  templates,
@@ -809,20 +1019,20 @@ var DocumentationTemplateService = class extends BaseService {
809
1019
  }
810
1020
  const querySnapshot = await (0, import_firestore3.getDocs)(q);
811
1021
  const templates = [];
812
- querySnapshot.forEach((doc10) => {
813
- templates.push(doc10.data());
1022
+ querySnapshot.forEach((doc11) => {
1023
+ templates.push(doc11.data());
814
1024
  });
815
1025
  return templates;
816
1026
  }
817
1027
  };
818
1028
 
819
1029
  // src/services/documentation-templates/filled-document.service.ts
820
- var import_firestore6 = require("firebase/firestore");
1030
+ var import_firestore7 = require("firebase/firestore");
821
1031
 
822
1032
  // src/services/media/media.service.ts
823
- var import_firestore4 = require("firebase/firestore");
824
- var import_storage2 = require("firebase/storage");
825
1033
  var import_firestore5 = require("firebase/firestore");
1034
+ var import_storage2 = require("firebase/storage");
1035
+ var import_firestore6 = require("firebase/firestore");
826
1036
 
827
1037
  // src/backoffice/services/documentation-template.service.ts
828
1038
  var DocumentationTemplateServiceBackoffice = class {
@@ -832,8 +1042,8 @@ var DocumentationTemplateServiceBackoffice = class {
832
1042
  * @param auth - Firebase Auth instance
833
1043
  * @param app - Firebase App instance
834
1044
  */
835
- constructor(db, auth, app) {
836
- this.apiService = new DocumentationTemplateService(db, auth, app);
1045
+ constructor(...args) {
1046
+ this.apiService = new DocumentationTemplateService(...args);
837
1047
  }
838
1048
  /**
839
1049
  * Create a new document template
@@ -878,6 +1088,40 @@ var DocumentationTemplateServiceBackoffice = class {
878
1088
  async getActiveTemplates(pageSize = 20, lastDoc) {
879
1089
  return this.apiService.getActiveTemplates(pageSize, lastDoc);
880
1090
  }
1091
+ /**
1092
+ * Get all active templates with optional filters and pagination.
1093
+ * @param options - Options for filtering and pagination.
1094
+ * @returns A promise that resolves to the templates and the last visible document.
1095
+ */
1096
+ async getTemplates(options) {
1097
+ return this.apiService.getTemplates(options);
1098
+ }
1099
+ /**
1100
+ * Get the total count of active templates with optional filters.
1101
+ * @param options - Options for filtering.
1102
+ * @returns A promise that resolves to the total count of templates.
1103
+ */
1104
+ async getTemplatesCount(options) {
1105
+ return this.apiService.getTemplatesCount(options);
1106
+ }
1107
+ /**
1108
+ * Get all active templates without pagination for filtering purposes.
1109
+ * @returns A promise that resolves to an array of all active templates.
1110
+ */
1111
+ async getAllActiveTemplates() {
1112
+ return this.apiService.getAllActiveTemplates();
1113
+ }
1114
+ /**
1115
+ * Searches for active templates by title.
1116
+ * @param title - The title to search for.
1117
+ * @returns A list of templates that match the search criteria.
1118
+ */
1119
+ async search(title) {
1120
+ const { templates } = await this.apiService.getActiveTemplates(1e3);
1121
+ return templates.filter(
1122
+ (t) => t.title.toLowerCase().includes(title.toLowerCase())
1123
+ );
1124
+ }
881
1125
  /**
882
1126
  * Get templates by tags
883
1127
  * @param tags - Tags to filter by
@@ -918,7 +1162,7 @@ var DocumentationTemplateServiceBackoffice = class {
918
1162
  };
919
1163
 
920
1164
  // src/backoffice/services/product.service.ts
921
- var import_firestore7 = require("firebase/firestore");
1165
+ var import_firestore8 = require("firebase/firestore");
922
1166
 
923
1167
  // src/backoffice/types/product.types.ts
924
1168
  var PRODUCTS_COLLECTION = "products";
@@ -934,7 +1178,7 @@ var ProductService = class extends BaseService {
934
1178
  * @returns Firestore collection reference
935
1179
  */
936
1180
  getProductsRef(technologyId) {
937
- return (0, import_firestore7.collection)(
1181
+ return (0, import_firestore8.collection)(
938
1182
  this.db,
939
1183
  TECHNOLOGIES_COLLECTION,
940
1184
  technologyId,
@@ -954,47 +1198,129 @@ var ProductService = class extends BaseService {
954
1198
  updatedAt: now,
955
1199
  isActive: true
956
1200
  };
957
- const productRef = await (0, import_firestore7.addDoc)(
1201
+ const productRef = await (0, import_firestore8.addDoc)(
958
1202
  this.getProductsRef(technologyId),
959
1203
  newProduct
960
1204
  );
961
1205
  return { id: productRef.id, ...newProduct };
962
1206
  }
963
1207
  /**
964
- * Gets all products for a technology
1208
+ * Gets a paginated list of all products, with optional filters.
1209
+ * This uses a collectionGroup query to search across all technologies.
965
1210
  */
966
- async getAllByTechnology(technologyId) {
967
- const q = (0, import_firestore7.query)(
968
- this.getProductsRef(technologyId),
969
- (0, import_firestore7.where)("isActive", "==", true)
1211
+ async getAll(options) {
1212
+ const {
1213
+ rowsPerPage,
1214
+ lastVisible,
1215
+ categoryId,
1216
+ subcategoryId,
1217
+ technologyId
1218
+ } = options;
1219
+ const constraints = [
1220
+ (0, import_firestore8.where)("isActive", "==", true),
1221
+ (0, import_firestore8.orderBy)("name")
1222
+ ];
1223
+ if (categoryId) {
1224
+ constraints.push((0, import_firestore8.where)("categoryId", "==", categoryId));
1225
+ }
1226
+ if (subcategoryId) {
1227
+ constraints.push((0, import_firestore8.where)("subcategoryId", "==", subcategoryId));
1228
+ }
1229
+ if (technologyId) {
1230
+ constraints.push((0, import_firestore8.where)("technologyId", "==", technologyId));
1231
+ }
1232
+ if (lastVisible) {
1233
+ constraints.push((0, import_firestore8.startAfter)(lastVisible));
1234
+ }
1235
+ constraints.push((0, import_firestore8.limit)(rowsPerPage));
1236
+ const q = (0, import_firestore8.query)(
1237
+ (0, import_firestore8.collectionGroup)(this.db, PRODUCTS_COLLECTION),
1238
+ ...constraints
970
1239
  );
971
- const snapshot = await (0, import_firestore7.getDocs)(q);
972
- return snapshot.docs.map(
973
- (doc10) => ({
974
- id: doc10.id,
975
- ...doc10.data()
1240
+ const snapshot = await (0, import_firestore8.getDocs)(q);
1241
+ const products = snapshot.docs.map(
1242
+ (doc11) => ({
1243
+ id: doc11.id,
1244
+ ...doc11.data()
976
1245
  })
977
1246
  );
1247
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1248
+ return { products, lastVisible: newLastVisible };
1249
+ }
1250
+ /**
1251
+ * Gets the total count of active products, with optional filters.
1252
+ */
1253
+ async getProductsCount(options) {
1254
+ const { categoryId, subcategoryId, technologyId } = options;
1255
+ const constraints = [(0, import_firestore8.where)("isActive", "==", true)];
1256
+ if (categoryId) {
1257
+ constraints.push((0, import_firestore8.where)("categoryId", "==", categoryId));
1258
+ }
1259
+ if (subcategoryId) {
1260
+ constraints.push((0, import_firestore8.where)("subcategoryId", "==", subcategoryId));
1261
+ }
1262
+ if (technologyId) {
1263
+ constraints.push((0, import_firestore8.where)("technologyId", "==", technologyId));
1264
+ }
1265
+ const q = (0, import_firestore8.query)(
1266
+ (0, import_firestore8.collectionGroup)(this.db, PRODUCTS_COLLECTION),
1267
+ ...constraints
1268
+ );
1269
+ const snapshot = await (0, import_firestore8.getCountFromServer)(q);
1270
+ return snapshot.data().count;
1271
+ }
1272
+ /**
1273
+ * Gets counts of active products grouped by category, subcategory, and technology.
1274
+ * This uses a single collectionGroup query for efficiency.
1275
+ */
1276
+ async getProductCounts() {
1277
+ const q = (0, import_firestore8.query)(
1278
+ (0, import_firestore8.collectionGroup)(this.db, PRODUCTS_COLLECTION),
1279
+ (0, import_firestore8.where)("isActive", "==", true)
1280
+ );
1281
+ const snapshot = await (0, import_firestore8.getDocs)(q);
1282
+ const counts = {
1283
+ byCategory: {},
1284
+ bySubcategory: {},
1285
+ byTechnology: {}
1286
+ };
1287
+ if (snapshot.empty) {
1288
+ return counts;
1289
+ }
1290
+ snapshot.docs.forEach((doc11) => {
1291
+ const product = doc11.data();
1292
+ const { categoryId, subcategoryId, technologyId } = product;
1293
+ if (categoryId) {
1294
+ counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
1295
+ }
1296
+ if (subcategoryId) {
1297
+ counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
1298
+ }
1299
+ if (technologyId) {
1300
+ counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
1301
+ }
1302
+ });
1303
+ return counts;
978
1304
  }
979
1305
  /**
980
1306
  * Gets all products for a brand by filtering through all technologies
981
1307
  */
982
1308
  async getAllByBrand(brandId) {
983
- const allTechnologiesRef = (0, import_firestore7.collection)(this.db, TECHNOLOGIES_COLLECTION);
984
- const technologiesSnapshot = await (0, import_firestore7.getDocs)(allTechnologiesRef);
1309
+ const allTechnologiesRef = (0, import_firestore8.collection)(this.db, TECHNOLOGIES_COLLECTION);
1310
+ const technologiesSnapshot = await (0, import_firestore8.getDocs)(allTechnologiesRef);
985
1311
  const products = [];
986
1312
  for (const techDoc of technologiesSnapshot.docs) {
987
- const q = (0, import_firestore7.query)(
1313
+ const q = (0, import_firestore8.query)(
988
1314
  this.getProductsRef(techDoc.id),
989
- (0, import_firestore7.where)("brandId", "==", brandId),
990
- (0, import_firestore7.where)("isActive", "==", true)
1315
+ (0, import_firestore8.where)("brandId", "==", brandId),
1316
+ (0, import_firestore8.where)("isActive", "==", true)
991
1317
  );
992
- const snapshot = await (0, import_firestore7.getDocs)(q);
1318
+ const snapshot = await (0, import_firestore8.getDocs)(q);
993
1319
  products.push(
994
1320
  ...snapshot.docs.map(
995
- (doc10) => ({
996
- id: doc10.id,
997
- ...doc10.data()
1321
+ (doc11) => ({
1322
+ id: doc11.id,
1323
+ ...doc11.data()
998
1324
  })
999
1325
  )
1000
1326
  );
@@ -1009,8 +1335,8 @@ var ProductService = class extends BaseService {
1009
1335
  ...product,
1010
1336
  updatedAt: /* @__PURE__ */ new Date()
1011
1337
  };
1012
- const docRef = (0, import_firestore7.doc)(this.getProductsRef(technologyId), productId);
1013
- await (0, import_firestore7.updateDoc)(docRef, updateData);
1338
+ const docRef = (0, import_firestore8.doc)(this.getProductsRef(technologyId), productId);
1339
+ await (0, import_firestore8.updateDoc)(docRef, updateData);
1014
1340
  return this.getById(technologyId, productId);
1015
1341
  }
1016
1342
  /**
@@ -1025,8 +1351,8 @@ var ProductService = class extends BaseService {
1025
1351
  * Gets a product by ID
1026
1352
  */
1027
1353
  async getById(technologyId, productId) {
1028
- const docRef = (0, import_firestore7.doc)(this.getProductsRef(technologyId), productId);
1029
- const docSnap = await (0, import_firestore7.getDoc)(docRef);
1354
+ const docRef = (0, import_firestore8.doc)(this.getProductsRef(technologyId), productId);
1355
+ const docSnap = await (0, import_firestore8.getDoc)(docRef);
1030
1356
  if (!docSnap.exists()) return null;
1031
1357
  return {
1032
1358
  id: docSnap.id,
@@ -1036,7 +1362,7 @@ var ProductService = class extends BaseService {
1036
1362
  };
1037
1363
 
1038
1364
  // src/backoffice/services/requirement.service.ts
1039
- var import_firestore8 = require("firebase/firestore");
1365
+ var import_firestore9 = require("firebase/firestore");
1040
1366
 
1041
1367
  // src/backoffice/types/requirement.types.ts
1042
1368
  var TimeUnit = /* @__PURE__ */ ((TimeUnit2) => {
@@ -1057,7 +1383,7 @@ var RequirementService = class extends BaseService {
1057
1383
  * Referenca na Firestore kolekciju zahteva
1058
1384
  */
1059
1385
  get requirementsRef() {
1060
- return (0, import_firestore8.collection)(this.db, REQUIREMENTS_COLLECTION);
1386
+ return (0, import_firestore9.collection)(this.db, REQUIREMENTS_COLLECTION);
1061
1387
  }
1062
1388
  /**
1063
1389
  * Kreira novi globalni zahtev
@@ -1072,7 +1398,7 @@ var RequirementService = class extends BaseService {
1072
1398
  updatedAt: now,
1073
1399
  isActive: true
1074
1400
  };
1075
- const docRef = await (0, import_firestore8.addDoc)(this.requirementsRef, newRequirement);
1401
+ const docRef = await (0, import_firestore9.addDoc)(this.requirementsRef, newRequirement);
1076
1402
  return { id: docRef.id, ...newRequirement };
1077
1403
  }
1078
1404
  /**
@@ -1080,12 +1406,12 @@ var RequirementService = class extends BaseService {
1080
1406
  * @returns Lista aktivnih zahteva
1081
1407
  */
1082
1408
  async getAll() {
1083
- const q = (0, import_firestore8.query)(this.requirementsRef, (0, import_firestore8.where)("isActive", "==", true));
1084
- const snapshot = await (0, import_firestore8.getDocs)(q);
1409
+ const q = (0, import_firestore9.query)(this.requirementsRef, (0, import_firestore9.where)("isActive", "==", true));
1410
+ const snapshot = await (0, import_firestore9.getDocs)(q);
1085
1411
  return snapshot.docs.map(
1086
- (doc10) => ({
1087
- id: doc10.id,
1088
- ...doc10.data()
1412
+ (doc11) => ({
1413
+ id: doc11.id,
1414
+ ...doc11.data()
1089
1415
  })
1090
1416
  );
1091
1417
  }
@@ -1095,19 +1421,31 @@ var RequirementService = class extends BaseService {
1095
1421
  * @returns Lista zahteva određenog tipa
1096
1422
  */
1097
1423
  async getAllByType(type) {
1098
- const q = (0, import_firestore8.query)(
1424
+ const q = (0, import_firestore9.query)(
1099
1425
  this.requirementsRef,
1100
- (0, import_firestore8.where)("type", "==", type),
1101
- (0, import_firestore8.where)("isActive", "==", true)
1426
+ (0, import_firestore9.where)("type", "==", type),
1427
+ (0, import_firestore9.where)("isActive", "==", true)
1102
1428
  );
1103
- const snapshot = await (0, import_firestore8.getDocs)(q);
1429
+ const snapshot = await (0, import_firestore9.getDocs)(q);
1104
1430
  return snapshot.docs.map(
1105
- (doc10) => ({
1106
- id: doc10.id,
1107
- ...doc10.data()
1431
+ (doc11) => ({
1432
+ id: doc11.id,
1433
+ ...doc11.data()
1108
1434
  })
1109
1435
  );
1110
1436
  }
1437
+ /**
1438
+ * Searches for requirements by name.
1439
+ * @param name - The name to search for.
1440
+ * @param type - The type of requirement (pre/post).
1441
+ * @returns A list of requirements that match the search criteria.
1442
+ */
1443
+ async search(name, type) {
1444
+ const requirements = await this.getAllByType(type);
1445
+ return requirements.filter(
1446
+ (r) => r.name.toLowerCase().includes(name.toLowerCase())
1447
+ );
1448
+ }
1111
1449
  /**
1112
1450
  * Ažurira postojeći zahtev
1113
1451
  * @param id - ID zahteva koji se ažurira
@@ -1119,8 +1457,8 @@ var RequirementService = class extends BaseService {
1119
1457
  ...requirement,
1120
1458
  updatedAt: /* @__PURE__ */ new Date()
1121
1459
  };
1122
- const docRef = (0, import_firestore8.doc)(this.requirementsRef, id);
1123
- await (0, import_firestore8.updateDoc)(docRef, updateData);
1460
+ const docRef = (0, import_firestore9.doc)(this.requirementsRef, id);
1461
+ await (0, import_firestore9.updateDoc)(docRef, updateData);
1124
1462
  return this.getById(id);
1125
1463
  }
1126
1464
  /**
@@ -1136,8 +1474,8 @@ var RequirementService = class extends BaseService {
1136
1474
  * @returns Zahtev ili null ako ne postoji
1137
1475
  */
1138
1476
  async getById(id) {
1139
- const docRef = (0, import_firestore8.doc)(this.requirementsRef, id);
1140
- const docSnap = await (0, import_firestore8.getDoc)(docRef);
1477
+ const docRef = (0, import_firestore9.doc)(this.requirementsRef, id);
1478
+ const docSnap = await (0, import_firestore9.getDoc)(docRef);
1141
1479
  if (!docSnap.exists()) return null;
1142
1480
  return {
1143
1481
  id: docSnap.id,
@@ -1147,7 +1485,7 @@ var RequirementService = class extends BaseService {
1147
1485
  };
1148
1486
 
1149
1487
  // src/backoffice/services/subcategory.service.ts
1150
- var import_firestore9 = require("firebase/firestore");
1488
+ var import_firestore10 = require("firebase/firestore");
1151
1489
 
1152
1490
  // src/backoffice/types/subcategory.types.ts
1153
1491
  var SUBCATEGORIES_COLLECTION = "subcategories";
@@ -1159,7 +1497,7 @@ var SubcategoryService = class extends BaseService {
1159
1497
  * @param categoryId - ID roditeljske kategorije
1160
1498
  */
1161
1499
  getSubcategoriesRef(categoryId) {
1162
- return (0, import_firestore9.collection)(
1500
+ return (0, import_firestore10.collection)(
1163
1501
  this.db,
1164
1502
  CATEGORIES_COLLECTION,
1165
1503
  categoryId,
@@ -1181,27 +1519,117 @@ var SubcategoryService = class extends BaseService {
1181
1519
  updatedAt: now,
1182
1520
  isActive: true
1183
1521
  };
1184
- const docRef = await (0, import_firestore9.addDoc)(
1522
+ const docRef = await (0, import_firestore10.addDoc)(
1185
1523
  this.getSubcategoriesRef(categoryId),
1186
1524
  newSubcategory
1187
1525
  );
1188
1526
  return { id: docRef.id, ...newSubcategory };
1189
1527
  }
1190
1528
  /**
1191
- * Vraća sve aktivne podkategorije za određenu kategoriju
1529
+ * Returns counts of subcategories for all categories.
1530
+ * @param active - Whether to count active or inactive subcategories.
1531
+ * @returns A record mapping category ID to subcategory count.
1532
+ */
1533
+ async getSubcategoryCounts(active = true) {
1534
+ const categoriesRef = (0, import_firestore10.collection)(this.db, CATEGORIES_COLLECTION);
1535
+ const categoriesSnapshot = await (0, import_firestore10.getDocs)(categoriesRef);
1536
+ const counts = {};
1537
+ for (const categoryDoc of categoriesSnapshot.docs) {
1538
+ const categoryId = categoryDoc.id;
1539
+ const subcategoriesRef = this.getSubcategoriesRef(categoryId);
1540
+ const q = (0, import_firestore10.query)(subcategoriesRef, (0, import_firestore10.where)("isActive", "==", active));
1541
+ const snapshot = await (0, import_firestore10.getCountFromServer)(q);
1542
+ counts[categoryId] = snapshot.data().count;
1543
+ }
1544
+ return counts;
1545
+ }
1546
+ /**
1547
+ * Vraća sve aktivne podkategorije za određenu kategoriju sa paginacijom
1192
1548
  * @param categoryId - ID kategorije čije podkategorije tražimo
1193
- * @returns Lista aktivnih podkategorija
1549
+ * @param options - Pagination options
1550
+ * @returns Lista aktivnih podkategorija i poslednji vidljiv dokument
1551
+ */
1552
+ async getAllByCategoryId(categoryId, options = {}) {
1553
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1554
+ const constraints = [
1555
+ (0, import_firestore10.where)("isActive", "==", active),
1556
+ (0, import_firestore10.orderBy)("name"),
1557
+ queryLimit ? (0, import_firestore10.limit)(queryLimit) : void 0,
1558
+ lastVisible ? (0, import_firestore10.startAfter)(lastVisible) : void 0
1559
+ ].filter((c) => !!c);
1560
+ const q = (0, import_firestore10.query)(this.getSubcategoriesRef(categoryId), ...constraints);
1561
+ const querySnapshot = await (0, import_firestore10.getDocs)(q);
1562
+ const subcategories = querySnapshot.docs.map(
1563
+ (doc11) => ({
1564
+ id: doc11.id,
1565
+ ...doc11.data()
1566
+ })
1567
+ );
1568
+ const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
1569
+ return { subcategories, lastVisible: newLastVisible };
1570
+ }
1571
+ /**
1572
+ * Vraća sve podkategorije sa paginacijom koristeći collection group query.
1573
+ * NOTE: This query requires a composite index in Firestore on the 'subcategories' collection group.
1574
+ * The index should be on 'isActive' (ascending) and 'name' (ascending).
1575
+ * Firestore will provide a link to create this index in the console error if it's missing.
1576
+ * @param options - Pagination options
1577
+ * @returns Lista podkategorija i poslednji vidljiv dokument
1578
+ */
1579
+ async getAll(options = {}) {
1580
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1581
+ const constraints = [
1582
+ (0, import_firestore10.where)("isActive", "==", active),
1583
+ (0, import_firestore10.orderBy)("name"),
1584
+ queryLimit ? (0, import_firestore10.limit)(queryLimit) : void 0,
1585
+ lastVisible ? (0, import_firestore10.startAfter)(lastVisible) : void 0
1586
+ ].filter((c) => !!c);
1587
+ const q = (0, import_firestore10.query)(
1588
+ (0, import_firestore10.collectionGroup)(this.db, SUBCATEGORIES_COLLECTION),
1589
+ ...constraints
1590
+ );
1591
+ const querySnapshot = await (0, import_firestore10.getDocs)(q);
1592
+ const subcategories = querySnapshot.docs.map(
1593
+ (doc11) => ({
1594
+ id: doc11.id,
1595
+ ...doc11.data()
1596
+ })
1597
+ );
1598
+ const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
1599
+ return { subcategories, lastVisible: newLastVisible };
1600
+ }
1601
+ /**
1602
+ * Vraća sve subkategorije za određenu kategoriju za potrebe filtera (bez paginacije)
1603
+ * @param categoryId - ID kategorije čije subkategorije tražimo
1604
+ * @returns Lista svih aktivnih subkategorija
1194
1605
  */
1195
- async getAllByCategoryId(categoryId) {
1196
- const q = (0, import_firestore9.query)(
1606
+ async getAllForFilterByCategoryId(categoryId) {
1607
+ const q = (0, import_firestore10.query)(
1197
1608
  this.getSubcategoriesRef(categoryId),
1198
- (0, import_firestore9.where)("isActive", "==", true)
1609
+ (0, import_firestore10.where)("isActive", "==", true)
1199
1610
  );
1200
- const snapshot = await (0, import_firestore9.getDocs)(q);
1201
- return snapshot.docs.map(
1202
- (doc10) => ({
1203
- id: doc10.id,
1204
- ...doc10.data()
1611
+ const querySnapshot = await (0, import_firestore10.getDocs)(q);
1612
+ return querySnapshot.docs.map(
1613
+ (doc11) => ({
1614
+ id: doc11.id,
1615
+ ...doc11.data()
1616
+ })
1617
+ );
1618
+ }
1619
+ /**
1620
+ * Vraća sve subkategorije za potrebe filtera (bez paginacije)
1621
+ * @returns Lista svih aktivnih subkategorija
1622
+ */
1623
+ async getAllForFilter() {
1624
+ const q = (0, import_firestore10.query)(
1625
+ (0, import_firestore10.collectionGroup)(this.db, SUBCATEGORIES_COLLECTION),
1626
+ (0, import_firestore10.where)("isActive", "==", true)
1627
+ );
1628
+ const querySnapshot = await (0, import_firestore10.getDocs)(q);
1629
+ return querySnapshot.docs.map(
1630
+ (doc11) => ({
1631
+ id: doc11.id,
1632
+ ...doc11.data()
1205
1633
  })
1206
1634
  );
1207
1635
  }
@@ -1213,13 +1641,42 @@ var SubcategoryService = class extends BaseService {
1213
1641
  * @returns Ažurirana podkategorija
1214
1642
  */
1215
1643
  async update(categoryId, subcategoryId, subcategory) {
1216
- const updateData = {
1217
- ...subcategory,
1218
- updatedAt: /* @__PURE__ */ new Date()
1219
- };
1220
- const docRef = (0, import_firestore9.doc)(this.getSubcategoriesRef(categoryId), subcategoryId);
1221
- await (0, import_firestore9.updateDoc)(docRef, updateData);
1222
- return this.getById(categoryId, subcategoryId);
1644
+ const newCategoryId = subcategory.categoryId;
1645
+ if (newCategoryId && newCategoryId !== categoryId) {
1646
+ const oldDocRef = (0, import_firestore10.doc)(
1647
+ this.getSubcategoriesRef(categoryId),
1648
+ subcategoryId
1649
+ );
1650
+ const docSnap = await (0, import_firestore10.getDoc)(oldDocRef);
1651
+ if (!docSnap.exists()) {
1652
+ throw new Error("Subcategory to update does not exist.");
1653
+ }
1654
+ const existingData = docSnap.data();
1655
+ const newData = {
1656
+ ...existingData,
1657
+ ...subcategory,
1658
+ categoryId: newCategoryId,
1659
+ // Ensure categoryId is updated
1660
+ createdAt: existingData.createdAt,
1661
+ // Preserve original creation date
1662
+ updatedAt: /* @__PURE__ */ new Date()
1663
+ };
1664
+ const newDocRef = (0, import_firestore10.doc)(
1665
+ this.getSubcategoriesRef(newCategoryId),
1666
+ subcategoryId
1667
+ );
1668
+ await (0, import_firestore10.setDoc)(newDocRef, newData);
1669
+ await (0, import_firestore10.deleteDoc)(oldDocRef);
1670
+ return { id: subcategoryId, ...newData };
1671
+ } else {
1672
+ const updateData = {
1673
+ ...subcategory,
1674
+ updatedAt: /* @__PURE__ */ new Date()
1675
+ };
1676
+ const docRef = (0, import_firestore10.doc)(this.getSubcategoriesRef(categoryId), subcategoryId);
1677
+ await (0, import_firestore10.updateDoc)(docRef, updateData);
1678
+ return this.getById(categoryId, subcategoryId);
1679
+ }
1223
1680
  }
1224
1681
  /**
1225
1682
  * Soft delete podkategorije (postavlja isActive na false)
@@ -1229,6 +1686,14 @@ var SubcategoryService = class extends BaseService {
1229
1686
  async delete(categoryId, subcategoryId) {
1230
1687
  await this.update(categoryId, subcategoryId, { isActive: false });
1231
1688
  }
1689
+ /**
1690
+ * Reactivates a subcategory by setting its isActive flag to true.
1691
+ * @param categoryId - The ID of the category to which the subcategory belongs.
1692
+ * @param subcategoryId - The ID of the subcategory to reactivate.
1693
+ */
1694
+ async reactivate(categoryId, subcategoryId) {
1695
+ await this.update(categoryId, subcategoryId, { isActive: true });
1696
+ }
1232
1697
  /**
1233
1698
  * Vraća podkategoriju po ID-u
1234
1699
  * @param categoryId - ID kategorije kojoj pripada podkategorija
@@ -1236,8 +1701,8 @@ var SubcategoryService = class extends BaseService {
1236
1701
  * @returns Podkategorija ili null ako ne postoji
1237
1702
  */
1238
1703
  async getById(categoryId, subcategoryId) {
1239
- const docRef = (0, import_firestore9.doc)(this.getSubcategoriesRef(categoryId), subcategoryId);
1240
- const docSnap = await (0, import_firestore9.getDoc)(docRef);
1704
+ const docRef = (0, import_firestore10.doc)(this.getSubcategoriesRef(categoryId), subcategoryId);
1705
+ const docSnap = await (0, import_firestore10.getDoc)(docRef);
1241
1706
  if (!docSnap.exists()) return null;
1242
1707
  return {
1243
1708
  id: docSnap.id,
@@ -1247,7 +1712,7 @@ var SubcategoryService = class extends BaseService {
1247
1712
  };
1248
1713
 
1249
1714
  // src/backoffice/services/technology.service.ts
1250
- var import_firestore10 = require("firebase/firestore");
1715
+ var import_firestore11 = require("firebase/firestore");
1251
1716
 
1252
1717
  // src/backoffice/types/static/certification.types.ts
1253
1718
  var CertificationLevel = /* @__PURE__ */ ((CertificationLevel2) => {
@@ -1280,138 +1745,186 @@ var DEFAULT_CERTIFICATION_REQUIREMENT = {
1280
1745
  };
1281
1746
  var TechnologyService = class extends BaseService {
1282
1747
  /**
1283
- * Vraća referencu na Firestore kolekciju tehnologija
1748
+ * Reference to the Firestore collection of technologies.
1284
1749
  */
1285
- getTechnologiesRef() {
1286
- return (0, import_firestore10.collection)(this.db, TECHNOLOGIES_COLLECTION);
1750
+ get technologiesRef() {
1751
+ return (0, import_firestore11.collection)(this.db, TECHNOLOGIES_COLLECTION);
1287
1752
  }
1288
1753
  /**
1289
- * Kreira novu tehnologiju
1290
- * @param technology - Podaci za novu tehnologiju
1291
- * @returns Kreirana tehnologija sa generisanim ID-em
1754
+ * Creates a new technology.
1755
+ * @param technology - Data for the new technology.
1756
+ * @returns The created technology with its generated ID.
1292
1757
  */
1293
1758
  async create(technology) {
1294
1759
  const now = /* @__PURE__ */ new Date();
1295
1760
  const newTechnology = {
1296
- ...technology,
1297
- createdAt: now,
1298
- updatedAt: now,
1299
- isActive: true,
1300
- requirements: technology.requirements || {
1301
- pre: [],
1302
- post: []
1303
- },
1761
+ name: technology.name,
1762
+ description: technology.description,
1763
+ family: technology.family,
1764
+ categoryId: technology.categoryId,
1765
+ subcategoryId: technology.subcategoryId,
1766
+ requirements: technology.requirements || { pre: [], post: [] },
1304
1767
  blockingConditions: technology.blockingConditions || [],
1305
1768
  contraindications: technology.contraindications || [],
1306
1769
  benefits: technology.benefits || [],
1307
- certificationRequirement: technology.certificationRequirement || DEFAULT_CERTIFICATION_REQUIREMENT
1770
+ certificationRequirement: technology.certificationRequirement || DEFAULT_CERTIFICATION_REQUIREMENT,
1771
+ documentationTemplates: technology.documentationTemplates || [],
1772
+ isActive: true,
1773
+ createdAt: now,
1774
+ updatedAt: now
1308
1775
  };
1309
- const docRef = await (0, import_firestore10.addDoc)(this.getTechnologiesRef(), newTechnology);
1776
+ if (technology.technicalDetails) {
1777
+ newTechnology.technicalDetails = technology.technicalDetails;
1778
+ }
1779
+ const docRef = await (0, import_firestore11.addDoc)(this.technologiesRef, newTechnology);
1310
1780
  return { id: docRef.id, ...newTechnology };
1311
1781
  }
1312
1782
  /**
1313
- * Vraća sve aktivne tehnologije
1314
- * @returns Lista aktivnih tehnologija
1783
+ * Returns counts of technologies for each subcategory.
1784
+ * @param active - Whether to count active or inactive technologies.
1785
+ * @returns A record mapping subcategory ID to technology count.
1315
1786
  */
1316
- async getAll() {
1317
- const q = (0, import_firestore10.query)(this.getTechnologiesRef(), (0, import_firestore10.where)("isActive", "==", true));
1318
- const snapshot = await (0, import_firestore10.getDocs)(q);
1319
- return snapshot.docs.map(
1320
- (doc10) => ({
1321
- id: doc10.id,
1322
- ...doc10.data()
1323
- })
1324
- );
1787
+ async getTechnologyCounts(active = true) {
1788
+ const q = (0, import_firestore11.query)(this.technologiesRef, (0, import_firestore11.where)("isActive", "==", active));
1789
+ const snapshot = await (0, import_firestore11.getDocs)(q);
1790
+ const counts = {};
1791
+ snapshot.docs.forEach((doc11) => {
1792
+ const tech = doc11.data();
1793
+ counts[tech.subcategoryId] = (counts[tech.subcategoryId] || 0) + 1;
1794
+ });
1795
+ return counts;
1325
1796
  }
1326
1797
  /**
1327
- * Vraća sve aktivne tehnologije za određenu familiju
1328
- * @param family - Familija procedura
1329
- * @returns Lista aktivnih tehnologija
1798
+ * Returns counts of technologies for each category.
1799
+ * @param active - Whether to count active or inactive technologies.
1800
+ * @returns A record mapping category ID to technology count.
1330
1801
  */
1331
- async getAllByFamily(family) {
1332
- const q = (0, import_firestore10.query)(
1333
- this.getTechnologiesRef(),
1334
- (0, import_firestore10.where)("isActive", "==", true),
1335
- (0, import_firestore10.where)("family", "==", family)
1336
- );
1337
- const snapshot = await (0, import_firestore10.getDocs)(q);
1338
- return snapshot.docs.map(
1339
- (doc10) => ({
1340
- id: doc10.id,
1341
- ...doc10.data()
1802
+ async getTechnologyCountsByCategory(active = true) {
1803
+ const q = (0, import_firestore11.query)(this.technologiesRef, (0, import_firestore11.where)("isActive", "==", active));
1804
+ const snapshot = await (0, import_firestore11.getDocs)(q);
1805
+ const counts = {};
1806
+ snapshot.docs.forEach((doc11) => {
1807
+ const tech = doc11.data();
1808
+ counts[tech.categoryId] = (counts[tech.categoryId] || 0) + 1;
1809
+ });
1810
+ return counts;
1811
+ }
1812
+ /**
1813
+ * Returns all technologies with pagination.
1814
+ * @param options - Pagination and filter options.
1815
+ * @returns A list of technologies and the last visible document.
1816
+ */
1817
+ async getAll(options = {}) {
1818
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1819
+ const constraints = [
1820
+ (0, import_firestore11.where)("isActive", "==", active),
1821
+ (0, import_firestore11.orderBy)("name"),
1822
+ queryLimit ? (0, import_firestore11.limit)(queryLimit) : void 0,
1823
+ lastVisible ? (0, import_firestore11.startAfter)(lastVisible) : void 0
1824
+ ].filter((c) => !!c);
1825
+ const q = (0, import_firestore11.query)(this.technologiesRef, ...constraints);
1826
+ const snapshot = await (0, import_firestore11.getDocs)(q);
1827
+ const technologies = snapshot.docs.map(
1828
+ (doc11) => ({
1829
+ id: doc11.id,
1830
+ ...doc11.data()
1342
1831
  })
1343
1832
  );
1344
- }
1345
- /**
1346
- * Vraća sve aktivne tehnologije za određenu kategoriju
1347
- * @param categoryId - ID kategorije
1348
- * @returns Lista aktivnih tehnologija
1349
- */
1350
- async getAllByCategoryId(categoryId) {
1351
- const q = (0, import_firestore10.query)(
1352
- this.getTechnologiesRef(),
1353
- (0, import_firestore10.where)("isActive", "==", true),
1354
- (0, import_firestore10.where)("categoryId", "==", categoryId)
1833
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1834
+ return { technologies, lastVisible: newLastVisible };
1835
+ }
1836
+ /**
1837
+ * Returns all technologies for a specific category with pagination.
1838
+ * @param categoryId - The ID of the category.
1839
+ * @param options - Pagination options.
1840
+ * @returns A list of technologies for the specified category.
1841
+ */
1842
+ async getAllByCategoryId(categoryId, options = {}) {
1843
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1844
+ const constraints = [
1845
+ (0, import_firestore11.where)("categoryId", "==", categoryId),
1846
+ (0, import_firestore11.where)("isActive", "==", active),
1847
+ (0, import_firestore11.orderBy)("name"),
1848
+ queryLimit ? (0, import_firestore11.limit)(queryLimit) : void 0,
1849
+ lastVisible ? (0, import_firestore11.startAfter)(lastVisible) : void 0
1850
+ ].filter((c) => !!c);
1851
+ const q = (0, import_firestore11.query)(this.technologiesRef, ...constraints);
1852
+ const snapshot = await (0, import_firestore11.getDocs)(q);
1853
+ const technologies = snapshot.docs.map(
1854
+ (doc11) => ({
1855
+ id: doc11.id,
1856
+ ...doc11.data()
1857
+ })
1355
1858
  );
1356
- const snapshot = await (0, import_firestore10.getDocs)(q);
1357
- return snapshot.docs.map(
1358
- (doc10) => ({
1359
- id: doc10.id,
1360
- ...doc10.data()
1859
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1860
+ return { technologies, lastVisible: newLastVisible };
1861
+ }
1862
+ /**
1863
+ * Returns all technologies for a specific subcategory with pagination.
1864
+ * @param subcategoryId - The ID of the subcategory.
1865
+ * @param options - Pagination options.
1866
+ * @returns A list of technologies for the specified subcategory.
1867
+ */
1868
+ async getAllBySubcategoryId(subcategoryId, options = {}) {
1869
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
1870
+ const constraints = [
1871
+ (0, import_firestore11.where)("subcategoryId", "==", subcategoryId),
1872
+ (0, import_firestore11.where)("isActive", "==", active),
1873
+ (0, import_firestore11.orderBy)("name"),
1874
+ queryLimit ? (0, import_firestore11.limit)(queryLimit) : void 0,
1875
+ lastVisible ? (0, import_firestore11.startAfter)(lastVisible) : void 0
1876
+ ].filter((c) => !!c);
1877
+ const q = (0, import_firestore11.query)(this.technologiesRef, ...constraints);
1878
+ const snapshot = await (0, import_firestore11.getDocs)(q);
1879
+ const technologies = snapshot.docs.map(
1880
+ (doc11) => ({
1881
+ id: doc11.id,
1882
+ ...doc11.data()
1361
1883
  })
1362
1884
  );
1885
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1886
+ return { technologies, lastVisible: newLastVisible };
1363
1887
  }
1364
1888
  /**
1365
- * Vraća sve aktivne tehnologije za određenu podkategoriju
1366
- * @param subcategoryId - ID podkategorije
1367
- * @returns Lista aktivnih tehnologija
1889
+ * Updates an existing technology.
1890
+ * @param id - The ID of the technology to update.
1891
+ * @param technology - New data for the technology.
1892
+ * @returns The updated technology.
1368
1893
  */
1369
- async getAllBySubcategoryId(subcategoryId) {
1370
- const q = (0, import_firestore10.query)(
1371
- this.getTechnologiesRef(),
1372
- (0, import_firestore10.where)("isActive", "==", true),
1373
- (0, import_firestore10.where)("subcategoryId", "==", subcategoryId)
1374
- );
1375
- const snapshot = await (0, import_firestore10.getDocs)(q);
1376
- return snapshot.docs.map(
1377
- (doc10) => ({
1378
- id: doc10.id,
1379
- ...doc10.data()
1380
- })
1381
- );
1894
+ async update(id, technology) {
1895
+ const updateData = { ...technology };
1896
+ Object.keys(updateData).forEach((key) => {
1897
+ if (updateData[key] === void 0) {
1898
+ delete updateData[key];
1899
+ }
1900
+ });
1901
+ updateData.updatedAt = /* @__PURE__ */ new Date();
1902
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, id);
1903
+ await (0, import_firestore11.updateDoc)(docRef, updateData);
1904
+ return this.getById(id);
1382
1905
  }
1383
1906
  /**
1384
- * Ažurira postojeću tehnologiju
1385
- * @param technologyId - ID tehnologije
1386
- * @param technology - Novi podaci za tehnologiju
1387
- * @returns Ažurirana tehnologija
1907
+ * Soft deletes a technology.
1908
+ * @param id - The ID of the technology to delete.
1388
1909
  */
1389
- async update(technologyId, technology) {
1390
- const updateData = {
1391
- ...technology,
1392
- updatedAt: /* @__PURE__ */ new Date()
1393
- };
1394
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1395
- await (0, import_firestore10.updateDoc)(docRef, updateData);
1396
- return this.getById(technologyId);
1910
+ async delete(id) {
1911
+ await this.update(id, { isActive: false });
1397
1912
  }
1398
1913
  /**
1399
- * Soft delete tehnologije (postavlja isActive na false)
1400
- * @param technologyId - ID tehnologije koja se briše
1914
+ * Reactivates a technology.
1915
+ * @param id - The ID of the technology to reactivate.
1401
1916
  */
1402
- async delete(technologyId) {
1403
- await this.update(technologyId, {
1404
- isActive: false
1405
- });
1917
+ async reactivate(id) {
1918
+ await this.update(id, { isActive: true });
1406
1919
  }
1407
1920
  /**
1408
- * Vraća tehnologiju po ID-u
1409
- * @param technologyId - ID tražene tehnologije
1410
- * @returns Tehnologija ili null ako ne postoji
1921
+ * Returns a technology by its ID.
1922
+ * @param id - The ID of the requested technology.
1923
+ * @returns The technology or null if it doesn't exist.
1411
1924
  */
1412
- async getById(technologyId) {
1413
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1414
- const docSnap = await (0, import_firestore10.getDoc)(docRef);
1925
+ async getById(id) {
1926
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, id);
1927
+ const docSnap = await (0, import_firestore11.getDoc)(docRef);
1415
1928
  if (!docSnap.exists()) return null;
1416
1929
  return {
1417
1930
  id: docSnap.id,
@@ -1425,10 +1938,10 @@ var TechnologyService = class extends BaseService {
1425
1938
  * @returns Ažurirana tehnologija sa novim zahtevom
1426
1939
  */
1427
1940
  async addRequirement(technologyId, requirement) {
1428
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1941
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
1429
1942
  const requirementType = requirement.type === "pre" ? "requirements.pre" : "requirements.post";
1430
- await (0, import_firestore10.updateDoc)(docRef, {
1431
- [requirementType]: (0, import_firestore10.arrayUnion)(requirement),
1943
+ await (0, import_firestore11.updateDoc)(docRef, {
1944
+ [requirementType]: (0, import_firestore11.arrayUnion)(requirement),
1432
1945
  updatedAt: /* @__PURE__ */ new Date()
1433
1946
  });
1434
1947
  return this.getById(technologyId);
@@ -1440,10 +1953,10 @@ var TechnologyService = class extends BaseService {
1440
1953
  * @returns Ažurirana tehnologija bez uklonjenog zahteva
1441
1954
  */
1442
1955
  async removeRequirement(technologyId, requirement) {
1443
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1956
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
1444
1957
  const requirementType = requirement.type === "pre" ? "requirements.pre" : "requirements.post";
1445
- await (0, import_firestore10.updateDoc)(docRef, {
1446
- [requirementType]: (0, import_firestore10.arrayRemove)(requirement),
1958
+ await (0, import_firestore11.updateDoc)(docRef, {
1959
+ [requirementType]: (0, import_firestore11.arrayRemove)(requirement),
1447
1960
  updatedAt: /* @__PURE__ */ new Date()
1448
1961
  });
1449
1962
  return this.getById(technologyId);
@@ -1480,9 +1993,9 @@ var TechnologyService = class extends BaseService {
1480
1993
  * @returns Ažurirana tehnologija
1481
1994
  */
1482
1995
  async addBlockingCondition(technologyId, condition) {
1483
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1484
- await (0, import_firestore10.updateDoc)(docRef, {
1485
- blockingConditions: (0, import_firestore10.arrayUnion)(condition),
1996
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
1997
+ await (0, import_firestore11.updateDoc)(docRef, {
1998
+ blockingConditions: (0, import_firestore11.arrayUnion)(condition),
1486
1999
  updatedAt: /* @__PURE__ */ new Date()
1487
2000
  });
1488
2001
  return this.getById(technologyId);
@@ -1494,9 +2007,9 @@ var TechnologyService = class extends BaseService {
1494
2007
  * @returns Ažurirana tehnologija
1495
2008
  */
1496
2009
  async removeBlockingCondition(technologyId, condition) {
1497
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1498
- await (0, import_firestore10.updateDoc)(docRef, {
1499
- blockingConditions: (0, import_firestore10.arrayRemove)(condition),
2010
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2011
+ await (0, import_firestore11.updateDoc)(docRef, {
2012
+ blockingConditions: (0, import_firestore11.arrayRemove)(condition),
1500
2013
  updatedAt: /* @__PURE__ */ new Date()
1501
2014
  });
1502
2015
  return this.getById(technologyId);
@@ -1508,9 +2021,17 @@ var TechnologyService = class extends BaseService {
1508
2021
  * @returns Ažurirana tehnologija
1509
2022
  */
1510
2023
  async addContraindication(technologyId, contraindication) {
1511
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1512
- await (0, import_firestore10.updateDoc)(docRef, {
1513
- contraindications: (0, import_firestore10.arrayUnion)(contraindication),
2024
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2025
+ const technology = await this.getById(technologyId);
2026
+ if (!technology) {
2027
+ throw new Error(`Technology with id ${technologyId} not found`);
2028
+ }
2029
+ const existingContraindications = technology.contraindications || [];
2030
+ if (existingContraindications.some((c) => c.id === contraindication.id)) {
2031
+ return technology;
2032
+ }
2033
+ await (0, import_firestore11.updateDoc)(docRef, {
2034
+ contraindications: [...existingContraindications, contraindication],
1514
2035
  updatedAt: /* @__PURE__ */ new Date()
1515
2036
  });
1516
2037
  return this.getById(technologyId);
@@ -1522,9 +2043,45 @@ var TechnologyService = class extends BaseService {
1522
2043
  * @returns Ažurirana tehnologija
1523
2044
  */
1524
2045
  async removeContraindication(technologyId, contraindication) {
1525
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1526
- await (0, import_firestore10.updateDoc)(docRef, {
1527
- contraindications: (0, import_firestore10.arrayRemove)(contraindication),
2046
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2047
+ const technology = await this.getById(technologyId);
2048
+ if (!technology) {
2049
+ throw new Error(`Technology with id ${technologyId} not found`);
2050
+ }
2051
+ const updatedContraindications = (technology.contraindications || []).filter((c) => c.id !== contraindication.id);
2052
+ await (0, import_firestore11.updateDoc)(docRef, {
2053
+ contraindications: updatedContraindications,
2054
+ updatedAt: /* @__PURE__ */ new Date()
2055
+ });
2056
+ return this.getById(technologyId);
2057
+ }
2058
+ /**
2059
+ * Updates an existing contraindication in a technology's list.
2060
+ * If the contraindication does not exist, it will not be added.
2061
+ * @param technologyId - ID of the technology
2062
+ * @param contraindication - The updated contraindication object
2063
+ * @returns The updated technology
2064
+ */
2065
+ async updateContraindication(technologyId, contraindication) {
2066
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2067
+ const technology = await this.getById(technologyId);
2068
+ if (!technology) {
2069
+ throw new Error(`Technology with id ${technologyId} not found`);
2070
+ }
2071
+ const contraindications = technology.contraindications || [];
2072
+ const index = contraindications.findIndex(
2073
+ (c) => c.id === contraindication.id
2074
+ );
2075
+ if (index === -1) {
2076
+ console.warn(
2077
+ `Contraindication with id ${contraindication.id} not found for technology ${technologyId}. No update performed.`
2078
+ );
2079
+ return technology;
2080
+ }
2081
+ const updatedContraindications = [...contraindications];
2082
+ updatedContraindications[index] = contraindication;
2083
+ await (0, import_firestore11.updateDoc)(docRef, {
2084
+ contraindications: updatedContraindications,
1528
2085
  updatedAt: /* @__PURE__ */ new Date()
1529
2086
  });
1530
2087
  return this.getById(technologyId);
@@ -1536,9 +2093,17 @@ var TechnologyService = class extends BaseService {
1536
2093
  * @returns Ažurirana tehnologija
1537
2094
  */
1538
2095
  async addBenefit(technologyId, benefit) {
1539
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1540
- await (0, import_firestore10.updateDoc)(docRef, {
1541
- benefits: (0, import_firestore10.arrayUnion)(benefit),
2096
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2097
+ const technology = await this.getById(technologyId);
2098
+ if (!technology) {
2099
+ throw new Error(`Technology with id ${technologyId} not found`);
2100
+ }
2101
+ const existingBenefits = technology.benefits || [];
2102
+ if (existingBenefits.some((b) => b.id === benefit.id)) {
2103
+ return technology;
2104
+ }
2105
+ await (0, import_firestore11.updateDoc)(docRef, {
2106
+ benefits: [...existingBenefits, benefit],
1542
2107
  updatedAt: /* @__PURE__ */ new Date()
1543
2108
  });
1544
2109
  return this.getById(technologyId);
@@ -1550,9 +2115,45 @@ var TechnologyService = class extends BaseService {
1550
2115
  * @returns Ažurirana tehnologija
1551
2116
  */
1552
2117
  async removeBenefit(technologyId, benefit) {
1553
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1554
- await (0, import_firestore10.updateDoc)(docRef, {
1555
- benefits: (0, import_firestore10.arrayRemove)(benefit),
2118
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2119
+ const technology = await this.getById(technologyId);
2120
+ if (!technology) {
2121
+ throw new Error(`Technology with id ${technologyId} not found`);
2122
+ }
2123
+ const updatedBenefits = (technology.benefits || []).filter(
2124
+ (b) => b.id !== benefit.id
2125
+ );
2126
+ await (0, import_firestore11.updateDoc)(docRef, {
2127
+ benefits: updatedBenefits,
2128
+ updatedAt: /* @__PURE__ */ new Date()
2129
+ });
2130
+ return this.getById(technologyId);
2131
+ }
2132
+ /**
2133
+ * Updates an existing benefit in a technology's list.
2134
+ * If the benefit does not exist, it will not be added.
2135
+ * @param technologyId - ID of the technology
2136
+ * @param benefit - The updated benefit object
2137
+ * @returns The updated technology
2138
+ */
2139
+ async updateBenefit(technologyId, benefit) {
2140
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2141
+ const technology = await this.getById(technologyId);
2142
+ if (!technology) {
2143
+ throw new Error(`Technology with id ${technologyId} not found`);
2144
+ }
2145
+ const benefits = technology.benefits || [];
2146
+ const index = benefits.findIndex((b) => b.id === benefit.id);
2147
+ if (index === -1) {
2148
+ console.warn(
2149
+ `Benefit with id ${benefit.id} not found for technology ${technologyId}. No update performed.`
2150
+ );
2151
+ return technology;
2152
+ }
2153
+ const updatedBenefits = [...benefits];
2154
+ updatedBenefits[index] = benefit;
2155
+ await (0, import_firestore11.updateDoc)(docRef, {
2156
+ benefits: updatedBenefits,
1556
2157
  updatedAt: /* @__PURE__ */ new Date()
1557
2158
  });
1558
2159
  return this.getById(technologyId);
@@ -1591,8 +2192,8 @@ var TechnologyService = class extends BaseService {
1591
2192
  * @returns Ažurirana tehnologija
1592
2193
  */
1593
2194
  async updateCertificationRequirement(technologyId, certificationRequirement) {
1594
- const docRef = (0, import_firestore10.doc)(this.getTechnologiesRef(), technologyId);
1595
- await (0, import_firestore10.updateDoc)(docRef, {
2195
+ const docRef = (0, import_firestore11.doc)(this.technologiesRef, technologyId);
2196
+ await (0, import_firestore11.updateDoc)(docRef, {
1596
2197
  certificationRequirement,
1597
2198
  updatedAt: /* @__PURE__ */ new Date()
1598
2199
  });
@@ -1669,7 +2270,7 @@ var TechnologyService = class extends BaseService {
1669
2270
  */
1670
2271
  async getAllowedTechnologies(practitioner) {
1671
2272
  const allTechnologies = await this.getAll();
1672
- const allowedTechnologies = allTechnologies.filter(
2273
+ const allowedTechnologies = allTechnologies.technologies.filter(
1673
2274
  (technology) => this.validateCertification(
1674
2275
  technology.certificationRequirement,
1675
2276
  practitioner.certification
@@ -1689,6 +2290,276 @@ var TechnologyService = class extends BaseService {
1689
2290
  subcategories
1690
2291
  };
1691
2292
  }
2293
+ /**
2294
+ * Gets all active technologies for a subcategory for filter dropdowns.
2295
+ * @param categoryId - The ID of the parent category.
2296
+ * @param subcategoryId - The ID of the subcategory.
2297
+ */
2298
+ async getAllForFilterBySubcategoryId(categoryId, subcategoryId) {
2299
+ const q = (0, import_firestore11.query)(
2300
+ (0, import_firestore11.collection)(this.db, TECHNOLOGIES_COLLECTION),
2301
+ (0, import_firestore11.where)("isActive", "==", true),
2302
+ (0, import_firestore11.where)("categoryId", "==", categoryId),
2303
+ (0, import_firestore11.where)("subcategoryId", "==", subcategoryId),
2304
+ (0, import_firestore11.orderBy)("name")
2305
+ );
2306
+ const snapshot = await (0, import_firestore11.getDocs)(q);
2307
+ return snapshot.docs.map(
2308
+ (doc11) => ({
2309
+ id: doc11.id,
2310
+ ...doc11.data()
2311
+ })
2312
+ );
2313
+ }
2314
+ /**
2315
+ * Gets all active technologies for filter dropdowns.
2316
+ */
2317
+ async getAllForFilter() {
2318
+ const q = (0, import_firestore11.query)(
2319
+ (0, import_firestore11.collection)(this.db, TECHNOLOGIES_COLLECTION),
2320
+ (0, import_firestore11.where)("isActive", "==", true),
2321
+ (0, import_firestore11.orderBy)("name")
2322
+ );
2323
+ const snapshot = await (0, import_firestore11.getDocs)(q);
2324
+ return snapshot.docs.map(
2325
+ (doc11) => ({
2326
+ id: doc11.id,
2327
+ ...doc11.data()
2328
+ })
2329
+ );
2330
+ }
2331
+ };
2332
+
2333
+ // src/backoffice/services/constants.service.ts
2334
+ var import_firestore12 = require("firebase/firestore");
2335
+ var ADMIN_CONSTANTS_COLLECTION = "admin-constants";
2336
+ var TREATMENT_BENEFITS_DOC = "treatment-benefits";
2337
+ var CONTRAINDICATIONS_DOC = "contraindications";
2338
+ var ConstantsService = class extends BaseService {
2339
+ /**
2340
+ * @description Gets the reference to the document holding treatment benefits.
2341
+ * @private
2342
+ * @type {DocumentReference}
2343
+ */
2344
+ get treatmentBenefitsDocRef() {
2345
+ return (0, import_firestore12.doc)(this.db, ADMIN_CONSTANTS_COLLECTION, TREATMENT_BENEFITS_DOC);
2346
+ }
2347
+ /**
2348
+ * @description Gets the reference to the document holding contraindications.
2349
+ * @private
2350
+ * @type {DocumentReference}
2351
+ */
2352
+ get contraindicationsDocRef() {
2353
+ return (0, import_firestore12.doc)(this.db, ADMIN_CONSTANTS_COLLECTION, CONTRAINDICATIONS_DOC);
2354
+ }
2355
+ // =================================================================
2356
+ // Treatment Benefits
2357
+ // =================================================================
2358
+ /**
2359
+ * @description Retrieves all treatment benefits without pagination.
2360
+ * @returns {Promise<TreatmentBenefitDynamic[]>} An array of all treatment benefits.
2361
+ */
2362
+ async getAllBenefitsForFilter() {
2363
+ const docSnap = await (0, import_firestore12.getDoc)(this.treatmentBenefitsDocRef);
2364
+ if (!docSnap.exists()) {
2365
+ return [];
2366
+ }
2367
+ return docSnap.data().benefits;
2368
+ }
2369
+ /**
2370
+ * @description Retrieves a paginated list of treatment benefits.
2371
+ * @param {{ page: number; limit: number }} options - Pagination options.
2372
+ * @returns {Promise<{ benefits: TreatmentBenefitDynamic[]; total: number }>} A paginated list of benefits and the total count.
2373
+ */
2374
+ async getAllBenefits(options) {
2375
+ const allBenefits = await this.getAllBenefitsForFilter();
2376
+ const { page, limit: limit9 } = options;
2377
+ const startIndex = page * limit9;
2378
+ const endIndex = startIndex + limit9;
2379
+ const paginatedBenefits = allBenefits.slice(startIndex, endIndex);
2380
+ return { benefits: paginatedBenefits, total: allBenefits.length };
2381
+ }
2382
+ /**
2383
+ * @description Adds a new treatment benefit.
2384
+ * @param {Omit<TreatmentBenefitDynamic, "id">} benefit - The treatment benefit to add, without an ID.
2385
+ * @returns {Promise<TreatmentBenefitDynamic>} The newly created treatment benefit with its generated ID.
2386
+ */
2387
+ async addTreatmentBenefit(benefit) {
2388
+ const newBenefit = {
2389
+ id: this.generateId(),
2390
+ ...benefit
2391
+ };
2392
+ const docSnap = await (0, import_firestore12.getDoc)(this.treatmentBenefitsDocRef);
2393
+ if (!docSnap.exists()) {
2394
+ await (0, import_firestore12.setDoc)(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
2395
+ } else {
2396
+ await (0, import_firestore12.updateDoc)(this.treatmentBenefitsDocRef, {
2397
+ benefits: (0, import_firestore12.arrayUnion)(newBenefit)
2398
+ });
2399
+ }
2400
+ return newBenefit;
2401
+ }
2402
+ /**
2403
+ * @description Retrieves a single treatment benefit by its ID.
2404
+ * @param {string} benefitId - The ID of the treatment benefit to retrieve.
2405
+ * @returns {Promise<TreatmentBenefitDynamic | undefined>} The found treatment benefit or undefined.
2406
+ */
2407
+ async getBenefitById(benefitId) {
2408
+ const benefits = await this.getAllBenefitsForFilter();
2409
+ return benefits.find((b) => b.id === benefitId);
2410
+ }
2411
+ /**
2412
+ * @description Searches for treatment benefits by name (case-insensitive).
2413
+ * @param {string} searchTerm - The term to search for in the benefit names.
2414
+ * @returns {Promise<TreatmentBenefitDynamic[]>} An array of matching treatment benefits.
2415
+ */
2416
+ async searchBenefitsByName(searchTerm) {
2417
+ const benefits = await this.getAllBenefitsForFilter();
2418
+ const normalizedSearchTerm = searchTerm.toLowerCase();
2419
+ return benefits.filter(
2420
+ (b) => b.name.toLowerCase().includes(normalizedSearchTerm)
2421
+ );
2422
+ }
2423
+ /**
2424
+ * @description Updates an existing treatment benefit.
2425
+ * @param {TreatmentBenefitDynamic} benefit - The treatment benefit with updated data. Its ID must match an existing benefit.
2426
+ * @returns {Promise<TreatmentBenefitDynamic>} The updated treatment benefit.
2427
+ * @throws {Error} If the treatment benefit is not found.
2428
+ */
2429
+ async updateTreatmentBenefit(benefit) {
2430
+ const benefits = await this.getAllBenefitsForFilter();
2431
+ const benefitIndex = benefits.findIndex((b) => b.id === benefit.id);
2432
+ if (benefitIndex === -1) {
2433
+ throw new Error("Treatment benefit not found.");
2434
+ }
2435
+ benefits[benefitIndex] = benefit;
2436
+ await (0, import_firestore12.updateDoc)(this.treatmentBenefitsDocRef, { benefits });
2437
+ return benefit;
2438
+ }
2439
+ /**
2440
+ * @description Deletes a treatment benefit by its ID.
2441
+ * @param {string} benefitId - The ID of the treatment benefit to delete.
2442
+ * @returns {Promise<void>}
2443
+ */
2444
+ async deleteTreatmentBenefit(benefitId) {
2445
+ const benefits = await this.getAllBenefitsForFilter();
2446
+ const benefitToRemove = benefits.find((b) => b.id === benefitId);
2447
+ if (!benefitToRemove) {
2448
+ return;
2449
+ }
2450
+ await (0, import_firestore12.updateDoc)(this.treatmentBenefitsDocRef, {
2451
+ benefits: (0, import_firestore12.arrayRemove)(benefitToRemove)
2452
+ });
2453
+ }
2454
+ // =================================================================
2455
+ // Contraindications
2456
+ // =================================================================
2457
+ /**
2458
+ * @description Retrieves all contraindications without pagination.
2459
+ * @returns {Promise<ContraindicationDynamic[]>} An array of all contraindications.
2460
+ */
2461
+ async getAllContraindicationsForFilter() {
2462
+ const docSnap = await (0, import_firestore12.getDoc)(this.contraindicationsDocRef);
2463
+ if (!docSnap.exists()) {
2464
+ return [];
2465
+ }
2466
+ return docSnap.data().contraindications;
2467
+ }
2468
+ /**
2469
+ * @description Retrieves a paginated list of contraindications.
2470
+ * @param {{ page: number; limit: number }} options - Pagination options.
2471
+ * @returns {Promise<{ contraindications: ContraindicationDynamic[]; total: number }>} A paginated list and the total count.
2472
+ */
2473
+ async getAllContraindications(options) {
2474
+ const allContraindications = await this.getAllContraindicationsForFilter();
2475
+ const { page, limit: limit9 } = options;
2476
+ const startIndex = page * limit9;
2477
+ const endIndex = startIndex + limit9;
2478
+ const paginatedContraindications = allContraindications.slice(
2479
+ startIndex,
2480
+ endIndex
2481
+ );
2482
+ return {
2483
+ contraindications: paginatedContraindications,
2484
+ total: allContraindications.length
2485
+ };
2486
+ }
2487
+ /**
2488
+ * @description Adds a new contraindication.
2489
+ * @param {Omit<ContraindicationDynamic, "id">} contraindication - The contraindication to add, without an ID.
2490
+ * @returns {Promise<ContraindicationDynamic>} The newly created contraindication with its generated ID.
2491
+ */
2492
+ async addContraindication(contraindication) {
2493
+ const newContraindication = {
2494
+ id: this.generateId(),
2495
+ ...contraindication
2496
+ };
2497
+ const docSnap = await (0, import_firestore12.getDoc)(this.contraindicationsDocRef);
2498
+ if (!docSnap.exists()) {
2499
+ await (0, import_firestore12.setDoc)(this.contraindicationsDocRef, {
2500
+ contraindications: [newContraindication]
2501
+ });
2502
+ } else {
2503
+ await (0, import_firestore12.updateDoc)(this.contraindicationsDocRef, {
2504
+ contraindications: (0, import_firestore12.arrayUnion)(newContraindication)
2505
+ });
2506
+ }
2507
+ return newContraindication;
2508
+ }
2509
+ /**
2510
+ * @description Retrieves a single contraindication by its ID.
2511
+ * @param {string} contraindicationId - The ID of the contraindication to retrieve.
2512
+ * @returns {Promise<ContraindicationDynamic | undefined>} The found contraindication or undefined.
2513
+ */
2514
+ async getContraindicationById(contraindicationId) {
2515
+ const contraindications = await this.getAllContraindicationsForFilter();
2516
+ return contraindications.find((c) => c.id === contraindicationId);
2517
+ }
2518
+ /**
2519
+ * @description Searches for contraindications by name (case-insensitive).
2520
+ * @param {string} searchTerm - The term to search for in the contraindication names.
2521
+ * @returns {Promise<ContraindicationDynamic[]>} An array of matching contraindications.
2522
+ */
2523
+ async searchContraindicationsByName(searchTerm) {
2524
+ const contraindications = await this.getAllContraindicationsForFilter();
2525
+ const normalizedSearchTerm = searchTerm.toLowerCase();
2526
+ return contraindications.filter(
2527
+ (c) => c.name.toLowerCase().includes(normalizedSearchTerm)
2528
+ );
2529
+ }
2530
+ /**
2531
+ * @description Updates an existing contraindication.
2532
+ * @param {ContraindicationDynamic} contraindication - The contraindication with updated data. Its ID must match an existing one.
2533
+ * @returns {Promise<ContraindicationDynamic>} The updated contraindication.
2534
+ * @throws {Error} If the contraindication is not found.
2535
+ */
2536
+ async updateContraindication(contraindication) {
2537
+ const contraindications = await this.getAllContraindicationsForFilter();
2538
+ const index = contraindications.findIndex(
2539
+ (c) => c.id === contraindication.id
2540
+ );
2541
+ if (index === -1) {
2542
+ throw new Error("Contraindication not found.");
2543
+ }
2544
+ contraindications[index] = contraindication;
2545
+ await (0, import_firestore12.updateDoc)(this.contraindicationsDocRef, { contraindications });
2546
+ return contraindication;
2547
+ }
2548
+ /**
2549
+ * @description Deletes a contraindication by its ID.
2550
+ * @param {string} contraindicationId - The ID of the contraindication to delete.
2551
+ * @returns {Promise<void>}
2552
+ */
2553
+ async deleteContraindication(contraindicationId) {
2554
+ const contraindications = await this.getAllContraindicationsForFilter();
2555
+ const toRemove = contraindications.find((c) => c.id === contraindicationId);
2556
+ if (!toRemove) {
2557
+ return;
2558
+ }
2559
+ await (0, import_firestore12.updateDoc)(this.contraindicationsDocRef, {
2560
+ contraindications: (0, import_firestore12.arrayRemove)(toRemove)
2561
+ });
2562
+ }
1692
2563
  };
1693
2564
 
1694
2565
  // src/backoffice/types/static/blocking-condition.types.ts
@@ -1747,13 +2618,6 @@ var Currency = /* @__PURE__ */ ((Currency2) => {
1747
2618
  return Currency2;
1748
2619
  })(Currency || {});
1749
2620
 
1750
- // src/backoffice/types/static/procedure-family.types.ts
1751
- var ProcedureFamily = /* @__PURE__ */ ((ProcedureFamily2) => {
1752
- ProcedureFamily2["AESTHETICS"] = "aesthetics";
1753
- ProcedureFamily2["SURGERY"] = "surgery";
1754
- return ProcedureFamily2;
1755
- })(ProcedureFamily || {});
1756
-
1757
2621
  // src/backoffice/types/static/treatment-benefit.types.ts
1758
2622
  var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
1759
2623
  TreatmentBenefit2["WRINKLE_REDUCTION"] = "wrinkle_reduction";
@@ -1776,9 +2640,25 @@ var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
1776
2640
 
1777
2641
  // src/backoffice/validations/schemas.ts
1778
2642
  var import_zod2 = require("zod");
2643
+ var contraindicationDynamicSchema = import_zod2.z.object({
2644
+ id: import_zod2.z.string().min(1, "Contraindication ID is required").regex(
2645
+ /^[a-z0-9_]+$/,
2646
+ "ID must be in snake_case (lowercase, numbers, and underscores only)"
2647
+ ),
2648
+ name: import_zod2.z.string().min(1, "Contraindication name is required"),
2649
+ description: import_zod2.z.string().optional()
2650
+ });
2651
+ var treatmentBenefitDynamicSchema = import_zod2.z.object({
2652
+ id: import_zod2.z.string().min(1, "Benefit ID is required").regex(
2653
+ /^[a-z0-9_]+$/,
2654
+ "ID must be in snake_case (lowercase, numbers, and underscores only)"
2655
+ ),
2656
+ name: import_zod2.z.string().min(1, "Benefit name is required"),
2657
+ description: import_zod2.z.string().optional()
2658
+ });
1779
2659
  var blockingConditionSchemaBackoffice = import_zod2.z.nativeEnum(BlockingCondition);
1780
- var contraindicationSchemaBackoffice = import_zod2.z.nativeEnum(Contraindication);
1781
- var treatmentBenefitSchemaBackoffice = import_zod2.z.nativeEnum(TreatmentBenefit);
2660
+ var contraindicationSchemaBackoffice = contraindicationDynamicSchema;
2661
+ var treatmentBenefitSchemaBackoffice = treatmentBenefitDynamicSchema;
1782
2662
  var procedureFamilySchemaBackoffice = import_zod2.z.nativeEnum(ProcedureFamily);
1783
2663
  var timeUnitSchemaBackoffice = import_zod2.z.nativeEnum(TimeUnit);
1784
2664
  var requirementTypeSchema = import_zod2.z.nativeEnum(RequirementType);
@@ -2001,6 +2881,7 @@ var InvalidTreatmentBenefitError = class extends TreatmentBenefitError {
2001
2881
  CertificationLevel,
2002
2882
  CertificationSpecialty,
2003
2883
  CircularReferenceError,
2884
+ ConstantsService,
2004
2885
  Contraindication,
2005
2886
  ContraindicationError,
2006
2887
  Currency,
@@ -2048,6 +2929,7 @@ var InvalidTreatmentBenefitError = class extends TreatmentBenefitError {
2048
2929
  certificationLevelSchema,
2049
2930
  certificationRequirementSchema,
2050
2931
  certificationSpecialtySchema,
2932
+ contraindicationDynamicSchema,
2051
2933
  contraindicationSchemaBackoffice,
2052
2934
  createDocumentTemplateSchema,
2053
2935
  documentElementSchema,
@@ -2064,6 +2946,7 @@ var InvalidTreatmentBenefitError = class extends TreatmentBenefitError {
2064
2946
  technologyUpdateSchema,
2065
2947
  timeUnitSchemaBackoffice,
2066
2948
  timeframeSchema,
2949
+ treatmentBenefitDynamicSchema,
2067
2950
  treatmentBenefitSchemaBackoffice,
2068
2951
  updateDocumentTemplateSchema
2069
2952
  });