@blackcode_sa/metaestetics-api 1.11.3 → 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 (48) hide show
  1. package/dist/admin/index.d.mts +329 -318
  2. package/dist/admin/index.d.ts +329 -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 +4428 -4035
  8. package/dist/index.d.ts +4428 -4035
  9. package/dist/index.js +1642 -665
  10. package/dist/index.mjs +1406 -401
  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 +2 -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/clinic.schema.ts +1 -6
  42. package/src/validations/patient/medical-info.schema.ts +7 -2
  43. package/src/backoffice/services/__tests__/brand.service.test.ts +0 -196
  44. package/src/backoffice/services/__tests__/category.service.test.ts +0 -201
  45. package/src/backoffice/services/__tests__/product.service.test.ts +0 -358
  46. package/src/backoffice/services/__tests__/requirement.service.test.ts +0 -226
  47. package/src/backoffice/services/__tests__/subcategory.service.test.ts +0 -181
  48. package/src/backoffice/services/__tests__/technology.service.test.ts +0 -1097
@@ -2,9 +2,14 @@ import {
2
2
  addDoc,
3
3
  collection,
4
4
  doc,
5
+ DocumentData,
6
+ getCountFromServer,
5
7
  getDoc,
6
8
  getDocs,
9
+ limit,
10
+ orderBy,
7
11
  query,
12
+ startAfter,
8
13
  updateDoc,
9
14
  where,
10
15
  arrayUnion,
@@ -14,8 +19,8 @@ import {
14
19
  import { Technology, TECHNOLOGIES_COLLECTION } from "../types/technology.types";
15
20
  import { Requirement, RequirementType } from "../types/requirement.types";
16
21
  import { BlockingCondition } from "../types/static/blocking-condition.types";
17
- import { Contraindication } from "../types/static/contraindication.types";
18
- import { TreatmentBenefit } from "../types/static/treatment-benefit.types";
22
+ import { ContraindicationDynamic } from "../types/admin-constants.types";
23
+ import { TreatmentBenefitDynamic } from "../types/admin-constants.types";
19
24
  import {
20
25
  CertificationLevel,
21
26
  CertificationSpecialty,
@@ -37,186 +42,238 @@ const DEFAULT_CERTIFICATION_REQUIREMENT: CertificationRequirement = {
37
42
  };
38
43
 
39
44
  /**
40
- * Servis za upravljanje tehnologijama i njihovim zahtevima.
41
- * Tehnologije su sada top-level kolekcija koja referencira svoju putanju u hijerarhiji
42
- * kroz family, categoryId i subcategoryId.
43
- *
44
- * @example
45
- * const technologyService = new TechnologyService();
46
- *
47
- * // Kreiranje nove tehnologije
48
- * const technology = await technologyService.create({
49
- * name: "Botulinum Toxin",
50
- * description: "Neurotoxin injections for wrinkle reduction",
51
- * family: ProcedureFamily.AESTHETICS,
52
- * categoryId: "category123",
53
- * subcategoryId: "subcategory456"
54
- * });
55
- *
56
- * // Dodavanje zahteva
57
- * await technologyService.addRequirement(technology.id, {
58
- * type: "pre",
59
- * name: "Stay Hydrated",
60
- * description: "Drink plenty of water"
61
- * });
45
+ * Service for managing technologies.
62
46
  */
63
47
  export class TechnologyService extends BaseService {
64
48
  /**
65
- * Vraća referencu na Firestore kolekciju tehnologija
49
+ * Reference to the Firestore collection of technologies.
66
50
  */
67
- private getTechnologiesRef() {
51
+ private get technologiesRef() {
68
52
  return collection(this.db, TECHNOLOGIES_COLLECTION);
69
53
  }
70
54
 
71
55
  /**
72
- * Kreira novu tehnologiju
73
- * @param technology - Podaci za novu tehnologiju
74
- * @returns Kreirana tehnologija sa generisanim ID-em
56
+ * Creates a new technology.
57
+ * @param technology - Data for the new technology.
58
+ * @returns The created technology with its generated ID.
75
59
  */
76
60
  async create(technology: Omit<Technology, "id" | "createdAt" | "updatedAt">) {
77
61
  const now = new Date();
62
+ // Explicitly construct the object to ensure no undefined values are passed.
78
63
  const newTechnology: Omit<Technology, "id"> = {
79
- ...technology,
80
- createdAt: now,
81
- updatedAt: now,
82
- isActive: true,
83
- requirements: technology.requirements || {
84
- pre: [],
85
- post: [],
86
- },
64
+ name: technology.name,
65
+ description: technology.description,
66
+ family: technology.family,
67
+ categoryId: technology.categoryId,
68
+ subcategoryId: technology.subcategoryId,
69
+ requirements: technology.requirements || { pre: [], post: [] },
87
70
  blockingConditions: technology.blockingConditions || [],
88
71
  contraindications: technology.contraindications || [],
89
72
  benefits: technology.benefits || [],
90
73
  certificationRequirement:
91
74
  technology.certificationRequirement ||
92
75
  DEFAULT_CERTIFICATION_REQUIREMENT,
76
+ documentationTemplates: technology.documentationTemplates || [],
77
+ isActive: true,
78
+ createdAt: now,
79
+ updatedAt: now,
93
80
  };
94
81
 
95
- const docRef = await addDoc(this.getTechnologiesRef(), newTechnology);
82
+ // Add optional fields only if they are not undefined
83
+ if (technology.technicalDetails) {
84
+ newTechnology.technicalDetails = technology.technicalDetails;
85
+ }
86
+
87
+ const docRef = await addDoc(this.technologiesRef, newTechnology as any);
96
88
  return { id: docRef.id, ...newTechnology };
97
89
  }
98
90
 
99
91
  /**
100
- * Vraća sve aktivne tehnologije
101
- * @returns Lista aktivnih tehnologija
92
+ * Returns counts of technologies for each subcategory.
93
+ * @param active - Whether to count active or inactive technologies.
94
+ * @returns A record mapping subcategory ID to technology count.
102
95
  */
103
- async getAll() {
104
- const q = query(this.getTechnologiesRef(), where("isActive", "==", true));
96
+ async getTechnologyCounts(active = true) {
97
+ const q = query(this.technologiesRef, where("isActive", "==", active));
105
98
  const snapshot = await getDocs(q);
106
- return snapshot.docs.map(
107
- (doc) =>
108
- ({
109
- id: doc.id,
110
- ...doc.data(),
111
- } as Technology)
112
- );
99
+ const counts: Record<string, number> = {};
100
+ snapshot.docs.forEach((doc) => {
101
+ const tech = doc.data() as Technology;
102
+ counts[tech.subcategoryId] = (counts[tech.subcategoryId] || 0) + 1;
103
+ });
104
+ return counts;
113
105
  }
114
106
 
115
107
  /**
116
- * Vraća sve aktivne tehnologije za određenu familiju
117
- * @param family - Familija procedura
118
- * @returns Lista aktivnih tehnologija
108
+ * Returns counts of technologies for each category.
109
+ * @param active - Whether to count active or inactive technologies.
110
+ * @returns A record mapping category ID to technology count.
119
111
  */
120
- async getAllByFamily(family: ProcedureFamily) {
121
- const q = query(
122
- this.getTechnologiesRef(),
123
- where("isActive", "==", true),
124
- where("family", "==", family)
125
- );
112
+ async getTechnologyCountsByCategory(active = true) {
113
+ const q = query(this.technologiesRef, where("isActive", "==", active));
126
114
  const snapshot = await getDocs(q);
127
- return snapshot.docs.map(
115
+ const counts: Record<string, number> = {};
116
+ snapshot.docs.forEach((doc) => {
117
+ const tech = doc.data() as Technology;
118
+ counts[tech.categoryId] = (counts[tech.categoryId] || 0) + 1;
119
+ });
120
+ return counts;
121
+ }
122
+
123
+ /**
124
+ * Returns all technologies with pagination.
125
+ * @param options - Pagination and filter options.
126
+ * @returns A list of technologies and the last visible document.
127
+ */
128
+ async getAll(
129
+ options: {
130
+ active?: boolean;
131
+ limit?: number;
132
+ lastVisible?: DocumentData;
133
+ } = {}
134
+ ) {
135
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
136
+ const constraints = [
137
+ where("isActive", "==", active),
138
+ orderBy("name"),
139
+ queryLimit ? limit(queryLimit) : undefined,
140
+ lastVisible ? startAfter(lastVisible) : undefined,
141
+ ].filter((c): c is NonNullable<typeof c> => !!c);
142
+
143
+ const q = query(this.technologiesRef, ...constraints);
144
+ const snapshot = await getDocs(q);
145
+ const technologies = snapshot.docs.map(
128
146
  (doc) =>
129
147
  ({
130
148
  id: doc.id,
131
149
  ...doc.data(),
132
150
  } as Technology)
133
151
  );
152
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
153
+ return { technologies, lastVisible: newLastVisible };
134
154
  }
135
155
 
136
156
  /**
137
- * Vraća sve aktivne tehnologije za određenu kategoriju
138
- * @param categoryId - ID kategorije
139
- * @returns Lista aktivnih tehnologija
157
+ * Returns all technologies for a specific category with pagination.
158
+ * @param categoryId - The ID of the category.
159
+ * @param options - Pagination options.
160
+ * @returns A list of technologies for the specified category.
140
161
  */
141
- async getAllByCategoryId(categoryId: string) {
142
- const q = query(
143
- this.getTechnologiesRef(),
144
- where("isActive", "==", true),
145
- where("categoryId", "==", categoryId)
146
- );
162
+ async getAllByCategoryId(
163
+ categoryId: string,
164
+ options: {
165
+ active?: boolean;
166
+ limit?: number;
167
+ lastVisible?: DocumentData;
168
+ } = {}
169
+ ) {
170
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
171
+ const constraints = [
172
+ where("categoryId", "==", categoryId),
173
+ where("isActive", "==", active),
174
+ orderBy("name"),
175
+ queryLimit ? limit(queryLimit) : undefined,
176
+ lastVisible ? startAfter(lastVisible) : undefined,
177
+ ].filter((c): c is NonNullable<typeof c> => !!c);
178
+
179
+ const q = query(this.technologiesRef, ...constraints);
147
180
  const snapshot = await getDocs(q);
148
- return snapshot.docs.map(
181
+ const technologies = snapshot.docs.map(
149
182
  (doc) =>
150
183
  ({
151
184
  id: doc.id,
152
185
  ...doc.data(),
153
186
  } as Technology)
154
187
  );
188
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
189
+ return { technologies, lastVisible: newLastVisible };
155
190
  }
156
191
 
157
192
  /**
158
- * Vraća sve aktivne tehnologije za određenu podkategoriju
159
- * @param subcategoryId - ID podkategorije
160
- * @returns Lista aktivnih tehnologija
193
+ * Returns all technologies for a specific subcategory with pagination.
194
+ * @param subcategoryId - The ID of the subcategory.
195
+ * @param options - Pagination options.
196
+ * @returns A list of technologies for the specified subcategory.
161
197
  */
162
- async getAllBySubcategoryId(subcategoryId: string) {
163
- const q = query(
164
- this.getTechnologiesRef(),
165
- where("isActive", "==", true),
166
- where("subcategoryId", "==", subcategoryId)
167
- );
198
+ async getAllBySubcategoryId(
199
+ subcategoryId: string,
200
+ options: {
201
+ active?: boolean;
202
+ limit?: number;
203
+ lastVisible?: DocumentData;
204
+ } = {}
205
+ ) {
206
+ const { active = true, limit: queryLimit = 10, lastVisible } = options;
207
+ const constraints = [
208
+ where("subcategoryId", "==", subcategoryId),
209
+ where("isActive", "==", active),
210
+ orderBy("name"),
211
+ queryLimit ? limit(queryLimit) : undefined,
212
+ lastVisible ? startAfter(lastVisible) : undefined,
213
+ ].filter((c): c is NonNullable<typeof c> => !!c);
214
+
215
+ const q = query(this.technologiesRef, ...constraints);
168
216
  const snapshot = await getDocs(q);
169
- return snapshot.docs.map(
217
+ const technologies = snapshot.docs.map(
170
218
  (doc) =>
171
219
  ({
172
220
  id: doc.id,
173
221
  ...doc.data(),
174
222
  } as Technology)
175
223
  );
224
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
225
+ return { technologies, lastVisible: newLastVisible };
176
226
  }
177
227
 
178
228
  /**
179
- * Ažurira postojeću tehnologiju
180
- * @param technologyId - ID tehnologije
181
- * @param technology - Novi podaci za tehnologiju
182
- * @returns Ažurirana tehnologija
229
+ * Updates an existing technology.
230
+ * @param id - The ID of the technology to update.
231
+ * @param technology - New data for the technology.
232
+ * @returns The updated technology.
183
233
  */
184
234
  async update(
185
- technologyId: string,
186
- technology: Partial<
187
- Omit<
188
- Technology,
189
- "id" | "createdAt" | "family" | "categoryId" | "subcategoryId"
190
- >
191
- >
235
+ id: string,
236
+ technology: Partial<Omit<Technology, "id" | "createdAt">>
192
237
  ) {
193
- const updateData = {
194
- ...technology,
195
- updatedAt: new Date(),
196
- };
238
+ const updateData: { [key: string]: any } = { ...technology };
197
239
 
198
- const docRef = doc(this.getTechnologiesRef(), technologyId);
240
+ // Remove undefined fields to prevent Firestore errors
241
+ Object.keys(updateData).forEach((key) => {
242
+ if (updateData[key] === undefined) {
243
+ delete updateData[key];
244
+ }
245
+ });
246
+
247
+ updateData.updatedAt = new Date();
248
+
249
+ const docRef = doc(this.technologiesRef, id);
199
250
  await updateDoc(docRef, updateData);
200
- return this.getById(technologyId);
251
+ return this.getById(id);
201
252
  }
202
253
 
203
254
  /**
204
- * Soft delete tehnologije (postavlja isActive na false)
205
- * @param technologyId - ID tehnologije koja se briše
255
+ * Soft deletes a technology.
256
+ * @param id - The ID of the technology to delete.
206
257
  */
207
- async delete(technologyId: string) {
208
- await this.update(technologyId, {
209
- isActive: false,
210
- });
258
+ async delete(id: string) {
259
+ await this.update(id, { isActive: false });
260
+ }
261
+
262
+ /**
263
+ * Reactivates a technology.
264
+ * @param id - The ID of the technology to reactivate.
265
+ */
266
+ async reactivate(id: string) {
267
+ await this.update(id, { isActive: true });
211
268
  }
212
269
 
213
270
  /**
214
- * Vraća tehnologiju po ID-u
215
- * @param technologyId - ID tražene tehnologije
216
- * @returns Tehnologija ili null ako ne postoji
271
+ * Returns a technology by its ID.
272
+ * @param id - The ID of the requested technology.
273
+ * @returns The technology or null if it doesn't exist.
217
274
  */
218
- async getById(technologyId: string): Promise<Technology | null> {
219
- const docRef = doc(this.getTechnologiesRef(), technologyId);
275
+ async getById(id: string): Promise<Technology | null> {
276
+ const docRef = doc(this.technologiesRef, id);
220
277
  const docSnap = await getDoc(docRef);
221
278
  if (!docSnap.exists()) return null;
222
279
  return {
@@ -232,7 +289,7 @@ export class TechnologyService extends BaseService {
232
289
  * @returns Ažurirana tehnologija sa novim zahtevom
233
290
  */
234
291
  async addRequirement(technologyId: string, requirement: Requirement) {
235
- const docRef = doc(this.getTechnologiesRef(), technologyId);
292
+ const docRef = doc(this.technologiesRef, technologyId);
236
293
 
237
294
  const requirementType =
238
295
  requirement.type === "pre" ? "requirements.pre" : "requirements.post";
@@ -252,7 +309,7 @@ export class TechnologyService extends BaseService {
252
309
  * @returns Ažurirana tehnologija bez uklonjenog zahteva
253
310
  */
254
311
  async removeRequirement(technologyId: string, requirement: Requirement) {
255
- const docRef = doc(this.getTechnologiesRef(), technologyId);
312
+ const docRef = doc(this.technologiesRef, technologyId);
256
313
 
257
314
  const requirementType =
258
315
  requirement.type === "pre" ? "requirements.pre" : "requirements.post";
@@ -308,7 +365,7 @@ export class TechnologyService extends BaseService {
308
365
  technologyId: string,
309
366
  condition: BlockingCondition
310
367
  ) {
311
- const docRef = doc(this.getTechnologiesRef(), technologyId);
368
+ const docRef = doc(this.technologiesRef, technologyId);
312
369
 
313
370
  await updateDoc(docRef, {
314
371
  blockingConditions: arrayUnion(condition),
@@ -328,7 +385,7 @@ export class TechnologyService extends BaseService {
328
385
  technologyId: string,
329
386
  condition: BlockingCondition
330
387
  ) {
331
- const docRef = doc(this.getTechnologiesRef(), technologyId);
388
+ const docRef = doc(this.technologiesRef, technologyId);
332
389
 
333
390
  await updateDoc(docRef, {
334
391
  blockingConditions: arrayRemove(condition),
@@ -346,12 +403,21 @@ export class TechnologyService extends BaseService {
346
403
  */
347
404
  async addContraindication(
348
405
  technologyId: string,
349
- contraindication: Contraindication
406
+ contraindication: ContraindicationDynamic
350
407
  ) {
351
- const docRef = doc(this.getTechnologiesRef(), technologyId);
408
+ const docRef = doc(this.technologiesRef, technologyId);
409
+ const technology = await this.getById(technologyId);
410
+ if (!technology) {
411
+ throw new Error(`Technology with id ${technologyId} not found`);
412
+ }
413
+
414
+ const existingContraindications = technology.contraindications || [];
415
+ if (existingContraindications.some((c) => c.id === contraindication.id)) {
416
+ return technology; // Already exists, do nothing
417
+ }
352
418
 
353
419
  await updateDoc(docRef, {
354
- contraindications: arrayUnion(contraindication),
420
+ contraindications: [...existingContraindications, contraindication],
355
421
  updatedAt: new Date(),
356
422
  });
357
423
 
@@ -366,12 +432,62 @@ export class TechnologyService extends BaseService {
366
432
  */
367
433
  async removeContraindication(
368
434
  technologyId: string,
369
- contraindication: Contraindication
435
+ contraindication: ContraindicationDynamic
370
436
  ) {
371
- const docRef = doc(this.getTechnologiesRef(), technologyId);
437
+ const docRef = doc(this.technologiesRef, technologyId);
438
+ const technology = await this.getById(technologyId);
439
+ if (!technology) {
440
+ throw new Error(`Technology with id ${technologyId} not found`);
441
+ }
442
+
443
+ const updatedContraindications = (
444
+ technology.contraindications || []
445
+ ).filter((c) => c.id !== contraindication.id);
372
446
 
373
447
  await updateDoc(docRef, {
374
- contraindications: arrayRemove(contraindication),
448
+ contraindications: updatedContraindications,
449
+ updatedAt: new Date(),
450
+ });
451
+
452
+ return this.getById(technologyId);
453
+ }
454
+
455
+ /**
456
+ * Updates an existing contraindication in a technology's list.
457
+ * If the contraindication does not exist, it will not be added.
458
+ * @param technologyId - ID of the technology
459
+ * @param contraindication - The updated contraindication object
460
+ * @returns The updated technology
461
+ */
462
+ async updateContraindication(
463
+ technologyId: string,
464
+ contraindication: ContraindicationDynamic
465
+ ) {
466
+ const docRef = doc(this.technologiesRef, technologyId);
467
+ const technology = await this.getById(technologyId);
468
+ if (!technology) {
469
+ throw new Error(`Technology with id ${technologyId} not found`);
470
+ }
471
+
472
+ const contraindications = technology.contraindications || [];
473
+ const index = contraindications.findIndex(
474
+ (c) => c.id === contraindication.id
475
+ );
476
+
477
+ if (index === -1) {
478
+ // If contraindication doesn't exist, do not update
479
+ // Consider throwing an error if this is an unexpected state
480
+ console.warn(
481
+ `Contraindication with id ${contraindication.id} not found for technology ${technologyId}. No update performed.`
482
+ );
483
+ return technology;
484
+ }
485
+
486
+ const updatedContraindications = [...contraindications];
487
+ updatedContraindications[index] = contraindication;
488
+
489
+ await updateDoc(docRef, {
490
+ contraindications: updatedContraindications,
375
491
  updatedAt: new Date(),
376
492
  });
377
493
 
@@ -384,11 +500,20 @@ export class TechnologyService extends BaseService {
384
500
  * @param benefit - Benefit koji se dodaje
385
501
  * @returns Ažurirana tehnologija
386
502
  */
387
- async addBenefit(technologyId: string, benefit: TreatmentBenefit) {
388
- const docRef = doc(this.getTechnologiesRef(), technologyId);
503
+ async addBenefit(technologyId: string, benefit: TreatmentBenefitDynamic) {
504
+ const docRef = doc(this.technologiesRef, technologyId);
505
+ const technology = await this.getById(technologyId);
506
+ if (!technology) {
507
+ throw new Error(`Technology with id ${technologyId} not found`);
508
+ }
509
+
510
+ const existingBenefits = technology.benefits || [];
511
+ if (existingBenefits.some((b) => b.id === benefit.id)) {
512
+ return technology; // Already exists, do nothing
513
+ }
389
514
 
390
515
  await updateDoc(docRef, {
391
- benefits: arrayUnion(benefit),
516
+ benefits: [...existingBenefits, benefit],
392
517
  updatedAt: new Date(),
393
518
  });
394
519
 
@@ -401,11 +526,55 @@ export class TechnologyService extends BaseService {
401
526
  * @param benefit - Benefit koji se uklanja
402
527
  * @returns Ažurirana tehnologija
403
528
  */
404
- async removeBenefit(technologyId: string, benefit: TreatmentBenefit) {
405
- const docRef = doc(this.getTechnologiesRef(), technologyId);
529
+ async removeBenefit(technologyId: string, benefit: TreatmentBenefitDynamic) {
530
+ const docRef = doc(this.technologiesRef, technologyId);
531
+ const technology = await this.getById(technologyId);
532
+ if (!technology) {
533
+ throw new Error(`Technology with id ${technologyId} not found`);
534
+ }
535
+
536
+ const updatedBenefits = (technology.benefits || []).filter(
537
+ (b) => b.id !== benefit.id
538
+ );
539
+
540
+ await updateDoc(docRef, {
541
+ benefits: updatedBenefits,
542
+ updatedAt: new Date(),
543
+ });
544
+
545
+ return this.getById(technologyId);
546
+ }
547
+
548
+ /**
549
+ * Updates an existing benefit in a technology's list.
550
+ * If the benefit does not exist, it will not be added.
551
+ * @param technologyId - ID of the technology
552
+ * @param benefit - The updated benefit object
553
+ * @returns The updated technology
554
+ */
555
+ async updateBenefit(technologyId: string, benefit: TreatmentBenefitDynamic) {
556
+ const docRef = doc(this.technologiesRef, technologyId);
557
+ const technology = await this.getById(technologyId);
558
+ if (!technology) {
559
+ throw new Error(`Technology with id ${technologyId} not found`);
560
+ }
561
+
562
+ const benefits = technology.benefits || [];
563
+ const index = benefits.findIndex((b) => b.id === benefit.id);
564
+
565
+ if (index === -1) {
566
+ // If benefit doesn't exist, do not update
567
+ console.warn(
568
+ `Benefit with id ${benefit.id} not found for technology ${technologyId}. No update performed.`
569
+ );
570
+ return technology;
571
+ }
572
+
573
+ const updatedBenefits = [...benefits];
574
+ updatedBenefits[index] = benefit;
406
575
 
407
576
  await updateDoc(docRef, {
408
- benefits: arrayRemove(benefit),
577
+ benefits: updatedBenefits,
409
578
  updatedAt: new Date(),
410
579
  });
411
580
 
@@ -452,7 +621,7 @@ export class TechnologyService extends BaseService {
452
621
  technologyId: string,
453
622
  certificationRequirement: CertificationRequirement
454
623
  ) {
455
- const docRef = doc(this.getTechnologiesRef(), technologyId);
624
+ const docRef = doc(this.technologiesRef, technologyId);
456
625
 
457
626
  await updateDoc(docRef, {
458
627
  certificationRequirement,
@@ -557,11 +726,12 @@ export class TechnologyService extends BaseService {
557
726
  const allTechnologies = await this.getAll();
558
727
 
559
728
  // Filter technologies based on certification requirements
560
- const allowedTechnologies = allTechnologies.filter((technology) =>
561
- this.validateCertification(
562
- technology.certificationRequirement,
563
- practitioner.certification
564
- )
729
+ const allowedTechnologies = allTechnologies.technologies.filter(
730
+ (technology) =>
731
+ this.validateCertification(
732
+ technology.certificationRequirement,
733
+ practitioner.certification
734
+ )
565
735
  );
566
736
 
567
737
  // Extract unique families, categories, and subcategories
@@ -580,4 +750,49 @@ export class TechnologyService extends BaseService {
580
750
  subcategories,
581
751
  };
582
752
  }
753
+
754
+ /**
755
+ * Gets all active technologies for a subcategory for filter dropdowns.
756
+ * @param categoryId - The ID of the parent category.
757
+ * @param subcategoryId - The ID of the subcategory.
758
+ */
759
+ async getAllForFilterBySubcategoryId(
760
+ categoryId: string,
761
+ subcategoryId: string
762
+ ): Promise<Technology[]> {
763
+ const q = query(
764
+ collection(this.db, TECHNOLOGIES_COLLECTION),
765
+ where("isActive", "==", true),
766
+ where("categoryId", "==", categoryId),
767
+ where("subcategoryId", "==", subcategoryId),
768
+ orderBy("name")
769
+ );
770
+ const snapshot = await getDocs(q);
771
+ return snapshot.docs.map(
772
+ (doc) =>
773
+ ({
774
+ id: doc.id,
775
+ ...doc.data(),
776
+ } as Technology)
777
+ );
778
+ }
779
+
780
+ /**
781
+ * Gets all active technologies for filter dropdowns.
782
+ */
783
+ async getAllForFilter(): Promise<Technology[]> {
784
+ const q = query(
785
+ collection(this.db, TECHNOLOGIES_COLLECTION),
786
+ where("isActive", "==", true),
787
+ orderBy("name")
788
+ );
789
+ const snapshot = await getDocs(q);
790
+ return snapshot.docs.map(
791
+ (doc) =>
792
+ ({
793
+ id: doc.id,
794
+ ...doc.data(),
795
+ } as Technology)
796
+ );
797
+ }
583
798
  }