fix: D0140 claim sync, schedule column prefill, multi-appt, DentaQuest OTP session
- Fix Express route ordering in appointments-procedures so /prefill-from-appointment is matched before /:id (D0140 and other codes now always reach the claim) - claim-form: always fetch AppointmentProcedure records when an existing claim loads so post-save procedure edits (e.g. adding D0140) are reflected immediately - appointments-page: replace sessionStorage with React state (newApptPrefill) for slot-click prefill so columns B-F correctly carry their staffId into the form - add-appointment-modal / appointment-form: thread prefillData prop; add NewAppointmentPrefill interface; useEffect applies values via setValue - appointments upsert: remove per-patient dedup so the same patient can have multiple appointments on the same day in the same column - DentaQuest / TuftsSCO: navigate to about:blank and minimize instead of quit_driver after each run — session cookie stays in memory so OTP is only required once per app startup, not on every eligibility or claim check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,25 +8,7 @@ import {
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /api/appointment-procedures/:appointmentId
|
||||
* Get all procedures for an appointment
|
||||
*/
|
||||
router.get("/:appointmentId", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const appointmentId = Number(req.params.appointmentId);
|
||||
if (isNaN(appointmentId)) {
|
||||
return res.status(400).json({ message: "Invalid appointmentId" });
|
||||
}
|
||||
|
||||
const rows = await storage.getByAppointmentId(appointmentId);
|
||||
|
||||
return res.json(rows);
|
||||
} catch (err: any) {
|
||||
console.error("GET appointment procedures error", err);
|
||||
return res.status(500).json({ message: err.message ?? "Server error" });
|
||||
}
|
||||
});
|
||||
// Specific routes must be registered before generic /:id routes to avoid shadowing.
|
||||
|
||||
router.get(
|
||||
"/prefill-from-appointment/:appointmentId",
|
||||
@@ -54,6 +36,26 @@ router.get(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/appointment-procedures/:appointmentId
|
||||
* Get all procedures for an appointment
|
||||
*/
|
||||
router.get("/:appointmentId", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const appointmentId = Number(req.params.appointmentId);
|
||||
if (isNaN(appointmentId)) {
|
||||
return res.status(400).json({ message: "Invalid appointmentId" });
|
||||
}
|
||||
|
||||
const rows = await storage.getByAppointmentId(appointmentId);
|
||||
|
||||
return res.json(rows);
|
||||
} catch (err: any) {
|
||||
console.error("GET appointment procedures error", err);
|
||||
return res.status(500).json({ message: err.message ?? "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /api/appointment-procedures/set-npi-provider/:appointmentId
|
||||
* Set the npiProviderId on all procedures for an appointment (lightweight update).
|
||||
@@ -196,26 +198,6 @@ router.put("/:id", async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/appointment-procedures/:id
|
||||
* Delete single procedure
|
||||
*/
|
||||
router.delete("/:id", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({ message: "Invalid id" });
|
||||
}
|
||||
|
||||
await storage.deleteProcedure(id);
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err: any) {
|
||||
console.error("DELETE appointment procedure error", err);
|
||||
return res.status(500).json({ message: err.message ?? "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/appointment-procedures/clear/:appointmentId
|
||||
* Clear all procedures for appointment
|
||||
@@ -236,4 +218,24 @@ router.delete("/clear/:appointmentId", async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/appointment-procedures/:id
|
||||
* Delete single procedure
|
||||
*/
|
||||
router.delete("/:id", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({ message: "Invalid id" });
|
||||
}
|
||||
|
||||
await storage.deleteProcedure(id);
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err: any) {
|
||||
console.error("DELETE appointment procedure error", err);
|
||||
return res.status(500).json({ message: err.message ?? "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -193,20 +193,13 @@ router.post(
|
||||
const originalStartTime = appointmentData.startTime;
|
||||
const MAX_END_TIME = "18:30";
|
||||
|
||||
// 1. Verify patient exists and belongs to user
|
||||
// 1. Verify patient exists
|
||||
const patient = await storage.getPatient(appointmentData.patientId);
|
||||
if (!patient) {
|
||||
return res.status(404).json({ message: "Patient not found" });
|
||||
}
|
||||
|
||||
// 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
|
||||
// 2. Find the next available slot for this staff member (multiple same-patient same-day bookings allowed)
|
||||
let [hour, minute] = originalStartTime.split(":").map(Number);
|
||||
const pad = (n: number) => n.toString().padStart(2, "0");
|
||||
|
||||
@@ -216,45 +209,24 @@ router.post(
|
||||
while (`${pad(hour)}:${pad(minute)}` <= MAX_END_TIME) {
|
||||
const currentStartTime = `${pad(hour)}:${pad(minute)}`;
|
||||
|
||||
// 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,
|
||||
existingPatientAppointment?.id
|
||||
undefined
|
||||
);
|
||||
|
||||
if (!staffConflict) {
|
||||
const endMinute = minute + APPT_DURATION_MINUTES;
|
||||
let endHour = hour + Math.floor(endMinute / 60);
|
||||
let realEndMinute = endMinute % 60;
|
||||
|
||||
const endHour = hour + Math.floor(endMinute / 60);
|
||||
const realEndMinute = endMinute % 60;
|
||||
const currentEndTime = `${pad(endHour)}:${pad(realEndMinute)}`;
|
||||
|
||||
const payload = {
|
||||
const created = await storage.createAppointment({
|
||||
...appointmentData,
|
||||
startTime: currentStartTime,
|
||||
endTime: currentEndTime,
|
||||
};
|
||||
|
||||
if (existingPatientAppointment?.id !== undefined) {
|
||||
// Replace the existing appointment in-place (preserves linked claims/procedures)
|
||||
const updated = await storage.updateAppointment(
|
||||
existingPatientAppointment.id,
|
||||
payload
|
||||
);
|
||||
return res.status(200).json({
|
||||
...updated,
|
||||
originalRequestedTime: originalStartTime,
|
||||
finalScheduledTime: currentStartTime,
|
||||
message:
|
||||
originalStartTime !== currentStartTime
|
||||
? `Your requested time (${originalStartTime}) was unavailable. Appointment was updated to ${currentStartTime}.`
|
||||
: `Appointment successfully updated at ${currentStartTime}.`,
|
||||
});
|
||||
}
|
||||
|
||||
const created = await storage.createAppointment(payload);
|
||||
});
|
||||
return res.status(201).json({
|
||||
...created,
|
||||
originalRequestedTime: originalStartTime,
|
||||
@@ -266,7 +238,6 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
// Move to next STEP_MINUTES slot
|
||||
minute += STEP_MINUTES;
|
||||
if (minute >= 60) {
|
||||
hour += Math.floor(minute / 60);
|
||||
@@ -275,8 +246,7 @@ router.post(
|
||||
}
|
||||
|
||||
return res.status(409).json({
|
||||
message:
|
||||
"No available slots remaining until 6:30 PM for this Staff. Please choose another day.",
|
||||
message: "No available slots remaining until 6:30 PM for this staff member. Please choose another day.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in upsert appointment:", error);
|
||||
|
||||
Reference in New Issue
Block a user