diff --git a/VERSION b/VERSION index c5d54ec..6e8bf73 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.9 +0.1.0 diff --git a/backend/app.py b/backend/app.py index 410ddf1..aab2a96 100644 --- a/backend/app.py +++ b/backend/app.py @@ -27,10 +27,9 @@ def index(): outstanding_invoices = cursor.fetchone()["outstanding_invoices"] cursor.execute(""" - SELECT COALESCE(SUM(payment_amount), 0) AS revenue_received + SELECT COALESCE(SUM(cad_value_at_payment), 0) AS revenue_received FROM payments WHERE payment_status = 'confirmed' - AND payment_currency = 'CAD' """) revenue_received = cursor.fetchone()["revenue_received"] @@ -65,6 +64,38 @@ def clients(): conn.close() return render_template("clients/list.html", clients=clients) +@app.route("/clients/new", methods=["GET", "POST"]) +def new_client(): + if request.method == "POST": + company_name = request.form["company_name"] + contact_name = request.form["contact_name"] + email = request.form["email"] + phone = request.form["phone"] + + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT MAX(id) AS last_id FROM clients") + result = cursor.fetchone() + last_number = result["last_id"] if result["last_id"] else 0 + + client_code = generate_client_code(company_name, last_number) + + insert_cursor = conn.cursor() + insert_cursor.execute( + """ + INSERT INTO clients + (client_code, company_name, contact_name, email, phone) + VALUES (%s, %s, %s, %s, %s) + """, + (client_code, company_name, contact_name, email, phone) + ) + conn.commit() + conn.close() + + return redirect("/clients") + + return render_template("clients/new.html") + @app.route("/services") def services(): conn = get_db_connection() @@ -79,6 +110,68 @@ def services(): conn.close() return render_template("services/list.html", services=services) +@app.route("/services/new", methods=["GET", "POST"]) +def new_service(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + + if request.method == "POST": + client_id = request.form["client_id"] + service_name = request.form["service_name"] + service_type = request.form["service_type"] + billing_cycle = request.form["billing_cycle"] + currency_code = request.form["currency_code"] + recurring_amount = request.form["recurring_amount"] + status = request.form["status"] + start_date = request.form["start_date"] or None + description = request.form["description"] + + cursor.execute("SELECT MAX(id) AS last_id FROM services") + result = cursor.fetchone() + last_number = result["last_id"] if result["last_id"] else 0 + service_code = generate_service_code(service_name, last_number) + + insert_cursor = conn.cursor() + insert_cursor.execute( + """ + INSERT INTO services + ( + client_id, + service_code, + service_name, + service_type, + billing_cycle, + status, + currency_code, + recurring_amount, + start_date, + description + ) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + client_id, + service_code, + service_name, + service_type, + billing_cycle, + status, + currency_code, + recurring_amount, + start_date, + description + ) + ) + conn.commit() + conn.close() + + return redirect("/services") + + cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC") + clients = cursor.fetchall() + conn.close() + return render_template("services/new.html", clients=clients) + @app.route("/invoices") def invoices(): conn = get_db_connection() @@ -131,18 +224,10 @@ def new_invoice(): errors.append("Total amount must be a valid number.") if errors: - cursor.execute(""" - SELECT id, client_code, company_name - FROM clients - ORDER BY company_name - """) + cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name") clients = cursor.fetchall() - cursor.execute(""" - SELECT id, service_code, service_name - FROM services - ORDER BY service_name - """) + cursor.execute("SELECT id, service_code, service_name FROM services ORDER BY service_name") services = cursor.fetchall() conn.close() @@ -166,12 +251,11 @@ def new_invoice(): cursor.execute("SELECT MAX(id) AS last_id FROM invoices") result = cursor.fetchone() - number = (result["last_id"] or 0) + 1 invoice_number = f"INV-{number:04d}" - insert = conn.cursor() - insert.execute(""" + insert_cursor = conn.cursor() + insert_cursor.execute(""" INSERT INTO invoices ( client_id, @@ -202,18 +286,10 @@ def new_invoice(): return redirect("/invoices") - cursor.execute(""" - SELECT id, client_code, company_name - FROM clients - ORDER BY company_name - """) + cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name") clients = cursor.fetchall() - cursor.execute(""" - SELECT id, service_code, service_name - FROM services - ORDER BY service_name - """) + cursor.execute("SELECT id, service_code, service_name FROM services ORDER BY service_name") services = cursor.fetchall() conn.close() @@ -226,99 +302,209 @@ def new_invoice(): form_data={}, ) -@app.route("/clients/new", methods=["GET", "POST"]) -def new_client(): +@app.route("/payments") +def payments(): + 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 + ORDER BY p.id DESC + """) + payments = cursor.fetchall() + + conn.close() + return render_template("payments/list.html", payments=payments) + +@app.route("/payments/new", methods=["GET", "POST"]) +def new_payment(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + if request.method == "POST": - company_name = request.form["company_name"] - contact_name = request.form["contact_name"] - email = request.form["email"] - phone = request.form["phone"] + invoice_id = request.form.get("invoice_id", "").strip() + 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() - conn = get_db_connection() - cursor = conn.cursor(dictionary=True) - cursor.execute("SELECT MAX(id) AS last_id FROM clients") - result = cursor.fetchone() - last_number = result["last_id"] if result["last_id"] else 0 + errors = [] - client_code = generate_client_code(company_name, last_number) + if not invoice_id: + errors.append("Invoice is required.") + 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.") - cursor = conn.cursor() - cursor.execute( - """ - INSERT INTO clients - (client_code, company_name, contact_name, email, phone) - VALUES (%s, %s, %s, %s, %s) - """, - (client_code, company_name, contact_name, email, phone) - ) - conn.commit() - conn.close() + amount_value = None + cad_value = None - return redirect("/clients") + 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.") - return render_template("clients/new.html") + 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.") -@app.route("/services/new", methods=["GET", "POST"]) -def new_service(): - conn = get_db_connection() - cursor = conn.cursor(dictionary=True) + if errors: + cursor.execute(""" + SELECT + i.id, + i.invoice_number, + c.client_code, + c.company_name, + i.total_amount, + i.amount_paid, + i.currency_code + FROM invoices i + JOIN clients c ON i.client_id = c.id + ORDER BY i.id DESC + """) + invoices = cursor.fetchall() + conn.close() - if request.method == "POST": - client_id = request.form["client_id"] - service_name = request.form["service_name"] - service_type = request.form["service_type"] - billing_cycle = request.form["billing_cycle"] - currency_code = request.form["currency_code"] - recurring_amount = request.form["recurring_amount"] - status = request.form["status"] - start_date = request.form["start_date"] or None - description = request.form["description"] + form_data = { + "invoice_id": invoice_id, + "payment_method": payment_method, + "payment_currency": payment_currency, + "payment_amount": payment_amount, + "cad_value_at_payment": cad_value_at_payment, + "reference": reference, + "sender_name": sender_name, + "txid": txid, + "wallet_address": wallet_address, + "notes": notes, + } - cursor.execute("SELECT MAX(id) AS last_id FROM services") - result = cursor.fetchone() - last_number = result["last_id"] if result["last_id"] else 0 - service_code = generate_service_code(service_name, last_number) + return render_template( + "payments/new.html", + invoices=invoices, + errors=errors, + form_data=form_data, + ) + + cursor.execute("SELECT client_id, total_amount, amount_paid FROM invoices WHERE id = %s", (invoice_id,)) + invoice = cursor.fetchone() + + if not invoice: + conn.close() + 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" + 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( - """ - INSERT INTO services + insert_cursor.execute(""" + INSERT INTO payments ( + invoice_id, client_id, - service_code, - service_name, - service_type, - billing_cycle, - status, - currency_code, - recurring_amount, - start_date, - description - ) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, - ( - client_id, - service_code, - service_name, - service_type, - billing_cycle, - status, - currency_code, - recurring_amount, - start_date, - description + payment_method, + payment_currency, + payment_amount, + cad_value_at_payment, + reference, + sender_name, + txid, + wallet_address, + payment_status, + received_at, + notes ) - ) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'confirmed', NOW(), %s) + """, ( + invoice_id, + client_id, + 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 + )) + + update_cursor = conn.cursor() + if new_status == "paid": + update_cursor.execute(""" + UPDATE invoices + SET amount_paid = %s, + status = %s, + paid_at = NOW() + 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() - return redirect("/services") + return redirect("/payments") - cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC") - clients = cursor.fetchall() + cursor.execute(""" + SELECT + i.id, + i.invoice_number, + c.client_code, + c.company_name, + i.total_amount, + i.amount_paid, + i.currency_code + FROM invoices i + JOIN clients c ON i.client_id = c.id + ORDER BY i.id DESC + """) + invoices = cursor.fetchall() conn.close() - return render_template("services/new.html", clients=clients) + + return render_template( + "payments/new.html", + invoices=invoices, + errors=[], + form_data={}, + ) if __name__ == "__main__": app.run(host="0.0.0.0", port=5050) diff --git a/templates/dashboard.html b/templates/dashboard.html index b815201..df249b9 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -10,6 +10,7 @@
+| ID | +Invoice | +Client | +Method | +Currency | +Amount | +CAD Value | +Reference | +Received | +
|---|---|---|---|---|---|---|---|---|
| {{ p.id }} | +{{ p.invoice_number }} | +{{ p.client_code }} - {{ p.company_name }} | +{{ p.payment_method }} | +{{ p.payment_currency }} | +{{ p.payment_amount }} | +{{ p.cad_value_at_payment }} | +{{ p.reference }} | +{{ p.received_at }} | +