@blackcode_sa/metaestetics-api 1.12.36 → 1.12.39

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.12.36",
4
+ "version": "1.12.39",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -1549,15 +1549,25 @@ export class AppointmentAggregationService {
1549
1549
  return true;
1550
1550
  }
1551
1551
 
1552
- // Compare before and after photos and notes
1553
- if (
1554
- beforeZonePhotos.before !== afterZonePhotos.before ||
1555
- beforeZonePhotos.after !== afterZonePhotos.after ||
1556
- beforeZonePhotos.beforeNote !== afterZonePhotos.beforeNote ||
1557
- beforeZonePhotos.afterNote !== afterZonePhotos.afterNote
1558
- ) {
1552
+ // Compare before and after photos arrays
1553
+ // If array lengths differ or any entry differs, consider it changed
1554
+ if (beforeZonePhotos.length !== afterZonePhotos.length) {
1559
1555
  return true;
1560
1556
  }
1557
+
1558
+ // Compare each entry in the arrays
1559
+ for (let i = 0; i < beforeZonePhotos.length; i++) {
1560
+ const beforeEntry = beforeZonePhotos[i];
1561
+ const afterEntry = afterZonePhotos[i];
1562
+ if (
1563
+ beforeEntry.before !== afterEntry.before ||
1564
+ beforeEntry.after !== afterEntry.after ||
1565
+ beforeEntry.beforeNote !== afterEntry.beforeNote ||
1566
+ beforeEntry.afterNote !== afterEntry.afterNote
1567
+ ) {
1568
+ return true;
1569
+ }
1570
+ }
1561
1571
  }
1562
1572
 
1563
1573
  return false;
