feat: cloud storage updates, claims storage, appointment and insurance routes
This commit is contained in:
@@ -186,31 +186,29 @@ router.post(
|
||||
return res.status(404).json({ message: "Patient not found" });
|
||||
}
|
||||
|
||||
// 2. Attempt to find the next available slot
|
||||
// 2. One patient per column per day: find existing appointment for this patient in the same staff column today
|
||||
const existingPatientAppointment = await storage.getPatientAppointmentByDateAndStaff(
|
||||
appointmentData.patientId,
|
||||
appointmentData.date,
|
||||
appointmentData.staffId
|
||||
);
|
||||
|
||||
// 3. Attempt to find the next available slot
|
||||
let [hour, minute] = originalStartTime.split(":").map(Number);
|
||||
const pad = (n: number) => n.toString().padStart(2, "0");
|
||||
|
||||
// Step by 15 minutes to support quarter-hour starts, but keep appointment duration 30 mins
|
||||
const STEP_MINUTES = 15;
|
||||
const APPT_DURATION_MINUTES = 30;
|
||||
|
||||
while (`${pad(hour)}:${pad(minute)}` <= MAX_END_TIME) {
|
||||
const currentStartTime = `${pad(hour)}:${pad(minute)}`;
|
||||
|
||||
// Check patient appointment at this time
|
||||
const sameDayAppointment =
|
||||
await storage.getPatientAppointmentByDateTime(
|
||||
appointmentData.patientId,
|
||||
appointmentData.date,
|
||||
currentStartTime
|
||||
);
|
||||
|
||||
// Check staff conflict at this time
|
||||
// Check staff conflict at this time (exclude the patient's existing appointment so it can move)
|
||||
const staffConflict = await storage.getStaffAppointmentByDateTime(
|
||||
appointmentData.staffId,
|
||||
appointmentData.date,
|
||||
currentStartTime,
|
||||
sameDayAppointment?.id // Ignore self if updating
|
||||
existingPatientAppointment?.id
|
||||
);
|
||||
|
||||
if (!staffConflict) {
|
||||
@@ -226,14 +224,13 @@ router.post(
|
||||
endTime: currentEndTime,
|
||||
};
|
||||
|
||||
let responseData;
|
||||
|
||||
if (sameDayAppointment?.id !== undefined) {
|
||||
if (existingPatientAppointment?.id !== undefined) {
|
||||
// Replace the existing appointment in-place (preserves linked claims/procedures)
|
||||
const updated = await storage.updateAppointment(
|
||||
sameDayAppointment.id,
|
||||
existingPatientAppointment.id,
|
||||
payload
|
||||
);
|
||||
responseData = {
|
||||
return res.status(200).json({
|
||||
...updated,
|
||||
originalRequestedTime: originalStartTime,
|
||||
finalScheduledTime: currentStartTime,
|
||||
@@ -241,12 +238,11 @@ router.post(
|
||||
originalStartTime !== currentStartTime
|
||||
? `Your requested time (${originalStartTime}) was unavailable. Appointment was updated to ${currentStartTime}.`
|
||||
: `Appointment successfully updated at ${currentStartTime}.`,
|
||||
};
|
||||
return res.status(200).json(responseData);
|
||||
});
|
||||
}
|
||||
|
||||
const created = await storage.createAppointment(payload);
|
||||
responseData = {
|
||||
return res.status(201).json({
|
||||
...created,
|
||||
originalRequestedTime: originalStartTime,
|
||||
finalScheduledTime: currentStartTime,
|
||||
@@ -254,8 +250,7 @@ router.post(
|
||||
originalStartTime !== currentStartTime
|
||||
? `Your requested time (${originalStartTime}) was unavailable. Appointment was scheduled at ${currentStartTime}.`
|
||||
: `Appointment successfully scheduled at ${currentStartTime}.`,
|
||||
};
|
||||
return res.status(201).json(responseData);
|
||||
});
|
||||
}
|
||||
|
||||
// Move to next STEP_MINUTES slot
|
||||
|
||||
@@ -351,28 +351,6 @@ router.post(
|
||||
return sendError(res, 400, "Invalid file id");
|
||||
|
||||
try {
|
||||
// Ask storage for the file (includes chunks in your implementation)
|
||||
const file = await storage.getFile(id);
|
||||
if (!file) return sendError(res, 404, "File not found");
|
||||
|
||||
// Sum chunks' sizes (storage.getFile returns chunks ordered by seq in your impl)
|
||||
const chunks = (file as any).chunks ?? [];
|
||||
if (!chunks.length) return sendError(res, 400, "No chunks uploaded");
|
||||
|
||||
let total = 0;
|
||||
for (const c of chunks) {
|
||||
// c.data is Bytes / Buffer-like
|
||||
total += c.data.length;
|
||||
// early bailout
|
||||
if (total > MAX_FILE_BYTES) {
|
||||
return sendError(
|
||||
res,
|
||||
413,
|
||||
`Assembled file is too large (${Math.round(total / 1024 / 1024)} MB). Max allowed is ${MAX_FILE_MB} MB.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await storage.finalizeFileUpload(id);
|
||||
return res.json({ error: false, data: result });
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -215,7 +215,7 @@ router.post(
|
||||
router.post(
|
||||
"/appointments/check-all-eligibilities",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
// Query param: date=YYYY-MM-DD (required)
|
||||
// Query param: date=YYYY-MM-DD (required), staffIds=1,2,3 (optional)
|
||||
const date = String(req.query.date ?? "").trim();
|
||||
if (!date) {
|
||||
return res
|
||||
@@ -223,6 +223,11 @@ router.post(
|
||||
.json({ error: "Missing date query param (YYYY-MM-DD)" });
|
||||
}
|
||||
|
||||
const staffIdsRaw = String(req.query.staffIds ?? "").trim();
|
||||
const staffIdFilter: Set<number> | null = staffIdsRaw
|
||||
? new Set(staffIdsRaw.split(",").map(Number).filter((n) => !isNaN(n) && n > 0))
|
||||
: null;
|
||||
|
||||
if (!req.user || !req.user.id) {
|
||||
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
||||
}
|
||||
@@ -232,16 +237,21 @@ router.post(
|
||||
|
||||
try {
|
||||
// 1) fetch appointments for the day (reuse your storage API)
|
||||
const dayAppointments = await storage.getAppointmentsByDateForUser(
|
||||
const allDayAppointments = await storage.getAppointmentsByDateForUser(
|
||||
date,
|
||||
req.user.id
|
||||
);
|
||||
if (!Array.isArray(dayAppointments)) {
|
||||
if (!Array.isArray(allDayAppointments)) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Failed to load appointments for date" });
|
||||
}
|
||||
|
||||
// Filter by selected staff columns if provided
|
||||
const dayAppointments = staffIdFilter && staffIdFilter.size > 0
|
||||
? allDayAppointments.filter((a) => staffIdFilter.has(Number(a.staffId)))
|
||||
: allDayAppointments;
|
||||
|
||||
const results: Array<any> = [];
|
||||
|
||||
// process sequentially so selenium agent / python semaphore isn't overwhelmed
|
||||
|
||||
Reference in New Issue
Block a user