70 changed files with 36799 additions and 37 deletions
@ -1 +1,24 @@
|
||||
# otb-billing |
||||
|
||||
## v0.3.0 — 2026-03-09 |
||||
|
||||
Major operational milestone release. |
||||
|
||||
New Features |
||||
------------ |
||||
- Revenue reporting system |
||||
- JSON export for reports |
||||
- Batch invoice printing |
||||
- Batch CSV export |
||||
- Filtered invoice export |
||||
- Invoice logo support (PDF + print) |
||||
- Business identity settings |
||||
- Report frequency selector (monthly / quarterly / yearly) |
||||
|
||||
Infrastructure |
||||
-------------- |
||||
- Improved reporting backend |
||||
- Cleaner filter handling |
||||
- Settings system extended |
||||
- Print layouts stabilized |
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,100 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Payments</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.status-confirmed { background: #dcfce7; color: #166534; } |
||||
.status-reversed { background: #fee2e2; color: #991b1b; } |
||||
|
||||
.invoice-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 11px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.invoice-pending { background: #dbeafe; color: #1d4ed8; } |
||||
.invoice-partial { background: #fef3c7; color: #92400e; } |
||||
.invoice-paid { background: #dcfce7; color: #166534; } |
||||
.invoice-overdue { background: #fee2e2; color: #991b1b; } |
||||
.invoice-cancelled { background: #e5e7eb; color: #4b5563; } |
||||
.invoice-draft { background: #e5e7eb; color: #111827; } |
||||
|
||||
.inline-form { |
||||
display: inline; |
||||
margin: 0; |
||||
} |
||||
.void-btn { |
||||
background: #991b1b; |
||||
color: white; |
||||
border: 0; |
||||
padding: 4px 8px; |
||||
cursor: pointer; |
||||
} |
||||
.void-btn:hover { |
||||
opacity: 0.9; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Payments</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/payments/new">Record Payment</a></p> |
||||
|
||||
<table border="1" cellpadding="6"> |
||||
<tr> |
||||
<th>ID</th> |
||||
<th>Invoice</th> |
||||
<th>Client</th> |
||||
<th>Method</th> |
||||
<th>Currency</th> |
||||
<th>Amount</th> |
||||
<th>CAD Value</th> |
||||
<th>Payment Status</th> |
||||
<th>Invoice Status</th> |
||||
<th>Received</th> |
||||
<th>Actions</th> |
||||
</tr> |
||||
|
||||
{% for p in payments %} |
||||
<tr> |
||||
<td>{{ p.id }}</td> |
||||
<td>{{ p.invoice_number }}</td> |
||||
<td>{{ p.client_code }} - {{ p.company_name }}</td> |
||||
<td>{{ p.payment_method }}</td> |
||||
<td>{{ p.payment_currency }}</td> |
||||
<td>{{ p.payment_amount|money(p.payment_currency) }}</td> |
||||
<td>{{ p.cad_value_at_payment|money('CAD') }}</td> |
||||
<td><span class="status-badge status-{{ p.payment_status }}">{{ p.payment_status }}</span></td> |
||||
<td><span class="invoice-badge invoice-{{ p.invoice_status }}">{{ p.invoice_status }}</span></td> |
||||
<td>{{ p.received_at|localtime }}</td> |
||||
<td> |
||||
<a href="/payments/edit/{{ p.id }}">Edit</a> |
||||
{% if p.payment_status == 'confirmed' %} |
||||
| |
||||
<form method="post" action="/payments/void/{{ p.id }}" class="inline-form" onsubmit="return confirm('Void this payment? This will reverse it from invoice totals but keep the record for history.');"> |
||||
<button type="submit" class="void-btn">Void</button> |
||||
</form> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>OTB Billing Dashboard</title> |
||||
</head> |
||||
<body> |
||||
|
||||
<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="/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,202 @@
|
||||
<!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"> |
||||
<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,169 @@
|
||||
<!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; |
||||
} |
||||
</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> |
||||
|
||||
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> |
||||
</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>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> |
||||
These settings become the identity and delivery configuration for this installation. |
||||
</p> |
||||
<p> |
||||
Email sending is not wired yet, but these SMTP settings are stored now so the next step can use them. |
||||
</p> |
||||
<p> |
||||
Tax settings are also stored now so invoice and automation logic can use them later. |
||||
</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,143 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.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; } |
||||
|
||||
.locked-note { |
||||
color: #92400e; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.filter-box { |
||||
border: 1px solid #ccc; |
||||
padding: 12px; |
||||
margin: 14px 0; |
||||
max-width: 950px; |
||||
} |
||||
.filter-row { |
||||
display: flex; |
||||
gap: 14px; |
||||
align-items: end; |
||||
flex-wrap: wrap; |
||||
} |
||||
.filter-row div { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
input[type="date"], |
||||
select { |
||||
padding: 6px; |
||||
min-width: 150px; |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/invoices/new">Create Invoice</a></p> |
||||
|
||||
<div class="filter-box"> |
||||
<form method="get" action="/invoices"> |
||||
<div class="filter-row"> |
||||
<div> |
||||
<label for="start_date">Issued From</label> |
||||
<input type="date" id="start_date" name="start_date" value="{{ filters.start_date if filters is defined else '' }}"> |
||||
</div> |
||||
|
||||
<div> |
||||
<label for="end_date">Issued To</label> |
||||
<input type="date" id="end_date" name="end_date" value="{{ filters.end_date if filters is defined else '' }}"> |
||||
</div> |
||||
|
||||
<div> |
||||
<label for="status">Status</label> |
||||
<select id="status" name="status"> |
||||
<option value="" {% if not filters.status %}selected{% endif %}>All</option> |
||||
<option value="draft" {% if filters.status == 'draft' %}selected{% endif %}>draft</option> |
||||
<option value="pending" {% if filters.status == 'pending' %}selected{% endif %}>pending</option> |
||||
<option value="partial" {% if filters.status == 'partial' %}selected{% endif %}>partial</option> |
||||
<option value="paid" {% if filters.status == 'paid' %}selected{% endif %}>paid</option> |
||||
<option value="overdue" {% if filters.status == 'overdue' %}selected{% endif %}>overdue</option> |
||||
<option value="cancelled" {% if filters.status == 'cancelled' %}selected{% endif %}>cancelled</option> |
||||
</select> |
||||
</div> |
||||
|
||||
<div> |
||||
<button type="submit">Apply Filters</button> |
||||
</div> |
||||
|
||||
<div> |
||||
<a href="/invoices">Clear Filters</a> |
||||
</div> |
||||
|
||||
<div> |
||||
<a href="/invoices/export.csv?start_date={{ filters.start_date if filters is defined else '' }}&end_date={{ filters.end_date if filters is defined else '' }}&status={{ filters.status if filters is defined else '' }}">Export Filtered CSV</a> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
|
||||
<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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td> |
||||
<span class="status-badge status-{{ i.status }}">{{ i.status }}</span> |
||||
</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/view/{{ i.id }}">View</a> | |
||||
<a href="/invoices/pdf/{{ i.id }}">PDF</a> | |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
<span class="locked-note">(Locked)</span> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,169 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.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; } |
||||
|
||||
.locked-note { |
||||
color: #92400e; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.filter-box { |
||||
border: 1px solid #ccc; |
||||
padding: 12px; |
||||
margin: 14px 0; |
||||
max-width: 1100px; |
||||
} |
||||
.filter-row { |
||||
display: flex; |
||||
gap: 14px; |
||||
align-items: end; |
||||
flex-wrap: wrap; |
||||
} |
||||
.filter-row div { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
input[type="date"], |
||||
input[type="number"], |
||||
select { |
||||
padding: 6px; |
||||
min-width: 150px; |
||||
} |
||||
.action-links { |
||||
margin-top: 10px; |
||||
} |
||||
.action-links a { |
||||
margin-right: 18px; |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/invoices/new">Create Invoice</a></p> |
||||
|
||||
<div class="filter-box"> |
||||
<form method="get" action="/invoices"> |
||||
<div class="filter-row"> |
||||
<div> |
||||
<label for="start_date">Issued From</label> |
||||
<input type="date" id="start_date" name="start_date" value="{{ filters.start_date if filters is defined else '' }}"> |
||||
</div> |
||||
|
||||
<div> |
||||
<label for="end_date">Issued To</label> |
||||
<input type="date" id="end_date" name="end_date" value="{{ filters.end_date if filters is defined else '' }}"> |
||||
</div> |
||||
|
||||
<div> |
||||
<label for="status">Status</label> |
||||
<select id="status" name="status"> |
||||
<option value="" {% if not filters.status %}selected{% endif %}>All</option> |
||||
<option value="draft" {% if filters.status == 'draft' %}selected{% endif %}>draft</option> |
||||
<option value="pending" {% if filters.status == 'pending' %}selected{% endif %}>pending</option> |
||||
<option value="partial" {% if filters.status == 'partial' %}selected{% endif %}>partial</option> |
||||
<option value="paid" {% if filters.status == 'paid' %}selected{% endif %}>paid</option> |
||||
<option value="overdue" {% if filters.status == 'overdue' %}selected{% endif %}>overdue</option> |
||||
<option value="cancelled" {% if filters.status == 'cancelled' %}selected{% endif %}>cancelled</option> |
||||
</select> |
||||
</div> |
||||
|
||||
<div> |
||||
<label for="client_id">Client</label> |
||||
<select id="client_id" name="client_id"> |
||||
<option value="" {% if not filters.client_id %}selected{% endif %}>All Clients</option> |
||||
{% for c in clients %} |
||||
<option value="{{ c.id }}" {% if filters.client_id == (c.id|string) %}selected{% endif %}> |
||||
{{ c.client_code }} - {{ c.company_name }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</div> |
||||
|
||||
<div> |
||||
<label for="limit">Limit</label> |
||||
<input type="number" id="limit" name="limit" min="1" step="1" value="{{ filters.limit if filters is defined else '' }}"> |
||||
</div> |
||||
|
||||
<div> |
||||
<button type="submit">Apply Filters</button> |
||||
</div> |
||||
|
||||
<div> |
||||
<a href="/invoices">Clear Filters</a> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="action-links"> |
||||
<a href="/invoices/export.csv?start_date={{ filters.start_date if filters is defined else '' }}&end_date={{ filters.end_date if filters is defined else '' }}&status={{ filters.status if filters is defined else '' }}&client_id={{ filters.client_id if filters is defined else '' }}&limit={{ filters.limit if filters is defined else '' }}">Export Filtered CSV</a> |
||||
|
||||
<a href="/invoices/export-pdf.zip?start_date={{ filters.start_date if filters is defined else '' }}&end_date={{ filters.end_date if filters is defined else '' }}&status={{ filters.status if filters is defined else '' }}&client_id={{ filters.client_id if filters is defined else '' }}&limit={{ filters.limit if filters is defined else '' }}">Export Filtered PDF ZIP</a> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
|
||||
<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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td> |
||||
<span class="status-badge status-{{ i.status }}">{{ i.status }}</span> |
||||
</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/view/{{ i.id }}">View</a> | |
||||
<a href="/invoices/pdf/{{ i.id }}">PDF</a> | |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
<span class="locked-note">(Locked)</span> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Clients</title> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Clients</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/clients/new">Add Client</a></p> |
||||
|
||||
<table border="1" cellpadding="6"> |
||||
<tr> |
||||
<th>ID</th> |
||||
<th>Code</th> |
||||
<th>Company</th> |
||||
<th>Contact</th> |
||||
<th>Email</th> |
||||
<th>Phone</th> |
||||
<th>Status</th> |
||||
<th>Actions</th> |
||||
</tr> |
||||
|
||||
{% for c in clients %} |
||||
<tr> |
||||
<td>{{ c.id }}</td> |
||||
<td>{{ c.client_code }}</td> |
||||
<td>{{ c.company_name }}</td> |
||||
<td>{{ c.contact_name }}</td> |
||||
<td>{{ c.email }}</td> |
||||
<td>{{ c.phone }}</td> |
||||
<td>{{ c.status }}</td> |
||||
<td> |
||||
<a href="/clients/edit/{{ c.id }}">Edit</a> | |
||||
<a href="/credits/{{ c.id }}" |
||||
style="{% if c.credit_balance > 0 %}color: green;{% elif c.credit_balance < 0 %}color: red;{% else %}color: blue;{% endif %}"> |
||||
Ledger ({{ c.credit_balance|money('CAD') }}) |
||||
</a> |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,80 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.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; } |
||||
|
||||
.locked-note { |
||||
color: #92400e; |
||||
font-weight: bold; |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td> |
||||
<span class="status-badge status-{{ i.status }}">{{ i.status }}</span> |
||||
</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/view/{{ i.id }}">View</a> | |
||||
<a href="/invoices/pdf/{{ i.id }}">PDF</a> | |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
<span class="locked-note">(Locked)</span> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,102 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Payments</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.status-confirmed { background: #dcfce7; color: #166534; } |
||||
.status-reversed { background: #fee2e2; color: #991b1b; } |
||||
|
||||
.invoice-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 11px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.invoice-draft { background: #e5e7eb; color: #111827; } |
||||
.invoice-pending { background: #dbeafe; color: #1d4ed8; } |
||||
.invoice-partial { background: #fef3c7; color: #92400e; } |
||||
.invoice-paid { background: #dcfce7; color: #166534; } |
||||
.invoice-overdue { background: #fee2e2; color: #991b1b; } |
||||
.invoice-cancelled { background: #e5e7eb; color: #4b5563; } |
||||
|
||||
.inline-form { |
||||
display: inline; |
||||
margin: 0; |
||||
} |
||||
.void-btn { |
||||
background: #991b1b; |
||||
color: white; |
||||
border: 0; |
||||
padding: 4px 8px; |
||||
cursor: pointer; |
||||
} |
||||
.void-btn:hover { |
||||
opacity: 0.9; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Payments</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/payments/new">Record Payment</a></p> |
||||
|
||||
<table border="1" cellpadding="6"> |
||||
<tr> |
||||
<th>ID</th> |
||||
<th>Invoice</th> |
||||
<th>Client</th> |
||||
<th>Method</th> |
||||
<th>Currency</th> |
||||
<th>Amount</th> |
||||
<th>CAD Value</th> |
||||
<th>Payment Status</th> |
||||
<th>Invoice Status</th> |
||||
<th>Remaining</th> |
||||
<th>Received</th> |
||||
<th>Actions</th> |
||||
</tr> |
||||
|
||||
{% for p in payments %} |
||||
<tr> |
||||
<td>{{ p.id }}</td> |
||||
<td>{{ p.invoice_number }}</td> |
||||
<td>{{ p.client_code }} - {{ p.company_name }}</td> |
||||
<td>{{ p.payment_method }}</td> |
||||
<td>{{ p.payment_currency }}</td> |
||||
<td>{{ p.payment_amount|money(p.payment_currency) }}</td> |
||||
<td>{{ p.cad_value_at_payment|money('CAD') }}</td> |
||||
<td><span class="status-badge status-{{ p.payment_status }}">{{ p.payment_status }}</span></td> |
||||
<td><span class="invoice-badge invoice-{{ p.invoice_status }}">{{ p.invoice_status }}</span></td> |
||||
<td>{{ (p.total_amount - p.amount_paid)|money(p.invoice_currency_code) }}</td> |
||||
<td>{{ p.received_at|localtime }}</td> |
||||
<td> |
||||
<a href="/payments/edit/{{ p.id }}">Edit</a> |
||||
{% if p.payment_status == 'confirmed' %} |
||||
| |
||||
<form method="post" action="/payments/void/{{ p.id }}" class="inline-form" onsubmit="return confirm('Void this payment? This will reverse it from invoice totals but keep the record for history.');"> |
||||
<button type="submit" class="void-btn">Void</button> |
||||
</form> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,81 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>New Invoice</title> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Create Invoice</h1> |
||||
|
||||
{% 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> |
||||
Client *<br> |
||||
<select name="client_id" required> |
||||
<option value="">Select client</option> |
||||
{% for c in clients %} |
||||
<option value="{{ c.id }}" {% if form_data.get('client_id') == (c.id|string) %}selected{% endif %}> |
||||
{{ c.client_code }} - {{ c.company_name }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Service *<br> |
||||
<select name="service_id" required> |
||||
<option value="">Select service</option> |
||||
{% for s in services %} |
||||
<option value="{{ s.id }}" {% if form_data.get('service_id') == (s.id|string) %}selected{% endif %}> |
||||
{{ s.service_code }} - {{ s.service_name }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Currency *<br> |
||||
<select name="currency_code" required> |
||||
<option value="CAD" {% if form_data.get('currency_code', 'CAD') == 'CAD' %}selected{% endif %}>CAD</option> |
||||
<option value="ETHO" {% if form_data.get('currency_code') == 'ETHO' %}selected{% endif %}>ETHO</option> |
||||
<option value="EGAZ" {% if form_data.get('currency_code') == 'EGAZ' %}selected{% endif %}>EGAZ</option> |
||||
<option value="ALT" {% if form_data.get('currency_code') == 'ALT' %}selected{% endif %}>ALT</option> |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Total Amount *<br> |
||||
<input type="number" step="0.00000001" min="0.00000001" name="total_amount" value="{{ form_data.get('total_amount', '') }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
Due Date *<br> |
||||
<input type="date" name="due_at" value="{{ form_data.get('due_at', '') }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
Notes<br> |
||||
<textarea name="notes">{{ form_data.get('notes', '') }}</textarea> |
||||
</p> |
||||
|
||||
<p> |
||||
<button type="submit">Create Invoice</button> |
||||
</p> |
||||
|
||||
</form> |
||||
|
||||
</body> |
||||
</html> |
||||
{% include "footer.html" %} |
||||
@ -0,0 +1,5 @@
|
||||
Flask |
||||
mysql-connector-python |
||||
reportlab |
||||
python-dateutil |
||||
pytz |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,79 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.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; } |
||||
|
||||
.locked-note { |
||||
color: #92400e; |
||||
font-weight: bold; |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td> |
||||
<span class="status-badge status-{{ i.status }}">{{ i.status }}</span> |
||||
</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/view/{{ i.id }}">View</a> | |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
<span class="locked-note">(Locked)</span> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,187 @@
|
||||
<!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; |
||||
} |
||||
.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> |
||||
</div> |
||||
|
||||
<div class="header-row"> |
||||
<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>OTB Billing</strong><br> |
||||
By a contractor, for contractors |
||||
</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 }} |
||||
</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>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 invoice.notes %} |
||||
<div class="notes-box"> |
||||
<strong>Notes</strong><br><br> |
||||
{{ invoice.notes }} |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,78 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.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; } |
||||
|
||||
.locked-note { |
||||
color: #92400e; |
||||
font-weight: bold; |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td> |
||||
<span class="status-badge status-{{ i.status }}">{{ i.status }}</span> |
||||
</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
<span class="locked-note">(Locked)</span> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,81 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.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; } |
||||
|
||||
.locked-note { |
||||
color: #92400e; |
||||
font-weight: bold; |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/invoices/new">Create Invoice</a></p> |
||||
<p><a href="/invoices/export.csv">Export CSV</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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td> |
||||
<span class="status-badge status-{{ i.status }}">{{ i.status }}</span> |
||||
</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/view/{{ i.id }}">View</a> | |
||||
<a href="/invoices/pdf/{{ i.id }}">PDF</a> | |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
<span class="locked-note">(Locked)</span> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,103 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>New Payment</title> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Record Payment</h1> |
||||
|
||||
{% 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> |
||||
Invoice *<br> |
||||
<select name="invoice_id" required> |
||||
<option value="">Select invoice</option> |
||||
{% for i in invoices %} |
||||
<option value="{{ i.id }}" {% if form_data.get('invoice_id') == (i.id|string) %}selected{% endif %}> |
||||
{{ i.invoice_number }} - {{ i.client_code }} - {{ i.company_name }} - Due {{ i.total_amount - i.amount_paid }} {{ i.currency_code }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Payment Method *<br> |
||||
<select name="payment_method" required> |
||||
<option value="">Select method</option> |
||||
<option value="square" {% if form_data.get('payment_method') == 'square' %}selected{% endif %}>square</option> |
||||
<option value="etransfer" {% if form_data.get('payment_method') == 'etransfer' %}selected{% endif %}>etransfer</option> |
||||
<option value="crypto_etho" {% if form_data.get('payment_method') == 'crypto_etho' %}selected{% endif %}>crypto_etho</option> |
||||
<option value="crypto_egaz" {% if form_data.get('payment_method') == 'crypto_egaz' %}selected{% endif %}>crypto_egaz</option> |
||||
<option value="crypto_alt" {% if form_data.get('payment_method') == 'crypto_alt' %}selected{% endif %}>crypto_alt</option> |
||||
<option value="cash" {% if form_data.get('payment_method') == 'cash' %}selected{% endif %}>cash</option> |
||||
<option value="other" {% if form_data.get('payment_method') == 'other' %}selected{% endif %}>other</option> |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Payment Currency *<br> |
||||
<select name="payment_currency" required> |
||||
<option value="">Select currency</option> |
||||
<option value="CAD" {% if form_data.get('payment_currency') == 'CAD' %}selected{% endif %}>CAD</option> |
||||
<option value="ETHO" {% if form_data.get('payment_currency') == 'ETHO' %}selected{% endif %}>ETHO</option> |
||||
<option value="EGAZ" {% if form_data.get('payment_currency') == 'EGAZ' %}selected{% endif %}>EGAZ</option> |
||||
<option value="ALT" {% if form_data.get('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="{{ form_data.get('payment_amount', '') }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
CAD Value At Payment *<br> |
||||
<input type="number" step="0.00000001" min="0" name="cad_value_at_payment" value="{{ form_data.get('cad_value_at_payment', '') }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
Reference<br> |
||||
<input name="reference" value="{{ form_data.get('reference', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
Sender Name<br> |
||||
<input name="sender_name" value="{{ form_data.get('sender_name', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
TXID<br> |
||||
<input name="txid" value="{{ form_data.get('txid', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
Wallet Address<br> |
||||
<input name="wallet_address" value="{{ form_data.get('wallet_address', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
Notes<br> |
||||
<textarea name="notes">{{ form_data.get('notes', '') }}</textarea> |
||||
</p> |
||||
|
||||
<p> |
||||
<button type="submit">Record Payment</button> |
||||
</p> |
||||
|
||||
</form> |
||||
|
||||
</body> |
||||
</html> |
||||
{% include "footer.html" %} |
||||
File diff suppressed because it is too large
Load Diff
@ -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> |
||||
@ -0,0 +1,139 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>New Payment</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 2px 7px; |
||||
border-radius: 999px; |
||||
font-size: 11px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.status-pending { background: #dbeafe; color: #1d4ed8; } |
||||
.status-partial { background: #fef3c7; color: #92400e; } |
||||
.status-overdue { background: #fee2e2; color: #991b1b; } |
||||
|
||||
.info-box { |
||||
border: 1px solid #2563eb; |
||||
background: #eff6ff; |
||||
padding: 10px; |
||||
margin-bottom: 15px; |
||||
} |
||||
.error-box { |
||||
border: 1px solid red; |
||||
padding: 10px; |
||||
margin-bottom: 15px; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Record Payment</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/payments">Back to Payments</a></p> |
||||
|
||||
<div class="info-box"> |
||||
Only invoices with an outstanding balance are shown here.<br> |
||||
Paid and cancelled invoices are excluded from payment entry. |
||||
</div> |
||||
|
||||
{% if errors %} |
||||
<div class="error-box"> |
||||
<strong>Please fix the following:</strong> |
||||
<ul> |
||||
{% for error in errors %} |
||||
<li>{{ error }}</li> |
||||
{% endfor %} |
||||
</ul> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
<form method="post"> |
||||
|
||||
<p> |
||||
Invoice *<br> |
||||
<select name="invoice_id" required> |
||||
<option value="">Select invoice</option> |
||||
{% for i in invoices %} |
||||
<option value="{{ i.id }}" {% if form_data.get('invoice_id') == (i.id|string) %}selected{% endif %}> |
||||
{{ i.invoice_number }} - {{ i.client_code }} - {{ i.company_name }} - |
||||
Remaining {{ (i.total_amount - i.amount_paid)|money(i.currency_code) }} {{ i.currency_code }} - |
||||
{{ i.status }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Payment Method *<br> |
||||
<select name="payment_method" required> |
||||
<option value="">Select method</option> |
||||
<option value="square" {% if form_data.get('payment_method') == 'square' %}selected{% endif %}>square</option> |
||||
<option value="etransfer" {% if form_data.get('payment_method') == 'etransfer' %}selected{% endif %}>etransfer</option> |
||||
<option value="crypto_etho" {% if form_data.get('payment_method') == 'crypto_etho' %}selected{% endif %}>crypto_etho</option> |
||||
<option value="crypto_egaz" {% if form_data.get('payment_method') == 'crypto_egaz' %}selected{% endif %}>crypto_egaz</option> |
||||
<option value="crypto_alt" {% if form_data.get('payment_method') == 'crypto_alt' %}selected{% endif %}>crypto_alt</option> |
||||
<option value="cash" {% if form_data.get('payment_method') == 'cash' %}selected{% endif %}>cash</option> |
||||
<option value="other" {% if form_data.get('payment_method') == 'other' %}selected{% endif %}>other</option> |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Payment Currency *<br> |
||||
<select name="payment_currency" required> |
||||
<option value="">Select currency</option> |
||||
<option value="CAD" {% if form_data.get('payment_currency') == 'CAD' %}selected{% endif %}>CAD</option> |
||||
<option value="ETHO" {% if form_data.get('payment_currency') == 'ETHO' %}selected{% endif %}>ETHO</option> |
||||
<option value="EGAZ" {% if form_data.get('payment_currency') == 'EGAZ' %}selected{% endif %}>EGAZ</option> |
||||
<option value="ALT" {% if form_data.get('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="{{ form_data.get('payment_amount', '') }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
CAD Value At Payment *<br> |
||||
<input type="number" step="0.00000001" min="0" name="cad_value_at_payment" value="{{ form_data.get('cad_value_at_payment', '') }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
Reference<br> |
||||
<input name="reference" value="{{ form_data.get('reference', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
Sender Name<br> |
||||
<input name="sender_name" value="{{ form_data.get('sender_name', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
TXID<br> |
||||
<input name="txid" value="{{ form_data.get('txid', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
Wallet Address<br> |
||||
<input name="wallet_address" value="{{ form_data.get('wallet_address', '') }}"> |
||||
</p> |
||||
|
||||
<p> |
||||
Notes<br> |
||||
<textarea name="notes">{{ form_data.get('notes', '') }}</textarea> |
||||
</p> |
||||
|
||||
<p> |
||||
<button type="submit">Record Payment</button> |
||||
</p> |
||||
|
||||
</form> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,100 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Payments</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.status-confirmed { background: #dcfce7; color: #166534; } |
||||
.status-reversed { background: #fee2e2; color: #991b1b; } |
||||
|
||||
.invoice-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 11px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.invoice-pending { background: #dbeafe; color: #1d4ed8; } |
||||
.invoice-partial { background: #fef3c7; color: #92400e; } |
||||
.invoice-paid { background: #dcfce7; color: #166534; } |
||||
.invoice-overdue { background: #fee2e2; color: #991b1b; } |
||||
.invoice-cancelled { background: #e5e7eb; color: #4b5563; } |
||||
.invoice-draft { background: #e5e7eb; color: #111827; } |
||||
|
||||
.inline-form { |
||||
display: inline; |
||||
margin: 0; |
||||
} |
||||
.void-btn { |
||||
background: #991b1b; |
||||
color: white; |
||||
border: 0; |
||||
padding: 4px 8px; |
||||
cursor: pointer; |
||||
} |
||||
.void-btn:hover { |
||||
opacity: 0.9; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Payments</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/payments/new">Record Payment</a></p> |
||||
|
||||
<table border="1" cellpadding="6"> |
||||
<tr> |
||||
<th>ID</th> |
||||
<th>Invoice</th> |
||||
<th>Client</th> |
||||
<th>Method</th> |
||||
<th>Currency</th> |
||||
<th>Amount</th> |
||||
<th>CAD Value</th> |
||||
<th>Payment Status</th> |
||||
<th>Invoice Status</th> |
||||
<th>Received</th> |
||||
<th>Actions</th> |
||||
</tr> |
||||
|
||||
{% for p in payments %} |
||||
<tr> |
||||
<td>{{ p.id }}</td> |
||||
<td>{{ p.invoice_number }}</td> |
||||
<td>{{ p.client_code }} - {{ p.company_name }}</td> |
||||
<td>{{ p.payment_method }}</td> |
||||
<td>{{ p.payment_currency }}</td> |
||||
<td>{{ p.payment_amount|money(p.payment_currency) }}</td> |
||||
<td>{{ p.cad_value_at_payment|money('CAD') }}</td> |
||||
<td><span class="status-badge status-{{ p.payment_status }}">{{ p.payment_status }}</span></td> |
||||
<td><span class="invoice-badge invoice-{{ p.invoice_status }}">{{ p.invoice_status }}</span></td> |
||||
<td>{{ p.received_at|localtime }}</td> |
||||
<td> |
||||
<a href="/payments/edit/{{ p.id }}">Edit</a> |
||||
{% if p.payment_status == 'confirmed' %} |
||||
| |
||||
<form method="post" action="/payments/void/{{ p.id }}" class="inline-form" onsubmit="return confirm('Void this payment? This will reverse it from invoice totals but keep the record for history.');"> |
||||
<button type="submit" class="void-btn">Void</button> |
||||
</form> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,100 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Payments</title> |
||||
<style> |
||||
.status-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.status-confirmed { background: #dcfce7; color: #166534; } |
||||
.status-reversed { background: #fee2e2; color: #991b1b; } |
||||
|
||||
.invoice-badge { |
||||
display: inline-block; |
||||
padding: 3px 8px; |
||||
border-radius: 999px; |
||||
font-size: 11px; |
||||
font-weight: bold; |
||||
text-transform: uppercase; |
||||
letter-spacing: 0.03em; |
||||
} |
||||
.invoice-pending { background: #dbeafe; color: #1d4ed8; } |
||||
.invoice-partial { background: #fef3c7; color: #92400e; } |
||||
.invoice-paid { background: #dcfce7; color: #166534; } |
||||
.invoice-overdue { background: #fee2e2; color: #991b1b; } |
||||
.invoice-cancelled { background: #e5e7eb; color: #4b5563; } |
||||
.invoice-draft { background: #e5e7eb; color: #111827; } |
||||
|
||||
.inline-form { |
||||
display: inline; |
||||
margin: 0; |
||||
} |
||||
.void-btn { |
||||
background: #991b1b; |
||||
color: white; |
||||
border: 0; |
||||
padding: 4px 8px; |
||||
cursor: pointer; |
||||
} |
||||
.void-btn:hover { |
||||
opacity: 0.9; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Payments</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/payments/new">Record Payment</a></p> |
||||
|
||||
<table border="1" cellpadding="6"> |
||||
<tr> |
||||
<th>ID</th> |
||||
<th>Invoice</th> |
||||
<th>Client</th> |
||||
<th>Method</th> |
||||
<th>Currency</th> |
||||
<th>Amount</th> |
||||
<th>CAD Value</th> |
||||
<th>Payment Status</th> |
||||
<th>Invoice Status</th> |
||||
<th>Received</th> |
||||
<th>Actions</th> |
||||
</tr> |
||||
|
||||
{% for p in payments %} |
||||
<tr> |
||||
<td>{{ p.id }}</td> |
||||
<td>{{ p.invoice_number }}</td> |
||||
<td>{{ p.client_code }} - {{ p.company_name }}</td> |
||||
<td>{{ p.payment_method }}</td> |
||||
<td>{{ p.payment_currency }}</td> |
||||
<td>{{ p.payment_amount|money(p.payment_currency) }}</td> |
||||
<td>{{ p.cad_value_at_payment|money('CAD') }}</td> |
||||
<td><span class="status-badge status-{{ p.payment_status }}">{{ p.payment_status }}</span></td> |
||||
<td><span class="invoice-badge invoice-{{ p.invoice_status }}">{{ p.invoice_status }}</span></td> |
||||
<td>{{ p.received_at|localtime }}</td> |
||||
<td> |
||||
<a href="/payments/edit/{{ p.id }}">Edit</a> |
||||
{% if p.payment_status == 'confirmed' %} |
||||
| |
||||
<form method="post" action="/payments/void/{{ p.id }}" class="inline-form" onsubmit="return confirm('Void this payment? This will reverse it from invoice totals but keep the record for history.');"> |
||||
<button type="submit" class="void-btn">Void</button> |
||||
</form> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,42 @@
|
||||
<!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="/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,192 @@
|
||||
<!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> |
||||
</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,35 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>OTB Billing Dashboard</title> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>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="/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,188 @@
|
||||
<!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; |
||||
} |
||||
.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"> |
||||
<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>OTB Billing</strong><br> |
||||
By a contractor, for contractors |
||||
</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 }} |
||||
</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>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 invoice.notes %} |
||||
<div class="notes-box"> |
||||
<strong>Notes</strong><br><br> |
||||
{{ invoice.notes }} |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,113 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Edit Invoice</title> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Edit Invoice</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<p><a href="/invoices">Back to Invoices</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 %} |
||||
|
||||
{% if locked %} |
||||
<div style="border:1px solid #aa6600; padding:10px; margin-bottom:15px; background:#fff4dd;"> |
||||
<strong>This invoice is locked for core edits because payments exist.</strong><br> |
||||
Core accounting fields cannot be changed after payment activity begins. |
||||
</div> |
||||
{% endif %} |
||||
|
||||
<form method="post"> |
||||
|
||||
<p> |
||||
Invoice Number<br> |
||||
<input value="{{ invoice.invoice_number }}" readonly> |
||||
</p> |
||||
|
||||
{% if not locked %} |
||||
<p> |
||||
Client *<br> |
||||
<select name="client_id" required> |
||||
{% for c in clients %} |
||||
<option value="{{ c.id }}" {% if invoice.client_id == c.id %}selected{% endif %}> |
||||
{{ c.client_code }} - {{ c.company_name }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Service *<br> |
||||
<select name="service_id" required> |
||||
{% for s in services %} |
||||
<option value="{{ s.id }}" {% if invoice.service_id == s.id %}selected{% endif %}> |
||||
{{ s.service_code }} - {{ s.service_name }} |
||||
</option> |
||||
{% endfor %} |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Currency *<br> |
||||
<select name="currency_code" required> |
||||
<option value="CAD" {% if invoice.currency_code == 'CAD' %}selected{% endif %}>CAD</option> |
||||
<option value="ETHO" {% if invoice.currency_code == 'ETHO' %}selected{% endif %}>ETHO</option> |
||||
<option value="EGAZ" {% if invoice.currency_code == 'EGAZ' %}selected{% endif %}>EGAZ</option> |
||||
<option value="ALT" {% if invoice.currency_code == 'ALT' %}selected{% endif %}>ALT</option> |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Total Amount *<br> |
||||
<input type="number" step="0.00000001" min="0" name="total_amount" value="{{ invoice.total_amount }}" required> |
||||
</p> |
||||
{% else %} |
||||
<p>Client<br><input value="{{ invoice.client_id }}" readonly></p> |
||||
<p>Service<br><input value="{{ invoice.service_id }}" readonly></p> |
||||
<p>Currency<br><input value="{{ invoice.currency_code }}" readonly></p> |
||||
<p>Total Amount<br><input value="{{ invoice.total_amount|money(invoice.currency_code) }}" readonly></p> |
||||
{% endif %} |
||||
|
||||
<p> |
||||
Due Date *<br> |
||||
<input type="date" name="due_at" value="{{ invoice.due_at.strftime('%Y-%m-%d') if invoice.due_at else '' }}" required> |
||||
</p> |
||||
|
||||
<p> |
||||
Status *<br> |
||||
<select name="status" required> |
||||
<option value="draft" {% if invoice.status == 'draft' %}selected{% endif %}>draft</option> |
||||
<option value="pending" {% if invoice.status == 'pending' %}selected{% endif %}>pending</option> |
||||
<option value="partial" {% if invoice.status == 'partial' %}selected{% endif %}>partial</option> |
||||
<option value="paid" {% if invoice.status == 'paid' %}selected{% endif %}>paid</option> |
||||
<option value="overdue" {% if invoice.status == 'overdue' %}selected{% endif %}>overdue</option> |
||||
<option value="cancelled" {% if invoice.status == 'cancelled' %}selected{% endif %}>cancelled</option> |
||||
</select> |
||||
</p> |
||||
|
||||
<p> |
||||
Notes<br> |
||||
<textarea name="notes" rows="5" cols="60">{{ invoice.notes or '' }}</textarea> |
||||
</p> |
||||
|
||||
<p> |
||||
<button type="submit">Save Invoice</button> |
||||
</p> |
||||
|
||||
</form> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,54 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Invoices</title> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<h1>Invoices</h1> |
||||
|
||||
<p><a href="/">Home</a></p> |
||||
<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> |
||||
<th>Actions</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|money(i.currency_code) }}</td> |
||||
<td>{{ i.amount_paid|money(i.currency_code) }}</td> |
||||
<td>{{ (i.total_amount - i.amount_paid)|money(i.currency_code) }}</td> |
||||
<td>{{ i.status }}</td> |
||||
<td>{{ i.issued_at|localtime }}</td> |
||||
<td>{{ i.due_at|localtime }}</td> |
||||
<td> |
||||
<a href="/invoices/edit/{{ i.id }}">Edit</a> |
||||
{% if i.payment_count > 0 %} |
||||
(Locked) |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
|
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,222 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Batch Print Invoices</title> |
||||
<style> |
||||
body { |
||||
font-family: Arial, sans-serif; |
||||
margin: 24px; |
||||
color: #111; |
||||
} |
||||
.top-links { |
||||
margin-bottom: 20px; |
||||
} |
||||
.top-links a { |
||||
margin-right: 16px; |
||||
} |
||||
.batch-header { |
||||
margin-bottom: 24px; |
||||
} |
||||
.invoice-wrap { |
||||
page-break-after: always; |
||||
margin-bottom: 40px; |
||||
padding-bottom: 30px; |
||||
border-bottom: 2px solid #ddd; |
||||
} |
||||
.invoice-wrap:last-child { |
||||
page-break-after: auto; |
||||
border-bottom: none; |
||||
} |
||||
.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; |
||||
} |
||||
.logo { |
||||
max-height: 70px; |
||||
max-width: 120px; |
||||
margin-bottom: 10px; |
||||
} |
||||
@media print { |
||||
.top-links, footer { |
||||
display: none !important; |
||||
} |
||||
body { |
||||
margin: 0; |
||||
} |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<div class="top-links"> |
||||
<a href="/invoices">Back to Invoices</a> |
||||
<a href="#" onclick="window.print(); return false;">Print This Page</a> |
||||
</div> |
||||
|
||||
<div class="batch-header"> |
||||
<h1>Batch Invoice Print</h1> |
||||
<p> |
||||
Filters: |
||||
From={{ filters.start_date or 'Any' }}, |
||||
To={{ filters.end_date or 'Any' }}, |
||||
Status={{ filters.status or 'All' }}, |
||||
Client={{ filters.client_id or 'All' }}, |
||||
Limit={{ filters.limit or 'None' }} |
||||
</p> |
||||
</div> |
||||
|
||||
{% for invoice in invoices %} |
||||
<div class="invoice-wrap"> |
||||
<div class="header-row"> |
||||
<div class="title-box"> |
||||
{% if settings.business_logo_url %} |
||||
<img src="{{ settings.business_logo_url }}" class="logo" alt="Logo"> |
||||
{% endif %} |
||||
<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> |
||||
{% endfor %} |
||||
|
||||
{% 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,77 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<title>Print Revenue Report</title> |
||||
<style> |
||||
body { font-family: Arial, sans-serif; margin: 24px; } |
||||
.top-links a { margin-right: 16px; } |
||||
table { |
||||
border-collapse: collapse; |
||||
width: 100%; |
||||
max-width: 700px; |
||||
margin-top: 20px; |
||||
} |
||||
th, td { |
||||
border: 1px solid #ccc; |
||||
padding: 10px; |
||||
text-align: left; |
||||
} |
||||
@media print { |
||||
.top-links, footer { display: none !important; } |
||||
body { margin: 0; } |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<div class="top-links"> |
||||
<a href="/reports/revenue">Back to Revenue Report</a> |
||||
<a href="#" onclick="window.print(); return false;">Print This Page</a> |
||||
</div> |
||||
|
||||
<h1>Revenue Report</h1> |
||||
<p> |
||||
Frequency: <strong>{{ report.frequency }}</strong><br> |
||||
Period: <strong>{{ report.period_label }}</strong><br> |
||||
Start (UTC): {{ report.period_start }}<br> |
||||
End (UTC): {{ report.period_end }} |
||||
</p> |
||||
|
||||
<table> |
||||
<tr> |
||||
<th>Metric</th> |
||||
<th>Value</th> |
||||
</tr> |
||||
<tr> |
||||
<td>Collected (CAD)</td> |
||||
<td>{{ report.collected_cad|money('CAD') }} CAD</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Invoices Issued</td> |
||||
<td>{{ report.invoice_count }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Invoiced Total</td> |
||||
<td>{{ report.invoiced_total|money('CAD') }} CAD</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Outstanding Invoices</td> |
||||
<td>{{ report.outstanding_count }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Outstanding Balance</td> |
||||
<td>{{ report.outstanding_balance|money('CAD') }} CAD</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Overdue Invoices</td> |
||||
<td>{{ report.overdue_count }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Overdue Balance</td> |
||||
<td>{{ report.overdue_balance|money('CAD') }} CAD</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,173 @@
|
||||
<!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; |
||||
} |
||||
</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> |
||||
Business Logo URL<br> |
||||
<input type="text" name="business_logo_url" value="{{ settings.business_logo_url }}"><br> |
||||
<small>Example: /static/logo.png or https://site.com/logo.png</small><br><br> |
||||
|
||||
<input type="text" name="business_name" value="{{ settings.business_name }}"><br> |
||||
|
||||
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> |
||||
</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>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> |
||||
These settings become the identity and delivery configuration for this installation. |
||||
</p> |
||||
<p> |
||||
Email sending is not wired yet, but these SMTP settings are stored now so the next step can use them. |
||||
</p> |
||||
<p> |
||||
Tax settings are also stored now so invoice and automation logic can use them later. |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="save-row"> |
||||
<button type="submit">Save Settings</button> |
||||
</div> |
||||
</form> |
||||
|
||||
{% include "footer.html" %} |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash |
||||
set -euo pipefail |
||||
|
||||
PROJECT_DIR="/home/def/otb_billing" |
||||
STATE_FILE="$PROJECT_DIR/PROJECT_STATE.md" |
||||
|
||||
if [ ! -f "$STATE_FILE" ]; then |
||||
echo "ERROR: $STATE_FILE not found" |
||||
exit 1 |
||||
fi |
||||
|
||||
VERSION="${1:-}" |
||||
if [ -z "$VERSION" ]; then |
||||
VERSION="$(grep -m1 '^Version:' "$STATE_FILE" | sed 's/^Version:[[:space:]]*//')" |
||||
fi |
||||
|
||||
TODAY="$(date +%F)" |
||||
|
||||
sed -i \ |
||||
-e "s/^Last Updated: .*/Last Updated: $TODAY/" \ |
||||
-e "s/^Version: .*/Version: $VERSION/" \ |
||||
"$STATE_FILE" |
||||
|
||||
echo "Updated $STATE_FILE" |
||||
echo "Last Updated: $TODAY" |
||||
echo "Version: $VERSION" |
||||
Loading…
Reference in new issue