@blackcode_sa/metaestetics-api 1.12.1 → 1.12.3

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.
@@ -20,62 +20,41 @@ import {
20
20
  startAfter,
21
21
  QueryConstraint,
22
22
  documentId,
23
- } from "firebase/firestore";
24
- import { BaseService } from "../base.service";
23
+ } from 'firebase/firestore';
24
+ import { BaseService } from '../base.service';
25
25
  import {
26
26
  Procedure,
27
27
  CreateProcedureData,
28
28
  UpdateProcedureData,
29
29
  PROCEDURES_COLLECTION,
30
30
  ProcedureSummaryInfo,
31
- } from "../../types/procedure";
32
- import {
33
- createProcedureSchema,
34
- updateProcedureSchema,
35
- } from "../../validations/procedure.schema";
36
- import { z } from "zod";
37
- import { Auth } from "firebase/auth";
38
- import { Firestore } from "firebase/firestore";
39
- import { FirebaseApp } from "firebase/app";
40
- import {
41
- Category,
42
- CATEGORIES_COLLECTION,
43
- } from "../../backoffice/types/category.types";
44
- import {
45
- Subcategory,
46
- SUBCATEGORIES_COLLECTION,
47
- } from "../../backoffice/types/subcategory.types";
48
- import {
49
- Technology,
50
- TECHNOLOGIES_COLLECTION,
51
- } from "../../backoffice/types/technology.types";
52
- import {
53
- Product,
54
- PRODUCTS_COLLECTION,
55
- } from "../../backoffice/types/product.types";
56
- import { CategoryService } from "../../backoffice/services/category.service";
57
- import { SubcategoryService } from "../../backoffice/services/subcategory.service";
58
- import { TechnologyService } from "../../backoffice/services/technology.service";
59
- import { ProductService } from "../../backoffice/services/product.service";
60
- import {
61
- Practitioner,
62
- PRACTITIONERS_COLLECTION,
63
- } from "../../types/practitioner";
31
+ } from '../../types/procedure';
32
+ import { createProcedureSchema, updateProcedureSchema } from '../../validations/procedure.schema';
33
+ import { z } from 'zod';
34
+ import { Auth } from 'firebase/auth';
35
+ import { Firestore } from 'firebase/firestore';
36
+ import { FirebaseApp } from 'firebase/app';
37
+ import { Category, CATEGORIES_COLLECTION } from '../../backoffice/types/category.types';
38
+ import { Subcategory, SUBCATEGORIES_COLLECTION } from '../../backoffice/types/subcategory.types';
39
+ import { Technology, TECHNOLOGIES_COLLECTION } from '../../backoffice/types/technology.types';
40
+ import { Product, PRODUCTS_COLLECTION } from '../../backoffice/types/product.types';
41
+ import { CategoryService } from '../../backoffice/services/category.service';
42
+ import { SubcategoryService } from '../../backoffice/services/subcategory.service';
43
+ import { TechnologyService } from '../../backoffice/services/technology.service';
44
+ import { ProductService } from '../../backoffice/services/product.service';
45
+ import { Practitioner, PRACTITIONERS_COLLECTION } from '../../types/practitioner';
64
46
  import {
65
47
  CertificationLevel,
66
48
  CertificationSpecialty,
67
49
  ProcedureFamily,
68
50
  type TreatmentBenefitDynamic,
69
- } from "../../backoffice/types";
70
- import {
71
- Currency,
72
- PricingMeasure,
73
- } from "../../backoffice/types/static/pricing.types";
74
- import { Clinic, CLINICS_COLLECTION } from "../../types/clinic";
75
- import { ProcedureReviewInfo } from "../../types/reviews";
76
- import { distanceBetween, geohashQueryBounds } from "geofire-common";
77
- import { MediaService, MediaAccessLevel } from "../media/media.service";
78
- import type { ProcedureProduct } from "../../backoffice/types/procedure-product.types";
51
+ } from '../../backoffice/types';
52
+ import { Currency, PricingMeasure } from '../../backoffice/types/static/pricing.types';
53
+ import { Clinic, CLINICS_COLLECTION } from '../../types/clinic';
54
+ import { ProcedureReviewInfo } from '../../types/reviews';
55
+ import { distanceBetween, geohashQueryBounds } from 'geofire-common';
56
+ import { MediaService, MediaAccessLevel } from '../media/media.service';
57
+ import type { ProcedureProduct } from '../../backoffice/types/procedure-product.types';
79
58
 
