feat(report page) - export button added
This commit is contained in:
99
apps/Backend/src/routes/export-payments-reports.ts
Normal file
99
apps/Backend/src/routes/export-payments-reports.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Router } from "express";
|
||||
import type { Request, Response } from "express";
|
||||
import { storage } from "../storage";
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /api/reports/export
|
||||
* query:
|
||||
* - type = patients_with_balance | collections_by_doctor
|
||||
* - from, to = optional ISO date strings (YYYY-MM-DD)
|
||||
* - staffId = required for collections_by_doctor
|
||||
* - format = csv (we expect csv; if missing default to csv)
|
||||
*/
|
||||
function escapeCsvCell(v: any) {
|
||||
if (v === null || v === undefined) return "";
|
||||
const s = String(v).replace(/\r?\n/g, " ");
|
||||
if (s.includes('"') || s.includes(",") || s.includes("\n")) {
|
||||
return `"${s.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
router.get("/export", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const type = String(req.query.type || "");
|
||||
const from = req.query.from ? new Date(String(req.query.from)) : undefined;
|
||||
const to = req.query.to ? new Date(String(req.query.to)) : undefined;
|
||||
const staffId = req.query.staffId ? Number(req.query.staffId) : undefined;
|
||||
const format = String(req.query.format || "csv").toLowerCase();
|
||||
|
||||
if (format !== "csv") {
|
||||
return res.status(400).json({ message: "Only CSV export is supported" });
|
||||
}
|
||||
|
||||
let patientsSummary: any[] = [];
|
||||
|
||||
if (type === "patients_with_balance") {
|
||||
patientsSummary = await storage.fetchAllPatientsWithBalances(from, to);
|
||||
} else if (type === "collections_by_doctor") {
|
||||
if (!staffId || !Number.isFinite(staffId) || staffId <= 0) {
|
||||
return res.status(400).json({ message: "Missing or invalid staffId for collections_by_doctor" });
|
||||
}
|
||||
patientsSummary = await storage.fetchAllPatientsForDoctor(staffId, from, to);
|
||||
} else {
|
||||
return res.status(400).json({ message: "Unsupported report type" });
|
||||
}
|
||||
|
||||
const patientsWithFinancials = await storage.buildExportRowsForPatients(patientsSummary, 5000);
|
||||
|
||||
// Build CSV - flattened rows
|
||||
// columns: patientId, patientName, currentBalance, type, date, procedureCode, billed, paid, adjusted, totalDue, status
|
||||
const header = [
|
||||
"patientId",
|
||||
"patientName",
|
||||
"currentBalance",
|
||||
"type",
|
||||
"date",
|
||||
"procedureCode",
|
||||
"billed",
|
||||
"paid",
|
||||
"adjusted",
|
||||
"totalDue",
|
||||
"status",
|
||||
];
|
||||
|
||||
const lines = [header.join(",")];
|
||||
|
||||
for (const p of patientsWithFinancials) {
|
||||
const name = `${p.firstName ?? ""} ${p.lastName ?? ""}`.trim();
|
||||
for (const fr of p.financialRows) {
|
||||
lines.push(
|
||||
[
|
||||
escapeCsvCell(p.patientId),
|
||||
escapeCsvCell(name),
|
||||
(Number(p.currentBalance ?? 0)).toFixed(2),
|
||||
escapeCsvCell(fr.type),
|
||||
escapeCsvCell(fr.date),
|
||||
escapeCsvCell(fr.procedureCode),
|
||||
(Number(fr.billed ?? 0)).toFixed(2),
|
||||
(Number(fr.paid ?? 0)).toFixed(2),
|
||||
(Number(fr.adjusted ?? 0)).toFixed(2),
|
||||
(Number(fr.totalDue ?? 0)).toFixed(2),
|
||||
escapeCsvCell(fr.status),
|
||||
].join(",")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fname = `report-${type}-${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
res.setHeader("Content-Type", "text/csv; charset=utf-8");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${fname}"`);
|
||||
return res.send(lines.join("\n"));
|
||||
} catch (err: any) {
|
||||
console.error("[/api/reports/export] error:", err?.message ?? err, err?.stack);
|
||||
return res.status(500).json({ message: "Export error" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -14,6 +14,7 @@ import notificationsRoutes from "./notifications";
|
||||
import paymentOcrRoutes from "./paymentOcrExtraction";
|
||||
import cloudStorageRoutes from "./cloud-storage";
|
||||
import paymentsReportsRoutes from "./payments-reports";
|
||||
import exportPaymentsReportsRoutes from "./export-payments-reports";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -32,5 +33,6 @@ router.use("/notifications", notificationsRoutes);
|
||||
router.use("/payment-ocr", paymentOcrRoutes);
|
||||
router.use("/cloud-storage", cloudStorageRoutes);
|
||||
router.use("/payments-reports", paymentsReportsRoutes);
|
||||
router.use("/export-payments-reports", exportPaymentsReportsRoutes);
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user