23 changed files with 12282 additions and 89 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>OTB Billing Dashboard</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
|
||||||
|
{% if app_settings.business_logo_url %} |
||||||
|
<div style="margin-bottom:15px;"> |
||||||
|
<img src="{{ app_settings.business_logo_url }}" style="height:60px;"> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
<h1>{{ app_settings.business_name or 'OTB Billing' }} Dashboard</h1> |
||||||
|
|
||||||
|
<p><a href="/clients">Clients</a></p> |
||||||
|
<p><a href="/services">Services</a></p> |
||||||
|
<p><a href="/invoices">Invoices</a></p> |
||||||
|
<p><a href="/payments">Payments</a></p> |
||||||
|
<p><a href="/reports/revenue">Revenue Report</a></p> |
||||||
|
<p><a href="/settings">Settings / Config</a></p> |
||||||
|
<p><a href="/dbtest">DB Test</a></p> |
||||||
|
|
||||||
|
<table border="1" cellpadding="10"> |
||||||
|
<tr> |
||||||
|
<th>Total Clients</th> |
||||||
|
<th>Active Services</th> |
||||||
|
<th>Outstanding Invoices</th> |
||||||
|
<th>Revenue Received (CAD)</th> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ total_clients }}</td> |
||||||
|
<td>{{ active_services }}</td> |
||||||
|
<td>{{ outstanding_invoices }}</td> |
||||||
|
<td>{{ revenue_received|money('CAD') }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
<p>Displayed times are shown in Eastern Time (Toronto).</p> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,326 @@ |
|||||||
|
# OTB Billing — Project State |
||||||
|
|
||||||
|
Last Updated: 2026-03-09 |
||||||
|
Version: v0.3.1 |
||||||
|
Project Path: ~/otb_billing |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Project Purpose |
||||||
|
|
||||||
|
OTB Billing is a contractor-focused billing system designed to be: |
||||||
|
|
||||||
|
- self-hosted |
||||||
|
- portable |
||||||
|
- database-backed |
||||||
|
- deployable on fresh Linux systems |
||||||
|
- suitable for managed hosting or client-installed deployments |
||||||
|
|
||||||
|
The system is being built as a practical alternative to overly restrictive SaaS billing tools, with emphasis on ownership, simplicity, and contractor workflow. |
||||||
|
|
||||||
|
Tagline direction: |
||||||
|
|
||||||
|
By a contractor, for contractors |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Current Stack |
||||||
|
|
||||||
|
Backend: |
||||||
|
Flask |
||||||
|
|
||||||
|
Database: |
||||||
|
MariaDB |
||||||
|
|
||||||
|
PDF Engine: |
||||||
|
ReportLab |
||||||
|
|
||||||
|
Primary Port: |
||||||
|
5050 |
||||||
|
|
||||||
|
Dependencies file: |
||||||
|
requirements.txt |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Deployment Philosophy |
||||||
|
|
||||||
|
OTB Billing must remain a deployable product, not just a dev-only app. |
||||||
|
|
||||||
|
Target install model: |
||||||
|
|
||||||
|
fresh server |
||||||
|
→ installer runs |
||||||
|
→ dependencies install |
||||||
|
→ MariaDB setup |
||||||
|
→ schema setup |
||||||
|
→ app launches |
||||||
|
|
||||||
|
This remains a core project rule. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Current Core Features |
||||||
|
|
||||||
|
## Clients |
||||||
|
- create client |
||||||
|
- edit client |
||||||
|
- list clients |
||||||
|
- status field |
||||||
|
- client code support |
||||||
|
|
||||||
|
## Services |
||||||
|
- create service |
||||||
|
- edit service |
||||||
|
- list services |
||||||
|
- service code support |
||||||
|
- service status support |
||||||
|
|
||||||
|
## Invoices |
||||||
|
- create invoice |
||||||
|
- edit invoice |
||||||
|
- list invoices |
||||||
|
- automatic invoice numbering |
||||||
|
- invoice print view |
||||||
|
- invoice PDF download |
||||||
|
- invoice lock after payment activity |
||||||
|
- invoice statuses |
||||||
|
- invoice email sending with PDF attachment |
||||||
|
- latest invoice email activity display |
||||||
|
|
||||||
|
Current invoice statuses: |
||||||
|
- draft |
||||||
|
- pending |
||||||
|
- partial |
||||||
|
- paid |
||||||
|
- overdue |
||||||
|
- cancelled |
||||||
|
|
||||||
|
## Payments |
||||||
|
- record payment |
||||||
|
- edit payment |
||||||
|
- list payments |
||||||
|
- overpayment guard on new payment |
||||||
|
- overpayment guard on payment edit |
||||||
|
- payment status display |
||||||
|
- payment void / reversal workflow |
||||||
|
- invoice recalculation after payment changes |
||||||
|
|
||||||
|
Current payment statuses: |
||||||
|
- confirmed |
||||||
|
- reversed |
||||||
|
|
||||||
|
## Credit Ledger |
||||||
|
- client credit ledger |
||||||
|
- manual credit entries |
||||||
|
- client balance color coding |
||||||
|
- ledger link visible from client list/edit pages |
||||||
|
|
||||||
|
## Invoice Rendering |
||||||
|
- HTML invoice view |
||||||
|
- print-friendly layout |
||||||
|
- PDF invoice generation |
||||||
|
- client details on invoice |
||||||
|
- status badge on invoice |
||||||
|
- totals, paid, remaining display |
||||||
|
- branding/logo support on HTML and PDF |
||||||
|
|
||||||
|
## Exports |
||||||
|
- clients CSV export |
||||||
|
- invoices CSV export |
||||||
|
- payments CSV export |
||||||
|
- filtered invoice CSV export |
||||||
|
- filtered invoice PDF ZIP export |
||||||
|
- monthly/quarterly/yearly accounting package ZIP export |
||||||
|
- revenue report JSON export |
||||||
|
|
||||||
|
## Batch / Print |
||||||
|
- filtered batch invoice print page |
||||||
|
- print-friendly revenue report |
||||||
|
|
||||||
|
## Reports |
||||||
|
- revenue report |
||||||
|
- report frequency selector |
||||||
|
- JSON report export |
||||||
|
- email revenue report JSON |
||||||
|
|
||||||
|
## Settings / Configuration System |
||||||
|
Accessible from: |
||||||
|
/settings |
||||||
|
|
||||||
|
Stored in database table: |
||||||
|
app_settings |
||||||
|
|
||||||
|
### Business Identity Settings |
||||||
|
- business name |
||||||
|
- business tagline |
||||||
|
- business logo URL |
||||||
|
- business email |
||||||
|
- business phone |
||||||
|
- business address |
||||||
|
- business website |
||||||
|
- business registration number |
||||||
|
|
||||||
|
### Tax Settings |
||||||
|
- tax label |
||||||
|
- tax rate |
||||||
|
- tax number |
||||||
|
- local country |
||||||
|
- apply local tax only flag |
||||||
|
|
||||||
|
### Invoice Behavior Settings |
||||||
|
- default currency |
||||||
|
- invoice footer |
||||||
|
- payment terms |
||||||
|
- report frequency |
||||||
|
|
||||||
|
### SMTP / Email Settings |
||||||
|
- SMTP host |
||||||
|
- SMTP port |
||||||
|
- SMTP username |
||||||
|
- SMTP password |
||||||
|
- SMTP from email |
||||||
|
- SMTP from name |
||||||
|
- TLS flag |
||||||
|
- SSL flag |
||||||
|
- report delivery email |
||||||
|
|
||||||
|
## Email Logging |
||||||
|
Stored in: |
||||||
|
email_log |
||||||
|
|
||||||
|
Tracks: |
||||||
|
- email_type |
||||||
|
- invoice_id |
||||||
|
- recipient_email |
||||||
|
- subject |
||||||
|
- status |
||||||
|
- error_message |
||||||
|
- sent_at |
||||||
|
|
||||||
|
Currently logs: |
||||||
|
- invoice email sends |
||||||
|
- revenue report email sends |
||||||
|
- accounting package email sends |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Current Known Good State |
||||||
|
|
||||||
|
Confirmed working: |
||||||
|
- dashboard |
||||||
|
- clients |
||||||
|
- services |
||||||
|
- invoice creation |
||||||
|
- auto invoice numbering |
||||||
|
- invoice view |
||||||
|
- invoice PDF generation |
||||||
|
- invoice email with PDF attachment |
||||||
|
- invoice email log display |
||||||
|
- payment entry |
||||||
|
- payment overpayment prevention |
||||||
|
- payment reversal / void |
||||||
|
- payments list with invoice status and remaining balance |
||||||
|
- settings/config page |
||||||
|
- business identity shown on invoice view/PDF |
||||||
|
- logo display in HTML and PDF |
||||||
|
- clients/invoices/payments CSV export |
||||||
|
- filtered invoice export |
||||||
|
- filtered invoice PDF ZIP export |
||||||
|
- batch invoice print |
||||||
|
- revenue report |
||||||
|
- revenue report JSON export |
||||||
|
- revenue report email |
||||||
|
- accounting package ZIP export |
||||||
|
- accounting package email |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Requirements |
||||||
|
|
||||||
|
Current requirements.txt should include: |
||||||
|
- Flask |
||||||
|
- mysql-connector-python |
||||||
|
- reportlab |
||||||
|
- python-dateutil |
||||||
|
- pytz |
||||||
|
|
||||||
|
This file must remain complete so installer-driven deployment works in one shot. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Business / Product Direction |
||||||
|
|
||||||
|
This system is intended to grow into a deployable billing product for small contractors and related service businesses. |
||||||
|
|
||||||
|
Target strengths versus typical SaaS billing tools: |
||||||
|
- simpler workflow |
||||||
|
- data ownership |
||||||
|
- exportability |
||||||
|
- portability |
||||||
|
- contractor-first design |
||||||
|
- no hostage-style software design |
||||||
|
|
||||||
|
Long-term success goal: |
||||||
|
build something users are happy to use and proud to own. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Planned Next Features |
||||||
|
|
||||||
|
## Near-Term |
||||||
|
- invoice defaults from settings |
||||||
|
- improved tax application logic |
||||||
|
- accountant package scheduling / reminders |
||||||
|
- client account statement export |
||||||
|
- backup/install polish |
||||||
|
|
||||||
|
## Medium-Term |
||||||
|
- quote / estimate system |
||||||
|
- recurring invoices |
||||||
|
- reminder workflows |
||||||
|
- better installer/update flow |
||||||
|
- email resend history view |
||||||
|
|
||||||
|
## Long-Term |
||||||
|
- client portal |
||||||
|
- role-based access |
||||||
|
- accountant/export workflows |
||||||
|
- job-tracking integration with related contractor platform modules |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Advanced Settings Direction |
||||||
|
|
||||||
|
Business identity and SMTP belong in settings UI. |
||||||
|
|
||||||
|
Database credentials should remain installer/config-file driven, not casually editable in standard UI. |
||||||
|
|
||||||
|
If advanced connection settings are ever exposed in UI, they must be clearly marked as dangerous / advanced and should avoid redisplaying stored passwords. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Repository Discipline |
||||||
|
|
||||||
|
For this project going forward: |
||||||
|
- keep PROJECT_STATE.md updated |
||||||
|
- update README.md with version/build notes |
||||||
|
- keep requirements.txt complete |
||||||
|
- make full ZIP backup on version bumps |
||||||
|
- push milestones to git |
||||||
|
|
||||||
|
Example future archive naming: |
||||||
|
otb_billing-v0.3.1.zip |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
# Restart / Run Notes |
||||||
|
|
||||||
|
Development run method: |
||||||
|
|
||||||
|
cd ~/otb_billing |
||||||
|
python3 backend/app.py |
||||||
|
|
||||||
|
During active development, run in a visible terminal so logs stay visible. |
||||||
|
|
||||||
|
Do not rely on hidden/background launch during normal debug workflow. |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,235 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Invoice {{ invoice.invoice_number }}</title> |
||||||
|
<style> |
||||||
|
body { |
||||||
|
font-family: Arial, sans-serif; |
||||||
|
margin: 30px; |
||||||
|
color: #111; |
||||||
|
} |
||||||
|
.top-links { |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
.top-links a { |
||||||
|
margin-right: 15px; |
||||||
|
} |
||||||
|
.invoice-wrap { |
||||||
|
max-width: 900px; |
||||||
|
} |
||||||
|
.header-row { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: flex-start; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.title-box h1 { |
||||||
|
margin: 0 0 8px 0; |
||||||
|
} |
||||||
|
.status-badge { |
||||||
|
display: inline-block; |
||||||
|
padding: 4px 10px; |
||||||
|
border-radius: 999px; |
||||||
|
font-size: 12px; |
||||||
|
font-weight: bold; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
.status-draft { background: #e5e7eb; color: #111827; } |
||||||
|
.status-pending { background: #dbeafe; color: #1d4ed8; } |
||||||
|
.status-partial { background: #fef3c7; color: #92400e; } |
||||||
|
.status-paid { background: #dcfce7; color: #166534; } |
||||||
|
.status-overdue { background: #fee2e2; color: #991b1b; } |
||||||
|
.status-cancelled { background: #e5e7eb; color: #4b5563; } |
||||||
|
|
||||||
|
.info-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 30px; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.info-card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
.info-card h3 { |
||||||
|
margin-top: 0; |
||||||
|
} |
||||||
|
.summary-table, |
||||||
|
.total-table { |
||||||
|
width: 100%; |
||||||
|
border-collapse: collapse; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.summary-table th, |
||||||
|
.summary-table td, |
||||||
|
.total-table th, |
||||||
|
.total-table td { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 10px; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
.total-table { |
||||||
|
max-width: 420px; |
||||||
|
margin-left: auto; |
||||||
|
} |
||||||
|
.notes-box { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 15px; |
||||||
|
white-space: pre-wrap; |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
.print-only { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
@media print { |
||||||
|
.top-links, |
||||||
|
.screen-only, |
||||||
|
footer { |
||||||
|
display: none !important; |
||||||
|
} |
||||||
|
.print-only { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<div class="invoice-wrap"> |
||||||
|
{% if request.args.get('email_sent') == '1' %} |
||||||
|
<div style="border:1px solid #166534;background:#dcfce7;padding:10px;margin-bottom:15px;"> |
||||||
|
Invoice email sent successfully. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
{% if request.args.get('email_failed') == '1' %} |
||||||
|
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;"> |
||||||
|
Invoice email failed. Check SMTP settings or server log. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
<div class="top-links screen-only"> |
||||||
|
<a href="/">Home</a> |
||||||
|
<a href="/invoices">Back to Invoices</a> |
||||||
|
<a href="/invoices/edit/{{ invoice.id }}">Edit Invoice</a> |
||||||
|
<a href="#" onclick="window.print(); return false;">Print</a> |
||||||
|
<a href="/invoices/pdf/{{ invoice.id }}">PDF</a> |
||||||
|
{% if invoice.email %} |
||||||
|
<form method="post" action="/invoices/email/{{ invoice.id }}" style="display:inline;"> |
||||||
|
<button type="submit">Send Email</button> |
||||||
|
</form> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="header-row"> |
||||||
|
|
||||||
|
{% if settings.business_logo_url %} |
||||||
|
<img src="{{ settings.business_logo_url }}" style="height:70px;margin-bottom:10px;"> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
<div class="title-box"> |
||||||
|
<h1>Invoice {{ invoice.invoice_number }}</h1> |
||||||
|
<span class="status-badge status-{{ invoice.status }}">{{ invoice.status }}</span> |
||||||
|
</div> |
||||||
|
<div style="text-align:right;"> |
||||||
|
<strong>{{ settings.business_name or 'OTB Billing' }}</strong><br> |
||||||
|
{{ settings.business_tagline or '' }}<br> |
||||||
|
{% if settings.business_address %}{{ settings.business_address }}<br>{% endif %} |
||||||
|
{% if settings.business_email %}{{ settings.business_email }}<br>{% endif %} |
||||||
|
{% if settings.business_phone %}{{ settings.business_phone }}<br>{% endif %} |
||||||
|
{% if settings.business_website %}{{ settings.business_website }}{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="info-grid"> |
||||||
|
<div class="info-card"> |
||||||
|
<h3>Bill To</h3> |
||||||
|
<strong>{{ invoice.company_name }}</strong><br> |
||||||
|
{% if invoice.contact_name %}{{ invoice.contact_name }}<br>{% endif %} |
||||||
|
{% if invoice.email %}{{ invoice.email }}<br>{% endif %} |
||||||
|
{% if invoice.phone %}{{ invoice.phone }}<br>{% endif %} |
||||||
|
Client Code: {{ invoice.client_code }} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="info-card"> |
||||||
|
<h3>Invoice Details</h3> |
||||||
|
Invoice #: {{ invoice.invoice_number }}<br> |
||||||
|
Issued: {{ invoice.issued_at|localtime }}<br> |
||||||
|
Due: {{ invoice.due_at|localtime }}<br> |
||||||
|
{% if invoice.paid_at %}Paid: {{ invoice.paid_at|localtime }}<br>{% endif %} |
||||||
|
Currency: {{ invoice.currency_code }}<br> |
||||||
|
{% if settings.tax_number %}{{ settings.tax_label or 'Tax' }} Number: {{ settings.tax_number }}<br>{% endif %} |
||||||
|
{% if settings.business_number %}Business Number: {{ settings.business_number }}{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<table class="summary-table"> |
||||||
|
<tr> |
||||||
|
<th>Service Code</th> |
||||||
|
<th>Service</th> |
||||||
|
<th>Description</th> |
||||||
|
<th>Total</th> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ invoice.service_code or '-' }}</td> |
||||||
|
<td>{{ invoice.service_name or '-' }}</td> |
||||||
|
<td>{{ invoice.notes or '-' }}</td> |
||||||
|
<td>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
<table class="total-table"> |
||||||
|
<tr> |
||||||
|
<th>Subtotal</th> |
||||||
|
<td>{{ invoice.subtotal_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>{{ settings.tax_label or 'Tax' }}</th> |
||||||
|
<td>{{ invoice.tax_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Total</th> |
||||||
|
<td><strong>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</strong></td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Paid</th> |
||||||
|
<td>{{ invoice.amount_paid|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Remaining</th> |
||||||
|
<td>{{ (invoice.total_amount - invoice.amount_paid)|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
{% if latest_email_log %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Latest Email Activity</strong><br><br> |
||||||
|
Status: {{ latest_email_log.status }}<br> |
||||||
|
Recipient: {{ latest_email_log.recipient_email }}<br> |
||||||
|
Subject: {{ latest_email_log.subject }}<br> |
||||||
|
Sent At: {{ latest_email_log.sent_at|localtime }}<br> |
||||||
|
{% if latest_email_log.error_message %} |
||||||
|
Error: {{ latest_email_log.error_message }} |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
{% if settings.payment_terms %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Payment Terms</strong><br><br> |
||||||
|
{{ settings.payment_terms }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
{% if settings.invoice_footer %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Footer</strong><br><br> |
||||||
|
{{ settings.invoice_footer }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>OTB Billing Dashboard</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
|
||||||
|
{% if app_settings.business_logo_url %} |
||||||
|
<div style="margin-bottom:15px;"> |
||||||
|
<img src="{{ app_settings.business_logo_url }}" style="height:60px;"> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
<h1>{{ app_settings.business_name or 'OTB Billing' }} Dashboard</h1> |
||||||
|
|
||||||
|
<p><a href="/clients">Clients</a></p> |
||||||
|
<p><a href="/services">Services</a></p> |
||||||
|
<p><a href="/invoices">Invoices</a></p> |
||||||
|
<p><a href="/payments">Payments</a></p> |
||||||
|
<p><a href="/reports/revenue">Revenue Report</a></p> |
||||||
|
<p><a href="/reports/accounting-package.zip">Monthly Accounting Package</a></p> |
||||||
|
<p><a href="/settings">Settings / Config</a></p> |
||||||
|
<p><a href="/dbtest">DB Test</a></p> |
||||||
|
|
||||||
|
<table border="1" cellpadding="10"> |
||||||
|
<tr> |
||||||
|
<th>Total Clients</th> |
||||||
|
<th>Active Services</th> |
||||||
|
<th>Outstanding Invoices</th> |
||||||
|
<th>Revenue Received (CAD)</th> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ total_clients }}</td> |
||||||
|
<td>{{ active_services }}</td> |
||||||
|
<td>{{ outstanding_invoices }}</td> |
||||||
|
<td>{{ revenue_received|money('CAD') }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
<p>Displayed times are shown in Eastern Time (Toronto).</p> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,207 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Invoice {{ invoice.invoice_number }}</title> |
||||||
|
<style> |
||||||
|
body { |
||||||
|
font-family: Arial, sans-serif; |
||||||
|
margin: 30px; |
||||||
|
color: #111; |
||||||
|
} |
||||||
|
.top-links { |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
.top-links a { |
||||||
|
margin-right: 15px; |
||||||
|
} |
||||||
|
.invoice-wrap { |
||||||
|
max-width: 900px; |
||||||
|
} |
||||||
|
.header-row { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: flex-start; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.title-box h1 { |
||||||
|
margin: 0 0 8px 0; |
||||||
|
} |
||||||
|
.status-badge { |
||||||
|
display: inline-block; |
||||||
|
padding: 4px 10px; |
||||||
|
border-radius: 999px; |
||||||
|
font-size: 12px; |
||||||
|
font-weight: bold; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
.status-draft { background: #e5e7eb; color: #111827; } |
||||||
|
.status-pending { background: #dbeafe; color: #1d4ed8; } |
||||||
|
.status-partial { background: #fef3c7; color: #92400e; } |
||||||
|
.status-paid { background: #dcfce7; color: #166534; } |
||||||
|
.status-overdue { background: #fee2e2; color: #991b1b; } |
||||||
|
.status-cancelled { background: #e5e7eb; color: #4b5563; } |
||||||
|
|
||||||
|
.info-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 30px; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.info-card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
.info-card h3 { |
||||||
|
margin-top: 0; |
||||||
|
} |
||||||
|
.summary-table, |
||||||
|
.total-table { |
||||||
|
width: 100%; |
||||||
|
border-collapse: collapse; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.summary-table th, |
||||||
|
.summary-table td, |
||||||
|
.total-table th, |
||||||
|
.total-table td { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 10px; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
.total-table { |
||||||
|
max-width: 420px; |
||||||
|
margin-left: auto; |
||||||
|
} |
||||||
|
.notes-box { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 15px; |
||||||
|
white-space: pre-wrap; |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
.print-only { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
@media print { |
||||||
|
.top-links, |
||||||
|
.screen-only, |
||||||
|
footer { |
||||||
|
display: none !important; |
||||||
|
} |
||||||
|
.print-only { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<div class="invoice-wrap"> |
||||||
|
<div class="top-links screen-only"> |
||||||
|
<a href="/">Home</a> |
||||||
|
<a href="/invoices">Back to Invoices</a> |
||||||
|
<a href="/invoices/edit/{{ invoice.id }}">Edit Invoice</a> |
||||||
|
<a href="#" onclick="window.print(); return false;">Print</a> |
||||||
|
<a href="/invoices/pdf/{{ invoice.id }}">PDF</a> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="header-row"> |
||||||
|
|
||||||
|
{% if settings.business_logo_url %} |
||||||
|
<img src="{{ settings.business_logo_url }}" style="height:70px;margin-bottom:10px;"> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
<div class="title-box"> |
||||||
|
<h1>Invoice {{ invoice.invoice_number }}</h1> |
||||||
|
<span class="status-badge status-{{ invoice.status }}">{{ invoice.status }}</span> |
||||||
|
</div> |
||||||
|
<div style="text-align:right;"> |
||||||
|
<strong>{{ settings.business_name or 'OTB Billing' }}</strong><br> |
||||||
|
{{ settings.business_tagline or '' }}<br> |
||||||
|
{% if settings.business_address %}{{ settings.business_address }}<br>{% endif %} |
||||||
|
{% if settings.business_email %}{{ settings.business_email }}<br>{% endif %} |
||||||
|
{% if settings.business_phone %}{{ settings.business_phone }}<br>{% endif %} |
||||||
|
{% if settings.business_website %}{{ settings.business_website }}{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="info-grid"> |
||||||
|
<div class="info-card"> |
||||||
|
<h3>Bill To</h3> |
||||||
|
<strong>{{ invoice.company_name }}</strong><br> |
||||||
|
{% if invoice.contact_name %}{{ invoice.contact_name }}<br>{% endif %} |
||||||
|
{% if invoice.email %}{{ invoice.email }}<br>{% endif %} |
||||||
|
{% if invoice.phone %}{{ invoice.phone }}<br>{% endif %} |
||||||
|
Client Code: {{ invoice.client_code }} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="info-card"> |
||||||
|
<h3>Invoice Details</h3> |
||||||
|
Invoice #: {{ invoice.invoice_number }}<br> |
||||||
|
Issued: {{ invoice.issued_at|localtime }}<br> |
||||||
|
Due: {{ invoice.due_at|localtime }}<br> |
||||||
|
{% if invoice.paid_at %}Paid: {{ invoice.paid_at|localtime }}<br>{% endif %} |
||||||
|
Currency: {{ invoice.currency_code }}<br> |
||||||
|
{% if settings.tax_number %}{{ settings.tax_label or 'Tax' }} Number: {{ settings.tax_number }}<br>{% endif %} |
||||||
|
{% if settings.business_number %}Business Number: {{ settings.business_number }}{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<table class="summary-table"> |
||||||
|
<tr> |
||||||
|
<th>Service Code</th> |
||||||
|
<th>Service</th> |
||||||
|
<th>Description</th> |
||||||
|
<th>Total</th> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ invoice.service_code or '-' }}</td> |
||||||
|
<td>{{ invoice.service_name or '-' }}</td> |
||||||
|
<td>{{ invoice.notes or '-' }}</td> |
||||||
|
<td>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
<table class="total-table"> |
||||||
|
<tr> |
||||||
|
<th>Subtotal</th> |
||||||
|
<td>{{ invoice.subtotal_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>{{ settings.tax_label or 'Tax' }}</th> |
||||||
|
<td>{{ invoice.tax_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Total</th> |
||||||
|
<td><strong>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</strong></td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Paid</th> |
||||||
|
<td>{{ invoice.amount_paid|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Remaining</th> |
||||||
|
<td>{{ (invoice.total_amount - invoice.amount_paid)|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
{% if settings.payment_terms %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Payment Terms</strong><br><br> |
||||||
|
{{ settings.payment_terms }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
{% if settings.invoice_footer %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Footer</strong><br><br> |
||||||
|
{{ settings.invoice_footer }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Revenue Report</title> |
||||||
|
<style> |
||||||
|
body { font-family: Arial, sans-serif; } |
||||||
|
.report-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(2, minmax(260px, 1fr)); |
||||||
|
gap: 18px; |
||||||
|
max-width: 900px; |
||||||
|
} |
||||||
|
.card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 16px; |
||||||
|
} |
||||||
|
.card h2 { |
||||||
|
margin-top: 0; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
.value { |
||||||
|
font-size: 28px; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.action-links a { |
||||||
|
margin-right: 16px; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<h1>Revenue Report</h1> |
||||||
|
|
||||||
|
<p><a href="/">Home</a></p> |
||||||
|
|
||||||
|
<div class="action-links"> |
||||||
|
<a href="/reports/revenue.json">Export JSON</a> |
||||||
|
<a href="/reports/revenue/print">Print Report Now</a> |
||||||
|
</div> |
||||||
|
|
||||||
|
<p> |
||||||
|
Frequency: <strong>{{ report.frequency }}</strong><br> |
||||||
|
Period: <strong>{{ report.period_label }}</strong> |
||||||
|
</p> |
||||||
|
|
||||||
|
<div class="report-grid"> |
||||||
|
<div class="card"> |
||||||
|
<h2>Collected (CAD)</h2> |
||||||
|
<div class="value">{{ report.collected_cad|money('CAD') }}</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Invoices Issued</h2> |
||||||
|
<div class="value">{{ report.invoice_count }}</div> |
||||||
|
<div>{{ report.invoiced_total|money('CAD') }} CAD total</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Outstanding Invoices</h2> |
||||||
|
<div class="value">{{ report.outstanding_count }}</div> |
||||||
|
<div>{{ report.outstanding_balance|money('CAD') }} CAD outstanding</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Overdue Invoices</h2> |
||||||
|
<div class="value">{{ report.overdue_count }}</div> |
||||||
|
<div>{{ report.overdue_balance|money('CAD') }} CAD overdue</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,199 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Settings</title> |
||||||
|
<style> |
||||||
|
body { font-family: Arial, sans-serif; } |
||||||
|
.form-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 24px; |
||||||
|
max-width: 1100px; |
||||||
|
} |
||||||
|
.card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 16px; |
||||||
|
} |
||||||
|
.card h2 { |
||||||
|
margin-top: 0; |
||||||
|
} |
||||||
|
input[type="text"], |
||||||
|
input[type="email"], |
||||||
|
input[type="password"], |
||||||
|
input[type="number"], |
||||||
|
textarea, |
||||||
|
select { |
||||||
|
width: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
margin-top: 4px; |
||||||
|
margin-bottom: 12px; |
||||||
|
padding: 8px; |
||||||
|
} |
||||||
|
textarea { min-height: 90px; } |
||||||
|
.checkbox-row { |
||||||
|
margin: 8px 0 14px 0; |
||||||
|
} |
||||||
|
.save-row { |
||||||
|
margin-top: 18px; |
||||||
|
} |
||||||
|
.logo-preview { |
||||||
|
margin: 10px 0 14px 0; |
||||||
|
} |
||||||
|
.logo-preview img { |
||||||
|
max-height: 70px; |
||||||
|
max-width: 220px; |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 6px; |
||||||
|
background: #fff; |
||||||
|
} |
||||||
|
small { |
||||||
|
color: #444; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<h1>Settings / Config</h1> |
||||||
|
|
||||||
|
<p><a href="/">Home</a></p> |
||||||
|
|
||||||
|
<form method="post"> |
||||||
|
<div class="form-grid"> |
||||||
|
<div class="card"> |
||||||
|
<h2>Business Identity</h2> |
||||||
|
|
||||||
|
Business Name<br> |
||||||
|
<input type="text" name="business_name" value="{{ settings.business_name }}"><br> |
||||||
|
|
||||||
|
Business Logo URL<br> |
||||||
|
<input type="text" name="business_logo_url" value="{{ settings.business_logo_url }}"><br> |
||||||
|
<small>Example: /static/favicon.png or https://site.com/logo.png</small><br> |
||||||
|
|
||||||
|
{% if settings.business_logo_url %} |
||||||
|
<div class="logo-preview"> |
||||||
|
<img src="{{ settings.business_logo_url }}" alt="Business Logo Preview"> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
Slogan / Tagline<br> |
||||||
|
<input type="text" name="business_tagline" value="{{ settings.business_tagline }}"><br> |
||||||
|
|
||||||
|
Business Email<br> |
||||||
|
<input type="email" name="business_email" value="{{ settings.business_email }}"><br> |
||||||
|
|
||||||
|
Business Phone<br> |
||||||
|
<input type="text" name="business_phone" value="{{ settings.business_phone }}"><br> |
||||||
|
|
||||||
|
Business Address<br> |
||||||
|
<textarea name="business_address">{{ settings.business_address }}</textarea><br> |
||||||
|
|
||||||
|
Website<br> |
||||||
|
<input type="text" name="business_website" value="{{ settings.business_website }}"><br> |
||||||
|
|
||||||
|
Business Number / Registration Number<br> |
||||||
|
<input type="text" name="business_number" value="{{ settings.business_number }}"><br> |
||||||
|
|
||||||
|
Default Currency<br> |
||||||
|
<select name="default_currency"> |
||||||
|
<option value="CAD" {% if settings.default_currency == 'CAD' %}selected{% endif %}>CAD</option> |
||||||
|
<option value="USD" {% if settings.default_currency == 'USD' %}selected{% endif %}>USD</option> |
||||||
|
<option value="ETHO" {% if settings.default_currency == 'ETHO' %}selected{% endif %}>ETHO</option> |
||||||
|
<option value="EGAZ" {% if settings.default_currency == 'EGAZ' %}selected{% endif %}>EGAZ</option> |
||||||
|
<option value="ALT" {% if settings.default_currency == 'ALT' %}selected{% endif %}>ALT</option> |
||||||
|
</select> |
||||||
|
|
||||||
|
Report Frequency<br> |
||||||
|
<select name="report_frequency"> |
||||||
|
<option value="monthly" {% if settings.report_frequency == 'monthly' %}selected{% endif %}>monthly</option> |
||||||
|
<option value="quarterly" {% if settings.report_frequency == 'quarterly' %}selected{% endif %}>quarterly</option> |
||||||
|
<option value="yearly" {% if settings.report_frequency == 'yearly' %}selected{% endif %}>yearly</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Tax Settings</h2> |
||||||
|
|
||||||
|
Local Country<br> |
||||||
|
<input type="text" name="local_country" value="{{ settings.local_country }}"><br> |
||||||
|
|
||||||
|
Tax Label<br> |
||||||
|
<input type="text" name="tax_label" value="{{ settings.tax_label }}"><br> |
||||||
|
|
||||||
|
Tax Rate (%)<br> |
||||||
|
<input type="number" step="0.01" name="tax_rate" value="{{ settings.tax_rate }}"><br> |
||||||
|
|
||||||
|
Tax Number<br> |
||||||
|
<input type="text" name="tax_number" value="{{ settings.tax_number }}"><br> |
||||||
|
|
||||||
|
<div class="checkbox-row"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" name="apply_local_tax_only" value="1" {% if settings.apply_local_tax_only == '1' %}checked{% endif %}> |
||||||
|
Apply tax only to local clients |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
Payment Terms<br> |
||||||
|
<textarea name="payment_terms">{{ settings.payment_terms }}</textarea><br> |
||||||
|
|
||||||
|
Invoice Footer<br> |
||||||
|
<textarea name="invoice_footer">{{ settings.invoice_footer }}</textarea><br> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Advanced / Email / SMTP</h2> |
||||||
|
|
||||||
|
SMTP Host<br> |
||||||
|
<input type="text" name="smtp_host" value="{{ settings.smtp_host }}"><br> |
||||||
|
|
||||||
|
SMTP Port<br> |
||||||
|
<input type="number" name="smtp_port" value="{{ settings.smtp_port }}"><br> |
||||||
|
|
||||||
|
SMTP Username<br> |
||||||
|
<input type="text" name="smtp_user" value="{{ settings.smtp_user }}"><br> |
||||||
|
|
||||||
|
SMTP Password<br> |
||||||
|
<input type="password" name="smtp_pass" value="{{ settings.smtp_pass }}"><br> |
||||||
|
|
||||||
|
From Email<br> |
||||||
|
<input type="email" name="smtp_from_email" value="{{ settings.smtp_from_email }}"><br> |
||||||
|
|
||||||
|
From Name<br> |
||||||
|
<input type="text" name="smtp_from_name" value="{{ settings.smtp_from_name }}"><br> |
||||||
|
|
||||||
|
<div class="checkbox-row"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" name="smtp_use_tls" value="1" {% if settings.smtp_use_tls == '1' %}checked{% endif %}> |
||||||
|
Use TLS |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="checkbox-row"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" name="smtp_use_ssl" value="1" {% if settings.smtp_use_ssl == '1' %}checked{% endif %}> |
||||||
|
Use SSL |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Notes</h2> |
||||||
|
<p> |
||||||
|
Branding, tax identity, and SMTP values are stored here for this installation. |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
Logo can be a local static path like <strong>/static/favicon.png</strong> or a full external/IPFS URL. |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
Email sending is not wired yet, but these SMTP settings are stored now so the next step can use them. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="save-row"> |
||||||
|
<button type="submit">Save Settings</button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,60 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>OTB Billing Dashboard</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
|
||||||
|
{% if app_settings.business_logo_url %} |
||||||
|
<div style="margin-bottom:15px;"> |
||||||
|
<img src="{{ app_settings.business_logo_url }}" style="height:60px;"> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
<h1>{{ app_settings.business_name or 'OTB Billing' }} Dashboard</h1> |
||||||
|
{% if request.args.get('pkg_email') == '1' %} |
||||||
|
<div style="border:1px solid #166534;background:#dcfce7;padding:10px;margin-bottom:15px;max-width:900px;"> |
||||||
|
Accounting package emailed successfully. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
{% if request.args.get('pkg_email_failed') == '1' %} |
||||||
|
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;max-width:900px;"> |
||||||
|
Accounting package email failed. Check SMTP settings or server log. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
{% if request.args.get('pkg_email_failed') == '1' %} |
||||||
|
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;max-width:900px;"> |
||||||
|
Accounting package email failed. Check SMTP settings or server log. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
<p><a href="/clients">Clients</a></p> |
||||||
|
<p><a href="/services">Services</a></p> |
||||||
|
<p><a href="/invoices">Invoices</a></p> |
||||||
|
<p><a href="/payments">Payments</a></p> |
||||||
|
<p><a href="/reports/revenue">Revenue Report</a></p> |
||||||
|
<p><a href="/reports/accounting-package.zip">Monthly Accounting Package</a></p> |
||||||
|
<form method="post" action="/reports/accounting-package/email" style="margin:0 0 16px 0;"><button type="submit">Email Accounting Package</button></form> |
||||||
|
<p><a href="/settings">Settings / Config</a></p> |
||||||
|
<p><a href="/dbtest">DB Test</a></p> |
||||||
|
|
||||||
|
<table border="1" cellpadding="10"> |
||||||
|
<tr> |
||||||
|
<th>Total Clients</th> |
||||||
|
<th>Active Services</th> |
||||||
|
<th>Outstanding Invoices</th> |
||||||
|
<th>Revenue Received (CAD)</th> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ total_clients }}</td> |
||||||
|
<td>{{ active_services }}</td> |
||||||
|
<td>{{ outstanding_invoices }}</td> |
||||||
|
<td>{{ revenue_received|money('CAD') }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
<p>Displayed times are shown in Eastern Time (Toronto).</p> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,235 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Invoice {{ invoice.invoice_number }}</title> |
||||||
|
<style> |
||||||
|
body { |
||||||
|
font-family: Arial, sans-serif; |
||||||
|
margin: 30px; |
||||||
|
color: #111; |
||||||
|
} |
||||||
|
.top-links { |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
.top-links a { |
||||||
|
margin-right: 15px; |
||||||
|
} |
||||||
|
.invoice-wrap { |
||||||
|
max-width: 900px; |
||||||
|
} |
||||||
|
.header-row { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: flex-start; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.title-box h1 { |
||||||
|
margin: 0 0 8px 0; |
||||||
|
} |
||||||
|
.status-badge { |
||||||
|
display: inline-block; |
||||||
|
padding: 4px 10px; |
||||||
|
border-radius: 999px; |
||||||
|
font-size: 12px; |
||||||
|
font-weight: bold; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
.status-draft { background: #e5e7eb; color: #111827; } |
||||||
|
.status-pending { background: #dbeafe; color: #1d4ed8; } |
||||||
|
.status-partial { background: #fef3c7; color: #92400e; } |
||||||
|
.status-paid { background: #dcfce7; color: #166534; } |
||||||
|
.status-overdue { background: #fee2e2; color: #991b1b; } |
||||||
|
.status-cancelled { background: #e5e7eb; color: #4b5563; } |
||||||
|
|
||||||
|
.info-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 30px; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.info-card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
.info-card h3 { |
||||||
|
margin-top: 0; |
||||||
|
} |
||||||
|
.summary-table, |
||||||
|
.total-table { |
||||||
|
width: 100%; |
||||||
|
border-collapse: collapse; |
||||||
|
margin-bottom: 25px; |
||||||
|
} |
||||||
|
.summary-table th, |
||||||
|
.summary-table td, |
||||||
|
.total-table th, |
||||||
|
.total-table td { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 10px; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
.total-table { |
||||||
|
max-width: 420px; |
||||||
|
margin-left: auto; |
||||||
|
} |
||||||
|
.notes-box { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 15px; |
||||||
|
white-space: pre-wrap; |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
.print-only { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
@media print { |
||||||
|
.top-links, |
||||||
|
.screen-only, |
||||||
|
footer { |
||||||
|
display: none !important; |
||||||
|
} |
||||||
|
.print-only { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<div class="invoice-wrap"> |
||||||
|
{% if request.args.get('email_sent') == '1' %} |
||||||
|
<div style="border:1px solid #166534;background:#dcfce7;padding:10px;margin-bottom:15px;"> |
||||||
|
Invoice email sent successfully. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
{% if request.args.get('email_failed') == '1' %} |
||||||
|
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;"> |
||||||
|
Invoice email failed. Check SMTP settings or server log. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
<div class="top-links screen-only"> |
||||||
|
<a href="/">Home</a> |
||||||
|
<a href="/invoices">Back to Invoices</a> |
||||||
|
<a href="/invoices/edit/{{ invoice.id }}">Edit Invoice</a> |
||||||
|
<a href="#" onclick="window.print(); return false;">Print</a> |
||||||
|
<a href="/invoices/pdf/{{ invoice.id }}">PDF</a> |
||||||
|
{% if invoice.email %} |
||||||
|
<form method="post" action="/invoices/email/{{ invoice.id }}" style="display:inline;"> |
||||||
|
<button type="submit">Send Email</button> |
||||||
|
</form> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="header-row"> |
||||||
|
|
||||||
|
{% if settings.business_logo_url %} |
||||||
|
<img src="{{ settings.business_logo_url }}" style="height:70px;margin-bottom:10px;"> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
<div class="title-box"> |
||||||
|
<h1>Invoice {{ invoice.invoice_number }}</h1> |
||||||
|
<span class="status-badge status-{{ invoice.status }}">{{ invoice.status }}</span> |
||||||
|
</div> |
||||||
|
<div style="text-align:right;"> |
||||||
|
<strong>{{ settings.business_name or 'OTB Billing' }}</strong><br> |
||||||
|
{{ settings.business_tagline or '' }}<br> |
||||||
|
{% if settings.business_address %}{{ settings.business_address }}<br>{% endif %} |
||||||
|
{% if settings.business_email %}{{ settings.business_email }}<br>{% endif %} |
||||||
|
{% if settings.business_phone %}{{ settings.business_phone }}<br>{% endif %} |
||||||
|
{% if settings.business_website %}{{ settings.business_website }}{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="info-grid"> |
||||||
|
<div class="info-card"> |
||||||
|
<h3>Bill To</h3> |
||||||
|
<strong>{{ invoice.company_name }}</strong><br> |
||||||
|
{% if invoice.contact_name %}{{ invoice.contact_name }}<br>{% endif %} |
||||||
|
{% if invoice.email %}{{ invoice.email }}<br>{% endif %} |
||||||
|
{% if invoice.phone %}{{ invoice.phone }}<br>{% endif %} |
||||||
|
Client Code: {{ invoice.client_code }} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="info-card"> |
||||||
|
<h3>Invoice Details</h3> |
||||||
|
Invoice #: {{ invoice.invoice_number }}<br> |
||||||
|
Issued: {{ invoice.issued_at|localtime }}<br> |
||||||
|
Due: {{ invoice.due_at|localtime }}<br> |
||||||
|
{% if invoice.paid_at %}Paid: {{ invoice.paid_at|localtime }}<br>{% endif %} |
||||||
|
Currency: {{ invoice.currency_code }}<br> |
||||||
|
{% if settings.tax_number %}{{ settings.tax_label or 'Tax' }} Number: {{ settings.tax_number }}<br>{% endif %} |
||||||
|
{% if settings.business_number %}Business Number: {{ settings.business_number }}{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<table class="summary-table"> |
||||||
|
<tr> |
||||||
|
<th>Service Code</th> |
||||||
|
<th>Service</th> |
||||||
|
<th>Description</th> |
||||||
|
<th>Total</th> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ invoice.service_code or '-' }}</td> |
||||||
|
<td>{{ invoice.service_name or '-' }}</td> |
||||||
|
<td>{{ invoice.notes or '-' }}</td> |
||||||
|
<td>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
<table class="total-table"> |
||||||
|
<tr> |
||||||
|
<th>Subtotal</th> |
||||||
|
<td>{{ invoice.subtotal_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>{{ settings.tax_label or 'Tax' }}</th> |
||||||
|
<td>{{ invoice.tax_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Total</th> |
||||||
|
<td><strong>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</strong></td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Paid</th> |
||||||
|
<td>{{ invoice.amount_paid|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th>Remaining</th> |
||||||
|
<td>{{ (invoice.total_amount - invoice.amount_paid)|money(invoice.currency_code) }} {{ invoice.currency_code }}</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
|
||||||
|
{% if latest_email_log %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Latest Email Activity</strong><br><br> |
||||||
|
Status: {{ latest_email_log.status }}<br> |
||||||
|
Recipient: {{ latest_email_log.recipient_email }}<br> |
||||||
|
Subject: {{ latest_email_log.subject }}<br> |
||||||
|
Sent At: {{ latest_email_log.sent_at|localtime }}<br> |
||||||
|
{% if latest_email_log.error_message %} |
||||||
|
Error: {{ latest_email_log.error_message }} |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
{% if settings.payment_terms %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Payment Terms</strong><br><br> |
||||||
|
{{ settings.payment_terms }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
{% if settings.invoice_footer %} |
||||||
|
<div class="notes-box"> |
||||||
|
<strong>Footer</strong><br><br> |
||||||
|
{{ settings.invoice_footer }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,91 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Revenue Report</title> |
||||||
|
<style> |
||||||
|
body { font-family: Arial, sans-serif; } |
||||||
|
.report-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(2, minmax(260px, 1fr)); |
||||||
|
gap: 18px; |
||||||
|
max-width: 900px; |
||||||
|
} |
||||||
|
.card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 16px; |
||||||
|
} |
||||||
|
.card h2 { |
||||||
|
margin-top: 0; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
.value { |
||||||
|
font-size: 28px; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.action-links a { |
||||||
|
margin-right: 16px; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<h1>Revenue Report</h1> |
||||||
|
{% if request.args.get('email_sent') == '1' %} |
||||||
|
<div style="border:1px solid #166534;background:#dcfce7;padding:10px;margin-bottom:15px;max-width:900px;"> |
||||||
|
Revenue report JSON emailed successfully. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
{% if request.args.get('email_failed') == '1' %} |
||||||
|
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;max-width:900px;"> |
||||||
|
Revenue report email failed. Check SMTP settings or server log. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
{% if request.args.get('email_failed') == '1' %} |
||||||
|
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;max-width:900px;"> |
||||||
|
Revenue report email failed. Check SMTP settings or server log. |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
<p><a href="/">Home</a></p> |
||||||
|
|
||||||
|
<div class="action-links"> |
||||||
|
<a href="/reports/revenue.json">Export JSON</a> |
||||||
|
<a href="/reports/revenue/print">Print Report Now</a> |
||||||
|
<form method="post" action="/reports/revenue/email" style="display:inline;"> |
||||||
|
<button type="submit">Email JSON Report</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
|
||||||
|
<p> |
||||||
|
Frequency: <strong>{{ report.frequency }}</strong><br> |
||||||
|
Period: <strong>{{ report.period_label }}</strong> |
||||||
|
</p> |
||||||
|
|
||||||
|
<div class="report-grid"> |
||||||
|
<div class="card"> |
||||||
|
<h2>Collected (CAD)</h2> |
||||||
|
<div class="value">{{ report.collected_cad|money('CAD') }}</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Invoices Issued</h2> |
||||||
|
<div class="value">{{ report.invoice_count }}</div> |
||||||
|
<div>{{ report.invoiced_total|money('CAD') }} CAD total</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Outstanding Invoices</h2> |
||||||
|
<div class="value">{{ report.outstanding_count }}</div> |
||||||
|
<div>{{ report.outstanding_balance|money('CAD') }} CAD outstanding</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Overdue Invoices</h2> |
||||||
|
<div class="value">{{ report.overdue_count }}</div> |
||||||
|
<div>{{ report.overdue_balance|money('CAD') }} CAD overdue</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,202 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Settings</title> |
||||||
|
<style> |
||||||
|
body { font-family: Arial, sans-serif; } |
||||||
|
.form-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 24px; |
||||||
|
max-width: 1100px; |
||||||
|
} |
||||||
|
.card { |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 16px; |
||||||
|
} |
||||||
|
.card h2 { |
||||||
|
margin-top: 0; |
||||||
|
} |
||||||
|
input[type="text"], |
||||||
|
input[type="email"], |
||||||
|
input[type="password"], |
||||||
|
input[type="number"], |
||||||
|
textarea, |
||||||
|
select { |
||||||
|
width: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
margin-top: 4px; |
||||||
|
margin-bottom: 12px; |
||||||
|
padding: 8px; |
||||||
|
} |
||||||
|
textarea { min-height: 90px; } |
||||||
|
.checkbox-row { |
||||||
|
margin: 8px 0 14px 0; |
||||||
|
} |
||||||
|
.save-row { |
||||||
|
margin-top: 18px; |
||||||
|
} |
||||||
|
.logo-preview { |
||||||
|
margin: 10px 0 14px 0; |
||||||
|
} |
||||||
|
.logo-preview img { |
||||||
|
max-height: 70px; |
||||||
|
max-width: 220px; |
||||||
|
border: 1px solid #ccc; |
||||||
|
padding: 6px; |
||||||
|
background: #fff; |
||||||
|
} |
||||||
|
small { |
||||||
|
color: #444; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
|
||||||
|
<h1>Settings / Config</h1> |
||||||
|
|
||||||
|
<p><a href="/">Home</a></p> |
||||||
|
|
||||||
|
<form method="post"> |
||||||
|
<div class="form-grid"> |
||||||
|
<div class="card"> |
||||||
|
<h2>Business Identity</h2> |
||||||
|
|
||||||
|
Business Name<br> |
||||||
|
<input type="text" name="business_name" value="{{ settings.business_name }}"><br> |
||||||
|
|
||||||
|
Business Logo URL<br> |
||||||
|
<input type="text" name="business_logo_url" value="{{ settings.business_logo_url }}"><br> |
||||||
|
<small>Example: /static/favicon.png or https://site.com/logo.png</small><br> |
||||||
|
|
||||||
|
{% if settings.business_logo_url %} |
||||||
|
<div class="logo-preview"> |
||||||
|
<img src="{{ settings.business_logo_url }}" alt="Business Logo Preview"> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
Slogan / Tagline<br> |
||||||
|
<input type="text" name="business_tagline" value="{{ settings.business_tagline }}"><br> |
||||||
|
|
||||||
|
Business Email<br> |
||||||
|
<input type="email" name="business_email" value="{{ settings.business_email }}"><br> |
||||||
|
|
||||||
|
Business Phone<br> |
||||||
|
<input type="text" name="business_phone" value="{{ settings.business_phone }}"><br> |
||||||
|
|
||||||
|
Business Address<br> |
||||||
|
<textarea name="business_address">{{ settings.business_address }}</textarea><br> |
||||||
|
|
||||||
|
Website<br> |
||||||
|
<input type="text" name="business_website" value="{{ settings.business_website }}"><br> |
||||||
|
|
||||||
|
Business Number / Registration Number<br> |
||||||
|
<input type="text" name="business_number" value="{{ settings.business_number }}"><br> |
||||||
|
|
||||||
|
Default Currency<br> |
||||||
|
<select name="default_currency"> |
||||||
|
<option value="CAD" {% if settings.default_currency == 'CAD' %}selected{% endif %}>CAD</option> |
||||||
|
<option value="USD" {% if settings.default_currency == 'USD' %}selected{% endif %}>USD</option> |
||||||
|
<option value="ETHO" {% if settings.default_currency == 'ETHO' %}selected{% endif %}>ETHO</option> |
||||||
|
<option value="EGAZ" {% if settings.default_currency == 'EGAZ' %}selected{% endif %}>EGAZ</option> |
||||||
|
<option value="ALT" {% if settings.default_currency == 'ALT' %}selected{% endif %}>ALT</option> |
||||||
|
</select> |
||||||
|
|
||||||
|
Report Frequency<br> |
||||||
|
<select name="report_frequency"> |
||||||
|
<option value="monthly" {% if settings.report_frequency == 'monthly' %}selected{% endif %}>monthly</option> |
||||||
|
<option value="quarterly" {% if settings.report_frequency == 'quarterly' %}selected{% endif %}>quarterly</option> |
||||||
|
<option value="yearly" {% if settings.report_frequency == 'yearly' %}selected{% endif %}>yearly</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Tax Settings</h2> |
||||||
|
|
||||||
|
Local Country<br> |
||||||
|
<input type="text" name="local_country" value="{{ settings.local_country }}"><br> |
||||||
|
|
||||||
|
Tax Label<br> |
||||||
|
<input type="text" name="tax_label" value="{{ settings.tax_label }}"><br> |
||||||
|
|
||||||
|
Tax Rate (%)<br> |
||||||
|
<input type="number" step="0.01" name="tax_rate" value="{{ settings.tax_rate }}"><br> |
||||||
|
|
||||||
|
Tax Number<br> |
||||||
|
<input type="text" name="tax_number" value="{{ settings.tax_number }}"><br> |
||||||
|
|
||||||
|
<div class="checkbox-row"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" name="apply_local_tax_only" value="1" {% if settings.apply_local_tax_only == '1' %}checked{% endif %}> |
||||||
|
Apply tax only to local clients |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
Payment Terms<br> |
||||||
|
<textarea name="payment_terms">{{ settings.payment_terms }}</textarea><br> |
||||||
|
|
||||||
|
Invoice Footer<br> |
||||||
|
<textarea name="invoice_footer">{{ settings.invoice_footer }}</textarea><br> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Advanced / Email / SMTP</h2> |
||||||
|
|
||||||
|
SMTP Host<br> |
||||||
|
<input type="text" name="smtp_host" value="{{ settings.smtp_host }}"><br> |
||||||
|
|
||||||
|
SMTP Port<br> |
||||||
|
<input type="number" name="smtp_port" value="{{ settings.smtp_port }}"><br> |
||||||
|
|
||||||
|
SMTP Username<br> |
||||||
|
<input type="text" name="smtp_user" value="{{ settings.smtp_user }}"><br> |
||||||
|
|
||||||
|
SMTP Password<br> |
||||||
|
<input type="password" name="smtp_pass" value="{{ settings.smtp_pass }}"><br> |
||||||
|
|
||||||
|
From Email<br> |
||||||
|
<input type="email" name="smtp_from_email" value="{{ settings.smtp_from_email }}"><br> |
||||||
|
|
||||||
|
From Name<br> |
||||||
|
<input type="text" name="smtp_from_name" value="{{ settings.smtp_from_name }}"><br> |
||||||
|
|
||||||
|
Report / Accounting Delivery Email<br> |
||||||
|
<input type="email" name="report_delivery_email" value="{{ settings.report_delivery_email }}"><br> |
||||||
|
|
||||||
|
<div class="checkbox-row"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" name="smtp_use_tls" value="1" {% if settings.smtp_use_tls == '1' %}checked{% endif %}> |
||||||
|
Use TLS |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="checkbox-row"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" name="smtp_use_ssl" value="1" {% if settings.smtp_use_ssl == '1' %}checked{% endif %}> |
||||||
|
Use SSL |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<h2>Notes</h2> |
||||||
|
<p> |
||||||
|
Branding, tax identity, and SMTP values are stored here for this installation. |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
Logo can be a local static path like <strong>/static/favicon.png</strong> or a full external/IPFS URL. |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
Email sending is not wired yet, but these SMTP settings are stored now so the next step can use them. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="save-row"> |
||||||
|
<button type="submit">Save Settings</button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
|
||||||
|
{% include "footer.html" %} |
||||||
|
</body> |
||||||
|
</html> |
||||||
Loading…
Reference in new issue