|
|
|
|
@ -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/<int:invoice_id>") |
|
|
|
|
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/<int:invoice_id>", 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, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|