diff --git a/backend/app.py b/backend/app.py index bd115e4..6f22104 100644 --- a/backend/app.py +++ b/backend/app.py @@ -115,6 +115,75 @@ def fmt_money(value, currency_code="CAD"): return f"{amount:.2f}" return f"{amount:.8f}" +def payment_method_label(method, currency=None): + method_key = str(method or "").strip().lower() + currency_key = str(currency or "").strip().upper() + + if method_key == "square": + return "Square" + if method_key == "etransfer": + return "e-Transfer" + if method_key == "cash": + return "Cash" + if method_key == "other": + if currency_key in {"ETH", "ETHO", "ETI", "USDC", "EGAZ", "ALT", "CAD"}: + return currency_key + return "Other" + if method_key == "crypto_etho": + return "ETHO" + if method_key == "crypto_egaz": + return "EGAZ" + if method_key == "crypto_alt": + return "ALT" + + if currency_key in {"ETH", "ETHO", "ETI", "USDC", "EGAZ", "ALT"}: + return currency_key + + return method or "Unknown" + +def get_invoice_payments(invoice_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute(""" + SELECT + id, + payment_method, + payment_currency, + payment_amount, + cad_value_at_payment, + reference, + sender_name, + txid, + wallet_address, + payment_status, + confirmations, + confirmation_required, + received_at, + created_at, + notes + FROM payments + WHERE invoice_id = %s + ORDER BY COALESCE(received_at, created_at) ASC, id ASC + """, (invoice_id,)) + rows = cursor.fetchall() + conn.close() + + out = [] + for row in rows: + item = dict(row) + item["payment_method_label"] = payment_method_label( + item.get("payment_method"), + item.get("payment_currency"), + ) + item["payment_amount_display"] = fmt_money( + item.get("payment_amount"), + item.get("payment_currency") or "CAD", + ) + item["cad_value_display"] = fmt_money(item.get("cad_value_at_payment"), "CAD") + item["received_at_local"] = fmt_local(item.get("received_at") or item.get("created_at")) + out.append(item) + return out + def normalize_oracle_datetime(value): if not value: return None @@ -3354,6 +3423,52 @@ def invoice_pdf(invoice_id): pdf.drawString(left, y, line_text) y -= 13 + if invoice_payments: + y -= 8 + if y < 170: + pdf.showPage() + y = height - 50 + + pdf.setFont("Helvetica-Bold", 11) + pdf.drawString(left, y, "Payments Applied") + y -= 16 + + for p in invoice_payments: + if y < 110: + pdf.showPage() + y = height - 50 + + pdf.setFont("Helvetica-Bold", 10) + pdf.drawString( + left, + y, + f"{p.get('payment_method_label', 'Unknown')} | {p.get('payment_amount_display', '')} {p.get('payment_currency', '')} | {str(p.get('payment_status') or '').upper()}" + ) + y -= 13 + + details_parts = [] + if p.get("received_at_local"): + details_parts.append(f"At: {p.get('received_at_local')}") + if p.get("txid"): + details_parts.append(f"TXID: {p.get('txid')}") + elif p.get("reference"): + details_parts.append(f"Ref: {p.get('reference')}") + if p.get("wallet_address"): + details_parts.append(f"Wallet: {p.get('wallet_address')}") + + details = " | ".join(details_parts) + if details: + pdf.setFont("Helvetica", 9) + for chunk_start in range(0, len(details), 108): + if y < 95: + pdf.showPage() + y = height - 50 + pdf.setFont("Helvetica", 9) + pdf.drawString(left + 10, y, details[chunk_start:chunk_start+108]) + y -= 11 + + y -= 6 + if settings.get("invoice_footer"): y -= 8 pdf.setFont("Helvetica-Bold", 11) @@ -3379,7 +3494,6 @@ def invoice_pdf(invoice_id): @app.route("/invoices/view/") def view_invoice(invoice_id): - ensure_invoice_quote_columns() conn = get_db_connection() cursor = conn.cursor(dictionary=True) @@ -3404,17 +3518,15 @@ def view_invoice(invoice_id): conn.close() return "Invoice not found", 404 - invoice["oracle_quote"] = None - invoice["quote_expires_at_local"] = fmt_local(invoice.get("quote_expires_at")) - if invoice.get("oracle_snapshot"): - try: - invoice["oracle_quote"] = json.loads(invoice["oracle_snapshot"]) - except Exception: - invoice["oracle_quote"] = None - conn.close() settings = get_app_settings() - return render_template("invoices/view.html", invoice=invoice, settings=settings) + invoice_payments = get_invoice_payments(invoice_id) + return render_template( + "invoices/view.html", + invoice=invoice, + settings=settings, + invoice_payments=invoice_payments + ) @app.route("/invoices/edit/", methods=["GET", "POST"]) @@ -4703,6 +4815,7 @@ def portal_invoice_detail(invoice_id): crypto_error = "Transaction was not confirmed in time. Please refresh your quote and try again." pdf_url = f"/invoices/pdf/{invoice_id}" + invoice_payments = get_invoice_payments(invoice_id) conn.close() @@ -4719,6 +4832,7 @@ def portal_invoice_detail(invoice_id): pending_crypto_payment=pending_crypto_payment, crypto_quote_window_expires_iso=crypto_quote_window_expires_iso, crypto_quote_window_expires_local=crypto_quote_window_expires_local, + invoice_payments=invoice_payments, ) diff --git a/templates/invoices/view.html b/templates/invoices/view.html index 5ff998d..e06c405 100644 --- a/templates/invoices/view.html +++ b/templates/invoices/view.html @@ -216,6 +216,39 @@ body { {% endif %} + {% if invoice_payments %} +
+ Payments Applied

+ + + + + + + + + {% for p in invoice_payments %} + + + + + + + + {% endfor %} +
MethodAmountStatusReceivedReference / TXID
{{ p.payment_method_label }}{{ p.payment_amount_display }} {{ p.payment_currency }}{{ p.payment_status }}{{ p.received_at_local }} + {% if p.txid %} + {{ p.txid }} + {% elif p.reference %} + {{ p.reference }} + {% else %} + - + {% endif %} + {% if p.wallet_address %}
Wallet: {{ p.wallet_address }}{% endif %} +
+
+ {% endif %} + {% if settings.payment_terms %}
Payment Terms

diff --git a/templates/portal_invoice_detail.html b/templates/portal_invoice_detail.html index 7e22afa..811327a 100644 --- a/templates/portal_invoice_detail.html +++ b/templates/portal_invoice_detail.html @@ -341,6 +341,43 @@
{% endif %} + {% if invoice_payments %} +
+

Payments Applied

+ + + + + + + + + + + + {% for p in invoice_payments %} + + + + + + + + {% endfor %} + +
MethodAmountStatusReceivedReference / TXID
{{ p.payment_method_label }}{{ p.payment_amount_display }} {{ p.payment_currency }}{{ p.payment_status }}{{ p.received_at_local }} + {% if p.txid %} + {{ p.txid }} + {% elif p.reference %} + {{ p.reference }} + {% else %} + - + {% endif %} + {% if p.wallet_address %}
{{ p.wallet_address }}{% endif %} +
+
+ {% endif %} + {% if pdf_url %}
Open Invoice PDF
{% endif %}