Browse Source

Add v0.1.7 payment edit page with invoice recalculation

main
def 2 weeks ago
parent
commit
9525f94aed
  1. 2
      VERSION
  2. 187
      backend/app.py
  3. 107
      templates/payments/edit.html
  4. 2
      templates/payments/list.html

2
VERSION

@ -1 +1 @@
0.1.6
0.1.7

187
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/<int:payment_id>", 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)

107
templates/payments/edit.html

@ -0,0 +1,107 @@
<!doctype html>
<html>
<head>
<title>Edit Payment</title>
</head>
<body>
<h1>Edit Payment</h1>
<p><a href="/">Home</a></p>
<p><a href="/payments">Back to Payments</a></p>
{% if errors %}
<div style="border:1px solid red; padding:10px; margin-bottom:15px;">
<strong>Please fix the following:</strong>
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<form method="post">
<p>
Payment ID<br>
<input value="{{ payment.id }}" readonly>
</p>
<p>
Invoice<br>
<input value="{{ payment.invoice_number }} - {{ payment.client_code }} - {{ payment.company_name }}" readonly>
</p>
<p>
Received<br>
<input value="{{ payment.received_at|localtime }}" readonly>
</p>
<p>
Payment Method *<br>
<select name="payment_method" required>
<option value="square" {% if payment.payment_method == 'square' %}selected{% endif %}>square</option>
<option value="etransfer" {% if payment.payment_method == 'etransfer' %}selected{% endif %}>etransfer</option>
<option value="crypto_etho" {% if payment.payment_method == 'crypto_etho' %}selected{% endif %}>crypto_etho</option>
<option value="crypto_egaz" {% if payment.payment_method == 'crypto_egaz' %}selected{% endif %}>crypto_egaz</option>
<option value="crypto_alt" {% if payment.payment_method == 'crypto_alt' %}selected{% endif %}>crypto_alt</option>
<option value="cash" {% if payment.payment_method == 'cash' %}selected{% endif %}>cash</option>
<option value="other" {% if payment.payment_method == 'other' %}selected{% endif %}>other</option>
</select>
</p>
<p>
Payment Currency *<br>
<select name="payment_currency" required>
<option value="CAD" {% if payment.payment_currency == 'CAD' %}selected{% endif %}>CAD</option>
<option value="ETHO" {% if payment.payment_currency == 'ETHO' %}selected{% endif %}>ETHO</option>
<option value="EGAZ" {% if payment.payment_currency == 'EGAZ' %}selected{% endif %}>EGAZ</option>
<option value="ALT" {% if payment.payment_currency == 'ALT' %}selected{% endif %}>ALT</option>
</select>
</p>
<p>
Payment Amount *<br>
<input type="number" step="0.00000001" min="0.00000001" name="payment_amount" value="{{ payment.payment_amount }}" required>
</p>
<p>
CAD Value At Payment *<br>
<input type="number" step="0.00000001" min="0" name="cad_value_at_payment" value="{{ payment.cad_value_at_payment }}" required>
</p>
<p>
Reference<br>
<input name="reference" value="{{ payment.reference or '' }}">
</p>
<p>
Sender Name<br>
<input name="sender_name" value="{{ payment.sender_name or '' }}">
</p>
<p>
TXID<br>
<input name="txid" value="{{ payment.txid or '' }}">
</p>
<p>
Wallet Address<br>
<input name="wallet_address" value="{{ payment.wallet_address or '' }}">
</p>
<p>
Notes<br>
<textarea name="notes" rows="5" cols="60">{{ payment.notes or '' }}</textarea>
</p>
<p>
<button type="submit">Save Payment</button>
</p>
</form>
{% include "footer.html" %}
</body>
</html>

2
templates/payments/list.html

@ -21,6 +21,7 @@
<th>CAD Value</th>
<th>Reference</th>
<th>Received</th>
<th>Actions</th>
</tr>
{% for p in payments %}
@ -34,6 +35,7 @@
<td>{{ p.cad_value_at_payment|money('CAD') }}</td>
<td>{{ p.reference }}</td>
<td>{{ p.received_at|localtime }}</td>
<td><a href="/payments/edit/{{ p.id }}">Edit</a></td>
</tr>
{% endfor %}

Loading…
Cancel
Save