Spaces:
Sleeping
Sleeping
Updated the load invoice to see the old history of patient
Browse files- api/__pycache__/invoices.cpython-313.pyc +0 -0
- api/invoices.py +14 -8
- frontend/js/app.js +44 -1
- frontend/js/db.js +20 -1
- frontend/js/history.js +28 -3
api/__pycache__/invoices.cpython-313.pyc
CHANGED
|
Binary files a/api/__pycache__/invoices.cpython-313.pyc and b/api/__pycache__/invoices.cpython-313.pyc differ
|
|
|
api/invoices.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from fastapi import APIRouter, Depends
|
| 2 |
from sqlalchemy.orm import Session
|
| 3 |
from datetime import datetime
|
| 4 |
from typing import Optional, List
|
|
@@ -73,18 +73,22 @@ def get_invoice(invoice_id: int, db: Session = Depends(get_db)):
|
|
| 73 |
raise HTTPException(status_code=404, detail="Invoice not found")
|
| 74 |
|
| 75 |
return {
|
|
|
|
| 76 |
"invoice_no": invoice.invoice_number,
|
| 77 |
-
"doctor": invoice.doctor_name,
|
| 78 |
-
"clinic": invoice.clinic_name,
|
| 79 |
-
"patient": invoice.patient_name,
|
| 80 |
-
"total": invoice.total_amount,
|
| 81 |
"date": invoice.date,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
"items": [
|
| 83 |
{
|
| 84 |
"description": item.description,
|
| 85 |
"quantity": item.quantity,
|
| 86 |
-
"
|
| 87 |
-
"
|
| 88 |
} for item in invoice.items
|
| 89 |
]
|
| 90 |
}
|
|
@@ -97,8 +101,10 @@ def get_all_invoices(db: Session = Depends(get_db)):
|
|
| 97 |
"id": inv.id,
|
| 98 |
"invoice_no": inv.invoice_number,
|
| 99 |
"doctor_name": inv.doctor_name,
|
|
|
|
| 100 |
"patient_name": inv.patient_name,
|
| 101 |
"total_amount": inv.total_amount,
|
|
|
|
| 102 |
"date": inv.date
|
| 103 |
} for inv in invoices]
|
| 104 |
|
|
@@ -110,4 +116,4 @@ def delete_invoice(invoice_id: int, db: Session = Depends(get_db)):
|
|
| 110 |
raise HTTPException(status_code=404, detail="Invoice not found")
|
| 111 |
db.delete(db_invoice)
|
| 112 |
db.commit()
|
| 113 |
-
return {"message": "Deleted successfully"}
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 2 |
from sqlalchemy.orm import Session
|
| 3 |
from datetime import datetime
|
| 4 |
from typing import Optional, List
|
|
|
|
| 73 |
raise HTTPException(status_code=404, detail="Invoice not found")
|
| 74 |
|
| 75 |
return {
|
| 76 |
+
"id": invoice.id,
|
| 77 |
"invoice_no": invoice.invoice_number,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
"date": invoice.date,
|
| 79 |
+
"doctor_name": invoice.doctor_name,
|
| 80 |
+
"clinic_name": invoice.clinic_name,
|
| 81 |
+
"patient_name": invoice.patient_name,
|
| 82 |
+
"shade": invoice.shade,
|
| 83 |
+
"total_amount": invoice.total_amount,
|
| 84 |
+
"received_amount": invoice.received_amount,
|
| 85 |
+
"remaining_balance": invoice.remaining_balance,
|
| 86 |
"items": [
|
| 87 |
{
|
| 88 |
"description": item.description,
|
| 89 |
"quantity": item.quantity,
|
| 90 |
+
"price_per_unit": item.price_per_unit,
|
| 91 |
+
"total_price": item.total_price
|
| 92 |
} for item in invoice.items
|
| 93 |
]
|
| 94 |
}
|
|
|
|
| 101 |
"id": inv.id,
|
| 102 |
"invoice_no": inv.invoice_number,
|
| 103 |
"doctor_name": inv.doctor_name,
|
| 104 |
+
"clinic_name": inv.clinic_name,
|
| 105 |
"patient_name": inv.patient_name,
|
| 106 |
"total_amount": inv.total_amount,
|
| 107 |
+
"received_amount": inv.received_amount,
|
| 108 |
"date": inv.date
|
| 109 |
} for inv in invoices]
|
| 110 |
|
|
|
|
| 116 |
raise HTTPException(status_code=404, detail="Invoice not found")
|
| 117 |
db.delete(db_invoice)
|
| 118 |
db.commit()
|
| 119 |
+
return {"message": "Deleted successfully"}
|
frontend/js/app.js
CHANGED
|
@@ -144,7 +144,50 @@ var App = (function() {
|
|
| 144 |
function print() { _preparePrint(); window.print(); }
|
| 145 |
function downloadPDF() { _preparePrint(); window.print(); }
|
| 146 |
function exportExcel() { console.log("Exporting..."); }
|
| 147 |
-
function loadEdit(id) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
// ── Boot ──
|
| 150 |
document.addEventListener("DOMContentLoaded", async function() {
|
|
|
|
| 144 |
function print() { _preparePrint(); window.print(); }
|
| 145 |
function downloadPDF() { _preparePrint(); window.print(); }
|
| 146 |
function exportExcel() { console.log("Exporting..."); }
|
| 147 |
+
async function loadEdit(id) {
|
| 148 |
+
try {
|
| 149 |
+
if (!id || isNaN(id)) {
|
| 150 |
+
if (typeof showToast === 'function') showToast("❌ Invalid invoice id", "error");
|
| 151 |
+
return;
|
| 152 |
+
}
|
| 153 |
+
// Give immediate feedback and switch view
|
| 154 |
+
if (typeof showPage === "function") showPage("invoice");
|
| 155 |
+
if (typeof showToast === 'function') showToast("Loading invoice...", "info");
|
| 156 |
+
if (typeof dbGet !== 'function') throw new Error("dbGet function not found. Check db.js");
|
| 157 |
+
const inv = await dbGet(id);
|
| 158 |
+
|
| 159 |
+
_currentId = inv.id || id;
|
| 160 |
+
if (document.getElementById("inv-number")) {
|
| 161 |
+
document.getElementById("inv-number").value = inv.invoice_no || "Auto-Generated";
|
| 162 |
+
}
|
| 163 |
+
if (document.getElementById("inv-date")) {
|
| 164 |
+
const dateStr = inv.date ? String(inv.date).split("T")[0] : "";
|
| 165 |
+
document.getElementById("inv-date").value = dateStr;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
const doctorEl = document.getElementById("doctor-name");
|
| 169 |
+
const clinicEl = document.getElementById("clinic");
|
| 170 |
+
const patientEl = document.getElementById("patient");
|
| 171 |
+
const shadeEl = document.getElementById("shade");
|
| 172 |
+
const receivedEl = document.getElementById("received-input");
|
| 173 |
+
|
| 174 |
+
if (doctorEl) doctorEl.value = inv.doctor_name || "";
|
| 175 |
+
if (clinicEl) clinicEl.value = inv.clinic_name || "";
|
| 176 |
+
if (patientEl) patientEl.value = inv.patient_name || "";
|
| 177 |
+
if (shadeEl) shadeEl.value = inv.shade || "";
|
| 178 |
+
if (receivedEl) receivedEl.value = (inv.received_amount || 0);
|
| 179 |
+
|
| 180 |
+
if (typeof Rows !== 'undefined' && typeof Rows.load === 'function') {
|
| 181 |
+
Rows.load(inv.items || []);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
if (typeof updateHeaderBadge === 'function') updateHeaderBadge();
|
| 185 |
+
showPage("invoice");
|
| 186 |
+
} catch (err) {
|
| 187 |
+
console.error("Load Error:", err);
|
| 188 |
+
if (typeof showToast === 'function') showToast("❌ Load failed. Check console.", "error");
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
|
| 192 |
// ── Boot ──
|
| 193 |
document.addEventListener("DOMContentLoaded", async function() {
|
frontend/js/db.js
CHANGED
|
@@ -48,6 +48,25 @@ async function dbAll() {
|
|
| 48 |
}
|
| 49 |
}
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
/* Delete one invoice by id from Neon */
|
| 52 |
async function dbDelete(id) {
|
| 53 |
try {
|
|
@@ -60,4 +79,4 @@ async function dbDelete(id) {
|
|
| 60 |
console.error("Delete Error:", error);
|
| 61 |
throw error;
|
| 62 |
}
|
| 63 |
-
}
|
|
|
|
| 48 |
}
|
| 49 |
}
|
| 50 |
|
| 51 |
+
/* Load one invoice by id from Neon */
|
| 52 |
+
async function dbGet(id) {
|
| 53 |
+
try {
|
| 54 |
+
const response = await fetch(`${API_URL}/${id}`);
|
| 55 |
+
if (!response.ok) {
|
| 56 |
+
let detail = "";
|
| 57 |
+
try {
|
| 58 |
+
const err = await response.json();
|
| 59 |
+
detail = err.detail ? ` (${err.detail})` : "";
|
| 60 |
+
} catch (e) {}
|
| 61 |
+
throw new Error(`Could not fetch invoice${detail}`);
|
| 62 |
+
}
|
| 63 |
+
return await response.json();
|
| 64 |
+
} catch (error) {
|
| 65 |
+
console.error("Fetch One Error:", error);
|
| 66 |
+
throw error;
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
/* Delete one invoice by id from Neon */
|
| 71 |
async function dbDelete(id) {
|
| 72 |
try {
|
|
|
|
| 79 |
console.error("Delete Error:", error);
|
| 80 |
throw error;
|
| 81 |
}
|
| 82 |
+
}
|
frontend/js/history.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
var History = (function() {
|
| 6 |
|
| 7 |
var _allInvoices = []; // cache of loaded invoices
|
|
|
|
| 8 |
|
| 9 |
/* Format PKR */
|
| 10 |
function _fmt(n) { return fmt(n); }
|
|
@@ -29,8 +30,8 @@ var History = (function() {
|
|
| 29 |
'<div style="font-size:0.75rem;color:var(--green);text-align:right;margin-top:2px">Rcvd: ' + _fmt(inv.received_amount || 0) + '</div>',
|
| 30 |
'</div>',
|
| 31 |
'<div class="hist-actions">',
|
| 32 |
-
'<button class="btn-xs btn-xs-blue"
|
| 33 |
-
'<button class="btn-xs btn-xs-red"
|
| 34 |
'</div>',
|
| 35 |
'</div>',
|
| 36 |
].join("");
|
|
@@ -84,6 +85,9 @@ var History = (function() {
|
|
| 84 |
function _render(list) {
|
| 85 |
var container = document.getElementById("hist-list");
|
| 86 |
var count = document.getElementById("history-count");
|
|
|
|
|
|
|
|
|
|
| 87 |
count.textContent = list.length + " invoice" + (list.length !== 1 ? "s" : "");
|
| 88 |
|
| 89 |
if (list.length === 0) {
|
|
@@ -94,6 +98,27 @@ var History = (function() {
|
|
| 94 |
}
|
| 95 |
}
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
return { load: load, filter: filter, deleteInvoice: deleteInvoice, getById: getById };
|
| 98 |
|
| 99 |
-
})();
|
|
|
|
| 5 |
var History = (function() {
|
| 6 |
|
| 7 |
var _allInvoices = []; // cache of loaded invoices
|
| 8 |
+
var _boundClicks = false;
|
| 9 |
|
| 10 |
/* Format PKR */
|
| 11 |
function _fmt(n) { return fmt(n); }
|
|
|
|
| 30 |
'<div style="font-size:0.75rem;color:var(--green);text-align:right;margin-top:2px">Rcvd: ' + _fmt(inv.received_amount || 0) + '</div>',
|
| 31 |
'</div>',
|
| 32 |
'<div class="hist-actions">',
|
| 33 |
+
'<button class="btn-xs btn-xs-blue" data-action="load" data-id="' + inv.id + '">✏️ Load</button>',
|
| 34 |
+
'<button class="btn-xs btn-xs-red" data-action="delete" data-id="' + inv.id + '">🗑️</button>',
|
| 35 |
'</div>',
|
| 36 |
'</div>',
|
| 37 |
].join("");
|
|
|
|
| 85 |
function _render(list) {
|
| 86 |
var container = document.getElementById("hist-list");
|
| 87 |
var count = document.getElementById("history-count");
|
| 88 |
+
if (!container || !count) return;
|
| 89 |
+
|
| 90 |
+
_bindClicks();
|
| 91 |
count.textContent = list.length + " invoice" + (list.length !== 1 ? "s" : "");
|
| 92 |
|
| 93 |
if (list.length === 0) {
|
|
|
|
| 98 |
}
|
| 99 |
}
|
| 100 |
|
| 101 |
+
function _bindClicks() {
|
| 102 |
+
if (_boundClicks) return;
|
| 103 |
+
document.addEventListener("click", function(e) {
|
| 104 |
+
var btn = e.target.closest("button[data-action]");
|
| 105 |
+
if (!btn) return;
|
| 106 |
+
var id = parseInt(btn.getAttribute("data-id"), 10);
|
| 107 |
+
var action = btn.getAttribute("data-action");
|
| 108 |
+
if (action === "load") {
|
| 109 |
+
if (typeof showToast === "function") showToast("Loading invoice...", "info");
|
| 110 |
+
if (typeof App !== "undefined" && typeof App.loadEdit === "function") {
|
| 111 |
+
App.loadEdit(id);
|
| 112 |
+
} else if (typeof showToast === "function") {
|
| 113 |
+
showToast("Load handler missing", "error");
|
| 114 |
+
}
|
| 115 |
+
} else if (action === "delete") {
|
| 116 |
+
deleteInvoice(id);
|
| 117 |
+
}
|
| 118 |
+
});
|
| 119 |
+
_boundClicks = true;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
return { load: load, filter: filter, deleteInvoice: deleteInvoice, getById: getById };
|
| 123 |
|
| 124 |
+
})();
|