80
59
  export class ProcedureService extends BaseService {
81
60
  private categoryService: CategoryService;
@@ -92,7 +71,7 @@ export class ProcedureService extends BaseService {
92
71
  subcategoryService: SubcategoryService,
93
72
  technologyService: TechnologyService,
94
73
  productService: ProductService,
95
- mediaService: MediaService
74
+ mediaService: MediaService,
96
75
  ) {
97
76
  super(db, auth, app);
98
77
  this.categoryService = categoryService;
@@ -112,25 +91,23 @@ export class ProcedureService extends BaseService {
112
91
  private async processMedia(
113
92
  media: string | File | Blob | null | undefined,
114
93
  ownerId: string,
115
- collectionName: string
94
+ collectionName: string,
116
95
  ): Promise<string | null> {
117
96
  if (!media) return null;
118
97
 
119
98
  // If already a string URL, return it directly
120
- if (typeof media === "string") {
99
+ if (typeof media === 'string') {
121
100
  return media;
122
101
  }
123
102
 
124
103
  // If it's a File, upload it using MediaService
125
104
  if (media instanceof File || media instanceof Blob) {
126
- console.log(
127
- `[ProcedureService] Uploading ${collectionName} media for ${ownerId}`
128
- );
105
+ console.log(`[ProcedureService] Uploading ${collectionName} media for ${ownerId}`);
129
106
  const metadata = await this.mediaService.uploadMedia(
130
107
  media,
131
108
  ownerId,
132
109
  MediaAccessLevel.PUBLIC,
133
- collectionName
110
+ collectionName,
134
111
  );
135
112
  return metadata.url;
136
113
  }
@@ -148,18 +125,14 @@ export class ProcedureService extends BaseService {
148
125
  private async processMediaArray(
149
126
  mediaArray: (string | File | Blob)[] | undefined,
150
127
  ownerId: string,
151
- collectionName: string
128
+ collectionName: string,
152
129
  ): Promise<string[]> {
153
130
  if (!mediaArray || mediaArray.length === 0) return [];
154
131
 
155
132
  const result: string[] = [];
156
133
 
157
134
  for (const media of mediaArray) {
158
- const processedUrl = await this.processMedia(
159
- media,
160
- ownerId,
161
- collectionName
162
- );
135
+ const processedUrl = await this.processMedia(media, ownerId, collectionName);
163
136
  if (processedUrl) {
164
137
  result.push(processedUrl);
165
138
  }
@@ -182,19 +155,16 @@ export class ProcedureService extends BaseService {
182
155
  pricingMeasure: PricingMeasure;
183
156
  isDefault?: boolean;
184
157
  }[],
185
- technologyId: string
158
+ technologyId: string,
186
159
  ): Promise<ProcedureProduct[]> {
187
160
  const transformedProducts: ProcedureProduct[] = [];
188
161
 
189
162
  for (const productData of productsMetadata) {
190
163
  // Fetch the full product object
191
- const product = await this.productService.getById(
192
- technologyId,
193
- productData.productId
194
- );
164
+ const product = await this.productService.getById(technologyId, productData.productId);
195
165
  if (!product) {
196
166
  throw new Error(
197
- `Product with ID ${productData.productId} not found for technology ${technologyId}`
167
+ `Product with ID ${productData.productId} not found for technology ${technologyId}`,
198
168
  );
199
169
  }
200
170
 
@@ -225,45 +195,27 @@ export class ProcedureService extends BaseService {
225
195
  // Get references to related entities (Category, Subcategory, Technology, Product)
226
196
  const [category, subcategory, technology, product] = await Promise.all([
227
197
  this.categoryService.getById(validatedData.categoryId),
228
- this.subcategoryService.getById(
229
- validatedData.categoryId,
230
- validatedData.subcategoryId
231
- ),
198
+ this.subcategoryService.getById(validatedData.categoryId, validatedData.subcategoryId),
232
199
  this.technologyService.getById(validatedData.technologyId),
233
- this.productService.getById(
234
- validatedData.technologyId,
235
- validatedData.productId
236
- ),
200
+ this.productService.getById(validatedData.technologyId, validatedData.productId),
237
201
  ]);
238
202
 
239
203
  if (!category || !subcategory || !technology || !product) {
240
- throw new Error("One or more required base entities not found");
204
+ throw new Error('One or more required base entities not found');
241
205
  }
242
206
 
243
207
  // Get clinic and practitioner information for aggregation
244
- const clinicRef = doc(
245
- this.db,
246
- CLINICS_COLLECTION,
247
- validatedData.clinicBranchId
248
- );
208
+ const clinicRef = doc(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId);
249
209
  const clinicSnapshot = await getDoc(clinicRef);
250
210
  if (!clinicSnapshot.exists()) {
251
- throw new Error(
252
- `Clinic with ID ${validatedData.clinicBranchId} not found`
253
- );
211
+ throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
254
212
  }
255
213
  const clinic = clinicSnapshot.data() as Clinic; // Assert type
256
214
 
257
- const practitionerRef = doc(
258
- this.db,
259
- PRACTITIONERS_COLLECTION,
260
- validatedData.practitionerId
261
- );
215
+ const practitionerRef = doc(this.db, PRACTITIONERS_COLLECTION, validatedData.practitionerId);
262
216
  const practitionerSnapshot = await getDoc(practitionerRef);
263
217
  if (!practitionerSnapshot.exists()) {
264
- throw new Error(
265
- `Practitioner with ID ${validatedData.practitionerId} not found`
266
- );
218
+ throw new Error(`Practitioner with ID ${validatedData.practitionerId} not found`);
267
219
  }
268
220
  const practitioner = practitionerSnapshot.data() as Practitioner; // Assert type
269
221
 
@@ -273,29 +225,29 @@ export class ProcedureService extends BaseService {
273
225
  processedPhotos = await this.processMediaArray(
274
226
  validatedData.photos,
275
227
  procedureId,
276
- "procedure-photos"
228
+ 'procedure-photos',
277
229
  );
278
230
  }
279
231
 
280
232
  // Transform productsMetadata from validation format to ProcedureProduct format
281
233
  const transformedProductsMetadata = await this.transformProductsMetadata(
282
234
  validatedData.productsMetadata,
283
- validatedData.technologyId
235
+ validatedData.technologyId,
284
236
  );
285
237
 
286
238
  // Create aggregated clinic info for the procedure document
287
239
  const clinicInfo = {
288
240
  id: clinicSnapshot.id,
289
241
  name: clinic.name,
290
- description: clinic.description || "",
242
+ description: clinic.description || '',
291
243
  featuredPhoto:
292
244
  clinic.featuredPhotos && clinic.featuredPhotos.length > 0
293
- ? typeof clinic.featuredPhotos[0] === "string"
245
+ ? typeof clinic.featuredPhotos[0] === 'string'
294
246
  ? clinic.featuredPhotos[0]
295
- : ""
296
- : typeof clinic.coverPhoto === "string"
247
+ : ''
248
+ : typeof clinic.coverPhoto === 'string'
297
249
  ? clinic.coverPhoto
298
- : "",
250
+ : '',
299
251
  location: clinic.location,
300
252
  contactInfo: clinic.contactInfo,
301
253
  };
@@ -304,33 +256,33 @@ export class ProcedureService extends BaseService {
304
256
  const doctorInfo = {
305
257
  id: practitionerSnapshot.id,
306
258
  name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
307
- description: practitioner.basicInfo.bio || "",
259
+ description: practitioner.basicInfo.bio || '',
308
260
  photo:
309
- typeof practitioner.basicInfo.profileImageUrl === "string"
261
+ typeof practitioner.basicInfo.profileImageUrl === 'string'
310
262
  ? practitioner.basicInfo.profileImageUrl
311
- : "", // Default to empty string if not a processed URL
263
+ : '', // Default to empty string if not a processed URL
312
264
  rating: practitioner.reviewInfo?.averageRating || 0,
313
265
  services: practitioner.procedures || [],
314
266
  };
315
267
 
316
268
  // Create the procedure object
317
- const newProcedure: Omit<Procedure, "createdAt" | "updatedAt"> = {
269
+ const { productsMetadata: _, ...validatedDataWithoutProductsMetadata } = validatedData;
270
+ const newProcedure: Omit<Procedure, 'createdAt' | 'updatedAt'> = {
318
271
  id: procedureId,
319
- ...validatedData,
272
+ ...validatedDataWithoutProductsMetadata,
320
273
  // Ensure nameLower is always set even if omitted by client
321
- nameLower:
322
- (validatedData as any).nameLower || validatedData.name.toLowerCase(),
274
+ nameLower: (validatedData as any).nameLower || validatedData.name.toLowerCase(),
323
275
  photos: processedPhotos,
324
276
  category, // Embed full objects
325
277
  subcategory,
326
278
  technology,
327
279
  product,
328
- productsMetadata: transformedProductsMetadata,
280
+ productsMetadata: transformedProductsMetadata, // Use transformed data, not original
329
281
  blockingConditions: technology.blockingConditions,
330
282
  contraindications: technology.contraindications || [],
331
- contraindicationIds: technology.contraindications?.map((c) => c.id) || [],
283
+ contraindicationIds: technology.contraindications?.map(c => c.id) || [],
332
284
  treatmentBenefits: technology.benefits,
333
- treatmentBenefitIds: technology.benefits?.map((b) => b.id) || [],
285
+ treatmentBenefitIds: technology.benefits?.map(b => b.id) || [],
334
286
  preRequirements: technology.requirements.pre,
335
287
  postRequirements: technology.requirements.post,
336
288
  certificationRequirement: technology.certificationRequirement,
@@ -373,12 +325,12 @@ export class ProcedureService extends BaseService {
373
325
  * @returns A promise that resolves to an array of the newly created procedures.
374
326
  */
375
327
  async bulkCreateProcedures(
376
- baseData: Omit<CreateProcedureData, "practitionerId">,
377
- practitionerIds: string[]
328
+ baseData: Omit<CreateProcedureData, 'practitionerId'>,
329
+ practitionerIds: string[],
378
330
  ): Promise<Procedure[]> {
379
331
  // 1. Validation
380
332
  if (!practitionerIds || practitionerIds.length === 0) {
381
- throw new Error("Practitioner IDs array cannot be empty.");
333
+ throw new Error('Practitioner IDs array cannot be empty.');
382
334
  }
383
335
 
384
336
  // Add a dummy practitionerId for the validation schema to pass
@@ -386,28 +338,19 @@ export class ProcedureService extends BaseService {
386
338
  const validatedData = createProcedureSchema.parse(validationData);
387
339
 
388
340
  // 2. Fetch common data once to avoid redundant reads
389
- const [category, subcategory, technology, product, clinicSnapshot] =
390
- await Promise.all([
391
- this.categoryService.getById(validatedData.categoryId),
392
- this.subcategoryService.getById(
393
- validatedData.categoryId,
394
- validatedData.subcategoryId
395
- ),
396
- this.technologyService.getById(validatedData.technologyId),
397
- this.productService.getById(
398
- validatedData.technologyId,
399
- validatedData.productId
400
- ),
401
- getDoc(doc(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId)),
402
- ]);
341
+ const [category, subcategory, technology, product, clinicSnapshot] = await Promise.all([
342
+ this.categoryService.getById(validatedData.categoryId),
343
+ this.subcategoryService.getById(validatedData.categoryId, validatedData.subcategoryId),
344
+ this.technologyService.getById(validatedData.technologyId),
345
+ this.productService.getById(validatedData.technologyId, validatedData.productId),
346
+ getDoc(doc(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId)),
347
+ ]);
403
348
 
404
349
  if (!category || !subcategory || !technology || !product) {
405
- throw new Error("One or more required base entities not found");
350
+ throw new Error('One or more required base entities not found');
406
351
  }
407
352
  if (!clinicSnapshot.exists()) {
408
- throw new Error(
409
- `Clinic with ID ${validatedData.clinicBranchId} not found`
410
- );
353
+ throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
411
354
  }
412
355
  const clinic = clinicSnapshot.data() as Clinic;
413
356
 
@@ -418,14 +361,14 @@ export class ProcedureService extends BaseService {
418
361
  processedPhotos = await this.processMediaArray(
419
362
  validatedData.photos,
420
363
  batchId,
421
- "procedure-photos-batch"
364
+ 'procedure-photos-batch',
422
365
  );
423
366
  }
424
367
 
425
368
  // Transform productsMetadata from validation format to ProcedureProduct format
426
369
  const transformedProductsMetadata = await this.transformProductsMetadata(
427
370
  validatedData.productsMetadata,
428
- validatedData.technologyId
371
+ validatedData.technologyId,
429
372
  );
430
373
 
431
374
  // 4. Fetch all practitioner data efficiently
@@ -435,10 +378,10 @@ export class ProcedureService extends BaseService {
435
378
  const chunk = practitionerIds.slice(i, i + 30);
436
379
  const practitionersQuery = query(
437
380
  collection(this.db, PRACTITIONERS_COLLECTION),
438
- where(documentId(), "in", chunk)
381
+ where(documentId(), 'in', chunk),
439
382
  );
440
383
  const practitionersSnapshot = await getDocs(practitionersQuery);
441
- practitionersSnapshot.docs.forEach((doc) => {
384
+ practitionersSnapshot.docs.forEach(doc => {
442
385
  practitionersMap.set(doc.id, doc.data() as Practitioner);
443
386
  });
444
387
  }
@@ -446,12 +389,8 @@ export class ProcedureService extends BaseService {
446
389
  // Verify all practitioners were found
447
390
  if (practitionersMap.size !== practitionerIds.length) {
448
391
  const foundIds = Array.from(practitionersMap.keys());
449
- const notFoundIds = practitionerIds.filter(
450
- (id) => !foundIds.includes(id)
451
- );
452
- throw new Error(
453
- `The following practitioners were not found: ${notFoundIds.join(", ")}`
454
- );
392
+ const notFoundIds = practitionerIds.filter(id => !foundIds.includes(id));
393
+ throw new Error(`The following practitioners were not found: ${notFoundIds.join(', ')}`);
455
394
  }
456
395
 
457
396
  // 5. Use a Firestore batch for atomic creation
@@ -460,15 +399,15 @@ export class ProcedureService extends BaseService {
460
399
  const clinicInfo = {
461
400
  id: clinicSnapshot.id,
462
401
  name: clinic.name,
463
- description: clinic.description || "",
402
+ description: clinic.description || '',
464
403
  featuredPhoto:
465
404
  clinic.featuredPhotos && clinic.featuredPhotos.length > 0
466
- ? typeof clinic.featuredPhotos[0] === "string"
405
+ ? typeof clinic.featuredPhotos[0] === 'string'
467
406
  ? clinic.featuredPhotos[0]
468
- : ""
469
- : typeof clinic.coverPhoto === "string"
407
+ : ''
408
+ : typeof clinic.coverPhoto === 'string'
470
409
  ? clinic.coverPhoto
471
- : "",
410
+ : '',
472
411
  location: clinic.location,
473
412
  contactInfo: clinic.contactInfo,
474
413
  };
@@ -479,11 +418,11 @@ export class ProcedureService extends BaseService {
479
418
  const doctorInfo = {
480
419
  id: practitioner.id,
481
420
  name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
482
- description: practitioner.basicInfo.bio || "",
421
+ description: practitioner.basicInfo.bio || '',
483
422
  photo:
484
- typeof practitioner.basicInfo.profileImageUrl === "string"
423
+ typeof practitioner.basicInfo.profileImageUrl === 'string'
485
424
  ? practitioner.basicInfo.profileImageUrl
486
- : "",
425
+ : '',
487
426
  rating: practitioner.reviewInfo?.averageRating || 0,
488
427
  services: practitioner.procedures || [],
489
428
  };
@@ -493,24 +432,23 @@ export class ProcedureService extends BaseService {
493
432
  const procedureRef = doc(this.db, PROCEDURES_COLLECTION, procedureId);
494
433
 
495
434
  // Construct the new procedure, reusing common data
496
- const newProcedure: Omit<Procedure, "createdAt" | "updatedAt"> = {
435
+ const { productsMetadata: _, ...validatedDataWithoutProductsMetadata } = validatedData;
436
+ const newProcedure: Omit<Procedure, 'createdAt' | 'updatedAt'> = {
497
437
  id: procedureId,
498
- ...validatedData,
499
- nameLower:
500
- (validatedData as any).nameLower || validatedData.name.toLowerCase(),
438
+ ...validatedDataWithoutProductsMetadata,
439
+ nameLower: (validatedData as any).nameLower || validatedData.name.toLowerCase(),
501
440
  practitionerId: practitionerId, // Override practitionerId with the correct one
502
441
  photos: processedPhotos,
503
442
  category,
504
443
  subcategory,
505
444
  technology,
506
445
  product,
507
- productsMetadata: transformedProductsMetadata,
446
+ productsMetadata: transformedProductsMetadata, // Use transformed data, not original
508
447
  blockingConditions: technology.blockingConditions,
509
448
  contraindications: technology.contraindications || [],
510
- contraindicationIds:
511
- technology.contraindications?.map((c) => c.id) || [],
449
+ contraindicationIds: technology.contraindications?.map(c => c.id) || [],
512
450
  treatmentBenefits: technology.benefits,
513
- treatmentBenefitIds: technology.benefits?.map((b) => b.id) || [],
451
+ treatmentBenefitIds: technology.benefits?.map(b => b.id) || [],
514
452
  preRequirements: technology.requirements.pre,
515
453
  postRequirements: technology.requirements.post,
516
454
  certificationRequirement: technology.certificationRequirement,
@@ -544,12 +482,9 @@ export class ProcedureService extends BaseService {
544
482
  const fetchedProcedures: Procedure[] = [];
545
483
  for (let i = 0; i < createdProcedureIds.length; i += 30) {
546
484
  const chunk = createdProcedureIds.slice(i, i + 30);
547
- const q = query(
548
- collection(this.db, PROCEDURES_COLLECTION),
549
- where(documentId(), "in", chunk)
550
- );
485
+ const q = query(collection(this.db, PROCEDURES_COLLECTION), where(documentId(), 'in', chunk));
551
486
  const snapshot = await getDocs(q);
552
- snapshot.forEach((doc) => {
487
+ snapshot.forEach(doc => {
553
488
  fetchedProcedures.push(doc.data() as Procedure);
554
489
  });
555
490
  }
@@ -578,16 +513,14 @@ export class ProcedureService extends BaseService {
578
513
  * @param clinicBranchId - The ID of the clinic branch
579
514
  * @returns List of procedures
580
515
  */
581
- async getProceduresByClinicBranch(
582
- clinicBranchId: string
583
- ): Promise<Procedure[]> {
516
+ async getProceduresByClinicBranch(clinicBranchId: string): Promise<Procedure[]> {
584
517
  const q = query(
585
518
  collection(this.db, PROCEDURES_COLLECTION),
586
- where("clinicBranchId", "==", clinicBranchId),
587
- where("isActive", "==", true)
519
+ where('clinicBranchId', '==', clinicBranchId),
520
+ where('isActive', '==', true),
588
521
  );
589
522
  const snapshot = await getDocs(q);
590
- return snapshot.docs.map((doc) => doc.data() as Procedure);
523
+ return snapshot.docs.map(doc => doc.data() as Procedure);
591
524
  }
592
525
 
593
526
  /**
@@ -595,16 +528,14 @@ export class ProcedureService extends BaseService {
595
528
  * @param practitionerId - The ID of the practitioner
596
529
  * @returns List of procedures
597
530
  */
598
- async getProceduresByPractitioner(
599
- practitionerId: string
600
- ): Promise<Procedure[]> {
531
+ async getProceduresByPractitioner(practitionerId: string): Promise<Procedure[]> {
601
532
  const q = query(
602
533
  collection(this.db, PROCEDURES_COLLECTION),
603
- where("practitionerId", "==", practitionerId),
604
- where("isActive", "==", true)
534
+ where('practitionerId', '==', practitionerId),
535
+ where('isActive', '==', true),
605
536
  );
606
537
  const snapshot = await getDocs(q);
607
- return snapshot.docs.map((doc) => doc.data() as Procedure);
538
+ return snapshot.docs.map(doc => doc.data() as Procedure);
608
539
  }
609
540
 
610
541
  /**
@@ -612,16 +543,14 @@ export class ProcedureService extends BaseService {
612
543
  * @param practitionerId - The ID of the practitioner
613
544
  * @returns List of inactive procedures
614
545
  */
615
- async getInactiveProceduresByPractitioner(
616
- practitionerId: string
617
- ): Promise<Procedure[]> {
546
+ async getInactiveProceduresByPractitioner(practitionerId: string): Promise<Procedure[]> {
618
547
  const q = query(
619
548
  collection(this.db, PROCEDURES_COLLECTION),
620
- where("practitionerId", "==", practitionerId),
621
- where("isActive", "==", false)
549
+ where('practitionerId', '==', practitionerId),
550
+ where('isActive', '==', false),
622
551
  );
623
552
  const snapshot = await getDocs(q);
624
- return snapshot.docs.map((doc) => doc.data() as Procedure);
553
+ return snapshot.docs.map(doc => doc.data() as Procedure);
625
554
  }
626
555
 
627
556
  /**
@@ -630,10 +559,7 @@ export class ProcedureService extends BaseService {
630
559
  * @param data - The data to update the procedure with
631
560
  * @returns The updated procedure
632
561
  */
633
- async updateProcedure(
634
- id: string,
635
- data: UpdateProcedureData
636
- ): Promise<Procedure> {
562
+ async updateProcedure(id: string, data: UpdateProcedureData): Promise<Procedure> {
637
563
  const validatedData = updateProcedureSchema.parse(data);
638
564
  const procedureRef = doc(this.db, PROCEDURES_COLLECTION, id);
639
565
  const procedureSnapshot = await getDoc(procedureRef);
@@ -646,12 +572,10 @@ export class ProcedureService extends BaseService {
646
572
  let updatedProcedureData: Partial<Procedure> = {};
647
573
 
648
574
  // Copy validated simple fields
649
- if (validatedData.name !== undefined)
650
- updatedProcedureData.name = validatedData.name;
575
+ if (validatedData.name !== undefined) updatedProcedureData.name = validatedData.name;
651
576
  if (validatedData.description !== undefined)
652
577
  updatedProcedureData.description = validatedData.description;
653
- if (validatedData.price !== undefined)
654
- updatedProcedureData.price = validatedData.price;
578
+ if (validatedData.price !== undefined) updatedProcedureData.price = validatedData.price;
655
579
  if (validatedData.currency !== undefined)
656
580
  updatedProcedureData.currency = validatedData.currency;
657
581
  if (validatedData.pricingMeasure !== undefined)
@@ -673,70 +597,54 @@ export class ProcedureService extends BaseService {
673
597
  updatedProcedureData.photos = await this.processMediaArray(
674
598
  validatedData.photos,
675
599
  id,
676
- "procedure-photos"
600
+ 'procedure-photos',
677
601
  );
678
602
  }
679
603
 
680
604
  // Transform productsMetadata if provided
681
605
  if (validatedData.productsMetadata !== undefined) {
682
- const technologyId =
683
- validatedData.technologyId ?? existingProcedure.technology.id;
606
+ const technologyId = validatedData.technologyId ?? existingProcedure.technology.id;
684
607
  if (!technologyId) {
685
- throw new Error(
686
- "Technology ID is required for updating products metadata"
687
- );
608
+ throw new Error('Technology ID is required for updating products metadata');
688
609
  }
689
- updatedProcedureData.productsMetadata =
690
- await this.transformProductsMetadata(
691
- validatedData.productsMetadata,
692
- technologyId
693
- );
610
+ updatedProcedureData.productsMetadata = await this.transformProductsMetadata(
611
+ validatedData.productsMetadata,
612
+ technologyId,
613
+ );
694
614
  }
695
615
 
696
616
  // --- Prepare updates and fetch new related data if IDs change ---
697
617
 
698
618
  // Handle Practitioner Change
699
- if (
700
- validatedData.practitionerId &&
701
- validatedData.practitionerId !== oldPractitionerId
702
- ) {
619
+ if (validatedData.practitionerId && validatedData.practitionerId !== oldPractitionerId) {
703
620
  practitionerChanged = true;
704
621
  const newPractitionerRef = doc(
705
622
  this.db,
706
623
  PRACTITIONERS_COLLECTION,
707
- validatedData.practitionerId
624
+ validatedData.practitionerId,
708
625
  );
709
626
  const newPractitionerSnap = await getDoc(newPractitionerRef);
710
627
  if (!newPractitionerSnap.exists())
711
- throw new Error(
712
- `New Practitioner ${validatedData.practitionerId} not found`
713
- );
628
+ throw new Error(`New Practitioner ${validatedData.practitionerId} not found`);
714
629
  newPractitioner = newPractitionerSnap.data() as Practitioner;
715
630
  // Update doctorInfo within the procedure document
716
631
  updatedProcedureData.doctorInfo = {
717
632
  id: newPractitioner.id,
718
633
  name: `${newPractitioner.basicInfo.firstName} ${newPractitioner.basicInfo.lastName}`,
719
- description: newPractitioner.basicInfo.bio || "",
634
+ description: newPractitioner.basicInfo.bio || '',
720
635
  photo:
721
- typeof newPractitioner.basicInfo.profileImageUrl === "string"
636
+ typeof newPractitioner.basicInfo.profileImageUrl === 'string'
722
637
  ? newPractitioner.basicInfo.profileImageUrl
723
- : "", // Default to empty string if not a processed URL
638
+ : '', // Default to empty string if not a processed URL
724
639
  rating: newPractitioner.reviewInfo?.averageRating || 0,
725
640
  services: newPractitioner.procedures || [],
726
641
  };
727
642
  }
728
643
 
729
644
  // Handle Clinic Change
730
- if (
731
- validatedData.clinicBranchId &&
732
- validatedData.clinicBranchId !== oldClinicId
733
- ) {
645
+ if (validatedData.clinicBranchId && validatedData.clinicBranchId !== oldClinicId) {
734
646
  clinicChanged = true;
735
- const newClinicRef = doc(
736
- this.db,
737
- CLINICS_COLLECTION,
738
- validatedData.clinicBranchId
739
- );
647
+ const newClinicRef = doc(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId);
740
648
  const newClinicSnap = await getDoc(newClinicRef);
741
649
  if (!newClinicSnap.exists())
742
650
  throw new Error(`New Clinic ${validatedData.clinicBranchId} not found`);
@@ -745,15 +653,15 @@ export class ProcedureService extends BaseService {
745
653
  updatedProcedureData.clinicInfo = {
746
654
  id: newClinic.id,
747
655
  name: newClinic.name,
748
- description: newClinic.description || "",
656
+ description: newClinic.description || '',
749
657
  featuredPhoto:
750
658
  newClinic.featuredPhotos && newClinic.featuredPhotos.length > 0
751
- ? typeof newClinic.featuredPhotos[0] === "string"
659
+ ? typeof newClinic.featuredPhotos[0] === 'string'
752
660
  ? newClinic.featuredPhotos[0]
753
- : ""
754
- : typeof newClinic.coverPhoto === "string"
661
+ : ''
662
+ : typeof newClinic.coverPhoto === 'string'
755
663
  ? newClinic.coverPhoto
756
- : "",
664
+ : '',
757
665
  location: newClinic.location,
758
666
  contactInfo: newClinic.contactInfo,
759
667
  };
@@ -765,11 +673,8 @@ export class ProcedureService extends BaseService {
765
673
  updatedProcedureData.nameLower = validatedData.name.toLowerCase();
766
674
  }
767
675
  if (validatedData.categoryId) {
768
- const category = await this.categoryService.getById(
769
- validatedData.categoryId
770
- );
771
- if (!category)
772
- throw new Error(`Category ${validatedData.categoryId} not found`);
676
+ const category = await this.categoryService.getById(validatedData.categoryId);
677
+ if (!category) throw new Error(`Category ${validatedData.categoryId} not found`);
773
678
  updatedProcedureData.category = category;
774
679
  finalCategoryId = category.id; // Update finalCategoryId if category changed
775
680
  }
@@ -778,58 +683,45 @@ export class ProcedureService extends BaseService {
778
683
  if (validatedData.subcategoryId && finalCategoryId) {
779
684
  const subcategory = await this.subcategoryService.getById(
780
685
  finalCategoryId,
781
- validatedData.subcategoryId
686
+ validatedData.subcategoryId,
782
687
  );
783
688
  if (!subcategory)
784
689
  throw new Error(
785
- `Subcategory ${validatedData.subcategoryId} not found for category ${finalCategoryId}`
690
+ `Subcategory ${validatedData.subcategoryId} not found for category ${finalCategoryId}`,
786
691
  );
787
692
  updatedProcedureData.subcategory = subcategory;
788
693
  } else if (validatedData.subcategoryId) {
789
- console.warn(
790
- "Attempted to update subcategory without a valid categoryId"
791
- );
694
+ console.warn('Attempted to update subcategory without a valid categoryId');
792
695
  }
793
696
 
794
697
  let finalTechnologyId = existingProcedure.technology.id;
795
698
  if (validatedData.technologyId) {
796
- const technology = await this.technologyService.getById(
797
- validatedData.technologyId
798
- );
799
- if (!technology)
800
- throw new Error(`Technology ${validatedData.technologyId} not found`);
699
+ const technology = await this.technologyService.getById(validatedData.technologyId);
700
+ if (!technology) throw new Error(`Technology ${validatedData.technologyId} not found`);
801
701
  updatedProcedureData.technology = technology;
802
702
  finalTechnologyId = technology.id; // Update finalTechnologyId if technology changed
803
703
  // Update related fields derived from technology
804
704
  updatedProcedureData.blockingConditions = technology.blockingConditions;
805
- updatedProcedureData.contraindications =
806
- technology.contraindications || [];
807
- updatedProcedureData.contraindicationIds =
808
- technology.contraindications?.map((c) => c.id) || [];
705
+ updatedProcedureData.contraindications = technology.contraindications || [];
706
+ updatedProcedureData.contraindicationIds = technology.contraindications?.map(c => c.id) || [];
809
707
  updatedProcedureData.treatmentBenefits = technology.benefits;
810
- updatedProcedureData.treatmentBenefitIds =
811
- technology.benefits?.map((b) => b.id) || [];
708
+ updatedProcedureData.treatmentBenefitIds = technology.benefits?.map(b => b.id) || [];
812
709
  updatedProcedureData.preRequirements = technology.requirements.pre;
813
710
  updatedProcedureData.postRequirements = technology.requirements.post;
814
- updatedProcedureData.certificationRequirement =
815
- technology.certificationRequirement;
816
- updatedProcedureData.documentationTemplates =
817
- technology.documentationTemplates || [];
711
+ updatedProcedureData.certificationRequirement = technology.certificationRequirement;
712
+ updatedProcedureData.documentationTemplates = technology.documentationTemplates || [];
818
713
  }
819
714
 
820
715
  // Only fetch product if its ID is provided AND we have a valid finalTechnologyId
821
716
  if (validatedData.productId && finalTechnologyId) {
822
- const product = await this.productService.getById(
823
- finalTechnologyId,
824
- validatedData.productId
825
- );
717
+ const product = await this.productService.getById(finalTechnologyId, validatedData.productId);
826
718
  if (!product)
827
719
  throw new Error(
828
- `Product ${validatedData.productId} not found for technology ${finalTechnologyId}`
720
+ `Product ${validatedData.productId} not found for technology ${finalTechnologyId}`,
829
721
  );
830
722
  updatedProcedureData.product = product;
831
723
  } else if (validatedData.productId) {
832
- console.warn("Attempted to update product without a valid technologyId");
724
+ console.warn('Attempted to update product without a valid technologyId');
833
725
  }
834
726
 
835
727
  // Update the procedure document
@@ -913,7 +805,7 @@ export class ProcedureService extends BaseService {
913
805
  */
914
806
  async getAllProcedures(
915
807
  pagination?: number,
916
- lastDoc?: any
808
+ lastDoc?: any,
917
809
  ): Promise<{ procedures: Procedure[]; lastDoc: any }> {
918
810
  try {
919
811
  const proceduresCollection = collection(this.db, PROCEDURES_COLLECTION);
@@ -921,31 +813,26 @@ export class ProcedureService extends BaseService {
921
813
 
922
814
  // Apply pagination if specified
923
815
  if (pagination && pagination > 0) {
924
- const { limit, startAfter } = await import("firebase/firestore"); // Use dynamic import if needed top-level
816
+ const { limit, startAfter } = await import('firebase/firestore'); // Use dynamic import if needed top-level
925
817
 
926
818
  if (lastDoc) {
927
819
  proceduresQuery = query(
928
820
  proceduresCollection,
929
- orderBy("name"), // Use imported orderBy
821
+ orderBy('name'), // Use imported orderBy
930
822
  startAfter(lastDoc),
931
- limit(pagination)
823
+ limit(pagination),
932
824
  );
933
825
  } else {
934
- proceduresQuery = query(
935
- proceduresCollection,
936
- orderBy("name"),
937
- limit(pagination)
938
- ); // Use imported orderBy
826
+ proceduresQuery = query(proceduresCollection, orderBy('name'), limit(pagination)); // Use imported orderBy
939
827
  }
940
828
  } else {
941
- proceduresQuery = query(proceduresCollection, orderBy("name")); // Use imported orderBy
829
+ proceduresQuery = query(proceduresCollection, orderBy('name')); // Use imported orderBy
942
830
  }
943
831
 
944
832
  const proceduresSnapshot = await getDocs(proceduresQuery);
945
- const lastVisible =
946
- proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
833
+ const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
947
834
 
948
- const procedures = proceduresSnapshot.docs.map((doc) => {
835
+ const procedures = proceduresSnapshot.docs.map(doc => {
949
836
  const data = doc.data() as Procedure;
950
837
  return {
951
838
  ...data,
@@ -958,7 +845,7 @@ export class ProcedureService extends BaseService {
958
845
  lastDoc: lastVisible,
959
846
  };
960
847
  } catch (error) {
961
- console.error("[PROCEDURE_SERVICE] Error getting all procedures:", error);
848
+ console.error('[PROCEDURE_SERVICE] Error getting all procedures:', error);
962
849
  throw error;
963
850
  }
964
851
  }
@@ -1007,32 +894,26 @@ export class ProcedureService extends BaseService {
1007
894
  lastDoc: any;
1008
895
  }> {
1009
896
  try {
1010
- console.log(
1011
- "[PROCEDURE_SERVICE] Starting procedure filtering with multiple strategies"
1012
- );
897
+ console.log('[PROCEDURE_SERVICE] Starting procedure filtering with multiple strategies');
1013
898
 
1014
899
  // Geo query debug i validacija
1015
900
  if (filters.location && filters.radiusInKm) {
1016
- console.log("[PROCEDURE_SERVICE] Executing geo query:", {
901
+ console.log('[PROCEDURE_SERVICE] Executing geo query:', {
1017
902
  location: filters.location,
1018
903
  radius: filters.radiusInKm,
1019
- serviceName: "ProcedureService",
904
+ serviceName: 'ProcedureService',
1020
905
  });
1021
906
 
1022
907
  // Validacija location podataka
1023
908
  if (!filters.location.latitude || !filters.location.longitude) {
1024
- console.warn(
1025
- "[PROCEDURE_SERVICE] Invalid location data:",
1026
- filters.location
1027
- );
909
+ console.warn('[PROCEDURE_SERVICE] Invalid location data:', filters.location);
1028
910
  filters.location = undefined;
1029
911
  filters.radiusInKm = undefined;
1030
912
  }
1031
913
  }
1032
914
 
1033
915
  // Handle geo queries separately (they work differently)
1034
- const isGeoQuery =
1035
- filters.location && filters.radiusInKm && filters.radiusInKm > 0;
916
+ const isGeoQuery = filters.location && filters.radiusInKm && filters.radiusInKm > 0;
1036
917
  if (isGeoQuery) {
1037
918
  return this.handleGeoQuery(filters);
1038
919
  }
@@ -1043,55 +924,39 @@ export class ProcedureService extends BaseService {
1043
924
 
1044
925
  // Active status filter
1045
926
  if (filters.isActive !== undefined) {
1046
- constraints.push(where("isActive", "==", filters.isActive));
927
+ constraints.push(where('isActive', '==', filters.isActive));
1047
928
  } else {
1048
- constraints.push(where("isActive", "==", true));
929
+ constraints.push(where('isActive', '==', true));
1049
930
  }
1050
931
 
1051
932
  // Filter constraints
1052
933
  if (filters.procedureFamily) {
1053
- constraints.push(where("family", "==", filters.procedureFamily));
934
+ constraints.push(where('family', '==', filters.procedureFamily));
1054
935
  }
1055
936
  if (filters.procedureCategory) {
1056
- constraints.push(
1057
- where("category.id", "==", filters.procedureCategory)
1058
- );
937
+ constraints.push(where('category.id', '==', filters.procedureCategory));
1059
938
  }
1060
939
  if (filters.procedureSubcategory) {
1061
- constraints.push(
1062
- where("subcategory.id", "==", filters.procedureSubcategory)
1063
- );
940
+ constraints.push(where('subcategory.id', '==', filters.procedureSubcategory));
1064
941
  }
1065
942
  if (filters.procedureTechnology) {
1066
- constraints.push(
1067
- where("technology.id", "==", filters.procedureTechnology)
1068
- );
943
+ constraints.push(where('technology.id', '==', filters.procedureTechnology));
1069
944
  }
1070
945
  if (filters.minPrice !== undefined) {
1071
- constraints.push(where("price", ">=", filters.minPrice));
946
+ constraints.push(where('price', '>=', filters.minPrice));
1072
947
  }
1073
948
  if (filters.maxPrice !== undefined) {
1074
- constraints.push(where("price", "<=", filters.maxPrice));
949
+ constraints.push(where('price', '<=', filters.maxPrice));
1075
950
  }
1076
951
  if (filters.minRating !== undefined) {
1077
- constraints.push(
1078
- where("reviewInfo.averageRating", ">=", filters.minRating)
1079
- );
952
+ constraints.push(where('reviewInfo.averageRating', '>=', filters.minRating));
1080
953
  }
1081
954
  if (filters.maxRating !== undefined) {
1082
- constraints.push(
1083
- where("reviewInfo.averageRating", "<=", filters.maxRating)
1084
- );
955
+ constraints.push(where('reviewInfo.averageRating', '<=', filters.maxRating));
1085
956
  }
1086
957
  if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
1087
958
  const benefitIdsToMatch = filters.treatmentBenefits;
1088
- constraints.push(
1089
- where(
1090
- "treatmentBenefitIds",
1091
- "array-contains-any",
1092
- benefitIdsToMatch
1093
- )
1094
- );
959
+ constraints.push(where('treatmentBenefitIds', 'array-contains-any', benefitIdsToMatch));
1095
960
  }
1096
961
 
1097
962
  return constraints;
@@ -1100,17 +965,15 @@ export class ProcedureService extends BaseService {
1100
965
  // Strategy 1: Try nameLower search if nameSearch exists
1101
966
  if (filters.nameSearch && filters.nameSearch.trim()) {
1102
967
  try {
1103
- console.log(
1104
- "[PROCEDURE_SERVICE] Strategy 1: Trying nameLower search"
1105
- );
968
+ console.log('[PROCEDURE_SERVICE] Strategy 1: Trying nameLower search');
1106
969
  const searchTerm = filters.nameSearch.trim().toLowerCase();
1107
970
  const constraints = getBaseConstraints();
1108
- constraints.push(where("nameLower", ">=", searchTerm));
1109
- constraints.push(where("nameLower", "<=", searchTerm + "\uf8ff"));
1110
- constraints.push(orderBy("nameLower"));
971
+ constraints.push(where('nameLower', '>=', searchTerm));
972
+ constraints.push(where('nameLower', '<=', searchTerm + '\uf8ff'));
973
+ constraints.push(orderBy('nameLower'));
1111
974
 
1112
975
  if (filters.lastDoc) {
1113
- if (typeof filters.lastDoc.data === "function") {
976
+ if (typeof filters.lastDoc.data === 'function') {
1114
977
  constraints.push(startAfter(filters.lastDoc));
1115
978
  } else if (Array.isArray(filters.lastDoc)) {
1116
979
  constraints.push(startAfter(...filters.lastDoc));
@@ -1120,22 +983,17 @@ export class ProcedureService extends BaseService {
1120
983
  }
1121
984
  constraints.push(limit(filters.pagination || 10));
1122
985
 
1123
- const q = query(
1124
- collection(this.db, PROCEDURES_COLLECTION),
1125
- ...constraints
1126
- );
986
+ const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1127
987
  const querySnapshot = await getDocs(q);
1128
988
  const procedures = querySnapshot.docs.map(
1129
- (doc) => ({ ...doc.data(), id: doc.id } as Procedure)
989
+ doc => ({ ...doc.data(), id: doc.id } as Procedure),
1130
990
  );
1131
991
  const lastDoc =
1132
992
  querySnapshot.docs.length > 0
1133
993
  ? querySnapshot.docs[querySnapshot.docs.length - 1]
1134
994
  : null;
1135
995
 
1136
- console.log(
1137
- `[PROCEDURE_SERVICE] Strategy 1 success: ${procedures.length} procedures`
1138
- );
996
+ console.log(`[PROCEDURE_SERVICE] Strategy 1 success: ${procedures.length} procedures`);
1139
997
 
1140
998
  // Fix Load More - ako je broj rezultata manji od pagination, nema više
1141
999
  if (procedures.length < (filters.pagination || 10)) {
@@ -1143,24 +1001,22 @@ export class ProcedureService extends BaseService {
1143
1001
  }
1144
1002
  return { procedures, lastDoc };
1145
1003
  } catch (error) {
1146
- console.log("[PROCEDURE_SERVICE] Strategy 1 failed:", error);
1004
+ console.log('[PROCEDURE_SERVICE] Strategy 1 failed:', error);
1147
1005
  }
1148
1006
  }
1149
1007
 
1150
1008
  // Strategy 2: Try name field search as fallback
1151
1009
  if (filters.nameSearch && filters.nameSearch.trim()) {
1152
1010
  try {
1153
- console.log(
1154
- "[PROCEDURE_SERVICE] Strategy 2: Trying name field search"
1155
- );
1011
+ console.log('[PROCEDURE_SERVICE] Strategy 2: Trying name field search');
1156
1012
  const searchTerm = filters.nameSearch.trim().toLowerCase();
1157
1013
  const constraints = getBaseConstraints();
1158
- constraints.push(where("name", ">=", searchTerm));
1159
- constraints.push(where("name", "<=", searchTerm + "\uf8ff"));
1160
- constraints.push(orderBy("name"));
1014
+ constraints.push(where('name', '>=', searchTerm));
1015
+ constraints.push(where('name', '<=', searchTerm + '\uf8ff'));
1016
+ constraints.push(orderBy('name'));
1161
1017
 
1162
1018
  if (filters.lastDoc) {
1163
- if (typeof filters.lastDoc.data === "function") {
1019
+ if (typeof filters.lastDoc.data === 'function') {
1164
1020
  constraints.push(startAfter(filters.lastDoc));
1165
1021
  } else if (Array.isArray(filters.lastDoc)) {
1166
1022
  constraints.push(startAfter(...filters.lastDoc));
@@ -1170,22 +1026,17 @@ export class ProcedureService extends BaseService {
1170
1026
  }
1171
1027
  constraints.push(limit(filters.pagination || 10));
1172
1028
 
1173
- const q = query(
1174
- collection(this.db, PROCEDURES_COLLECTION),
1175
- ...constraints
1176
- );
1029
+ const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1177
1030
  const querySnapshot = await getDocs(q);
1178
1031
  const procedures = querySnapshot.docs.map(
1179
- (doc) => ({ ...doc.data(), id: doc.id } as Procedure)
1032
+ doc => ({ ...doc.data(), id: doc.id } as Procedure),
1180
1033
  );
1181
1034
  const lastDoc =
1182
1035
  querySnapshot.docs.length > 0
1183
1036
  ? querySnapshot.docs[querySnapshot.docs.length - 1]
1184
1037
  : null;
1185
1038
 
1186
- console.log(
1187
- `[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`
1188
- );
1039
+ console.log(`[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`);
1189
1040
 
1190
1041
  // Fix Load More - ako je broj rezultata manji od pagination, nema više
1191
1042
  if (procedures.length < (filters.pagination || 10)) {
@@ -1193,20 +1044,20 @@ export class ProcedureService extends BaseService {
1193
1044
  }
1194
1045
  return { procedures, lastDoc };
1195
1046
  } catch (error) {
1196
- console.log("[PROCEDURE_SERVICE] Strategy 2 failed:", error);
1047
+ console.log('[PROCEDURE_SERVICE] Strategy 2 failed:', error);
1197
1048
  }
1198
1049
  }
1199
1050
 
1200
1051
  // Strategy 3: orderBy createdAt with client-side filtering
1201
1052
  try {
1202
1053
  console.log(
1203
- "[PROCEDURE_SERVICE] Strategy 3: Using createdAt orderBy with client-side filtering"
1054
+ '[PROCEDURE_SERVICE] Strategy 3: Using createdAt orderBy with client-side filtering',
1204
1055
  );
1205
1056
  const constraints = getBaseConstraints();
1206
- constraints.push(orderBy("createdAt", "desc"));
1057
+ constraints.push(orderBy('createdAt', 'desc'));
1207
1058
 
1208
1059
  if (filters.lastDoc) {
1209
- if (typeof filters.lastDoc.data === "function") {
1060
+ if (typeof filters.lastDoc.data === 'function') {
1210
1061
  constraints.push(startAfter(filters.lastDoc));
1211
1062
  } else if (Array.isArray(filters.lastDoc)) {
1212
1063
  constraints.push(startAfter(...filters.lastDoc));
@@ -1216,25 +1067,18 @@ export class ProcedureService extends BaseService {
1216
1067
  }
1217
1068
  constraints.push(limit(filters.pagination || 10));
1218
1069
 
1219
- const q = query(
1220
- collection(this.db, PROCEDURES_COLLECTION),
1221
- ...constraints
1222
- );
1070
+ const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1223
1071
  const querySnapshot = await getDocs(q);
1224
1072
  let procedures = querySnapshot.docs.map(
1225
- (doc) => ({ ...doc.data(), id: doc.id } as Procedure)
1073
+ doc => ({ ...doc.data(), id: doc.id } as Procedure),
1226
1074
  );
1227
1075
 
1228
1076
  // Apply all client-side filters using centralized function
1229
1077
  procedures = this.applyInMemoryFilters(procedures, filters);
1230
1078
 
1231
1079
  const lastDoc =
1232
- querySnapshot.docs.length > 0
1233
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
1234
- : null;
1235
- console.log(
1236
- `[PROCEDURE_SERVICE] Strategy 3 success: ${procedures.length} procedures`
1237
- );
1080
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1081
+ console.log(`[PROCEDURE_SERVICE] Strategy 3 success: ${procedures.length} procedures`);
1238
1082
 
1239
1083
  // Fix Load More - ako je broj rezultata manji od pagination, nema više
1240
1084
  if (procedures.length < (filters.pagination || 10)) {
@@ -1242,37 +1086,30 @@ export class ProcedureService extends BaseService {
1242
1086
  }
1243
1087
  return { procedures, lastDoc };
1244
1088
  } catch (error) {
1245
- console.log("[PROCEDURE_SERVICE] Strategy 3 failed:", error);
1089
+ console.log('[PROCEDURE_SERVICE] Strategy 3 failed:', error);
1246
1090
  }
1247
1091
 
1248
1092
  // Strategy 4: Minimal query fallback
1249
1093
  try {
1250
- console.log("[PROCEDURE_SERVICE] Strategy 4: Minimal query fallback");
1094
+ console.log('[PROCEDURE_SERVICE] Strategy 4: Minimal query fallback');
1251
1095
  const constraints: QueryConstraint[] = [
1252
- where("isActive", "==", true),
1253
- orderBy("createdAt", "desc"),
1096
+ where('isActive', '==', true),
1097
+ orderBy('createdAt', 'desc'),
1254
1098
  limit(filters.pagination || 10),
1255
1099
  ];
1256
1100
 
1257
- const q = query(
1258
- collection(this.db, PROCEDURES_COLLECTION),
1259
- ...constraints
1260
- );
1101
+ const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1261
1102
  const querySnapshot = await getDocs(q);
1262
1103
  let procedures = querySnapshot.docs.map(
1263
- (doc) => ({ ...doc.data(), id: doc.id } as Procedure)
1104
+ doc => ({ ...doc.data(), id: doc.id } as Procedure),
1264
1105
  );
1265
1106
 
1266
1107
  // Apply all client-side filters using centralized function
1267
1108
  procedures = this.applyInMemoryFilters(procedures, filters);
1268
1109
 
1269
1110
  const lastDoc =
1270
- querySnapshot.docs.length > 0
1271
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
1272
- : null;
1273
- console.log(
1274
- `[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`
1275
- );
1111
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1112
+ console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
1276
1113
 
1277
1114
  // Fix Load More - ako je broj rezultata manji od pagination, nema više
1278
1115
  if (procedures.length < (filters.pagination || 10)) {
@@ -1280,16 +1117,14 @@ export class ProcedureService extends BaseService {
1280
1117
  }
1281
1118
  return { procedures, lastDoc };
1282
1119
  } catch (error) {
1283
- console.log("[PROCEDURE_SERVICE] Strategy 4 failed:", error);
1120
+ console.log('[PROCEDURE_SERVICE] Strategy 4 failed:', error);
1284
1121
  }
1285
1122
 
1286
1123
  // All strategies failed
1287
- console.log(
1288
- "[PROCEDURE_SERVICE] All strategies failed, returning empty result"
1289
- );
1124
+ console.log('[PROCEDURE_SERVICE] All strategies failed, returning empty result');
1290
1125
  return { procedures: [], lastDoc: null };
1291
1126
  } catch (error) {
1292
- console.error("[PROCEDURE_SERVICE] Error filtering procedures:", error);
1127
+ console.error('[PROCEDURE_SERVICE] Error filtering procedures:', error);
1293
1128
  return { procedures: [], lastDoc: null };
1294
1129
  }
1295
1130
  }
@@ -1300,105 +1135,98 @@ export class ProcedureService extends BaseService {
1300
1135
  */
1301
1136
  private applyInMemoryFilters(
1302
1137
  procedures: Procedure[],
1303
- filters: any
1138
+ filters: any,
1304
1139
  ): (Procedure & { distance?: number })[] {
1305
1140
  let filteredProcedures = [...procedures]; // Create copy to avoid mutating original
1306
1141
 
1307
1142
  // Name search filter
1308
1143
  if (filters.nameSearch && filters.nameSearch.trim()) {
1309
1144
  const searchTerm = filters.nameSearch.trim().toLowerCase();
1310
- filteredProcedures = filteredProcedures.filter((procedure) => {
1311
- const name = (procedure.name || "").toLowerCase();
1312
- const nameLower = procedure.nameLower || "";
1145
+ filteredProcedures = filteredProcedures.filter(procedure => {
1146
+ const name = (procedure.name || '').toLowerCase();
1147
+ const nameLower = procedure.nameLower || '';
1313
1148
  return name.includes(searchTerm) || nameLower.includes(searchTerm);
1314
1149
  });
1315
- console.log(
1316
- `[PROCEDURE_SERVICE] Applied name filter, results: ${filteredProcedures.length}`
1317
- );
1150
+ console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${filteredProcedures.length}`);
1318
1151
  }
1319
1152
 
1320
1153
  // Price filtering
1321
1154
  if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
1322
- filteredProcedures = filteredProcedures.filter((procedure) => {
1155
+ filteredProcedures = filteredProcedures.filter(procedure => {
1323
1156
  const price = procedure.price || 0;
1324
- if (filters.minPrice !== undefined && price < filters.minPrice)
1325
- return false;
1326
- if (filters.maxPrice !== undefined && price > filters.maxPrice)
1327
- return false;
1157
+ if (filters.minPrice !== undefined && price < filters.minPrice) return false;
1158
+ if (filters.maxPrice !== undefined && price > filters.maxPrice) return false;
1328
1159
  return true;
1329
1160
  });
1330
1161
  console.log(
1331
- `[PROCEDURE_SERVICE] Applied price filter (${filters.minPrice}-${filters.maxPrice}), results: ${filteredProcedures.length}`
1162
+ `[PROCEDURE_SERVICE] Applied price filter (${filters.minPrice}-${filters.maxPrice}), results: ${filteredProcedures.length}`,
1332
1163
  );
1333
1164
  }
1334
1165
 
1335
1166
  // Rating filtering
1336
1167
  if (filters.minRating !== undefined || filters.maxRating !== undefined) {
1337
- filteredProcedures = filteredProcedures.filter((procedure) => {
1168
+ filteredProcedures = filteredProcedures.filter(procedure => {
1338
1169
  const rating = procedure.reviewInfo?.averageRating || 0;
1339
- if (filters.minRating !== undefined && rating < filters.minRating)
1340
- return false;
1341
- if (filters.maxRating !== undefined && rating > filters.maxRating)
1342
- return false;
1170
+ if (filters.minRating !== undefined && rating < filters.minRating) return false;
1171
+ if (filters.maxRating !== undefined && rating > filters.maxRating) return false;
1343
1172
  return true;
1344
1173
  });
1345
1174
  console.log(
1346
- `[PROCEDURE_SERVICE] Applied rating filter, results: ${filteredProcedures.length}`
1175
+ `[PROCEDURE_SERVICE] Applied rating filter, results: ${filteredProcedures.length}`,
1347
1176
  );
1348
1177
  }
1349
1178
 
1350
1179
  // Treatment benefits filtering
1351
1180
  if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
1352
1181
  const benefitIdsToMatch = filters.treatmentBenefits;
1353
- filteredProcedures = filteredProcedures.filter((procedure) => {
1182
+ filteredProcedures = filteredProcedures.filter(procedure => {
1354
1183
  const procedureBenefitIds = procedure.treatmentBenefitIds || [];
1355
1184
  return benefitIdsToMatch.some((benefitId: string) =>
1356
- procedureBenefitIds.includes(benefitId)
1185
+ procedureBenefitIds.includes(benefitId),
1357
1186
  );
1358
1187
  });
1359
1188
  console.log(
1360
- `[PROCEDURE_SERVICE] Applied benefits filter, results: ${filteredProcedures.length}`
1189
+ `[PROCEDURE_SERVICE] Applied benefits filter, results: ${filteredProcedures.length}`,
1361
1190
  );
1362
1191
  }
1363
1192
 
1364
1193
  // Procedure family filtering
1365
1194
  if (filters.procedureFamily) {
1366
1195
  filteredProcedures = filteredProcedures.filter(
1367
- (procedure) => procedure.family === filters.procedureFamily
1196
+ procedure => procedure.family === filters.procedureFamily,
1368
1197
  );
1369
1198
  console.log(
1370
- `[PROCEDURE_SERVICE] Applied family filter, results: ${filteredProcedures.length}`
1199
+ `[PROCEDURE_SERVICE] Applied family filter, results: ${filteredProcedures.length}`,
1371
1200
  );
1372
1201
  }
1373
1202
 
1374
1203
  // Category filtering
1375
1204
  if (filters.procedureCategory) {
1376
1205
  filteredProcedures = filteredProcedures.filter(
1377
- (procedure) => procedure.category?.id === filters.procedureCategory
1206
+ procedure => procedure.category?.id === filters.procedureCategory,
1378
1207
  );
1379
1208
  console.log(
1380
- `[PROCEDURE_SERVICE] Applied category filter, results: ${filteredProcedures.length}`
1209
+ `[PROCEDURE_SERVICE] Applied category filter, results: ${filteredProcedures.length}`,
1381
1210
  );
1382
1211
  }
1383
1212
 
1384
1213
  // Subcategory filtering
1385
1214
  if (filters.procedureSubcategory) {
1386
1215
  filteredProcedures = filteredProcedures.filter(
1387
- (procedure) =>
1388
- procedure.subcategory?.id === filters.procedureSubcategory
1216
+ procedure => procedure.subcategory?.id === filters.procedureSubcategory,
1389
1217
  );
1390
1218
  console.log(
1391
- `[PROCEDURE_SERVICE] Applied subcategory filter, results: ${filteredProcedures.length}`
1219
+ `[PROCEDURE_SERVICE] Applied subcategory filter, results: ${filteredProcedures.length}`,
1392
1220
  );
1393
1221
  }
1394
1222
 
1395
1223
  // Technology filtering
1396
1224
  if (filters.procedureTechnology) {
1397
1225
  filteredProcedures = filteredProcedures.filter(
1398
- (procedure) => procedure.technology?.id === filters.procedureTechnology
1226
+ procedure => procedure.technology?.id === filters.procedureTechnology,
1399
1227
  );
1400
1228
  console.log(
1401
- `[PROCEDURE_SERVICE] Applied technology filter, results: ${filteredProcedures.length}`
1229
+ `[PROCEDURE_SERVICE] Applied technology filter, results: ${filteredProcedures.length}`,
1402
1230
  );
1403
1231
  }
1404
1232
 
@@ -1406,7 +1234,7 @@ export class ProcedureService extends BaseService {
1406
1234
  if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
1407
1235
  const location = filters.location;
1408
1236
  const radiusInKm = filters.radiusInKm;
1409
- filteredProcedures = filteredProcedures.filter((procedure) => {
1237
+ filteredProcedures = filteredProcedures.filter(procedure => {
1410
1238
  const clinicLocation = procedure.clinicInfo?.location;
1411
1239
  if (!clinicLocation?.latitude || !clinicLocation?.longitude) {
1412
1240
  return false;
@@ -1415,7 +1243,7 @@ export class ProcedureService extends BaseService {
1415
1243
  const distance =
1416
1244
  distanceBetween(
1417
1245
  [location.latitude, location.longitude],
1418
- [clinicLocation.latitude, clinicLocation.longitude]
1246
+ [clinicLocation.latitude, clinicLocation.longitude],
1419
1247
  ) / 1000; // Convert to km
1420
1248
 
1421
1249
  // Attach distance for frontend sorting/display
@@ -1423,14 +1251,10 @@ export class ProcedureService extends BaseService {
1423
1251
 
1424
1252
  return distance <= radiusInKm;
1425
1253
  });
1426
- console.log(
1427
- `[PROCEDURE_SERVICE] Applied geo filter, results: ${filteredProcedures.length}`
1428
- );
1254
+ console.log(`[PROCEDURE_SERVICE] Applied geo filter, results: ${filteredProcedures.length}`);
1429
1255
 
1430
1256
  // Sort by distance when geo filtering is applied
1431
- filteredProcedures.sort(
1432
- (a, b) => ((a as any).distance || 0) - ((b as any).distance || 0)
1433
- );
1257
+ filteredProcedures.sort((a, b) => ((a as any).distance || 0) - ((b as any).distance || 0));
1434
1258
  }
1435
1259
 
1436
1260
  return filteredProcedures as (Procedure & { distance?: number })[];
@@ -1440,7 +1264,7 @@ export class ProcedureService extends BaseService {
1440
1264
  procedures: (Procedure & { distance?: number })[];
1441
1265
  lastDoc: any;
1442
1266
  }> {
1443
- console.log("[PROCEDURE_SERVICE] Executing geo query with geohash bounds");
1267
+ console.log('[PROCEDURE_SERVICE] Executing geo query with geohash bounds');
1444
1268
  try {
1445
1269
  const location = filters.location;
1446
1270
  const radiusInKm = filters.radiusInKm;
@@ -1449,33 +1273,22 @@ export class ProcedureService extends BaseService {
1449
1273
  return Promise.resolve({ procedures: [], lastDoc: null });
1450
1274
  }
1451
1275
 
1452
- const bounds = geohashQueryBounds(
1453
- [location.latitude, location.longitude],
1454
- radiusInKm * 1000
1455
- );
1276
+ const bounds = geohashQueryBounds([location.latitude, location.longitude], radiusInKm * 1000);
1456
1277
 
1457
- const fetches = bounds.map((b) => {
1278
+ const fetches = bounds.map(b => {
1458
1279
  const constraints: QueryConstraint[] = [
1459
- where("clinicInfo.location.geohash", ">=", b[0]),
1460
- where("clinicInfo.location.geohash", "<=", b[1]),
1461
- where(
1462
- "isActive",
1463
- "==",
1464
- filters.isActive !== undefined ? filters.isActive : true
1465
- ),
1280
+ where('clinicInfo.location.geohash', '>=', b[0]),
1281
+ where('clinicInfo.location.geohash', '<=', b[1]),
1282
+ where('isActive', '==', filters.isActive !== undefined ? filters.isActive : true),
1466
1283
  ];
1467
- return getDocs(
1468
- query(collection(this.db, PROCEDURES_COLLECTION), ...constraints)
1469
- );
1284
+ return getDocs(query(collection(this.db, PROCEDURES_COLLECTION), ...constraints));
1470
1285
  });
1471
1286
 
1472
1287
  return Promise.all(fetches)
1473
- .then((snaps) => {
1288
+ .then(snaps => {
1474
1289
  const collected: Procedure[] = [];
1475
- snaps.forEach((snap) => {
1476
- snap.docs.forEach((d) =>
1477
- collected.push({ ...(d.data() as Procedure), id: d.id })
1478
- );
1290
+ snaps.forEach(snap => {
1291
+ snap.docs.forEach(d => collected.push({ ...(d.data() as Procedure), id: d.id }));
1479
1292
  });
1480
1293
 
1481
1294
  // Deduplicate by id
@@ -1493,29 +1306,26 @@ export class ProcedureService extends BaseService {
1493
1306
  let startIndex = 0;
1494
1307
  if (
1495
1308
  filters.lastDoc &&
1496
- typeof filters.lastDoc === "object" &&
1309
+ typeof filters.lastDoc === 'object' &&
1497
1310
  (filters.lastDoc as any).id
1498
1311
  ) {
1499
- const idx = procedures.findIndex(
1500
- (p) => p.id === (filters.lastDoc as any).id
1501
- );
1312
+ const idx = procedures.findIndex(p => p.id === (filters.lastDoc as any).id);
1502
1313
  if (idx >= 0) startIndex = idx + 1;
1503
1314
  }
1504
1315
  const page = procedures.slice(startIndex, startIndex + pageSize);
1505
- const newLastDoc =
1506
- page.length === pageSize ? page[page.length - 1] : null;
1316
+ const newLastDoc = page.length === pageSize ? page[page.length - 1] : null;
1507
1317
 
1508
1318
  console.log(
1509
- `[PROCEDURE_SERVICE] Geo query success: ${page.length} (of ${procedures.length}) within ${radiusInKm}km`
1319
+ `[PROCEDURE_SERVICE] Geo query success: ${page.length} (of ${procedures.length}) within ${radiusInKm}km`,
1510
1320
  );
1511
1321
  return { procedures: page, lastDoc: newLastDoc };
1512
1322
  })
1513
- .catch((err) => {
1514
- console.error("[PROCEDURE_SERVICE] Geo bounds fetch failed:", err);
1323
+ .catch(err => {
1324
+ console.error('[PROCEDURE_SERVICE] Geo bounds fetch failed:', err);
1515
1325
  return { procedures: [], lastDoc: null };
1516
1326
  });
1517
1327
  } catch (error) {
1518
- console.error("[PROCEDURE_SERVICE] Geo query failed:", error);
1328
+ console.error('[PROCEDURE_SERVICE] Geo query failed:', error);
1519
1329
  return Promise.resolve({ procedures: [], lastDoc: null });
1520
1330
  }
1521
1331
  }
@@ -1527,7 +1337,7 @@ export class ProcedureService extends BaseService {
1527
1337
  * @returns The created procedure
1528
1338
  */
1529
1339
  async createConsultationProcedure(
1530
- data: Omit<CreateProcedureData, "productId">
1340
+ data: Omit<CreateProcedureData, 'productId'>,
1531
1341
  ): Promise<Procedure> {
1532
1342
  // Generate procedure ID first so we can use it for media uploads
1533
1343
  const procedureId = this.generateId();
@@ -1541,7 +1351,7 @@ export class ProcedureService extends BaseService {
1541
1351
  ]);
1542
1352
 
1543
1353
  if (!category || !subcategory || !technology) {
1544
- throw new Error("One or more required base entities not found");
1354
+ throw new Error('One or more required base entities not found');
1545
1355
  }
1546
1356
 
1547
1357
  // Get clinic and practitioner information for aggregation
@@ -1552,11 +1362,7 @@ export class ProcedureService extends BaseService {
1552
1362
  }
1553
1363
  const clinic = clinicSnapshot.data() as Clinic;
1554
1364
 
1555
- const practitionerRef = doc(
1556
- this.db,
1557
- PRACTITIONERS_COLLECTION,
1558
- data.practitionerId
1559
- );
1365
+ const practitionerRef = doc(this.db, PRACTITIONERS_COLLECTION, data.practitionerId);
1560
1366
  const practitionerSnapshot = await getDoc(practitionerRef);
1561
1367
  if (!practitionerSnapshot.exists()) {
1562
1368
  throw new Error(`Practitioner with ID ${data.practitionerId} not found`);
@@ -1566,32 +1372,28 @@ export class ProcedureService extends BaseService {
1566
1372
  // Process photos if provided
1567
1373
  let processedPhotos: string[] = [];
1568
1374
  if (data.photos && data.photos.length > 0) {
1569
- processedPhotos = await this.processMediaArray(
1570
- data.photos,
1571
- procedureId,
1572
- "procedure-photos"
1573
- );
1375
+ processedPhotos = await this.processMediaArray(data.photos, procedureId, 'procedure-photos');
1574
1376
  }
1575
1377
 
1576
1378
  // Transform productsMetadata from validation format to ProcedureProduct format
1577
1379
  const transformedProductsMetadata = await this.transformProductsMetadata(
1578
1380
  data.productsMetadata,
1579
- data.technologyId
1381
+ data.technologyId,
1580
1382
  );
1581
1383
 
1582
1384
  // Create aggregated clinic info for the procedure document
1583
1385
  const clinicInfo = {
1584
1386
  id: clinicSnapshot.id,
1585
1387
  name: clinic.name,
1586
- description: clinic.description || "",
1388
+ description: clinic.description || '',
1587
1389
  featuredPhoto:
1588
1390
  clinic.featuredPhotos && clinic.featuredPhotos.length > 0
1589
- ? typeof clinic.featuredPhotos[0] === "string"
1391
+ ? typeof clinic.featuredPhotos[0] === 'string'
1590
1392
  ? clinic.featuredPhotos[0]
1591
- : ""
1592
- : typeof clinic.coverPhoto === "string"
1393
+ : ''
1394
+ : typeof clinic.coverPhoto === 'string'
1593
1395
  ? clinic.coverPhoto
1594
- : "",
1396
+ : '',
1595
1397
  location: clinic.location,
1596
1398
  contactInfo: clinic.contactInfo,
1597
1399
  };
@@ -1600,22 +1402,22 @@ export class ProcedureService extends BaseService {
1600
1402
  const doctorInfo = {
1601
1403
  id: practitionerSnapshot.id,
1602
1404
  name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
1603
- description: practitioner.basicInfo.bio || "",
1405
+ description: practitioner.basicInfo.bio || '',
1604
1406
  photo:
1605
- typeof practitioner.basicInfo.profileImageUrl === "string"
1407
+ typeof practitioner.basicInfo.profileImageUrl === 'string'
1606
1408
  ? practitioner.basicInfo.profileImageUrl
1607
- : "",
1409
+ : '',
1608
1410
  rating: practitioner.reviewInfo?.averageRating || 0,
1609
1411
  services: practitioner.procedures || [],
1610
1412
  };
1611
1413
 
1612
1414
  // Create a placeholder product for consultation procedures
1613
1415
  const consultationProduct: Product = {
1614
- id: "consultation-no-product",
1615
- name: "No Product Required",
1616
- description: "Consultation procedures do not require specific products",
1617
- brandId: "consultation-brand",
1618
- brandName: "Consultation",
1416
+ id: 'consultation-no-product',
1417
+ name: 'No Product Required',
1418
+ description: 'Consultation procedures do not require specific products',
1419
+ brandId: 'consultation-brand',
1420
+ brandName: 'Consultation',
1619
1421
  technologyId: data.technologyId,
1620
1422
  technologyName: technology.name,
1621
1423
  categoryId: technology.categoryId,
@@ -1626,21 +1428,22 @@ export class ProcedureService extends BaseService {
1626
1428
  };
1627
1429
 
1628
1430
  // Create the procedure object
1629
- const newProcedure: Omit<Procedure, "createdAt" | "updatedAt"> = {
1431
+ const { productsMetadata: _, ...dataWithoutProductsMetadata } = data;
1432
+ const newProcedure: Omit<Procedure, 'createdAt' | 'updatedAt'> = {
1630
1433
  id: procedureId,
1631
- ...data,
1434
+ ...dataWithoutProductsMetadata,
1632
1435
  nameLower: (data as any).nameLower || data.name.toLowerCase(),
1633
1436
  photos: processedPhotos,
1634
1437
  category,
1635
1438
  subcategory,
1636
1439
  technology,
1637
1440
  product: consultationProduct, // Use placeholder product
1638
- productsMetadata: transformedProductsMetadata,
1441
+ productsMetadata: transformedProductsMetadata, // Use transformed data, not original
1639
1442
  blockingConditions: technology.blockingConditions,
1640
1443
  contraindications: technology.contraindications || [],
1641
- contraindicationIds: technology.contraindications?.map((c) => c.id) || [],
1444
+ contraindicationIds: technology.contraindications?.map(c => c.id) || [],
1642
1445
  treatmentBenefits: technology.benefits,
1643
- treatmentBenefitIds: technology.benefits?.map((b) => b.id) || [],
1446
+ treatmentBenefitIds: technology.benefits?.map(b => b.id) || [],
1644
1447
  preRequirements: technology.requirements.pre,
1645
1448
  postRequirements: technology.requirements.post,
1646
1449
  certificationRequirement: technology.certificationRequirement,
@@ -1691,14 +1494,14 @@ export class ProcedureService extends BaseService {
1691
1494
  > {
1692
1495
  const proceduresRef = collection(this.db, PROCEDURES_COLLECTION);
1693
1496
  const snapshot = await getDocs(proceduresRef);
1694
- const proceduresForMap = snapshot.docs.map((doc) => {
1497
+ const proceduresForMap = snapshot.docs.map(doc => {
1695
1498
  const data = doc.data();
1696
1499
  return {
1697
1500
  id: doc.id,
1698
1501
  name: data.name,
1699
1502
  clinicId: data.clinicInfo?.id,
1700
1503
  clinicName: data.clinicInfo?.name,
1701
- address: data.clinicInfo?.location?.address || "",
1504
+ address: data.clinicInfo?.location?.address || '',
1702
1505
  latitude: data.clinicInfo?.location?.latitude,
1703
1506
  longitude: data.clinicInfo?.location?.longitude,
1704
1507
  };