@@ -1580,27 +1590,48 @@ export class AppointmentAggregationService {
1580
1590
  const newPhotoTypes: { zoneId: string; photoType: 'before' | 'after' }[] = [];
1581
1591
 
1582
1592
  for (const zoneId of Object.keys(afterPhotos)) {
1583
- const beforeZonePhotos = beforePhotos[zoneId];
1584
- const afterZonePhotos = afterPhotos[zoneId];
1593
+ const beforeZonePhotos = beforePhotos[zoneId] || [];
1594
+ const afterZonePhotos = afterPhotos[zoneId] || [];
1585
1595
 
1586
- if (!beforeZonePhotos) {
1596
+ if (beforeZonePhotos.length === 0 && afterZonePhotos.length > 0) {
1587
1597
  // New zone with photos
1588
1598
  updatedZones.push(zoneId);
1589
- if (afterZonePhotos.before) {
1590
- newPhotoTypes.push({ zoneId, photoType: 'before' });
1591
- }
1592
- if (afterZonePhotos.after) {
1593
- newPhotoTypes.push({ zoneId, photoType: 'after' });
1594
- }
1599
+ afterZonePhotos.forEach(entry => {
1600
+ if (entry.before) {
1601
+ newPhotoTypes.push({ zoneId, photoType: 'before' });
1602
+ }
1603
+ if (entry.after) {
1604
+ newPhotoTypes.push({ zoneId, photoType: 'after' });
1605
+ }
1606
+ });
1607
+ } else if (afterZonePhotos.length > beforeZonePhotos.length) {
1608
+ // New photos added to existing zone
1609
+ updatedZones.push(zoneId);
1610
+ const newEntries = afterZonePhotos.slice(beforeZonePhotos.length);
1611
+ newEntries.forEach(entry => {
1612
+ if (entry.before) {
1613
+ newPhotoTypes.push({ zoneId, photoType: 'before' });
1614
+ }
1615
+ if (entry.after) {
1616
+ newPhotoTypes.push({ zoneId, photoType: 'after' });
1617
+ }
1618
+ });
1595
1619
  } else {
1596
- // Check for new or updated photos
1597
- if (beforeZonePhotos.before !== afterZonePhotos.before && afterZonePhotos.before) {
1598
- updatedZones.push(zoneId);
1599
- newPhotoTypes.push({ zoneId, photoType: 'before' });
1600
- }
1601
- if (beforeZonePhotos.after !== afterZonePhotos.after && afterZonePhotos.after) {
1602
- updatedZones.push(zoneId);
1603
- newPhotoTypes.push({ zoneId, photoType: 'after' });
1620
+ // Check for updated photos in existing entries
1621
+ for (let i = 0; i < afterZonePhotos.length; i++) {
1622
+ const beforeEntry = beforeZonePhotos[i];
1623
+ const afterEntry = afterZonePhotos[i];
1624
+
1625
+ if (beforeEntry && afterEntry) {
1626
+ if (beforeEntry.before !== afterEntry.before && afterEntry.before) {
1627
+ updatedZones.push(zoneId);
1628
+ newPhotoTypes.push({ zoneId, photoType: 'before' });
1629
+ }
1630
+ if (beforeEntry.after !== afterEntry.after && afterEntry.after) {
1631
+ updatedZones.push(zoneId);
1632
+ newPhotoTypes.push({ zoneId, photoType: 'after' });
1633
+ }
1634
+ }
1604
1635
  }
1605
1636
  }
1606
1637
  }
@@ -1629,7 +1660,7 @@ export class AppointmentAggregationService {
1629
1660
  if (selectedZones.length > 0) {
1630
1661
  const completedZones = selectedZones.filter(zoneId => {
1631
1662
  const zonePhotos = afterPhotos[zoneId];
1632
- return zonePhotos && (zonePhotos.before || zonePhotos.after);
1663
+ return zonePhotos && zonePhotos.length > 0 && zonePhotos.some(entry => entry.before || entry.after);
1633
1664
  });
1634
1665
 
1635
1666
  const completionPercentage = (completedZones.length / selectedZones.length) * 100;
@@ -893,6 +893,7 @@ export class BookingAdmin {
893
893
  zonesData: null,
894
894
  appointmentProducts: appointmentProducts,
895
895
  extendedProcedures: [],
896
+ recommendedProcedures: [],
896
897
  finalbilling: null,
897
898
  finalizationNotes: null,
898
899
  },
@@ -33,6 +33,7 @@ import {
33
33
  ZoneItemData,
34
34
  ExtendedProcedureInfo,
35
35
  AppointmentProductMetadata,
36
+ RecommendedProcedure,
36
37
  APPOINTMENTS_COLLECTION,
37
38
  } from '../../types/appointment';
38
39
  import {
@@ -48,7 +49,7 @@ import { PatientService } from '../patient/patient.service';
48
49
  import { PractitionerService } from '../practitioner/practitioner.service';
49
50
  import { ClinicService } from '../clinic/clinic.service';
50
51
  import { FilledDocumentService } from '../documentation-templates/filled-document.service';
51
- import { MediaService, MediaAccessLevel, MediaMetadata } from '../media/media.service';
52
+ import { MediaService, MediaAccessLevel, MediaMetadata, MediaResource } from '../media/media.service';
52
53
 
53
54
  // Import utility functions
54
55
  import {
@@ -70,6 +71,18 @@ import {
70
71
  getExtendedProceduresUtil,
71
72
  getAppointmentProductsUtil,
72
73
  } from './utils/extended-procedure.utils';
74
+ import {
75
+ addRecommendedProcedureUtil,
76
+ removeRecommendedProcedureUtil,
77
+ updateRecommendedProcedureUtil,
78
+ getRecommendedProceduresUtil,
79
+ } from './utils/recommended-procedure.utils';
80
+ import {
81
+ updateZonePhotoEntryUtil,
82
+ addAfterPhotoToEntryUtil,
83
+ updateZonePhotoNotesUtil,
84
+ getZonePhotoEntryUtil,
85
+ } from './utils/zone-photo.utils';
73
86
 
74
87
  /**
75
88
  * Interface for available booking slot
@@ -1274,35 +1287,34 @@ export class AppointmentService extends BaseService {
1274
1287
  zonesData: null,
1275
1288
  appointmentProducts: [],
1276
1289
  extendedProcedures: [],
1290
+ recommendedProcedures: [],
1277
1291
  zoneBilling: null,
1278
1292
  finalbilling: null,
1279
1293
  finalizationNotes: null,
1280
1294
  };
1281
1295
 
1282
- // Initialize zonePhotos if it doesn't exist
1283
- const currentZonePhotos = currentMetadata.zonePhotos || {};
1296
+ // Initialize zonePhotos if it doesn't exist (array model per zone)
1297
+ const currentZonePhotos: Record<string, BeforeAfterPerZone[]> =
1298
+ (currentMetadata.zonePhotos as Record<string, BeforeAfterPerZone[]>) || {};
1284
1299
 
1285
- // Initialize the zone entry if it doesn't exist
1300
+ // Initialize the zone array if it doesn't exist
1286
1301
  if (!currentZonePhotos[zoneId]) {
1287
- currentZonePhotos[zoneId] = {
1288
- before: null,
1289
- after: null,
1290
- beforeNote: null,
1291
- afterNote: null,
1292
- };
1302
+ currentZonePhotos[zoneId] = [];
1293
1303
  }
1294
1304
 
1295
- // Update the specific photo type
1296
- if (photoType === 'before') {
1297
- currentZonePhotos[zoneId].before = mediaMetadata.url;
1298
- if (notes) {
1299
- currentZonePhotos[zoneId].beforeNote = notes;
1300
- }
1301
- } else {
1302
- currentZonePhotos[zoneId].after = mediaMetadata.url;
1303
- if (notes) {
1304
- currentZonePhotos[zoneId].afterNote = notes;
1305
- }
1305
+ // Create a new entry for this uploaded photo with per-photo notes
1306
+ const newEntry: BeforeAfterPerZone = {
1307
+ before: photoType === 'before' ? mediaMetadata.url : null,
1308
+ after: photoType === 'after' ? mediaMetadata.url : null,
1309
+ beforeNote: photoType === 'before' ? (notes || null) : null,
1310
+ afterNote: photoType === 'after' ? (notes || null) : null,
1311
+ };
1312
+
1313
+ // Append to the zone's photo list
1314
+ currentZonePhotos[zoneId] = [...currentZonePhotos[zoneId], newEntry];
1315
+ // Enforce max 10 photos per zone by keeping the most recent 10
1316
+ if (currentZonePhotos[zoneId].length > 10) {
1317
+ currentZonePhotos[zoneId] = currentZonePhotos[zoneId].slice(-10);
1306
1318
  }
1307
1319
 
1308
1320
  // Update the appointment with new metadata
@@ -1313,6 +1325,7 @@ export class AppointmentService extends BaseService {
1313
1325
  zonesData: currentMetadata.zonesData || null,
1314
1326
  appointmentProducts: currentMetadata.appointmentProducts || [],
1315
1327
  extendedProcedures: currentMetadata.extendedProcedures || [],
1328
+ recommendedProcedures: currentMetadata.recommendedProcedures || [],
1316
1329
  // Only include zoneBilling if it exists (avoid undefined values in Firestore)
1317
1330
  ...(currentMetadata.zoneBilling !== undefined && {
1318
1331
  zoneBilling: currentMetadata.zoneBilling,
@@ -1349,7 +1362,7 @@ export class AppointmentService extends BaseService {
1349
1362
  async getZonePhotos(
1350
1363
  appointmentId: string,
1351
1364
  zoneId?: string,
1352
- ): Promise<Record<string, BeforeAfterPerZone> | BeforeAfterPerZone | null> {
1365
+ ): Promise<Record<string, BeforeAfterPerZone[]> | BeforeAfterPerZone[] | null> {
1353
1366
  try {
1354
1367
  console.log(`[APPOINTMENT_SERVICE] Getting zone photos for appointment ${appointmentId}`);
1355
1368
 
@@ -1358,7 +1371,10 @@ export class AppointmentService extends BaseService {
1358
1371
  throw new Error(`Appointment with ID ${appointmentId} not found`);
1359
1372
  }
1360
1373
 
1361
- const zonePhotos = appointment.metadata?.zonePhotos;
1374
+ const zonePhotos = appointment.metadata?.zonePhotos as
1375
+ | Record<string, BeforeAfterPerZone[]>
1376
+ | undefined
1377
+ | null;
1362
1378
  if (!zonePhotos) {
1363
1379
  return null;
1364
1380
  }
@@ -1377,21 +1393,21 @@ export class AppointmentService extends BaseService {
1377
1393
  }
1378
1394
 
1379
1395
  /**
1380
- * Deletes a zone photo and updates appointment metadata
1396
+ * Deletes a zone photo entry (by index) and updates appointment metadata
1381
1397
  *
1382
1398
  * @param appointmentId ID of the appointment
1383
1399
  * @param zoneId ID of the zone
1384
- * @param photoType Type of photo to delete ('before' or 'after')
1400
+ * @param photoIndex Index of the photo entry to delete in the zone array
1385
1401
  * @returns The updated appointment
1386
1402
  */
1387
1403
  async deleteZonePhoto(
1388
1404
  appointmentId: string,
1389
1405
  zoneId: string,
1390
- photoType: 'before' | 'after',
1406
+ photoIndex: number,
1391
1407
  ): Promise<Appointment> {
1392
1408
  try {
1393
1409
  console.log(
1394
- `[APPOINTMENT_SERVICE] Deleting ${photoType} photo for zone ${zoneId} in appointment ${appointmentId}`,
1410
+ `[APPOINTMENT_SERVICE] Deleting zone photo index ${photoIndex} for zone ${zoneId} in appointment ${appointmentId}`,
1395
1411
  );
1396
1412
 
1397
1413
  // Get current appointment
@@ -1400,15 +1416,23 @@ export class AppointmentService extends BaseService {
1400
1416
  throw new Error(`Appointment with ID ${appointmentId} not found`);
1401
1417
  }
1402
1418
 
1403
- const zonePhotos = appointment.metadata?.zonePhotos;
1404
- if (!zonePhotos || !zonePhotos[zoneId]) {
1419
+ const zonePhotos = appointment.metadata?.zonePhotos as
1420
+ | Record<string, BeforeAfterPerZone[]>
1421
+ | undefined
1422
+ | null;
1423
+ if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
1405
1424
  throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
1406
1425
  }
1407
1426
 
1408
- const photoUrl =
1409
- photoType === 'before' ? zonePhotos[zoneId].before : zonePhotos[zoneId].after;
1427
+ const zoneArray = [...zonePhotos[zoneId]];
1428
+ if (photoIndex < 0 || photoIndex >= zoneArray.length) {
1429
+ throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}`);
1430
+ }
1431
+
1432
+ const entry = zoneArray[photoIndex];
1433
+ const photoUrl = (entry.before || entry.after) as MediaResource | null;
1410
1434
  if (!photoUrl) {
1411
- throw new Error(`No ${photoType} photo found for zone ${zoneId}`);
1435
+ throw new Error(`No photo URL found for index ${photoIndex} in zone ${zoneId}`);
1412
1436
  }
1413
1437
 
1414
1438
  // Try to find and delete the media from storage
@@ -1429,19 +1453,14 @@ export class AppointmentService extends BaseService {
1429
1453
  // Continue with metadata update even if media deletion fails
1430
1454
  }
1431
1455
 
1432
- // Update appointment metadata to remove the photo reference
1433
- const updatedZonePhotos = { ...zonePhotos };
1434
- if (photoType === 'before') {
1435
- updatedZonePhotos[zoneId].before = null;
1436
- updatedZonePhotos[zoneId].beforeNote = null;
1437
- } else {
1438
- updatedZonePhotos[zoneId].after = null;
1439
- updatedZonePhotos[zoneId].afterNote = null;
1440
- }
1441
-
1442
- // If both photos are null, we could optionally remove the zone entry entirely
1443
- if (!updatedZonePhotos[zoneId].before && !updatedZonePhotos[zoneId].after) {
1456
+ // Update appointment metadata to remove the photo entry at the specified index
1457
+ const updatedZonePhotos: Record<string, BeforeAfterPerZone[]> = { ...zonePhotos } as any;
1458
+ const updatedZoneArray = [...zoneArray];
1459
+ updatedZoneArray.splice(photoIndex, 1);
1460
+ if (updatedZoneArray.length === 0) {
1444
1461
  delete updatedZonePhotos[zoneId];
1462
+ } else {
1463
+ updatedZonePhotos[zoneId] = updatedZoneArray;
1445
1464
  }
1446
1465
 
1447
1466
  const updateData: UpdateAppointmentData = {
@@ -1451,6 +1470,7 @@ export class AppointmentService extends BaseService {
1451
1470
  zonesData: appointment.metadata?.zonesData || null,
1452
1471
  appointmentProducts: appointment.metadata?.appointmentProducts || [],
1453
1472
  extendedProcedures: appointment.metadata?.extendedProcedures || [],
1473
+ recommendedProcedures: appointment.metadata?.recommendedProcedures || [],
1454
1474
  // Only include zoneBilling if it exists (avoid undefined values in Firestore)
1455
1475
  ...(appointment.metadata?.zoneBilling !== undefined && {
1456
1476
  zoneBilling: appointment.metadata.zoneBilling,
@@ -1464,7 +1484,7 @@ export class AppointmentService extends BaseService {
1464
1484
  const updatedAppointment = await this.updateAppointment(appointmentId, updateData);
1465
1485
 
1466
1486
  console.log(
1467
- `[APPOINTMENT_SERVICE] Successfully deleted ${photoType} photo for zone ${zoneId}`,
1487
+ `[APPOINTMENT_SERVICE] Successfully deleted photo index ${photoIndex} for zone ${zoneId}`,
1468
1488
  );
1469
1489
 
1470
1490
  return updatedAppointment;
@@ -1714,6 +1734,7 @@ export class AppointmentService extends BaseService {
1714
1734
  zonesData: null,
1715
1735
  appointmentProducts: [],
1716
1736
  extendedProcedures: [],
1737
+ recommendedProcedures: [],
1717
1738
  finalbilling: null,
1718
1739
  finalizationNotes: null,
1719
1740
  };
@@ -1725,6 +1746,7 @@ export class AppointmentService extends BaseService {
1725
1746
  zonesData: currentMetadata.zonesData,
1726
1747
  appointmentProducts: currentMetadata.appointmentProducts || [],
1727
1748
  extendedProcedures: currentMetadata.extendedProcedures || [],
1749
+ recommendedProcedures: currentMetadata.recommendedProcedures || [],
1728
1750
  // Only include zoneBilling if it exists (avoid undefined values in Firestore)
1729
1751
  ...(currentMetadata.zoneBilling !== undefined && {
1730
1752
  zoneBilling: currentMetadata.zoneBilling,
@@ -1741,4 +1763,216 @@ export class AppointmentService extends BaseService {
1741
1763
  throw error;
1742
1764
  }
1743
1765
  }
1766
+
1767
+ /**
1768
+ * Adds a recommended procedure to an appointment
1769
+ * Multiple recommendations of the same procedure are allowed (e.g., touch-up in 2 weeks, full treatment in 3 months)
1770
+ *
1771
+ * @param appointmentId ID of the appointment
1772
+ * @param procedureId ID of the procedure to recommend
1773
+ * @param note Note explaining the recommendation
1774
+ * @param timeframe Suggested timeframe for the procedure
1775
+ * @returns The updated appointment
1776
+ */
1777
+ async addRecommendedProcedure(
1778
+ appointmentId: string,
1779
+ procedureId: string,
1780
+ note: string,
1781
+ timeframe: { value: number; unit: 'day' | 'week' | 'month' | 'year' }
1782
+ ): Promise<Appointment> {
1783
+ try {
1784
+ console.log(
1785
+ `[APPOINTMENT_SERVICE] Adding recommended procedure ${procedureId} to appointment ${appointmentId}`,
1786
+ );
1787
+ return await addRecommendedProcedureUtil(this.db, appointmentId, procedureId, note, timeframe);
1788
+ } catch (error) {
1789
+ console.error(`[APPOINTMENT_SERVICE] Error adding recommended procedure:`, error);
1790
+ throw error;
1791
+ }
1792
+ }
1793
+
1794
+ /**
1795
+ * Removes a recommended procedure from an appointment by index
1796
+ *
1797
+ * @param appointmentId ID of the appointment
1798
+ * @param recommendationIndex Index of the recommendation to remove
1799
+ * @returns The updated appointment
1800
+ */
1801
+ async removeRecommendedProcedure(appointmentId: string, recommendationIndex: number): Promise<Appointment> {
1802
+ try {
1803
+ console.log(
1804
+ `[APPOINTMENT_SERVICE] Removing recommended procedure at index ${recommendationIndex} from appointment ${appointmentId}`,
1805
+ );
1806
+ return await removeRecommendedProcedureUtil(this.db, appointmentId, recommendationIndex);
1807
+ } catch (error) {
1808
+ console.error(`[APPOINTMENT_SERVICE] Error removing recommended procedure:`, error);
1809
+ throw error;
1810
+ }
1811
+ }
1812
+
1813
+ /**
1814
+ * Updates a recommended procedure in an appointment by index
1815
+ *
1816
+ * @param appointmentId ID of the appointment
1817
+ * @param recommendationIndex Index of the recommendation to update
1818
+ * @param updates Partial updates (note and/or timeframe)
1819
+ * @returns The updated appointment
1820
+ */
1821
+ async updateRecommendedProcedure(
1822
+ appointmentId: string,
1823
+ recommendationIndex: number,
1824
+ updates: {
1825
+ note?: string;
1826
+ timeframe?: { value: number; unit: 'day' | 'week' | 'month' | 'year' };
1827
+ }
1828
+ ): Promise<Appointment> {
1829
+ try {
1830
+ console.log(
1831
+ `[APPOINTMENT_SERVICE] Updating recommended procedure at index ${recommendationIndex} in appointment ${appointmentId}`,
1832
+ );
1833
+ return await updateRecommendedProcedureUtil(this.db, appointmentId, recommendationIndex, updates);
1834
+ } catch (error) {
1835
+ console.error(`[APPOINTMENT_SERVICE] Error updating recommended procedure:`, error);
1836
+ throw error;
1837
+ }
1838
+ }
1839
+
1840
+ /**
1841
+ * Gets all recommended procedures for an appointment
1842
+ *
1843
+ * @param appointmentId ID of the appointment
1844
+ * @returns Array of recommended procedures
1845
+ */
1846
+ async getRecommendedProcedures(appointmentId: string): Promise<RecommendedProcedure[]> {
1847
+ try {
1848
+ console.log(
1849
+ `[APPOINTMENT_SERVICE] Getting recommended procedures for appointment ${appointmentId}`,
1850
+ );
1851
+ return await getRecommendedProceduresUtil(this.db, appointmentId);
1852
+ } catch (error) {
1853
+ console.error(`[APPOINTMENT_SERVICE] Error getting recommended procedures:`, error);
1854
+ throw error;
1855
+ }
1856
+ }
1857
+
1858
+ /**
1859
+ * Updates a specific photo entry in a zone by index
1860
+ * Can update before/after photos and their notes
1861
+ *
1862
+ * @param appointmentId ID of the appointment
1863
+ * @param zoneId Zone ID
1864
+ * @param photoIndex Index of the photo entry to update
1865
+ * @param updates Partial updates to apply (before, after, beforeNote, afterNote)
1866
+ * @returns The updated appointment
1867
+ */
1868
+ async updateZonePhotoEntry(
1869
+ appointmentId: string,
1870
+ zoneId: string,
1871
+ photoIndex: number,
1872
+ updates: Partial<BeforeAfterPerZone>
1873
+ ): Promise<Appointment> {
1874
+ try {
1875
+ console.log(
1876
+ `[APPOINTMENT_SERVICE] Updating photo entry at index ${photoIndex} for zone ${zoneId} in appointment ${appointmentId}`,
1877
+ );
1878
+ return await updateZonePhotoEntryUtil(this.db, appointmentId, zoneId, photoIndex, updates);
1879
+ } catch (error) {
1880
+ console.error(`[APPOINTMENT_SERVICE] Error updating zone photo entry:`, error);
1881
+ throw error;
1882
+ }
1883
+ }
1884
+
1885
+ /**
1886
+ * Adds an after photo to an existing before photo entry
1887
+ *
1888
+ * @param appointmentId ID of the appointment
1889
+ * @param zoneId Zone ID
1890
+ * @param photoIndex Index of the entry to add after photo to
1891
+ * @param afterPhotoUrl URL of the after photo
1892
+ * @param afterNote Optional note for the after photo
1893
+ * @returns The updated appointment
1894
+ */
1895
+ async addAfterPhotoToEntry(
1896
+ appointmentId: string,
1897
+ zoneId: string,
1898
+ photoIndex: number,
1899
+ afterPhotoUrl: MediaResource,
1900
+ afterNote?: string
1901
+ ): Promise<Appointment> {
1902
+ try {
1903
+ console.log(
1904
+ `[APPOINTMENT_SERVICE] Adding after photo to entry at index ${photoIndex} for zone ${zoneId}`,
1905
+ );
1906
+ return await addAfterPhotoToEntryUtil(
1907
+ this.db,
1908
+ appointmentId,
1909
+ zoneId,
1910
+ photoIndex,
1911
+ afterPhotoUrl,
1912
+ afterNote
1913
+ );
1914
+ } catch (error) {
1915
+ console.error(`[APPOINTMENT_SERVICE] Error adding after photo to entry:`, error);
1916
+ throw error;
1917
+ }
1918
+ }
1919
+
1920
+ /**
1921
+ * Updates notes for a photo entry
1922
+ *
1923
+ * @param appointmentId ID of the appointment
1924
+ * @param zoneId Zone ID
1925
+ * @param photoIndex Index of the entry
1926
+ * @param beforeNote Optional note for before photo
1927
+ * @param afterNote Optional note for after photo
1928
+ * @returns The updated appointment
1929
+ */
1930
+ async updateZonePhotoNotes(
1931
+ appointmentId: string,
1932
+ zoneId: string,
1933
+ photoIndex: number,
1934
+ beforeNote?: string,
1935
+ afterNote?: string
1936
+ ): Promise<Appointment> {
1937
+ try {
1938
+ console.log(
1939
+ `[APPOINTMENT_SERVICE] Updating notes for photo entry at index ${photoIndex} for zone ${zoneId}`,
1940
+ );
1941
+ return await updateZonePhotoNotesUtil(
1942
+ this.db,
1943
+ appointmentId,
1944
+ zoneId,
1945
+ photoIndex,
1946
+ beforeNote,
1947
+ afterNote
1948
+ );
1949
+ } catch (error) {
1950
+ console.error(`[APPOINTMENT_SERVICE] Error updating zone photo notes:`, error);
1951
+ throw error;
1952
+ }
1953
+ }
1954
+
1955
+ /**
1956
+ * Gets a specific photo entry from a zone
1957
+ *
1958
+ * @param appointmentId ID of the appointment
1959
+ * @param zoneId Zone ID
1960
+ * @param photoIndex Index of the entry
1961
+ * @returns Photo entry
1962
+ */
1963
+ async getZonePhotoEntry(
1964
+ appointmentId: string,
1965
+ zoneId: string,
1966
+ photoIndex: number
1967
+ ): Promise<BeforeAfterPerZone> {
1968
+ try {
1969
+ console.log(
1970
+ `[APPOINTMENT_SERVICE] Getting photo entry at index ${photoIndex} for zone ${zoneId}`,
1971
+ );
1972
+ return await getZonePhotoEntryUtil(this.db, appointmentId, zoneId, photoIndex);
1973
+ } catch (error) {
1974
+ console.error(`[APPOINTMENT_SERVICE] Error getting zone photo entry:`, error);
1975
+ throw error;
1976
+ }
1977
+ }
1744
1978
  }