|
|
|
|
@ -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: |
|
|
|
|
|