From 9525f94aedd7bd59c458cbf1c372c04b85d9087a Mon Sep 17 00:00:00 2001 From: def Date: Sun, 8 Mar 2026 19:22:17 +0000 Subject: [PATCH] Add v0.1.7 payment edit page with invoice recalculation --- VERSION | 2 +- backend/app.py | 187 ++++++++++++++++++++++++++++++----- templates/payments/edit.html | 107 ++++++++++++++++++++ templates/payments/list.html | 2 + 4 files changed, 271 insertions(+), 27 deletions(-) create mode 100644 templates/payments/edit.html diff --git a/VERSION b/VERSION index c946ee6..1180819 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.6 +0.1.7 diff --git a/backend/app.py b/backend/app.py index 58c1b21..21ea6fb 100644 --- a/backend/app.py +++ b/backend/app.py @@ -65,6 +65,56 @@ def refresh_overdue_invoices(): conn.commit() conn.close() +def recalc_invoice_totals(invoice_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + + cursor.execute(""" + SELECT total_amount, due_at + FROM invoices + WHERE id = %s + """, (invoice_id,)) + invoice = cursor.fetchone() + + if not invoice: + conn.close() + return + + cursor.execute(""" + SELECT COALESCE(SUM(payment_amount), 0) AS total_paid + FROM payments + WHERE invoice_id = %s + AND payment_status = 'confirmed' + """, (invoice_id,)) + total_paid = float(cursor.fetchone()["total_paid"]) + + total_amount = float(invoice["total_amount"]) + + if total_paid >= total_amount and total_amount > 0: + new_status = "paid" + paid_at_clause = ", paid_at = UTC_TIMESTAMP()" + elif total_paid > 0: + new_status = "partial" + paid_at_clause = ", paid_at = NULL" + else: + if invoice["due_at"] and invoice["due_at"] < datetime.utcnow(): + new_status = "overdue" + else: + new_status = "pending" + paid_at_clause = ", paid_at = NULL" + + update_cursor = conn.cursor() + update_cursor.execute(f""" + UPDATE invoices + SET amount_paid = %s, + status = %s + {paid_at_clause} + WHERE id = %s + """, (total_paid, new_status, invoice_id)) + + conn.commit() + conn.close() + @app.template_filter("localtime") def localtime_filter(value): return fmt_local(value) @@ -776,7 +826,7 @@ def new_payment(): form_data=form_data, ) - cursor.execute("SELECT client_id, total_amount, amount_paid FROM invoices WHERE id = %s", (invoice_id,)) + cursor.execute("SELECT client_id FROM invoices WHERE id = %s", (invoice_id,)) invoice = cursor.fetchone() if not invoice: @@ -784,14 +834,6 @@ def new_payment(): return "Invoice not found", 404 client_id = invoice["client_id"] - new_amount_paid = float(invoice["amount_paid"]) + amount_value - - if new_amount_paid >= float(invoice["total_amount"]): - new_status = "paid" - elif new_amount_paid > 0: - new_status = "partial" - else: - new_status = "pending" insert_cursor = conn.cursor() insert_cursor.execute(""" @@ -826,26 +868,11 @@ def new_payment(): notes or None )) - update_cursor = conn.cursor() - if new_status == "paid": - update_cursor.execute(""" - UPDATE invoices - SET amount_paid = %s, - status = %s, - paid_at = UTC_TIMESTAMP() - WHERE id = %s - """, (new_amount_paid, new_status, invoice_id)) - else: - update_cursor.execute(""" - UPDATE invoices - SET amount_paid = %s, - status = %s - WHERE id = %s - """, (new_amount_paid, new_status, invoice_id)) - conn.commit() conn.close() + recalc_invoice_totals(invoice_id) + return redirect("/payments") cursor.execute(""" @@ -871,5 +898,113 @@ def new_payment(): form_data={}, ) +@app.route("/payments/edit/", methods=["GET", "POST"]) +def edit_payment(payment_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + + cursor.execute(""" + SELECT + p.*, + i.invoice_number, + c.client_code, + c.company_name + FROM payments p + JOIN invoices i ON p.invoice_id = i.id + JOIN clients c ON p.client_id = c.id + WHERE p.id = %s + """, (payment_id,)) + payment = cursor.fetchone() + + if not payment: + conn.close() + return "Payment not found", 404 + + if request.method == "POST": + payment_method = request.form.get("payment_method", "").strip() + payment_currency = request.form.get("payment_currency", "").strip() + payment_amount = request.form.get("payment_amount", "").strip() + cad_value_at_payment = request.form.get("cad_value_at_payment", "").strip() + reference = request.form.get("reference", "").strip() + sender_name = request.form.get("sender_name", "").strip() + txid = request.form.get("txid", "").strip() + wallet_address = request.form.get("wallet_address", "").strip() + notes = request.form.get("notes", "").strip() + + errors = [] + + if not payment_method: + errors.append("Payment method is required.") + if not payment_currency: + errors.append("Payment currency is required.") + if not payment_amount: + errors.append("Payment amount is required.") + if not cad_value_at_payment: + errors.append("CAD value at payment is required.") + + if not errors: + try: + amount_value = float(payment_amount) + if amount_value <= 0: + errors.append("Payment amount must be greater than zero.") + except ValueError: + errors.append("Payment amount must be a valid number.") + + try: + cad_value = float(cad_value_at_payment) + if cad_value < 0: + errors.append("CAD value at payment cannot be negative.") + except ValueError: + errors.append("CAD value at payment must be a valid number.") + + if errors: + payment["payment_method"] = payment_method or payment["payment_method"] + payment["payment_currency"] = payment_currency or payment["payment_currency"] + payment["payment_amount"] = payment_amount or payment["payment_amount"] + payment["cad_value_at_payment"] = cad_value_at_payment or payment["cad_value_at_payment"] + payment["reference"] = reference + payment["sender_name"] = sender_name + payment["txid"] = txid + payment["wallet_address"] = wallet_address + payment["notes"] = notes + conn.close() + return render_template("payments/edit.html", payment=payment, errors=errors) + + update_cursor = conn.cursor() + update_cursor.execute(""" + UPDATE payments + SET payment_method = %s, + payment_currency = %s, + payment_amount = %s, + cad_value_at_payment = %s, + reference = %s, + sender_name = %s, + txid = %s, + wallet_address = %s, + notes = %s + WHERE id = %s + """, ( + payment_method, + payment_currency, + payment_amount, + cad_value_at_payment, + reference or None, + sender_name or None, + txid or None, + wallet_address or None, + notes or None, + payment_id + )) + conn.commit() + invoice_id = payment["invoice_id"] + conn.close() + + recalc_invoice_totals(invoice_id) + + return redirect("/payments") + + conn.close() + return render_template("payments/edit.html", payment=payment, errors=[]) + if __name__ == "__main__": app.run(host="0.0.0.0", port=5050, debug=True) diff --git a/templates/payments/edit.html b/templates/payments/edit.html new file mode 100644 index 0000000..0c80a68 --- /dev/null +++ b/templates/payments/edit.html @@ -0,0 +1,107 @@ + + + +Edit Payment + + + +

Edit Payment

+ +

Home

+

Back to Payments

+ +{% if errors %} +
+ Please fix the following: +
    + {% for error in errors %} +
  • {{ error }}
  • + {% endfor %} +
+
+{% endif %} + +
+ +

+Payment ID
+ +

+ +

+Invoice
+ +

+ +

+Received
+ +

+ +

+Payment Method *
+ +

+ +

+Payment Currency *
+ +

+ +

+Payment Amount *
+ +

+ +

+CAD Value At Payment *
+ +

+ +

+Reference
+ +

+ +

+Sender Name
+ +

+ +

+TXID
+ +

+ +

+Wallet Address
+ +

+ +

+Notes
+ +

+ +

+ +

+ +
+ +{% include "footer.html" %} + + diff --git a/templates/payments/list.html b/templates/payments/list.html index 3d2dce0..21298fa 100644 --- a/templates/payments/list.html +++ b/templates/payments/list.html @@ -21,6 +21,7 @@ CAD Value Reference Received + Actions {% for p in payments %} @@ -34,6 +35,7 @@ {{ p.cad_value_at_payment|money('CAD') }} {{ p.reference }} {{ p.received_at|localtime }} + Edit {% endfor %}