@blackcode_sa/metaestetics-api 1.12.0 → 1.12.2

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