Browse Source

Add overdue detection and local time display

main
def 2 weeks ago
parent
commit
f259daced0
  1. 2
      VERSION
  2. 49
      backend/app.py
  3. 2
      templates/dashboard.html
  4. 11
      templates/invoices/list.html
  5. 2
      templates/payments/list.html

2
VERSION

@ -1 +1 @@
0.1.0
0.1.1

49
backend/app.py

@ -1,6 +1,8 @@
from flask import Flask, render_template, request, redirect
from db import get_db_connection
from utils import generate_client_code, generate_service_code
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
app = Flask(
__name__,
@ -8,8 +10,41 @@ app = Flask(
static_folder="../static",
)
LOCAL_TZ = ZoneInfo("America/Toronto")
def fmt_local(dt_value):
if not dt_value:
return ""
if isinstance(dt_value, str):
try:
dt_value = datetime.fromisoformat(dt_value)
except ValueError:
return str(dt_value)
if dt_value.tzinfo is None:
dt_value = dt_value.replace(tzinfo=timezone.utc)
return dt_value.astimezone(LOCAL_TZ).strftime("%Y-%m-%d %I:%M:%S %p")
def refresh_overdue_invoices():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("""
UPDATE invoices
SET status = 'overdue'
WHERE due_at IS NOT NULL
AND due_at < UTC_TIMESTAMP()
AND status IN ('pending', 'partial')
""")
conn.commit()
conn.close()
@app.template_filter("localtime")
def localtime_filter(value):
return fmt_local(value)
@app.route("/")
def index():
refresh_overdue_invoices()
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
@ -51,7 +86,7 @@ def dbtest():
cursor.execute("SELECT NOW()")
result = cursor.fetchone()
conn.close()
return f"<h1>Database OK</h1><p>{result[0]}</p>"
return f"<h1>Database OK</h1><p>DB server time (UTC): {result[0]}</p><p>Displayed local time: {fmt_local(result[0])}</p>"
except Exception as e:
return f"<h1>Database FAILED</h1><pre>{e}</pre>"
@ -174,6 +209,8 @@ def new_service():
@app.route("/invoices")
def invoices():
refresh_overdue_invoices()
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute("""
@ -269,7 +306,7 @@ def new_invoice():
status,
notes
)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, 'pending', %s)
VALUES (%s, %s, %s, %s, %s, %s, UTC_TIMESTAMP(), %s, 'pending', %s)
""", (
client_id,
service_id,
@ -354,7 +391,6 @@ def new_payment():
errors.append("CAD value at payment is required.")
amount_value = None
cad_value = None
if not errors:
try:
@ -420,13 +456,10 @@ def new_payment():
if new_amount_paid >= float(invoice["total_amount"]):
new_status = "paid"
paid_at_sql = "NOW()"
elif new_amount_paid > 0:
new_status = "partial"
paid_at_sql = "NULL"
else:
new_status = "pending"
paid_at_sql = "NULL"
insert_cursor = conn.cursor()
insert_cursor.execute("""
@ -446,7 +479,7 @@ def new_payment():
received_at,
notes
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'confirmed', NOW(), %s)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'confirmed', UTC_TIMESTAMP(), %s)
""", (
invoice_id,
client_id,
@ -467,7 +500,7 @@ def new_payment():
UPDATE invoices
SET amount_paid = %s,
status = %s,
paid_at = NOW()
paid_at = UTC_TIMESTAMP()
WHERE id = %s
""", (new_amount_paid, new_status, invoice_id))
else:

2
templates/dashboard.html

@ -28,5 +28,7 @@
</tr>
</table>
<p>Displayed times are shown in America/Toronto.</p>
</body>
</html>

11
templates/invoices/list.html

@ -12,31 +12,32 @@
<p><a href="/invoices/new">Create Invoice</a></p>
<table border="1" cellpadding="6">
<tr>
<th>ID</th>
<th>Invoice</th>
<th>Client</th>
<th>Currency</th>
<th>Total</th>
<th>Paid</th>
<th>Remaining</th>
<th>Status</th>
<th>Issued</th>
<th>Due</th>
</tr>
{% for i in invoices %}
<tr>
<td>{{ i.id }}</td>
<td>{{ i.invoice_number }}</td>
<td>{{ i.client_code }} - {{ i.company_name }}</td>
<td>{{ i.currency_code }}</td>
<td>{{ i.total_amount }}</td>
<td>{{ i.amount_paid }}</td>
<td>{{ i.total_amount - i.amount_paid }}</td>
<td>{{ i.status }}</td>
<td>{{ i.issued_at }}</td>
<td>{{ i.due_at }}</td>
<td>{{ i.issued_at|localtime }}</td>
<td>{{ i.due_at|localtime }}</td>
</tr>
{% endfor %}
</table>

2
templates/payments/list.html

@ -33,7 +33,7 @@
<td>{{ p.payment_amount }}</td>
<td>{{ p.cad_value_at_payment }}</td>
<td>{{ p.reference }}</td>
<td>{{ p.received_at }}</td>
<td>{{ p.received_at|localtime }}</td>
</tr>
{% endfor %}

Loading…
Cancel
Save