@blackcode_sa/metaestetics-api 1.14.43 → 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 -3
- package/dist/admin/index.d.ts +21 -3
- package/dist/index.d.mts +53 -3
- package/dist/index.d.ts +53 -3
- package/dist/index.js +184 -11
- package/dist/index.mjs +184 -11
- 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 -3
- package/src/validations/appointment.schema.ts +10 -2
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,6 +3823,7 @@ 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
3829
|
createdAt: z3.string().optional(),
|
|
@@ -3880,7 +3887,10 @@ var appointmentMetadataSchema = z3.object({
|
|
|
3880
3887
|
recommendedProcedures: z3.array(recommendedProcedureSchema).optional().default([]),
|
|
3881
3888
|
zoneBilling: z3.record(z3.string(), billingPerZoneSchema).nullable().optional(),
|
|
3882
3889
|
finalbilling: finalBillingSchema.nullable(),
|
|
3883
|
-
|
|
3890
|
+
finalizationNotesShared: z3.string().nullable().optional(),
|
|
3891
|
+
finalizationNotesInternal: z3.string().nullable().optional(),
|
|
3892
|
+
finalizationNotes: z3.string().nullable().optional()
|
|
3893
|
+
// @deprecated - kept for backward compatibility
|
|
3884
3894
|
});
|
|
3885
3895
|
var createAppointmentSchema = z3.object({
|
|
3886
3896
|
clinicBranchId: z3.string().min(MIN_STRING_LENGTH, "Clinic branch ID is required"),
|
|
@@ -4712,10 +4722,14 @@ function initializeMetadata(appointment) {
|
|
|
4712
4722
|
extendedProcedures: [],
|
|
4713
4723
|
recommendedProcedures: [],
|
|
4714
4724
|
finalbilling: null,
|
|
4725
|
+
finalizationNotesShared: null,
|
|
4726
|
+
finalizationNotesInternal: null,
|
|
4715
4727
|
finalizationNotes: null
|
|
4728
|
+
// @deprecated - kept for backward compatibility
|
|
4716
4729
|
};
|
|
4717
4730
|
}
|
|
4718
4731
|
async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
4732
|
+
var _a;
|
|
4719
4733
|
validateZoneKeyFormat(zoneId);
|
|
4720
4734
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
4721
4735
|
const metadata = initializeMetadata(appointment);
|
|
@@ -4729,6 +4743,8 @@ async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
|
4729
4743
|
parentZone: zoneId,
|
|
4730
4744
|
// Set parentZone to the zone key
|
|
4731
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,
|
|
4732
4748
|
createdAt: now,
|
|
4733
4749
|
updatedAt: now
|
|
4734
4750
|
};
|
|
@@ -5228,7 +5244,17 @@ async function getRecommendedProceduresUtil(db, appointmentId) {
|
|
|
5228
5244
|
|
|
5229
5245
|
// src/services/appointment/utils/zone-photo.utils.ts
|
|
5230
5246
|
import { updateDoc as updateDoc6, serverTimestamp as serverTimestamp6, doc as doc9 } from "firebase/firestore";
|
|
5231
|
-
|
|
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) {
|
|
5232
5258
|
var _a;
|
|
5233
5259
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
5234
5260
|
const zonePhotos = (_a = appointment.metadata) == null ? void 0 : _a.zonePhotos;
|
|
@@ -5239,11 +5265,12 @@ async function updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, u
|
|
|
5239
5265
|
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
5240
5266
|
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
|
|
5241
5267
|
}
|
|
5268
|
+
const updatesWithAudit = doctorId ? addVisibilityAudit(updates, doctorId) : updates;
|
|
5242
5269
|
const updatedZonePhotos = { ...zonePhotos };
|
|
5243
5270
|
updatedZonePhotos[zoneId] = [...zoneArray];
|
|
5244
5271
|
updatedZonePhotos[zoneId][photoIndex] = {
|
|
5245
5272
|
...zoneArray[photoIndex],
|
|
5246
|
-
...
|
|
5273
|
+
...updatesWithAudit
|
|
5247
5274
|
};
|
|
5248
5275
|
const appointmentRef = doc9(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
5249
5276
|
await updateDoc6(appointmentRef, {
|
|
@@ -5287,6 +5314,20 @@ async function getZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex) {
|
|
|
5287
5314
|
}
|
|
5288
5315
|
return zoneArray[photoIndex];
|
|
5289
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
|
+
}
|
|
5290
5331
|
|
|
5291
5332
|
// src/services/appointment/appointment.service.ts
|
|
5292
5333
|
var AppointmentService = class extends BaseService {
|
|
@@ -6153,6 +6194,7 @@ var AppointmentService = class extends BaseService {
|
|
|
6153
6194
|
* @returns The updated appointment
|
|
6154
6195
|
*/
|
|
6155
6196
|
async updateAppointmentZonePhoto(appointmentId, zoneId, photoType, mediaMetadata, notes) {
|
|
6197
|
+
var _a, _b, _c, _d;
|
|
6156
6198
|
try {
|
|
6157
6199
|
console.log(
|
|
6158
6200
|
`[APPOINTMENT_SERVICE] Updating appointment metadata for ${photoType} photo in zone ${zoneId}`
|
|
@@ -6170,7 +6212,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6170
6212
|
recommendedProcedures: [],
|
|
6171
6213
|
zoneBilling: null,
|
|
6172
6214
|
finalbilling: null,
|
|
6215
|
+
finalizationNotesShared: null,
|
|
6216
|
+
finalizationNotesInternal: null,
|
|
6173
6217
|
finalizationNotes: null
|
|
6218
|
+
// @deprecated - kept for backward compatibility
|
|
6174
6219
|
};
|
|
6175
6220
|
let currentZonePhotos = {};
|
|
6176
6221
|
if (currentMetadata.zonePhotos) {
|
|
@@ -6185,7 +6230,11 @@ var AppointmentService = class extends BaseService {
|
|
|
6185
6230
|
before: oldData.before || null,
|
|
6186
6231
|
after: oldData.after || null,
|
|
6187
6232
|
beforeNote: null,
|
|
6188
|
-
afterNote: null
|
|
6233
|
+
afterNote: null,
|
|
6234
|
+
showToPatient: false,
|
|
6235
|
+
// Default: not visible to patient (privacy-first)
|
|
6236
|
+
beforeNoteVisibleToPatient: false,
|
|
6237
|
+
afterNoteVisibleToPatient: false
|
|
6189
6238
|
}
|
|
6190
6239
|
];
|
|
6191
6240
|
}
|
|
@@ -6198,7 +6247,13 @@ var AppointmentService = class extends BaseService {
|
|
|
6198
6247
|
before: photoType === "before" ? mediaMetadata.url : null,
|
|
6199
6248
|
after: photoType === "after" ? mediaMetadata.url : null,
|
|
6200
6249
|
beforeNote: photoType === "before" ? notes || null : null,
|
|
6201
|
-
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
|
|
6202
6257
|
};
|
|
6203
6258
|
currentZonePhotos[zoneId] = [...currentZonePhotos[zoneId], newEntry];
|
|
6204
6259
|
if (currentZonePhotos[zoneId].length > 10) {
|
|
@@ -6217,7 +6272,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6217
6272
|
zoneBilling: currentMetadata.zoneBilling
|
|
6218
6273
|
},
|
|
6219
6274
|
finalbilling: currentMetadata.finalbilling,
|
|
6220
|
-
|
|
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
|
|
6221
6279
|
},
|
|
6222
6280
|
updatedAt: serverTimestamp7()
|
|
6223
6281
|
};
|
|
@@ -6271,7 +6329,7 @@ var AppointmentService = class extends BaseService {
|
|
|
6271
6329
|
* @returns The updated appointment
|
|
6272
6330
|
*/
|
|
6273
6331
|
async deleteZonePhoto(appointmentId, zoneId, photoIndex) {
|
|
6274
|
-
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;
|
|
6275
6333
|
try {
|
|
6276
6334
|
console.log(
|
|
6277
6335
|
`[APPOINTMENT_SERVICE] Deleting zone photo index ${photoIndex} for zone ${zoneId} in appointment ${appointmentId}`
|
|
@@ -6328,7 +6386,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6328
6386
|
zoneBilling: appointment.metadata.zoneBilling
|
|
6329
6387
|
},
|
|
6330
6388
|
finalbilling: ((_h = appointment.metadata) == null ? void 0 : _h.finalbilling) || null,
|
|
6331
|
-
|
|
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
|
|
6332
6393
|
},
|
|
6333
6394
|
updatedAt: serverTimestamp7()
|
|
6334
6395
|
};
|
|
@@ -6527,7 +6588,7 @@ var AppointmentService = class extends BaseService {
|
|
|
6527
6588
|
* @returns The updated appointment with recalculated billing
|
|
6528
6589
|
*/
|
|
6529
6590
|
async recalculateFinalBilling(appointmentId, taxRate) {
|
|
6530
|
-
var _a;
|
|
6591
|
+
var _a, _b, _c, _d, _e;
|
|
6531
6592
|
try {
|
|
6532
6593
|
console.log(
|
|
6533
6594
|
`[APPOINTMENT_SERVICE] Recalculating final billing for appointment ${appointmentId}`
|
|
@@ -6549,7 +6610,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6549
6610
|
extendedProcedures: [],
|
|
6550
6611
|
recommendedProcedures: [],
|
|
6551
6612
|
finalbilling: null,
|
|
6613
|
+
finalizationNotesShared: null,
|
|
6614
|
+
finalizationNotesInternal: null,
|
|
6552
6615
|
finalizationNotes: null
|
|
6616
|
+
// @deprecated
|
|
6553
6617
|
};
|
|
6554
6618
|
const shouldUpdatePaymentStatus = finalbilling.finalPrice > 0 && appointment.paymentStatus === "not_applicable" /* NOT_APPLICABLE */;
|
|
6555
6619
|
const updateData = {
|
|
@@ -6565,7 +6629,10 @@ var AppointmentService = class extends BaseService {
|
|
|
6565
6629
|
zoneBilling: currentMetadata.zoneBilling
|
|
6566
6630
|
},
|
|
6567
6631
|
finalbilling,
|
|
6568
|
-
|
|
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
|
|
6569
6636
|
},
|
|
6570
6637
|
...shouldUpdatePaymentStatus && {
|
|
6571
6638
|
paymentStatus: "unpaid" /* UNPAID */
|
|
@@ -6765,6 +6832,64 @@ var AppointmentService = class extends BaseService {
|
|
|
6765
6832
|
throw error;
|
|
6766
6833
|
}
|
|
6767
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
|
+
}
|
|
6768
6893
|
/**
|
|
6769
6894
|
* Gets a specific photo entry from a zone
|
|
6770
6895
|
*
|
|
@@ -6784,6 +6909,54 @@ var AppointmentService = class extends BaseService {
|
|
|
6784
6909
|
throw error;
|
|
6785
6910
|
}
|
|
6786
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
|
+
}
|
|
6787
6960
|
/**
|
|
6788
6961
|
* Gets all next steps recommendations for a patient from their past appointments.
|
|
6789
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
|
};
|