feat: add attachment upload to Edit Appointment form (disk, scanned, screenshot with crop)

Lets staff attach files to an appointment directly from the edit form via
local disk upload, picking a previously-saved scanned attachment, or an
inline screenshot with crop-before-save (matching the Copy Agent page's
crop UX), instead of detouring through the Copy Agent page. Also fixes a
latent data-loss bug in claim-form's save-for-appointment call, which
replaced all AppointmentFile rows instead of merging with existing ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 14:55:38 -04:00
co-authored by Claude Sonnet 5
parent f29064883c
commit e49dfdbee9
6 changed files with 499 additions and 2 deletions
@@ -46,6 +46,10 @@ export interface IAppointmentProceduresStorage {
deleteProcedure(id: number): Promise<void>;
clearByAppointmentId(appointmentId: number): Promise<void>;
getAppointmentFiles(appointmentId: number): Promise<AppointmentFileMeta[]>;
addAppointmentFile(
appointmentId: number,
file: AppointmentFileMeta
): Promise<AppointmentFileMeta & { id: number }>;
getAppointmentIdsWithProcedures(ids: number[]): Promise<Set<number>>;
getProcedureCodesByAppointmentIds(ids: number[]): Promise<Map<number, string[]>>;
}
@@ -201,4 +205,24 @@ export const appointmentProceduresStorage: IAppointmentProceduresStorage = {
filePath: f.filePath,
}));
},
async addAppointmentFile(
appointmentId: number,
file: AppointmentFileMeta
): Promise<AppointmentFileMeta & { id: number }> {
const row = await db.appointmentFile.create({
data: {
appointmentId,
filename: file.filename,
mimeType: file.mimeType ?? null,
filePath: file.filePath ?? null,
},
});
return {
id: row.id,
filename: row.filename,
mimeType: row.mimeType,
filePath: row.filePath,
};
},
};