12 changed files with 1273 additions and 193 deletions
@ -0,0 +1,5 @@
|
||||
Flask |
||||
mysql-connector-python |
||||
reportlab |
||||
python-dateutil |
||||
pytz |
||||
@ -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> |
||||
Loading…
Reference in new issue