@blackcode_sa/metaestetics-api 1.12.67 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/admin/index.d.mts +801 -2
  2. package/dist/admin/index.d.ts +801 -2
  3. package/dist/admin/index.js +2332 -153
  4. package/dist/admin/index.mjs +2321 -153
  5. package/dist/backoffice/index.d.mts +40 -0
  6. package/dist/backoffice/index.d.ts +40 -0
  7. package/dist/backoffice/index.js +118 -18
  8. package/dist/backoffice/index.mjs +118 -20
  9. package/dist/index.d.mts +1097 -2
  10. package/dist/index.d.ts +1097 -2
  11. package/dist/index.js +4224 -2091
  12. package/dist/index.mjs +3941 -1821
  13. package/package.json +1 -1
  14. package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +140 -0
  15. package/src/admin/analytics/analytics.admin.service.ts +278 -0
  16. package/src/admin/analytics/index.ts +2 -0
  17. package/src/admin/index.ts +6 -0
  18. package/src/backoffice/services/README.md +17 -0
  19. package/src/backoffice/services/analytics.service.proposal.md +863 -0
  20. package/src/backoffice/services/analytics.service.summary.md +143 -0
  21. package/src/backoffice/services/category.service.ts +49 -6
  22. package/src/backoffice/services/subcategory.service.ts +50 -6
  23. package/src/backoffice/services/technology.service.ts +53 -6
  24. package/src/services/analytics/ARCHITECTURE.md +199 -0
  25. package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -0
  26. package/src/services/analytics/GROUPED_ANALYTICS.md +501 -0
  27. package/src/services/analytics/QUICK_START.md +393 -0
  28. package/src/services/analytics/README.md +287 -0
  29. package/src/services/analytics/SUMMARY.md +141 -0
  30. package/src/services/analytics/USAGE_GUIDE.md +518 -0
  31. package/src/services/analytics/analytics-cloud.service.ts +222 -0
  32. package/src/services/analytics/analytics.service.ts +1632 -0
  33. package/src/services/analytics/index.ts +3 -0
  34. package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -0
  35. package/src/services/analytics/utils/cost-calculation.utils.ts +154 -0
  36. package/src/services/analytics/utils/grouping.utils.ts +394 -0
  37. package/src/services/analytics/utils/stored-analytics.utils.ts +347 -0
  38. package/src/services/analytics/utils/time-calculation.utils.ts +186 -0
  39. package/src/services/appointment/appointment.service.ts +50 -6
  40. package/src/services/index.ts +1 -0
  41. package/src/services/procedure/procedure.service.ts +3 -3
  42. package/src/types/analytics/analytics.types.ts +500 -0
  43. package/src/types/analytics/grouped-analytics.types.ts +148 -0
  44. package/src/types/analytics/index.ts +4 -0
  45. package/src/types/analytics/stored-analytics.types.ts +137 -0
  46. package/src/types/index.ts +3 -0
  47. package/src/types/notifications/index.ts +21 -0
@@ -243,7 +243,6 @@ import {
243
243
  addDoc as addDoc2,
244
244
  collection as collection2,
245
245
  doc as doc2,
246
- getCountFromServer as getCountFromServer2,
247
246
  getDoc as getDoc2,
248
247
  getDocs as getDocs2,
249
248
  limit as limit2,
@@ -265,7 +264,16 @@ var ProcedureFamily = /* @__PURE__ */ ((ProcedureFamily2) => {
265
264
  })(ProcedureFamily || {});
266
265
 
267
266
  // src/backoffice/services/category.service.ts
267
+ var EXCLUDED_CATEGORY_ID = "consultation";
268
268
  var CategoryService = class extends BaseService {
269
+ /**
270
+ * Filters out excluded categories from a list.
271
+ * @param categories - List of categories to filter
272
+ * @returns Filtered list without excluded categories
273
+ */
274
+ filterExcludedCategories(categories) {
275
+ return categories.filter((cat) => cat.id !== EXCLUDED_CATEGORY_ID);
276
+ }
269
277
  /**
270
278
  * Referenca na Firestore kolekciju kategorija
271
279
  */
@@ -302,8 +310,9 @@ var CategoryService = class extends BaseService {
302
310
  where2("family", "==", family),
303
311
  where2("isActive", "==", active)
304
312
  );
305
- const snapshot = await getCountFromServer2(q);
306
- counts[family] = snapshot.data().count;
313
+ const snapshot = await getDocs2(q);
314
+ const filteredDocs = snapshot.docs.filter((doc11) => doc11.id !== EXCLUDED_CATEGORY_ID);
315
+ counts[family] = filteredDocs.length;
307
316
  }
308
317
  return counts;
309
318
  }
