@blackcode_sa/metaestetics-api 1.14.44 → 1.14.45

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.
@@ -7,6 +7,30 @@ import {
7
7
  import { getAppointmentOrThrow } from './zone-management.utils';
8
8
  import { MediaResource } from '../../media/media.service';
9
9
 
10
+ /**
11
+ * Updates visibility fields with audit trail
12
+ * @param updates Partial updates object
13
+ * @param doctorId ID of the doctor making the change
14
+ */
15
+ function addVisibilityAudit(
16
+ updates: Partial<BeforeAfterPerZone>,
17
+ doctorId: string,
18
+ ): Partial<BeforeAfterPerZone> {
19
+ // Only add audit fields if visibility-related fields are being updated
20
+ if (
21
+ updates.showToPatient !== undefined ||
22
+ updates.beforeNoteVisibleToPatient !== undefined ||
23
+ updates.afterNoteVisibleToPatient !== undefined
24
+ ) {
25
+ return {
26
+ ...updates,
27
+ visibilityUpdatedAt: serverTimestamp() as any,
28
+ visibilityUpdatedBy: doctorId,
29
+ };
30
+ }
31
+ return updates;
32
+ }
33
+
10
34
  /**
11
35
  * Updates a specific photo entry in a zone by index
12
36
  * Can update before/after photos and their notes
@@ -16,6 +40,7 @@ import { MediaResource } from '../../media/media.service';
16
40
  * @param zoneId Zone ID
17
41
  * @param photoIndex Index of the photo entry to update
18
42
  * @param updates Partial updates to apply
43
+ * @param doctorId Optional doctor ID for audit trail (required if updating visibility)
19
44
  * @returns Updated appointment
20
45
  */
21
46
  export async function updateZonePhotoEntryUtil(
@@ -23,7 +48,8 @@ export async function updateZonePhotoEntryUtil(
23
48
  appointmentId: string,
24
49
  zoneId: string,
25
50
  photoIndex: number,
26
- updates: Partial<BeforeAfterPerZone>
51
+ updates: Partial<BeforeAfterPerZone>,
52
+ doctorId?: string,
27
53
  ): Promise<Appointment> {
28
54
  const appointment = await getAppointmentOrThrow(db, appointmentId);
29
55
 
@@ -41,12 +67,16 @@ export async function updateZonePhotoEntryUtil(
41
67
  throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
42
68
  }
43
69
 
44
- // Update the entry
70
+ // Update the entry with audit trail if visibility is being changed
71
+ const updatesWithAudit = doctorId
72
+ ? addVisibilityAudit(updates, doctorId)
73
+ : updates;
74
+
45
75
  const updatedZonePhotos = { ...zonePhotos };
46
76
  updatedZonePhotos[zoneId] = [...zoneArray];
47
77
  updatedZonePhotos[zoneId][photoIndex] = {
48
78
  ...zoneArray[photoIndex],
49
- ...updates,
79
+ ...updatesWithAudit,
50
80
  };
51
81
 
52
82
  // Update appointment
@@ -171,3 +201,61 @@ export async function getZonePhotoEntryUtil(
171
201
  return zoneArray[photoIndex];
172
202
  }
173
203
 
204
+ /**
205
+ * Updates visibility of a photo pair (before AND after together)
206
+ *
207
+ * @param db Firestore instance
208
+ * @param appointmentId Appointment ID
209
+ * @param zoneId Zone ID
210
+ * @param photoIndex Index of the photo entry
211
+ * @param showToPatient Whether the photo pair should be visible to patient
212
+ * @param doctorId ID of the doctor making the change (for audit trail)
213
+ * @returns Updated appointment
214
+ */
215
+ export async function updateZonePhotoVisibilityUtil(
216
+ db: Firestore,
217
+ appointmentId: string,
218
+ zoneId: string,
219
+ photoIndex: number,
220
+ showToPatient: boolean,
221
+ doctorId: string,
222
+ ): Promise<Appointment> {
223
+ return updateZonePhotoEntryUtil(
224
+ db,
225
+ appointmentId,
226
+ zoneId,
227
+ photoIndex,
228
+ { showToPatient },
229
+ doctorId,
230
+ );
231
+ }
232
+
233
+ /**
234
+ * Updates visibility of a photo note (before or after)
235
+ *
236
+ * @param db Firestore instance
237
+ * @param appointmentId Appointment ID
238
+ * @param zoneId Zone ID
239
+ * @param photoIndex Index of the photo entry
240
+ * @param noteType Type of note ('before' or 'after')
241
+ * @param visibleToPatient Whether the note should be visible to patient
242
+ * @param doctorId ID of the doctor making the change (for audit trail)
243
+ * @returns Updated appointment
244
+ */
245
+ export async function updateZonePhotoNoteVisibilityUtil(
246
+ db: Firestore,
247
+ appointmentId: string,
248
+ zoneId: string,
249
+ photoIndex: number,
250
+ noteType: 'before' | 'after',
251
+ visibleToPatient: boolean,
252
+ doctorId: string,
253
+ ): Promise<Appointment> {
254
+ const updates: Partial<BeforeAfterPerZone> =
255
+ noteType === 'before'
256
+ ? { beforeNoteVisibleToPatient: visibleToPatient }
257
+ : { afterNoteVisibleToPatient: visibleToPatient };
258
+
259
+ return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates, doctorId);
260
+ }
261
+
@@ -120,9 +120,20 @@ export interface BeforeAfterPerZone {
120
120
  before: MediaResource | null;
121
121
  /** URL for after photo or null if not available */
122
122
  after: MediaResource | null;
123
- /** Optional note for the zone */
124
- afterNote?: string | null;
123
+ /** Optional note for the before photo */
125
124
  beforeNote?: string | null;
125
+ /** Optional note for the after photo */
126
+ afterNote?: string | null;
127
+ /** Whether this photo pair (before AND after) should be visible to the patient */
128
+ showToPatient?: boolean;
129
+ /** Whether the before note should be visible to the patient */
130
+ beforeNoteVisibleToPatient?: boolean;
131
+ /** Whether the after note should be visible to the patient */
132
+ afterNoteVisibleToPatient?: boolean;
133
+ /** Timestamp when visibility was last updated */
134
+ visibilityUpdatedAt?: Timestamp;
135
+ /** ID of the doctor who last updated visibility */
136
+ visibilityUpdatedBy?: string;
126
137
  }
127
138
 
128
139
  /**
@@ -157,8 +168,6 @@ export interface ZoneItemData {
157
168
  notes?: string;
158
169
  subtotal?: number;
159
170
  ionNumber?: string;
160
- lotNumber?: string; // Optional lot/batch number for the product
161
- expiryDate?: string; // Optional expiry date (ISO date string)
162
171
  createdAt?: string; // ISO timestamp
163
172
  updatedAt?: string; // ISO timestamp
164
173
  }
@@ -249,7 +258,14 @@ export interface AppointmentMetadata {
249
258
  extendedProcedures?: ExtendedProcedureInfo[];
250
259
  recommendedProcedures: RecommendedProcedure[]
251
260
  finalbilling: FinalBilling | null;
252
- finalizationNotes: string | null;
261
+ /** Final treatment notes shared with patient */
262
+ finalizationNotesShared?: string | null;
263
+ /** Final treatment notes for internal use only (not shared with patient) */
264
+ finalizationNotesInternal?: string | null;
265
+ /**
266
+ * @deprecated Use finalizationNotesShared instead. Kept for backward compatibility during migration.
267
+ */
268
+ finalizationNotes?: string | null;
253
269
 
254
270
  /**
255
271
  * @deprecated Use zonesData instead
@@ -143,8 +143,13 @@ export const finalizedDetailsSchema = z.object({
143
143
  export const beforeAfterPerZoneSchema = z.object({
144
144
  before: mediaResourceSchema.nullable(),
145
145
  after: mediaResourceSchema.nullable(),
146
- afterNote: z.string().nullable().optional(),
147
146
  beforeNote: z.string().nullable().optional(),
147
+ afterNote: z.string().nullable().optional(),
148
+ showToPatient: z.boolean().optional().default(false),
149
+ beforeNoteVisibleToPatient: z.boolean().optional().default(false),
150
+ afterNoteVisibleToPatient: z.boolean().optional().default(false),
151
+ visibilityUpdatedAt: z.any().optional(), // Timestamp
152
+ visibilityUpdatedBy: z.string().optional(),
148
153
  });
149
154
 
150
155
  /**
@@ -220,10 +225,9 @@ export const zoneItemDataSchema = z
220
225
  },
221
226
  ),
222
227
  notes: z.string().max(MAX_STRING_LENGTH_LONG, 'Notes too long').optional(),
228
+ notesVisibleToPatient: z.boolean().optional().default(false),
223
229
  subtotal: z.number().min(0, 'Subtotal must be non-negative').optional(),
224
230
  ionNumber: z.string().optional(),
225
- lotNumber: z.string().max(MAX_STRING_LENGTH, 'Lot number too long').optional(),
226
- expiryDate: z.string().optional(), // ISO date string (YYYY-MM-DD format)
227
231
  createdAt: z.string().optional(),
228
232
  updatedAt: z.string().optional(),
229
233
  })
@@ -302,7 +306,9 @@ export const appointmentMetadataSchema = z.object({
302
306
  recommendedProcedures: z.array(recommendedProcedureSchema).optional().default([]),
303
307
  zoneBilling: z.record(z.string(), billingPerZoneSchema).nullable().optional(),
304
308
  finalbilling: finalBillingSchema.nullable(),
305
- finalizationNotes: z.string().nullable(),
309
+ finalizationNotesShared: z.string().nullable().optional(),
310
+ finalizationNotesInternal: z.string().nullable().optional(),
311
+ finalizationNotes: z.string().nullable().optional(), // @deprecated - kept for backward compatibility
306
312
  });
307
313
 
308
314
  // --- Main Appointment Schemas ---