@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.
- package/dist/admin/index.d.mts +21 -5
- package/dist/admin/index.d.ts +21 -5
- package/dist/index.d.mts +53 -5
- package/dist/index.d.ts +53 -5
- package/dist/index.js +184 -14
- package/dist/index.mjs +184 -14
- package/package.json +1 -1
- package/src/services/appointment/appointment.service.ts +167 -5
- package/src/services/appointment/utils/zone-management.utils.ts +5 -1
- package/src/services/appointment/utils/zone-photo.utils.ts +91 -3
- package/src/types/appointment/index.ts +21 -5
- package/src/validations/appointment.schema.ts +10 -4
package/dist/index.mjs
CHANGED
|
@@ -3758,8 +3758,14 @@ var finalizedDetailsSchema = z3.object({
|
|
|
3758
3758
|
var beforeAfterPerZoneSchema = z3.object({
|
|
3759
3759
|
before: mediaResourceSchema.nullable(),
|
|
3760
3760
|
after: mediaResourceSchema.nullable(),
|
|
3761
|
+
beforeNote: z3.string().nullable().optional(),
|
|
3761
3762
|
afterNote: z3.string().nullable().optional(),
|
|
3762
|
-
|
|
3763
|
+
showToPatient: z3.boolean().optional().default(false),
|
|
3764
|
+
beforeNoteVisibleToPatient: z3.boolean().optional().default(false),
|
|
3765
|
+
afterNoteVisibleToPatient: z3.boolean().optional().default(false),
|
|
3766
|
+
visibilityUpdatedAt: z3.any().optional(),
|
|
3767
|
+
// Timestamp
|
|
3768
|
+
visibilityUpdatedBy: z3.string().optional()
|
|
3763
3769
|
});
|
|
3764
3770
|
var billingPerZoneSchema = z3.object({
|
|
3765
3771
|
Product: z3.string().min(MIN_STRING_LENGTH, "Product name is required"),
|
|
@@ -3817,11 +3823,9 @@ var zoneItemDataSchema = z3.object({
|
|
|
3817
3823
|
}
|
|
3818
3824
|
),
|
|
3819
3825
|
notes: z3.string().max(MAX_STRING_LENGTH_LONG, "Notes too long").optional(),
|
|
3826
|
+
notesVisibleToPatient: z3.boolean().optional().default(false),
|
|
3820
3827
|
subtotal: z3.number().min(0, "Subtotal must be non-negative").optional(),
|
|
3821
3828
|
ionNumber: z3.string().optional(),
|
|
3822
|
-
lotNumber: z3.string().max(MAX_STRING_LENGTH, "Lot number too long").optional(),
|
|
3823
|
-
expiryDate: z3.string().optional(),
|
|
3824
|
-
// ISO date string (YYYY-MM-DD format)
|
|
3825
3829
|
createdAt: z3.string().optional(),
|
|
3826
3830
|
updatedAt: z3.string().optional()
|
|
3827
3831
|
}).refine(
|
|
@@ -3883,7 +3887,10 @@ var appointmentMetadataSchema = z3.object({
|
|
|
3883
3887
|
recommendedProcedures: z3.array(recommendedProcedureSchema).optional().default([]),
|
|
3884
3888
|
zoneBilling: z3.record(z3.string(), billingPerZoneSchema).nullable().optional(),
|
|
3885
3889
|
finalbilling: finalBillingSchema.nullable(),
|
|
3886
|
-
|
|
3890
|
+
finalizationNotesShared: z3.string().nullable().optional(),
|
|
3891
|
+
finalizationNotesInternal: z3.string().nullable().optional(),
|
|
3892
|
+
finalizationNotes: z3.string().nullable().optional()
|
|
3893
|
+
// @deprecated - kept for backward compatibility
|
|
3887
3894
|
});
|
|
3888
3895
|
var createAppointmentSchema = z3.object({
|
|
3889
3896
|
clinicBranchId: z3.string().min(MIN_STRING_LENGTH, "Clinic branch ID is required"),
|
|
@@ -4715,10 +4722,14 @@ function initializeMetadata(appointment) {
|
|
|
4715
4722
|
extendedProcedures: [],
|
|
4716
4723
|
recommendedProcedures: [],
|
|
4717
4724
|
finalbilling: null,
|
|
4725
|
+
finalizationNotesShared: null,
|
|
4726
|
+
finalizationNotesInternal: null,
|
|
4718
4727
|
finalizationNotes: null
|
|
4728
|
+
// @deprecated - kept for backward compatibility
|
|
4719
4729
|
};
|
|
4720
4730
|
}
|
|
4721
4731
|
async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
4732
|
+
var _a;
|
|
4722
4733
|
validateZoneKeyFormat(zoneId);
|
|
4723
4734
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
4724
4735
|
const metadata = initializeMetadata(appointment);
|
|
@@ -4732,6 +4743,8 @@ async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
|
4732
4743
|
parentZone: zoneId,
|
|
4733
4744
|
// Set parentZone to the zone key
|
|
4734
4745
|
subtotal: calculateItemSubtotal(item),
|
|
4746
|
+
// Set default visibility to false (privacy-first) if notes exist and visibility not explicitly set
|
|
4747
|
+
notesVisibleToPatient: (_a = item.notesVisibleToPatient) != null ? _a : item.notes ? false : void 0,
|
|
4735
4748
|
createdAt: now,
|
|
4736
4749
|
updatedAt: now
|
|
4737
4750
|
};
|
|
@@ -5231,7 +5244,17 @@ async function getRecommendedProceduresUtil(db, appointmentId) {
|
|
|
5231
5244
|
|
|
5232
5245
|
// src/services/appointment/utils/zone-photo.utils.ts
|
|
5233
5246
|
import { updateDoc as updateDoc6, serverTimestamp as serverTimestamp6, doc as doc9 } from "firebase/firestore";
|
|
5234
|
-
|
|
5247
|
+
function addVisibilityAudit(updates, doctorId) {
|
|
5248
|
+
if (updates.showToPatient !== void 0 || updates.beforeNoteVisibleToPatient !== void 0 || updates.afterNoteVisibleToPatient !== void 0) {
|
|
5249
|
+
return {
|
|
5250
|
+
...updates,
|
|
5251
|
+
visibilityUpdatedAt: serverTimestamp6(),
|
|
5252
|
+
visibilityUpdatedBy: doctorId
|
|
5253
|
+
};
|
|
5254
|
+
}
|
|
5255
|
+
return updates;
|
|
5256
|
+
}
|
|
5257
|
+
async function updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates, doctorId) {
|
|
5235
5258
|
var _a;
|
|
5236
5259
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
5237
5260
|
const zonePhotos = (_a = appointment.metadata) == null ? void 0 : _a.zonePhotos;
|
|
@@ -5242,11 +5265,12 @@ async function updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, u
|
|
|
5242
5265
|
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
5243
5266
|
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
|
|
5244
5267
|
}
|
|
5268
|
+
const updatesWithAudit = doctorId ? addVisibilityAudit(updates, doctorId) : updates;
|
|
5245
5269
|
const updatedZonePhotos = { ...zonePhotos };
|
|
5246
5270
|
updatedZonePhotos[zoneId] = [...zoneArray];
|
|
5247
5271
|
updatedZonePhotos[zoneId][photoIndex] = {
|
|
5248
5272
|
...zoneArray[photoIndex],
|
|
5249
|
-
...
|
|
5273
|
+
...updatesWithAudit
|
|
5250
5274
|
};
|
|
5251
5275
|
const appointmentRef = doc9(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
5252
5276
|
await updateDoc6(appointmentRef, {
|
|
@@ -5290,6 +5314,20 @@ async function getZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex) {
|
|
|
5290
5314
|
}
|
|
5291
5315
|
return zoneArray[photoIndex];
|
|
5292
5316
|
}
|
|
5317
|
+
async function updateZonePhotoVisibilityUtil(db, appointmentId, zoneId, photoIndex, showToPatient, doctorId) {
|
|
5318
|
+
return updateZonePhotoEntryUtil(
|
|
5319
|
+
db,
|
|
5320
|
+
appointmentId,
|
|
5321
|
+
zoneId,
|
|
5322
|
+
photoIndex,
|
|
5323
|
+
{ showToPatient },
|
|
5324
|
+
doctorId
|
|
5325
|
+
);
|
|
5326
|
+
}
|
|
5327
|
+
async function updateZonePhotoNoteVisibilityUtil(db, appointmentId, zoneId, photoIndex, noteType, visibleToPatient, doctorId) {
|
|
5328
|
+
const updates = noteType === "before" ? { beforeNoteVisibleToPatient: visibleToPatient } : { afterNoteVisibleToPatient: visibleToPatient };
|
|
5329
|
+
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates, doctorId);
|
|
5330
|
+
}
|
|
5293
5331
|
|
|
5294
5332
|
// src/services/appointment/appointment.service.ts
|
|
5295
5333
|
var AppointmentService = class extends BaseService {
|
|
@@ -6156,6 +6194,7 @@ var AppointmentService = class extends BaseService {
|
|
|
6156
6194
|
* @returns The updated appointment
|
|
6157
6195
|
*/
|
|
6158
6196
|
async updateAppointmentZonePhoto(appointmentId, zoneId, photoType, mediaMetadata, notes) {
|
|
6197
|
+
var _a, _b, _c, _d;
|
|
6159
6198
|
try {
|
|
6160
6199
|
console.log(
|
|
6161
6200
|
`[APPOINTMENT_SERVICE] Updating appointment metadata for ${photoType} photo in zone ${zoneId}`
|
|
@@ -6173,7 +6212,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6173
6212
|
recommendedProcedures: [],
|
|
6174
6213
|
zoneBilling: null,
|
|
6175
6214
|
finalbilling: null,
|
|
6215
|
+
finalizationNotesShared: null,
|
|
6216
|
+
finalizationNotesInternal: null,
|
|
6176
6217
|
finalizationNotes: null
|
|
6218
|
+
// @deprecated - kept for backward compatibility
|
|
6177
6219
|
};
|
|
6178
6220
|
let currentZonePhotos = {};
|
|
6179
6221
|
if (currentMetadata.zonePhotos) {
|
|
@@ -6188,7 +6230,11 @@ var AppointmentService = class extends BaseService {
|
|
|
6188
6230
|
before: oldData.before || null,
|
|
6189
6231
|
after: oldData.after || null,
|
|
6190
6232
|
beforeNote: null,
|
|
6191
|
-
afterNote: null
|
|
6233
|
+
afterNote: null,
|
|
6234
|
+
showToPatient: false,
|
|
6235
|
+
// Default: not visible to patient (privacy-first)
|
|
6236
|
+
beforeNoteVisibleToPatient: false,
|
|
6237
|
+
afterNoteVisibleToPatient: false
|
|
6192
6238
|
}
|
|
6193
6239
|
];
|
|
6194
6240
|
}
|
|
@@ -6201,7 +6247,13 @@ var AppointmentService = class extends BaseService {
|
|
|
6201
6247
|
before: photoType === "before" ? mediaMetadata.url : null,
|
|
6202
6248
|
after: photoType === "after" ? mediaMetadata.url : null,
|
|
6203
6249
|
beforeNote: photoType === "before" ? notes || null : null,
|
|
6204
|
-
afterNote: photoType === "after" ? notes || null : null
|
|
6250
|
+
afterNote: photoType === "after" ? notes || null : null,
|
|
6251
|
+
showToPatient: false,
|
|
6252
|
+
// Default: not visible to patient
|
|
6253
|
+
beforeNoteVisibleToPatient: false,
|
|
6254
|
+
// Default: not visible to patient
|
|
6255
|
+
afterNoteVisibleToPatient: false
|
|
6256
|
+
// Default: not visible to patient
|
|
6205
6257
|
};
|
|
6206
6258
|
currentZonePhotos[zoneId] = [...currentZonePhotos[zoneId], newEntry];
|
|
6207
6259
|
if (currentZonePhotos[zoneId].length > 10) {
|
|
@@ -6220,7 +6272,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6220
6272
|
zoneBilling: currentMetadata.zoneBilling
|
|
6221
6273
|
},
|
|
6222
6274
|
finalbilling: currentMetadata.finalbilling,
|
|
6223
|
-
|
|
6275
|
+
finalizationNotesShared: (_a = currentMetadata.finalizationNotesShared) != null ? _a : null,
|
|
6276
|
+
finalizationNotesInternal: (_b = currentMetadata.finalizationNotesInternal) != null ? _b : null,
|
|
6277
|
+
finalizationNotes: (_d = (_c = currentMetadata.finalizationNotes) != null ? _c : currentMetadata.finalizationNotesShared) != null ? _d : null
|
|
6278
|
+
// @deprecated - migrate from old field if exists
|
|
6224
6279
|
},
|
|
6225
6280
|
updatedAt: serverTimestamp7()
|
|
6226
6281
|
};
|
|
@@ -6274,7 +6329,7 @@ var AppointmentService = class extends BaseService {
|
|
|
6274
6329
|
* @returns The updated appointment
|
|
6275
6330
|
*/
|
|
6276
6331
|
async deleteZonePhoto(appointmentId, zoneId, photoIndex) {
|
|
6277
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
6332
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
|
|
6278
6333
|
try {
|
|
6279
6334
|
console.log(
|
|
6280
6335
|
`[APPOINTMENT_SERVICE] Deleting zone photo index ${photoIndex} for zone ${zoneId} in appointment ${appointmentId}`
|
|
@@ -6331,7 +6386,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6331
6386
|
zoneBilling: appointment.metadata.zoneBilling
|
|
6332
6387
|
},
|
|
6333
6388
|
finalbilling: ((_h = appointment.metadata) == null ? void 0 : _h.finalbilling) || null,
|
|
6334
|
-
|
|
6389
|
+
finalizationNotesShared: (_l = (_k = (_i = appointment.metadata) == null ? void 0 : _i.finalizationNotesShared) != null ? _k : (_j = appointment.metadata) == null ? void 0 : _j.finalizationNotes) != null ? _l : null,
|
|
6390
|
+
finalizationNotesInternal: (_n = (_m = appointment.metadata) == null ? void 0 : _m.finalizationNotesInternal) != null ? _n : null,
|
|
6391
|
+
finalizationNotes: (_p = (_o = appointment.metadata) == null ? void 0 : _o.finalizationNotes) != null ? _p : null
|
|
6392
|
+
// @deprecated
|
|
6335
6393
|
},
|
|
6336
6394
|
updatedAt: serverTimestamp7()
|
|
6337
6395
|
};
|
|
@@ -6530,7 +6588,7 @@ var AppointmentService = class extends BaseService {
|
|
|
6530
6588
|
* @returns The updated appointment with recalculated billing
|
|
6531
6589
|
*/
|
|
6532
6590
|
async recalculateFinalBilling(appointmentId, taxRate) {
|
|
6533
|
-
var _a;
|
|
6591
|
+
var _a, _b, _c, _d, _e;
|
|
6534
6592
|
try {
|
|
6535
6593
|
console.log(
|
|
6536
6594
|
`[APPOINTMENT_SERVICE] Recalculating final billing for appointment ${appointmentId}`
|
|
@@ -6552,7 +6610,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6552
6610
|
extendedProcedures: [],
|
|
6553
6611
|
recommendedProcedures: [],
|
|
6554
6612
|
finalbilling: null,
|
|
6613
|
+
finalizationNotesShared: null,
|
|
6614
|
+
finalizationNotesInternal: null,
|
|
6555
6615
|
finalizationNotes: null
|
|
6616
|
+
// @deprecated
|
|
6556
6617
|
};
|
|
6557
6618
|
const shouldUpdatePaymentStatus = finalbilling.finalPrice > 0 && appointment.paymentStatus === "not_applicable" /* NOT_APPLICABLE */;
|
|
6558
6619
|
const updateData = {
|
|
@@ -6568,7 +6629,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6568
6629
|
zoneBilling: currentMetadata.zoneBilling
|
|
6569
6630
|
},
|
|
6570
6631
|
finalbilling,
|
|
6571
|
-
|
|
6632
|
+
finalizationNotesShared: (_b = currentMetadata.finalizationNotesShared) != null ? _b : null,
|
|
6633
|
+
finalizationNotesInternal: (_c = currentMetadata.finalizationNotesInternal) != null ? _c : null,
|
|
6634
|
+
finalizationNotes: (_e = (_d = currentMetadata.finalizationNotes) != null ? _d : currentMetadata.finalizationNotesShared) != null ? _e : null
|
|
6635
|
+
// @deprecated
|
|
6572
6636
|
},
|
|
6573
6637
|
...shouldUpdatePaymentStatus && {
|
|
6574
6638
|
paymentStatus: "unpaid" /* UNPAID */
|
|
@@ -6768,6 +6832,64 @@ var AppointmentService = class extends BaseService {
|
|
|
6768
6832
|
throw error;
|
|
6769
6833
|
}
|
|
6770
6834
|
}
|
|
6835
|
+
/**
|
|
6836
|
+
* Updates visibility of a photo pair (before AND after together)
|
|
6837
|
+
*
|
|
6838
|
+
* @param appointmentId ID of the appointment
|
|
6839
|
+
* @param zoneId Zone ID
|
|
6840
|
+
* @param photoIndex Index of the photo entry
|
|
6841
|
+
* @param showToPatient Whether the photo pair should be visible to patient
|
|
6842
|
+
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
6843
|
+
* @returns The updated appointment
|
|
6844
|
+
*/
|
|
6845
|
+
async updateZonePhotoVisibility(appointmentId, zoneId, photoIndex, showToPatient, doctorId) {
|
|
6846
|
+
try {
|
|
6847
|
+
console.log(
|
|
6848
|
+
`[APPOINTMENT_SERVICE] Updating photo visibility at index ${photoIndex} for zone ${zoneId} to ${showToPatient}`
|
|
6849
|
+
);
|
|
6850
|
+
return await updateZonePhotoVisibilityUtil(
|
|
6851
|
+
this.db,
|
|
6852
|
+
appointmentId,
|
|
6853
|
+
zoneId,
|
|
6854
|
+
photoIndex,
|
|
6855
|
+
showToPatient,
|
|
6856
|
+
doctorId
|
|
6857
|
+
);
|
|
6858
|
+
} catch (error) {
|
|
6859
|
+
console.error(`[APPOINTMENT_SERVICE] Error updating zone photo visibility:`, error);
|
|
6860
|
+
throw error;
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
/**
|
|
6864
|
+
* Updates visibility of a photo note (before or after)
|
|
6865
|
+
*
|
|
6866
|
+
* @param appointmentId ID of the appointment
|
|
6867
|
+
* @param zoneId Zone ID
|
|
6868
|
+
* @param photoIndex Index of the photo entry
|
|
6869
|
+
* @param noteType Type of note ('before' or 'after')
|
|
6870
|
+
* @param visibleToPatient Whether the note should be visible to patient
|
|
6871
|
+
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
6872
|
+
* @returns The updated appointment
|
|
6873
|
+
*/
|
|
6874
|
+
async updateZonePhotoNoteVisibility(appointmentId, zoneId, photoIndex, noteType, visibleToPatient, doctorId) {
|
|
6875
|
+
try {
|
|
6876
|
+
console.log(
|
|
6877
|
+
`[APPOINTMENT_SERVICE] Updating ${noteType} note visibility at index ${photoIndex} for zone ${zoneId} to ${visibleToPatient}`
|
|
6878
|
+
);
|
|
6879
|
+
return await updateZonePhotoNoteVisibilityUtil(
|
|
6880
|
+
this.db,
|
|
6881
|
+
appointmentId,
|
|
6882
|
+
zoneId,
|
|
6883
|
+
photoIndex,
|
|
6884
|
+
noteType,
|
|
6885
|
+
visibleToPatient,
|
|
6886
|
+
doctorId
|
|
6887
|
+
);
|
|
6888
|
+
} catch (error) {
|
|
6889
|
+
console.error(`[APPOINTMENT_SERVICE] Error updating zone photo note visibility:`, error);
|
|
6890
|
+
throw error;
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6771
6893
|
/**
|
|
6772
6894
|
* Gets a specific photo entry from a zone
|
|
6773
6895
|
*
|
|
@@ -6787,6 +6909,54 @@ var AppointmentService = class extends BaseService {
|
|
|
6787
6909
|
throw error;
|
|
6788
6910
|
}
|
|
6789
6911
|
}
|
|
6912
|
+
/**
|
|
6913
|
+
* Updates finalization notes (shared with patient and/or internal only)
|
|
6914
|
+
*
|
|
6915
|
+
* @param appointmentId ID of the appointment
|
|
6916
|
+
* @param sharedNotes Notes to be shared with patient (optional)
|
|
6917
|
+
* @param internalNotes Notes for internal use only (optional)
|
|
6918
|
+
* @returns The updated appointment
|
|
6919
|
+
*/
|
|
6920
|
+
async updateFinalizationNotes(appointmentId, sharedNotes, internalNotes) {
|
|
6921
|
+
try {
|
|
6922
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
6923
|
+
if (!appointment) {
|
|
6924
|
+
throw new Error(`Appointment ${appointmentId} not found`);
|
|
6925
|
+
}
|
|
6926
|
+
const currentMetadata = appointment.metadata || {
|
|
6927
|
+
selectedZones: null,
|
|
6928
|
+
zonePhotos: null,
|
|
6929
|
+
zonesData: null,
|
|
6930
|
+
appointmentProducts: [],
|
|
6931
|
+
extendedProcedures: [],
|
|
6932
|
+
recommendedProcedures: [],
|
|
6933
|
+
finalbilling: null,
|
|
6934
|
+
finalizationNotesShared: null,
|
|
6935
|
+
finalizationNotesInternal: null,
|
|
6936
|
+
finalizationNotes: null
|
|
6937
|
+
};
|
|
6938
|
+
const updateData = {
|
|
6939
|
+
metadata: {
|
|
6940
|
+
selectedZones: currentMetadata.selectedZones,
|
|
6941
|
+
zonePhotos: currentMetadata.zonePhotos,
|
|
6942
|
+
zonesData: currentMetadata.zonesData || null,
|
|
6943
|
+
appointmentProducts: currentMetadata.appointmentProducts || [],
|
|
6944
|
+
extendedProcedures: currentMetadata.extendedProcedures || [],
|
|
6945
|
+
recommendedProcedures: currentMetadata.recommendedProcedures || [],
|
|
6946
|
+
finalbilling: currentMetadata.finalbilling,
|
|
6947
|
+
finalizationNotesShared: sharedNotes !== void 0 ? sharedNotes : currentMetadata.finalizationNotesShared,
|
|
6948
|
+
finalizationNotesInternal: internalNotes !== void 0 ? internalNotes : currentMetadata.finalizationNotesInternal,
|
|
6949
|
+
// Keep deprecated field for backward compatibility during migration
|
|
6950
|
+
finalizationNotes: sharedNotes !== void 0 ? sharedNotes : currentMetadata.finalizationNotes || currentMetadata.finalizationNotesShared
|
|
6951
|
+
},
|
|
6952
|
+
updatedAt: serverTimestamp7()
|
|
6953
|
+
};
|
|
6954
|
+
return await this.updateAppointment(appointmentId, updateData);
|
|
6955
|
+
} catch (error) {
|
|
6956
|
+
console.error(`[APPOINTMENT_SERVICE] Error updating finalization notes:`, error);
|
|
6957
|
+
throw error;
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6790
6960
|
/**
|
|
6791
6961
|
* Gets all next steps recommendations for a patient from their past appointments.
|
|
6792
6962
|
* Returns recommendations with context about which appointment, practitioner, and clinic suggested them.
|
package/package.json
CHANGED
|
@@ -92,6 +92,8 @@ import {
|
|
|
92
92
|
removeAfterPhotoFromEntryUtil,
|
|
93
93
|
updateZonePhotoNotesUtil,
|
|
94
94
|
getZonePhotoEntryUtil,
|
|
95
|
+
updateZonePhotoVisibilityUtil,
|
|
96
|
+
updateZonePhotoNoteVisibilityUtil,
|
|
95
97
|
} from './utils/zone-photo.utils';
|
|
96
98
|
|
|
97
99
|
/**
|
|
@@ -1364,7 +1366,9 @@ export class AppointmentService extends BaseService {
|
|
|
1364
1366
|
recommendedProcedures: [],
|
|
1365
1367
|
zoneBilling: null,
|
|
1366
1368
|
finalbilling: null,
|
|
1367
|
-
|
|
1369
|
+
finalizationNotesShared: null,
|
|
1370
|
+
finalizationNotesInternal: null,
|
|
1371
|
+
finalizationNotes: null, // @deprecated - kept for backward compatibility
|
|
1368
1372
|
};
|
|
1369
1373
|
|
|
1370
1374
|
// Initialize zonePhotos if it doesn't exist (array model per zone)
|
|
@@ -1386,6 +1390,9 @@ export class AppointmentService extends BaseService {
|
|
|
1386
1390
|
after: oldData.after || null,
|
|
1387
1391
|
beforeNote: null,
|
|
1388
1392
|
afterNote: null,
|
|
1393
|
+
showToPatient: false, // Default: not visible to patient (privacy-first)
|
|
1394
|
+
beforeNoteVisibleToPatient: false,
|
|
1395
|
+
afterNoteVisibleToPatient: false,
|
|
1389
1396
|
},
|
|
1390
1397
|
];
|
|
1391
1398
|
}
|
|
@@ -1398,11 +1405,15 @@ export class AppointmentService extends BaseService {
|
|
|
1398
1405
|
}
|
|
1399
1406
|
|
|
1400
1407
|
// Create a new entry for this uploaded photo with per-photo notes
|
|
1408
|
+
// Default visibility is false (privacy-first approach)
|
|
1401
1409
|
const newEntry: BeforeAfterPerZone = {
|
|
1402
1410
|
before: photoType === 'before' ? mediaMetadata.url : null,
|
|
1403
1411
|
after: photoType === 'after' ? mediaMetadata.url : null,
|
|
1404
1412
|
beforeNote: photoType === 'before' ? notes || null : null,
|
|
1405
1413
|
afterNote: photoType === 'after' ? notes || null : null,
|
|
1414
|
+
showToPatient: false, // Default: not visible to patient
|
|
1415
|
+
beforeNoteVisibleToPatient: false, // Default: not visible to patient
|
|
1416
|
+
afterNoteVisibleToPatient: false, // Default: not visible to patient
|
|
1406
1417
|
};
|
|
1407
1418
|
|
|
1408
1419
|
// Append to the zone's photo list
|
|
@@ -1426,7 +1437,12 @@ export class AppointmentService extends BaseService {
|
|
|
1426
1437
|
zoneBilling: currentMetadata.zoneBilling,
|
|
1427
1438
|
}),
|
|
1428
1439
|
finalbilling: currentMetadata.finalbilling,
|
|
1429
|
-
|
|
1440
|
+
finalizationNotesShared: currentMetadata.finalizationNotesShared ?? null,
|
|
1441
|
+
finalizationNotesInternal: currentMetadata.finalizationNotesInternal ?? null,
|
|
1442
|
+
finalizationNotes:
|
|
1443
|
+
currentMetadata.finalizationNotes ??
|
|
1444
|
+
currentMetadata.finalizationNotesShared ??
|
|
1445
|
+
null, // @deprecated - migrate from old field if exists
|
|
1430
1446
|
},
|
|
1431
1447
|
updatedAt: serverTimestamp(),
|
|
1432
1448
|
};
|
|
@@ -1571,7 +1587,12 @@ export class AppointmentService extends BaseService {
|
|
|
1571
1587
|
zoneBilling: appointment.metadata.zoneBilling,
|
|
1572
1588
|
}),
|
|
1573
1589
|
finalbilling: appointment.metadata?.finalbilling || null,
|
|
1574
|
-
|
|
1590
|
+
finalizationNotesShared:
|
|
1591
|
+
appointment.metadata?.finalizationNotesShared ??
|
|
1592
|
+
appointment.metadata?.finalizationNotes ??
|
|
1593
|
+
null,
|
|
1594
|
+
finalizationNotesInternal: appointment.metadata?.finalizationNotesInternal ?? null,
|
|
1595
|
+
finalizationNotes: appointment.metadata?.finalizationNotes ?? null, // @deprecated
|
|
1575
1596
|
},
|
|
1576
1597
|
updatedAt: serverTimestamp(),
|
|
1577
1598
|
};
|
|
@@ -1831,7 +1852,9 @@ export class AppointmentService extends BaseService {
|
|
|
1831
1852
|
extendedProcedures: [],
|
|
1832
1853
|
recommendedProcedures: [],
|
|
1833
1854
|
finalbilling: null,
|
|
1834
|
-
|
|
1855
|
+
finalizationNotesShared: null,
|
|
1856
|
+
finalizationNotesInternal: null,
|
|
1857
|
+
finalizationNotes: null, // @deprecated
|
|
1835
1858
|
};
|
|
1836
1859
|
|
|
1837
1860
|
// Update payment status if billing data exists but status is NOT_APPLICABLE
|
|
@@ -1853,7 +1876,12 @@ export class AppointmentService extends BaseService {
|
|
|
1853
1876
|
zoneBilling: currentMetadata.zoneBilling,
|
|
1854
1877
|
}),
|
|
1855
1878
|
finalbilling,
|
|
1856
|
-
|
|
1879
|
+
finalizationNotesShared: currentMetadata.finalizationNotesShared ?? null,
|
|
1880
|
+
finalizationNotesInternal: currentMetadata.finalizationNotesInternal ?? null,
|
|
1881
|
+
finalizationNotes:
|
|
1882
|
+
currentMetadata.finalizationNotes ??
|
|
1883
|
+
currentMetadata.finalizationNotesShared ??
|
|
1884
|
+
null, // @deprecated
|
|
1857
1885
|
},
|
|
1858
1886
|
...(shouldUpdatePaymentStatus && {
|
|
1859
1887
|
paymentStatus: PaymentStatus.UNPAID,
|
|
@@ -2099,6 +2127,79 @@ export class AppointmentService extends BaseService {
|
|
|
2099
2127
|
}
|
|
2100
2128
|
}
|
|
2101
2129
|
|
|
2130
|
+
/**
|
|
2131
|
+
* Updates visibility of a photo pair (before AND after together)
|
|
2132
|
+
*
|
|
2133
|
+
* @param appointmentId ID of the appointment
|
|
2134
|
+
* @param zoneId Zone ID
|
|
2135
|
+
* @param photoIndex Index of the photo entry
|
|
2136
|
+
* @param showToPatient Whether the photo pair should be visible to patient
|
|
2137
|
+
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
2138
|
+
* @returns The updated appointment
|
|
2139
|
+
*/
|
|
2140
|
+
async updateZonePhotoVisibility(
|
|
2141
|
+
appointmentId: string,
|
|
2142
|
+
zoneId: string,
|
|
2143
|
+
photoIndex: number,
|
|
2144
|
+
showToPatient: boolean,
|
|
2145
|
+
doctorId: string,
|
|
2146
|
+
): Promise<Appointment> {
|
|
2147
|
+
try {
|
|
2148
|
+
console.log(
|
|
2149
|
+
`[APPOINTMENT_SERVICE] Updating photo visibility at index ${photoIndex} for zone ${zoneId} to ${showToPatient}`,
|
|
2150
|
+
);
|
|
2151
|
+
return await updateZonePhotoVisibilityUtil(
|
|
2152
|
+
this.db,
|
|
2153
|
+
appointmentId,
|
|
2154
|
+
zoneId,
|
|
2155
|
+
photoIndex,
|
|
2156
|
+
showToPatient,
|
|
2157
|
+
doctorId,
|
|
2158
|
+
);
|
|
2159
|
+
} catch (error) {
|
|
2160
|
+
console.error(`[APPOINTMENT_SERVICE] Error updating zone photo visibility:`, error);
|
|
2161
|
+
throw error;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
/**
|
|
2166
|
+
* Updates visibility of a photo note (before or after)
|
|
2167
|
+
*
|
|
2168
|
+
* @param appointmentId ID of the appointment
|
|
2169
|
+
* @param zoneId Zone ID
|
|
2170
|
+
* @param photoIndex Index of the photo entry
|
|
2171
|
+
* @param noteType Type of note ('before' or 'after')
|
|
2172
|
+
* @param visibleToPatient Whether the note should be visible to patient
|
|
2173
|
+
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
2174
|
+
* @returns The updated appointment
|
|
2175
|
+
*/
|
|
2176
|
+
async updateZonePhotoNoteVisibility(
|
|
2177
|
+
appointmentId: string,
|
|
2178
|
+
zoneId: string,
|
|
2179
|
+
photoIndex: number,
|
|
2180
|
+
noteType: 'before' | 'after',
|
|
2181
|
+
visibleToPatient: boolean,
|
|
2182
|
+
doctorId: string,
|
|
2183
|
+
): Promise<Appointment> {
|
|
2184
|
+
try {
|
|
2185
|
+
console.log(
|
|
2186
|
+
`[APPOINTMENT_SERVICE] Updating ${noteType} note visibility at index ${photoIndex} for zone ${zoneId} to ${visibleToPatient}`,
|
|
2187
|
+
);
|
|
2188
|
+
return await updateZonePhotoNoteVisibilityUtil(
|
|
2189
|
+
this.db,
|
|
2190
|
+
appointmentId,
|
|
2191
|
+
zoneId,
|
|
2192
|
+
photoIndex,
|
|
2193
|
+
noteType,
|
|
2194
|
+
visibleToPatient,
|
|
2195
|
+
doctorId,
|
|
2196
|
+
);
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
console.error(`[APPOINTMENT_SERVICE] Error updating zone photo note visibility:`, error);
|
|
2199
|
+
throw error;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2102
2203
|
/**
|
|
2103
2204
|
* Gets a specific photo entry from a zone
|
|
2104
2205
|
*
|
|
@@ -2123,6 +2224,67 @@ export class AppointmentService extends BaseService {
|
|
|
2123
2224
|
}
|
|
2124
2225
|
}
|
|
2125
2226
|
|
|
2227
|
+
/**
|
|
2228
|
+
* Updates finalization notes (shared with patient and/or internal only)
|
|
2229
|
+
*
|
|
2230
|
+
* @param appointmentId ID of the appointment
|
|
2231
|
+
* @param sharedNotes Notes to be shared with patient (optional)
|
|
2232
|
+
* @param internalNotes Notes for internal use only (optional)
|
|
2233
|
+
* @returns The updated appointment
|
|
2234
|
+
*/
|
|
2235
|
+
async updateFinalizationNotes(
|
|
2236
|
+
appointmentId: string,
|
|
2237
|
+
sharedNotes?: string | null,
|
|
2238
|
+
internalNotes?: string | null,
|
|
2239
|
+
): Promise<Appointment> {
|
|
2240
|
+
try {
|
|
2241
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
2242
|
+
if (!appointment) {
|
|
2243
|
+
throw new Error(`Appointment ${appointmentId} not found`);
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
const currentMetadata = appointment.metadata || {
|
|
2247
|
+
selectedZones: null,
|
|
2248
|
+
zonePhotos: null,
|
|
2249
|
+
zonesData: null,
|
|
2250
|
+
appointmentProducts: [],
|
|
2251
|
+
extendedProcedures: [],
|
|
2252
|
+
recommendedProcedures: [],
|
|
2253
|
+
finalbilling: null,
|
|
2254
|
+
finalizationNotesShared: null,
|
|
2255
|
+
finalizationNotesInternal: null,
|
|
2256
|
+
finalizationNotes: null,
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
const updateData: UpdateAppointmentData = {
|
|
2260
|
+
metadata: {
|
|
2261
|
+
selectedZones: currentMetadata.selectedZones,
|
|
2262
|
+
zonePhotos: currentMetadata.zonePhotos,
|
|
2263
|
+
zonesData: currentMetadata.zonesData || null,
|
|
2264
|
+
appointmentProducts: currentMetadata.appointmentProducts || [],
|
|
2265
|
+
extendedProcedures: currentMetadata.extendedProcedures || [],
|
|
2266
|
+
recommendedProcedures: currentMetadata.recommendedProcedures || [],
|
|
2267
|
+
finalbilling: currentMetadata.finalbilling,
|
|
2268
|
+
finalizationNotesShared:
|
|
2269
|
+
sharedNotes !== undefined ? sharedNotes : currentMetadata.finalizationNotesShared,
|
|
2270
|
+
finalizationNotesInternal:
|
|
2271
|
+
internalNotes !== undefined ? internalNotes : currentMetadata.finalizationNotesInternal,
|
|
2272
|
+
// Keep deprecated field for backward compatibility during migration
|
|
2273
|
+
finalizationNotes:
|
|
2274
|
+
sharedNotes !== undefined
|
|
2275
|
+
? sharedNotes
|
|
2276
|
+
: currentMetadata.finalizationNotes || currentMetadata.finalizationNotesShared,
|
|
2277
|
+
},
|
|
2278
|
+
updatedAt: serverTimestamp(),
|
|
2279
|
+
};
|
|
2280
|
+
|
|
2281
|
+
return await this.updateAppointment(appointmentId, updateData);
|
|
2282
|
+
} catch (error) {
|
|
2283
|
+
console.error(`[APPOINTMENT_SERVICE] Error updating finalization notes:`, error);
|
|
2284
|
+
throw error;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2126
2288
|
/**
|
|
2127
2289
|
* Gets all next steps recommendations for a patient from their past appointments.
|
|
2128
2290
|
* Returns recommendations with context about which appointment, practitioner, and clinic suggested them.
|
|
@@ -121,7 +121,9 @@ export function initializeMetadata(appointment: Appointment): AppointmentMetadat
|
|
|
121
121
|
extendedProcedures: [],
|
|
122
122
|
recommendedProcedures: [],
|
|
123
123
|
finalbilling: null,
|
|
124
|
-
|
|
124
|
+
finalizationNotesShared: null,
|
|
125
|
+
finalizationNotesInternal: null,
|
|
126
|
+
finalizationNotes: null, // @deprecated - kept for backward compatibility
|
|
125
127
|
}
|
|
126
128
|
);
|
|
127
129
|
}
|
|
@@ -161,6 +163,8 @@ export async function addItemToZoneUtil(
|
|
|
161
163
|
...item,
|
|
162
164
|
parentZone: zoneId, // Set parentZone to the zone key
|
|
163
165
|
subtotal: calculateItemSubtotal(item),
|
|
166
|
+
// Set default visibility to false (privacy-first) if notes exist and visibility not explicitly set
|
|
167
|
+
notesVisibleToPatient: item.notesVisibleToPatient ?? (item.notes ? false : undefined),
|
|
164
168
|
createdAt: now,
|
|
165
169
|
updatedAt: now,
|
|
166
170
|
};
|