@@ -314,12 +323,13 @@ var CategoryService = class extends BaseService {
314
323
  async getAllForFilter() {
315
324
  const q = query2(this.categoriesRef, where2("isActive", "==", true));
316
325
  const snapshot = await getDocs2(q);
317
- return snapshot.docs.map(
326
+ const categories = snapshot.docs.map(
318
327
  (doc11) => ({
319
328
  id: doc11.id,
320
329
  ...doc11.data()
321
330
  })
322
331
  );
332
+ return this.filterExcludedCategories(categories);
323
333
  }
324
334
  /**
325
335
  * Vraća sve kategorije za određenu familiju za potrebe filtera (bez paginacije)
@@ -334,12 +344,13 @@ var CategoryService = class extends BaseService {
334
344
  orderBy2("name")
335
345
  );
336
346
  const snapshot = await getDocs2(q);
337
- return snapshot.docs.map(
347
+ const categories = snapshot.docs.map(
338
348
  (doc11) => ({
339
349
  id: doc11.id,
340
350
  ...doc11.data()
341
351
  })
342
352
  );
353
+ return this.filterExcludedCategories(categories);
343
354
  }
344
355
  /**
345
356
  * Vraća sve kategorije sa paginacijom
@@ -362,8 +373,9 @@ var CategoryService = class extends BaseService {
362
373
  ...doc11.data()
363
374
  })
364
375
  );
376
+ const filteredCategories = this.filterExcludedCategories(categories);
365
377
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
366
- return { categories, lastVisible: newLastVisible };
378
+ return { categories: filteredCategories, lastVisible: newLastVisible };
367
379
  }
368
380
  /**
369
381
  * Vraća sve aktivne kategorije za određenu familiju procedura sa paginacijom
@@ -388,8 +400,9 @@ var CategoryService = class extends BaseService {
388
400
  ...doc11.data()
389
401
  })
390
402
  );
403
+ const filteredCategories = this.filterExcludedCategories(categories);
391
404
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
392
- return { categories, lastVisible: newLastVisible };
405
+ return { categories: filteredCategories, lastVisible: newLastVisible };
393
406
  }
394
407
  /**
395
408
  * Ažurira postojeću kategoriju
@@ -426,6 +439,22 @@ var CategoryService = class extends BaseService {
426
439
  * @returns Kategorija ili null ako ne postoji
427
440
  */
428
441
  async getById(id) {
442
+ if (id === EXCLUDED_CATEGORY_ID) return null;
443
+ const docRef = doc2(this.categoriesRef, id);
444
+ const docSnap = await getDoc2(docRef);
445
+ if (!docSnap.exists()) return null;
446
+ return {
447
+ id: docSnap.id,
448
+ ...docSnap.data()
449
+ };
450
+ }
451
+ /**
452
+ * Internal method to get category by ID without filtering.
453
+ * Used internally for consultation procedures.
454
+ * @param id - ID of the category to get
455
+ * @returns Category or null if not found
456
+ */
457
+ async getByIdInternal(id) {
429
458
  const docRef = doc2(this.categoriesRef, id);
430
459
  const docSnap = await getDoc2(docRef);
431
460
  if (!docSnap.exists()) return null;
@@ -451,6 +480,7 @@ var CategoryService = class extends BaseService {
451
480
  const snapshot = await getDocs2(q);
452
481
  if (snapshot.empty) return null;
453
482
  const doc11 = snapshot.docs[0];
483
+ if (doc11.id === EXCLUDED_CATEGORY_ID) return null;
454
484
  return {
455
485
  id: doc11.id,
456
486
  ...doc11.data()
@@ -488,6 +518,7 @@ var CategoryService = class extends BaseService {
488
518
  const snapshot = await getDocs2(q);
489
519
  if (snapshot.empty) break;
490
520
  for (const d of snapshot.docs) {
521
+ if (d.id === EXCLUDED_CATEGORY_ID) continue;
491
522
  const category = { id: d.id, ...d.data() };
492
523
  rows.push(this.categoryToCsvRow(category));
493
524
  }
@@ -2236,7 +2267,6 @@ import {
2236
2267
  collectionGroup as collectionGroup2,
2237
2268
  deleteDoc as deleteDoc3,
2238
2269
  doc as doc8,
2239
- getCountFromServer as getCountFromServer5,
2240
2270
  getDoc as getDoc8,
2241
2271
  getDocs as getDocs8,
2242
2272
  limit as limit7,
@@ -2252,7 +2282,16 @@ import {
2252
2282
  var SUBCATEGORIES_COLLECTION = "subcategories";
2253
2283
 
2254
2284
  // src/backoffice/services/subcategory.service.ts
2285
+ var EXCLUDED_SUBCATEGORY_ID = "free-consultation";
2255
2286
  var SubcategoryService = class extends BaseService {
2287
+ /**
2288
+ * Filters out excluded subcategories from a list.
2289
+ * @param subcategories - List of subcategories to filter
2290
+ * @returns Filtered list without excluded subcategories
2291
+ */
2292
+ filterExcludedSubcategories(subcategories) {
2293
+ return subcategories.filter((sub) => sub.id !== EXCLUDED_SUBCATEGORY_ID);
2294
+ }
2256
2295
  /**
2257
2296
  * Vraća referencu na Firestore kolekciju podkategorija za određenu kategoriju
2258
2297
  * @param categoryId - ID roditeljske kategorije
@@ -2299,8 +2338,9 @@ var SubcategoryService = class extends BaseService {
2299
2338
  const categoryId = categoryDoc.id;
2300
2339
  const subcategoriesRef = this.getSubcategoriesRef(categoryId);
2301
2340
  const q = query8(subcategoriesRef, where8("isActive", "==", active));
2302
- const snapshot = await getCountFromServer5(q);
2303
- counts[categoryId] = snapshot.data().count;
2341
+ const snapshot = await getDocs8(q);
2342
+ const filteredDocs = snapshot.docs.filter((doc11) => doc11.id !== EXCLUDED_SUBCATEGORY_ID);
2343
+ counts[categoryId] = filteredDocs.length;
2304
2344
  }
2305
2345
  return counts;
2306
2346
  }
@@ -2326,8 +2366,9 @@ var SubcategoryService = class extends BaseService {
2326
2366
  ...doc11.data()
2327
2367
  })
2328
2368
  );
2369
+ const filteredSubcategories = this.filterExcludedSubcategories(subcategories);
2329
2370
  const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
2330
- return { subcategories, lastVisible: newLastVisible };
2371
+ return { subcategories: filteredSubcategories, lastVisible: newLastVisible };
2331
2372
  }
2332
2373
  /**
2333
2374
  * Vraća sve podkategorije sa paginacijom koristeći collection group query.
@@ -2356,8 +2397,9 @@ var SubcategoryService = class extends BaseService {
2356
2397
  ...doc11.data()
2357
2398
  })
2358
2399
  );
2400
+ const filteredSubcategories = this.filterExcludedSubcategories(subcategories);
2359
2401
  const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
2360
- return { subcategories, lastVisible: newLastVisible };
2402
+ return { subcategories: filteredSubcategories, lastVisible: newLastVisible };
2361
2403
  }
2362
2404
  /**
2363
2405
  * Vraća sve subkategorije za određenu kategoriju za potrebe filtera (bez paginacije)
@@ -2370,12 +2412,13 @@ var SubcategoryService = class extends BaseService {
2370
2412
  where8("isActive", "==", true)
2371
2413
  );
2372
2414
  const querySnapshot = await getDocs8(q);
2373
- return querySnapshot.docs.map(
2415
+ const subcategories = querySnapshot.docs.map(
2374
2416
  (doc11) => ({
2375
2417
  id: doc11.id,
2376
2418
  ...doc11.data()
2377
2419
  })
2378
2420
  );
2421
+ return this.filterExcludedSubcategories(subcategories);
2379
2422
  }
2380
2423
  /**
2381
2424
  * Vraća sve subkategorije za potrebe filtera (bez paginacije)
@@ -2387,12 +2430,13 @@ var SubcategoryService = class extends BaseService {
2387
2430
  where8("isActive", "==", true)
2388
2431
  );
2389
2432
  const querySnapshot = await getDocs8(q);
2390
- return querySnapshot.docs.map(
2433
+ const subcategories = querySnapshot.docs.map(
2391
2434
  (doc11) => ({
2392
2435
  id: doc11.id,
2393
2436
  ...doc11.data()
2394
2437
  })
2395
2438
  );
2439
+ return this.filterExcludedSubcategories(subcategories);
2396
2440
  }
2397
2441
  /**
2398
2442
  * Ažurira postojeću podkategoriju
@@ -2462,6 +2506,23 @@ var SubcategoryService = class extends BaseService {
2462
2506
  * @returns Podkategorija ili null ako ne postoji
2463
2507
  */
2464
2508
  async getById(categoryId, subcategoryId) {
2509
+ if (subcategoryId === EXCLUDED_SUBCATEGORY_ID) return null;
2510
+ const docRef = doc8(this.getSubcategoriesRef(categoryId), subcategoryId);
2511
+ const docSnap = await getDoc8(docRef);
2512
+ if (!docSnap.exists()) return null;
2513
+ return {
2514
+ id: docSnap.id,
2515
+ ...docSnap.data()
2516
+ };
2517
+ }
2518
+ /**
2519
+ * Internal method to get subcategory by ID without filtering.
2520
+ * Used internally for consultation procedures.
2521
+ * @param categoryId - ID of the category
2522
+ * @param subcategoryId - ID of the subcategory to get
2523
+ * @returns Subcategory or null if not found
2524
+ */
2525
+ async getByIdInternal(categoryId, subcategoryId) {
2465
2526
  const docRef = doc8(this.getSubcategoriesRef(categoryId), subcategoryId);
2466
2527
  const docSnap = await getDoc8(docRef);
2467
2528
  if (!docSnap.exists()) return null;
@@ -2486,6 +2547,7 @@ var SubcategoryService = class extends BaseService {
2486
2547
  const querySnapshot = await getDocs8(q);
2487
2548
  if (querySnapshot.empty) return null;
2488
2549
  const doc11 = querySnapshot.docs[0];
2550
+ if (doc11.id === EXCLUDED_SUBCATEGORY_ID) return null;
2489
2551
  return {
2490
2552
  id: doc11.id,
2491
2553
  ...doc11.data()
@@ -2526,6 +2588,7 @@ var SubcategoryService = class extends BaseService {
2526
2588
  const snapshot = await getDocs8(q);
2527
2589
  if (snapshot.empty) break;
2528
2590
  for (const d of snapshot.docs) {
2591
+ if (d.id === EXCLUDED_SUBCATEGORY_ID) continue;
2529
2592
  const subcategory = { id: d.id, ...d.data() };
2530
2593
  rows.push(this.subcategoryToCsvRow(subcategory));
2531
2594
  }
@@ -2604,11 +2667,20 @@ var CertificationSpecialty = /* @__PURE__ */ ((CertificationSpecialty3) => {
2604
2667
  })(CertificationSpecialty || {});
2605
2668
 
2606
2669
  // src/backoffice/services/technology.service.ts
2670
+ var EXCLUDED_TECHNOLOGY_ID = "free-consultation-tech";
2607
2671
  var DEFAULT_CERTIFICATION_REQUIREMENT = {
2608
2672
  minimumLevel: "aesthetician" /* AESTHETICIAN */,
2609
2673
  requiredSpecialties: []
2610
2674
  };
2611
2675
  var TechnologyService = class extends BaseService {
2676
+ /**
2677
+ * Filters out excluded technologies from a list.
2678
+ * @param technologies - List of technologies to filter
2679
+ * @returns Filtered list without excluded technologies
2680
+ */
2681
+ filterExcludedTechnologies(technologies) {
2682
+ return technologies.filter((tech) => tech.id !== EXCLUDED_TECHNOLOGY_ID);
2683
+ }
2612
2684
  /**
2613
2685
  * Reference to the Firestore collection of technologies.
2614
2686
  */
@@ -2657,6 +2729,7 @@ var TechnologyService = class extends BaseService {
2657
2729
  const snapshot = await getDocs9(q);
2658
2730
  const counts = {};
2659
2731
  snapshot.docs.forEach((doc11) => {
2732
+ if (doc11.id === EXCLUDED_TECHNOLOGY_ID) return;
2660
2733
  const tech = doc11.data();
2661
2734
  counts[tech.subcategoryId] = (counts[tech.subcategoryId] || 0) + 1;
2662
2735
  });
@@ -2672,6 +2745,7 @@ var TechnologyService = class extends BaseService {
2672
2745
  const snapshot = await getDocs9(q);
2673
2746
  const counts = {};
2674
2747
  snapshot.docs.forEach((doc11) => {
2748
+ if (doc11.id === EXCLUDED_TECHNOLOGY_ID) return;
2675
2749
  const tech = doc11.data();
2676
2750
  counts[tech.categoryId] = (counts[tech.categoryId] || 0) + 1;
2677
2751
  });
@@ -2698,8 +2772,9 @@ var TechnologyService = class extends BaseService {
2698
2772
  ...doc11.data()
2699
2773
  })
2700
2774
  );
2775
+ const filteredTechnologies = this.filterExcludedTechnologies(technologies);
2701
2776
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
2702
- return { technologies, lastVisible: newLastVisible };
2777
+ return { technologies: filteredTechnologies, lastVisible: newLastVisible };
2703
2778
  }
2704
2779
  /**
2705
2780
  * Returns all technologies for a specific category with pagination.
@@ -2724,8 +2799,9 @@ var TechnologyService = class extends BaseService {
2724
2799
  ...doc11.data()
2725
2800
  })
2726
2801
  );
2802
+ const filteredTechnologies = this.filterExcludedTechnologies(technologies);
2727
2803
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
2728
- return { technologies, lastVisible: newLastVisible };
2804
+ return { technologies: filteredTechnologies, lastVisible: newLastVisible };
2729
2805
  }
2730
2806
  /**
2731
2807
  * Returns all technologies for a specific subcategory with pagination.
@@ -2750,8 +2826,9 @@ var TechnologyService = class extends BaseService {
2750
2826
  ...doc11.data()
2751
2827
  })
2752
2828
  );
2829
+ const filteredTechnologies = this.filterExcludedTechnologies(technologies);
2753
2830
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
2754
- return { technologies, lastVisible: newLastVisible };
2831
+ return { technologies: filteredTechnologies, lastVisible: newLastVisible };
2755
2832
  }
2756
2833
  /**
2757
2834
  * Updates an existing technology.
@@ -2809,6 +2886,22 @@ var TechnologyService = class extends BaseService {
2809
2886
  * @returns The technology or null if it doesn't exist.
2810
2887
  */
2811
2888
  async getById(id) {
2889
+ if (id === EXCLUDED_TECHNOLOGY_ID) return null;
2890
+ const docRef = doc9(this.technologiesRef, id);
2891
+ const docSnap = await getDoc9(docRef);
2892
+ if (!docSnap.exists()) return null;
2893
+ return {
2894
+ id: docSnap.id,
2895
+ ...docSnap.data()
2896
+ };
2897
+ }
2898
+ /**
2899
+ * Internal method to get technology by ID without filtering.
2900
+ * Used internally for consultation procedures.
2901
+ * @param id - The ID of the requested technology
2902
+ * @returns The technology or null if it doesn't exist
2903
+ */
2904
+ async getByIdInternal(id) {
2812
2905
  const docRef = doc9(this.technologiesRef, id);
2813
2906
  const docSnap = await getDoc9(docRef);
2814
2907
  if (!docSnap.exists()) return null;
@@ -2832,6 +2925,7 @@ var TechnologyService = class extends BaseService {
2832
2925
  const snapshot = await getDocs9(q);
2833
2926
  if (snapshot.empty) return null;
2834
2927
  const doc11 = snapshot.docs[0];
2928
+ if (doc11.id === EXCLUDED_TECHNOLOGY_ID) return null;
2835
2929
  return {
2836
2930
  id: doc11.id,
2837
2931
  ...doc11.data()
@@ -3197,12 +3291,13 @@ var TechnologyService = class extends BaseService {
3197
3291
  orderBy9("name")
3198
3292
  );
3199
3293
  const snapshot = await getDocs9(q);
3200
- return snapshot.docs.map(
3294
+ const technologies = snapshot.docs.map(
3201
3295
  (doc11) => ({
3202
3296
  id: doc11.id,
3203
3297
  ...doc11.data()
3204
3298
  })
3205
3299
  );
3300
+ return this.filterExcludedTechnologies(technologies);
3206
3301
  }
3207
3302
  /**
3208
3303
  * Gets all active technologies for a subcategory for filter dropdowns.
@@ -3218,12 +3313,13 @@ var TechnologyService = class extends BaseService {
3218
3313
  orderBy9("name")
3219
3314
  );
3220
3315
  const snapshot = await getDocs9(q);
3221
- return snapshot.docs.map(
3316
+ const technologies = snapshot.docs.map(
3222
3317
  (doc11) => ({
3223
3318
  id: doc11.id,
3224
3319
  ...doc11.data()
3225
3320
  })
3226
3321
  );
3322
+ return this.filterExcludedTechnologies(technologies);
3227
3323
  }
3228
3324
  /**
3229
3325
  * Gets all active technologies for filter dropdowns.
@@ -3235,12 +3331,13 @@ var TechnologyService = class extends BaseService {
3235
3331
  orderBy9("name")
3236
3332
  );
3237
3333
  const snapshot = await getDocs9(q);
3238
- return snapshot.docs.map(
3334
+ const technologies = snapshot.docs.map(
3239
3335
  (doc11) => ({
3240
3336
  id: doc11.id,
3241
3337
  ...doc11.data()
3242
3338
  })
3243
3339
  );
3340
+ return this.filterExcludedTechnologies(technologies);
3244
3341
  }
3245
3342
  // ==========================================
3246
3343
  // NEW METHODS: Product assignment management
@@ -3406,6 +3503,7 @@ var TechnologyService = class extends BaseService {
3406
3503
  const snapshot = await getDocs9(q);
3407
3504
  if (snapshot.empty) break;
3408
3505
  for (const d of snapshot.docs) {
3506
+ if (d.id === EXCLUDED_TECHNOLOGY_ID) continue;
3409
3507
  const technology = { id: d.id, ...d.data() };
3410
3508
  const productNames = await this.getProductNamesForTechnology(technology.id);
3411
3509
  rows.push(this.technologyToCsvRow(technology, productNames));