Browse Source

v0.5.1 - crypto email fix, PDF attachment fix, payment layout improvements

main
def 1 month ago
parent
commit
4c337c6a30
  1. 9
      README.md
  2. 2
      VERSION
  3. 0
      backend/app-backups/app.py.bak_accounting_builder
  4. 0
      backend/app-backups/app.py.bak_email_log_safe
  5. 0
      backend/app-backups/app.py.bak_payments_query_fix
  6. 0
      backend/app-backups/app.py.bak_payments_route_exact_fix
  7. 0
      backend/app-backups/app.py.bak_payments_route_fix2
  8. 0
      backend/app-backups/app.py.bak_report_email_fix
  9. 0
      backend/app-backups/app.py.bak_void_fix
  10. 684
      backend/app.py
  11. 6603
      backend/app.py.attach-debug.20260326-020213.bak
  12. 6603
      backend/app.py.attachment-fix.20260326-021226.bak
  13. 6642
      backend/app.py.correct-payment-block.20260326-043806.bak
  14. 6582
      backend/app.py.email-attachment.20260325-022911.bak
  15. 6596
      backend/app.py.email-attachment.20260325-023107.bak
  16. 6663
      backend/app.py.email-body-explorer-fix.20260326-033501.bak
  17. 6698
      backend/app.py.email-body-fix-clean.20260326-040645.bak
  18. 6610
      backend/app.py.email-explorer-safe.20260326-040915.bak
  19. 6604
      backend/app.py.envload.20260326-025333.bak
  20. 6663
      backend/app.py.explorer-links.20260326-032929.bak
  21. 6663
      backend/app.py.explorer-links.20260326-032943.bak
  22. 6688
      backend/app.py.final-email-cleanup.20260327-020603.bak
  23. 6682
      backend/app.py.fix-missing-pdf-import.20260326-050344.bak
  24. 6682
      backend/app.py.helper-pdf-route-fix.20260327-015238.bak
  25. 6682
      backend/app.py.helper-rebuild.20260326-050853.bak
  26. 6641
      backend/app.py.invoice-pdf-order-fix.20260326-041449.bak
  27. 6610
      backend/app.py.invoice-pdf-payments-fix.20260326-030703.bak
  28. 6642
      backend/app.py.payment-block-global-fix.20260326-043120.bak
  29. 6642
      backend/app.py.payment-block-live-fix.20260326-044117.bak
  30. 6611
      backend/app.py.payment-block-polish.20260326-032040.bak
  31. 6641
      backend/app.py.payment-email-and-pdf-fix.20260326-041126.bak
  32. 6681
      backend/app.py.payment-email-logging.20260326-045639.bak
  33. 6682
      backend/app.py.payment-layout-fix.20260327-013334.bak
  34. 6642
      backend/app.py.payment-lines-and-rate.20260326-042154.bak
  35. 6610
      backend/app.py.pdf-context-fix.20260326-030343.bak
  36. 6607
      backend/app.py.pdf-fix.20260326-030059.bak
  37. 6684
      backend/app.py.pre-circular-import-removal.20260326-050604.bak
  38. 6603
      backend/app.py.reveal-payment-email-error.20260326-024852.bak
  39. 51
      backend/update1.sh
  40. 43
      backup_pre_accounting_package_2026-03-09/dashboard.html.bak
  41. 1872
      backup_pre_batch_pdf_export_2026-03-09/app.py.bak
  42. 143
      backup_pre_batch_pdf_export_2026-03-09/invoices_list.html.bak
  43. 2168
      backup_pre_batch_print_2026-03-09/app.py.bak
  44. 169
      backup_pre_batch_print_2026-03-09/invoices_list.html.bak
  45. 1593
      backup_pre_csv_export_2026-03-09/app.py.bak
  46. 49
      backup_pre_csv_export_2026-03-09/clients_list.html.bak
  47. 80
      backup_pre_csv_export_2026-03-09/invoices_list.html.bak
  48. 102
      backup_pre_csv_export_2026-03-09/payments_list.html.bak
  49. 326
      backup_pre_email_log_2026-03-09/PROJECT_STATE.md.bak
  50. 2806
      backup_pre_email_log_2026-03-09/app.py.bak
  51. 2719
      backup_pre_email_send_2026-03-09/app.py.bak
  52. 44
      backup_pre_email_send_2026-03-09/dashboard.html.bak
  53. 207
      backup_pre_email_send_2026-03-09/invoices_view.html.bak
  54. 73
      backup_pre_email_send_2026-03-09/revenue.html.bak
  55. 199
      backup_pre_email_send_2026-03-09/settings.html.bak
  56. 1437
      backup_pre_invoice_numbering_2026-03-09/app.py.bak
  57. 81
      backup_pre_invoice_numbering_2026-03-09/invoices_new.html.bak
  58. 5
      backup_pre_invoice_numbering_2026-03-09/requirements.txt.bak
  59. 1258
      backup_pre_invoice_pdf_2026-03-09/app.py.bak
  60. 79
      backup_pre_invoice_pdf_2026-03-09/invoices_list.html.bak
  61. 187
      backup_pre_invoice_pdf_2026-03-09/invoices_view.html.bak
  62. 1228
      backup_pre_invoice_print_view_2026-03-09/app.py.bak
  63. 78
      backup_pre_invoice_print_view_2026-03-09/invoices_list.html.bak
  64. 1813
      backup_pre_invoice_range_export_2026-03-09/app.py.bak
  65. 81
      backup_pre_invoice_range_export_2026-03-09/invoices_list.html.bak
  66. 1149
      backup_pre_new_payment_rebuild_2026-03-08/app.py.bak
  67. 1149
      backup_pre_overpayment_guard_2026-03-08/app.py.bak
  68. 1149
      backup_pre_payment_filter_2026-03-08/app.py.bak
  69. 103
      backup_pre_payment_filter_2026-03-08/payments_new.html.bak
  70. 1184
      backup_pre_payment_policy_guard_2026-03-08/app.py.bak
  71. 107
      backup_pre_payment_policy_guard_2026-03-08/payments_edit.html.bak
  72. 139
      backup_pre_payment_policy_guard_2026-03-08/payments_new.html.bak
  73. 1184
      backup_pre_payment_void_2026-03-08/app.py.bak
  74. 100
      backup_pre_payment_void_2026-03-08/payments_list.html.bak
  75. 1224
      backup_pre_payments_list_cleanup_2026-03-08/app.py.bak
  76. 100
      backup_pre_payments_list_cleanup_2026-03-08/payments_list.html.bak
  77. 1584
      backup_pre_pdf_logo_2026-03-09/app.py.bak
  78. 2242
      backup_pre_revenue_report_json_2026-03-09/app.py.bak
  79. 42
      backup_pre_revenue_report_json_2026-03-09/dashboard.html.bak
  80. 192
      backup_pre_revenue_report_json_2026-03-09/settings.html.bak
  81. 1462
      backup_pre_settings_config_2026-03-09/app.py.bak
  82. 35
      backup_pre_settings_config_2026-03-09/dashboard.html.bak
  83. 188
      backup_pre_settings_config_2026-03-09/invoices_view.html.bak
  84. 1138
      backup_pre_status_hardening_2026-03-08/app.py.bak
  85. 113
      backup_pre_status_hardening_2026-03-08/invoices_edit.html.bak
  86. 54
      backup_pre_status_hardening_2026-03-08/invoices_list.html.bak
  87. 2342
      backup_restore_email_layer_2026-03-09/app.py.bak
  88. 235
      backup_restore_email_layer_2026-03-09/invoices_view.html.bak
  89. 0
      build-backups/backup_fix_void_route_2026-03-08/app.py.bak
  90. 0
      build-backups/backup_fix_void_route_2026-03-08/payments_list.html.bak
  91. 0
      build-backups/backup_logo_support_2026-03-09/app.py.bak
  92. 0
      build-backups/backup_logo_support_2026-03-09/dashboard.html.bak
  93. 0
      build-backups/backup_logo_support_2026-03-09/invoice_view.html.bak
  94. 0
      build-backups/backup_logo_support_2026-03-09/settings.html.bak
  95. 0
      build-backups/backup_restore_email_layer_2026-03-09/app.py.bak
  96. 0
      build-backups/backup_restore_email_layer_2026-03-09/dashboard.html.bak
  97. 0
      build-backups/backup_restore_email_layer_2026-03-09/invoices_view.html.bak
  98. 0
      build-backups/backup_restore_email_layer_2026-03-09/revenue.html.bak
  99. 0
      build-backups/backup_restore_email_layer_2026-03-09/settings.html.bak
  100. 45
      emailfix.sh
  101. Some files were not shown because too many files have changed in this diff Show More

9
README.md

@ -1,3 +1,12 @@
## 2026-03-27 — v0.5.1
- Fixed crypto payment email auto-send failure
- Replaced internal PDF generator call with route-based PDF fetch
- Restored PDF attachments in payment emails
- Improved Payments Applied layout in invoice PDF (multi-line details + rate display)
- Stabilized send_payment_received_email() (removed debug raise, safe failure handling)
## v0.5.0 - 2026-03-14 22:01:59 ## v0.5.0 - 2026-03-14 22:01:59
- Added per-invoice Square payment links - Added per-invoice Square payment links

2
VERSION

@ -1 +1 @@
v0.5.0 v0.5.1

0
backend/app.py.bak_accounting_builder → backend/app-backups/app.py.bak_accounting_builder

0
backend/app.py.bak_email_log_safe → backend/app-backups/app.py.bak_email_log_safe

0
backend/app.py.bak_payments_query_fix → backend/app-backups/app.py.bak_payments_query_fix

0
backend/app.py.bak_payments_route_exact_fix → backend/app-backups/app.py.bak_payments_route_exact_fix

0
backend/app.py.bak_payments_route_fix2 → backend/app-backups/app.py.bak_payments_route_fix2

0
backend/app.py.bak_report_email_fix → backend/app-backups/app.py.bak_report_email_fix

0
backend/app.py.bak_void_fix → backend/app-backups/app.py.bak_void_fix

684
backend/app.py

File diff suppressed because it is too large Load Diff

6603
backend/app.py.attach-debug.20260326-020213.bak

File diff suppressed because it is too large Load Diff

6603
backend/app.py.attachment-fix.20260326-021226.bak

File diff suppressed because it is too large Load Diff

6642
backend/app.py.correct-payment-block.20260326-043806.bak

File diff suppressed because it is too large Load Diff

6582
backend/app.py.email-attachment.20260325-022911.bak

File diff suppressed because it is too large Load Diff

6596
backend/app.py.email-attachment.20260325-023107.bak

File diff suppressed because it is too large Load Diff

6663
backend/app.py.email-body-explorer-fix.20260326-033501.bak

File diff suppressed because it is too large Load Diff

6698
backend/app.py.email-body-fix-clean.20260326-040645.bak

File diff suppressed because it is too large Load Diff

6610
backend/app.py.email-explorer-safe.20260326-040915.bak

File diff suppressed because it is too large Load Diff

6604
backend/app.py.envload.20260326-025333.bak

File diff suppressed because it is too large Load Diff

6663
backend/app.py.explorer-links.20260326-032929.bak

File diff suppressed because it is too large Load Diff

6663
backend/app.py.explorer-links.20260326-032943.bak

File diff suppressed because it is too large Load Diff

6688
backend/app.py.final-email-cleanup.20260327-020603.bak

File diff suppressed because it is too large Load Diff

6682
backend/app.py.fix-missing-pdf-import.20260326-050344.bak

File diff suppressed because it is too large Load Diff

6682
backend/app.py.helper-pdf-route-fix.20260327-015238.bak

File diff suppressed because it is too large Load Diff

6682
backend/app.py.helper-rebuild.20260326-050853.bak

File diff suppressed because it is too large Load Diff

6641
backend/app.py.invoice-pdf-order-fix.20260326-041449.bak

File diff suppressed because it is too large Load Diff

6610
backend/app.py.invoice-pdf-payments-fix.20260326-030703.bak

File diff suppressed because it is too large Load Diff

6642
backend/app.py.payment-block-global-fix.20260326-043120.bak

File diff suppressed because it is too large Load Diff

6642
backend/app.py.payment-block-live-fix.20260326-044117.bak

File diff suppressed because it is too large Load Diff

6611
backend/app.py.payment-block-polish.20260326-032040.bak

File diff suppressed because it is too large Load Diff

6641
backend/app.py.payment-email-and-pdf-fix.20260326-041126.bak

File diff suppressed because it is too large Load Diff

6681
backend/app.py.payment-email-logging.20260326-045639.bak

File diff suppressed because it is too large Load Diff

6682
backend/app.py.payment-layout-fix.20260327-013334.bak

File diff suppressed because it is too large Load Diff

6642
backend/app.py.payment-lines-and-rate.20260326-042154.bak

File diff suppressed because it is too large Load Diff

6610
backend/app.py.pdf-context-fix.20260326-030343.bak

File diff suppressed because it is too large Load Diff

6607
backend/app.py.pdf-fix.20260326-030059.bak

File diff suppressed because it is too large Load Diff

6684
backend/app.py.pre-circular-import-removal.20260326-050604.bak

File diff suppressed because it is too large Load Diff

6603
backend/app.py.reveal-payment-email-error.20260326-024852.bak

File diff suppressed because it is too large Load Diff

51
backend/update1.sh

@ -0,0 +1,51 @@
cd /home/def/otb_billing || exit 1
set -e
NEWVER="v0.5.1"
STAMP="$(date '+%Y-%m-%d %H:%M:%S')"
ZIPNAME="otb_billing-${NEWVER}.zip"
echo "===== git status ====="
git status --short || true
echo "===== update VERSION ====="
echo "${NEWVER}" > VERSION
echo "===== update README.md ====="
cp README.md "README.md.bak.${NEWVER}"
python3 <<'PY'
from pathlib import Path
from datetime import datetime
p = Path("README.md")
text = p.read_text()
entry = f"""## {datetime.now().strftime('%Y-%m-%d')} — v0.5.1
- Fixed crypto payment email auto-send failure
- Replaced internal PDF generator call with route-based PDF fetch
- Restored PDF attachments in payment emails
- Improved Payments Applied layout in invoice PDF (multi-line details + rate display)
- Stabilized send_payment_received_email() (removed debug raise, safe failure handling)
"""
p.write_text(entry + "\n" + text)
print("OK: README updated")
PY
echo "===== git add ====="
git add .
echo "===== git commit ====="
git commit -m "v0.5.1 - crypto email fix, PDF attachment fix, payment layout improvements"
echo "===== git push ====="
git push
echo "===== build zip ====="
cd /home/def || exit 1
zip -r "${ZIPNAME}" otb_billing >/dev/null
echo "ZIP CREATED: /home/def/${ZIPNAME}"

43
backup_pre_accounting_package_2026-03-09/dashboard.html.bak

@ -1,43 +0,0 @@
<!doctype html>
<html>
<head>
<title>OTB Billing Dashboard</title>
</head>
<body>
{% if app_settings.business_logo_url %}
<div style="margin-bottom:15px;">
<img src="{{ app_settings.business_logo_url }}" style="height:60px;">
</div>
{% endif %}
<h1>{{ app_settings.business_name or 'OTB Billing' }} Dashboard</h1>
<p><a href="/clients">Clients</a></p>
<p><a href="/services">Services</a></p>
<p><a href="/invoices">Invoices</a></p>
<p><a href="/payments">Payments</a></p>
<p><a href="/reports/revenue">Revenue Report</a></p>
<p><a href="/settings">Settings / Config</a></p>
<p><a href="/dbtest">DB Test</a></p>
<table border="1" cellpadding="10">
<tr>
<th>Total Clients</th>
<th>Active Services</th>
<th>Outstanding Invoices</th>
<th>Revenue Received (CAD)</th>
</tr>
<tr>
<td>{{ total_clients }}</td>
<td>{{ active_services }}</td>
<td>{{ outstanding_invoices }}</td>
<td>{{ revenue_received|money('CAD') }}</td>
</tr>
</table>
<p>Displayed times are shown in Eastern Time (Toronto).</p>
{% include "footer.html" %}
</body>
</html>

1872
backup_pre_batch_pdf_export_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

143
backup_pre_batch_pdf_export_2026-03-09/invoices_list.html.bak

@ -1,143 +0,0 @@
<!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>

2168
backup_pre_batch_print_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

169
backup_pre_batch_print_2026-03-09/invoices_list.html.bak

@ -1,169 +0,0 @@
<!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>

1593
backup_pre_csv_export_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

49
backup_pre_csv_export_2026-03-09/clients_list.html.bak

@ -1,49 +0,0 @@
<!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>

80
backup_pre_csv_export_2026-03-09/invoices_list.html.bak

@ -1,80 +0,0 @@
<!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>

102
backup_pre_csv_export_2026-03-09/payments_list.html.bak

@ -1,102 +0,0 @@
<!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>

326
backup_pre_email_log_2026-03-09/PROJECT_STATE.md.bak

@ -1,326 +0,0 @@
# OTB Billing — Project State
Last Updated: 2026-03-09
Version: v0.3.1
Project Path: ~/otb_billing
---
# Project Purpose
OTB Billing is a contractor-focused billing system designed to be:
- self-hosted
- portable
- database-backed
- deployable on fresh Linux systems
- suitable for managed hosting or client-installed deployments
The system is being built as a practical alternative to overly restrictive SaaS billing tools, with emphasis on ownership, simplicity, and contractor workflow.
Tagline direction:
By a contractor, for contractors
---
# Current Stack
Backend:
Flask
Database:
MariaDB
PDF Engine:
ReportLab
Primary Port:
5050
Dependencies file:
requirements.txt
---
# Deployment Philosophy
OTB Billing must remain a deployable product, not just a dev-only app.
Target install model:
fresh server
→ installer runs
→ dependencies install
→ MariaDB setup
→ schema setup
→ app launches
This remains a core project rule.
---
# Current Core Features
## Clients
- create client
- edit client
- list clients
- status field
- client code support
## Services
- create service
- edit service
- list services
- service code support
- service status support
## Invoices
- create invoice
- edit invoice
- list invoices
- automatic invoice numbering
- invoice print view
- invoice PDF download
- invoice lock after payment activity
- invoice statuses
- invoice email sending with PDF attachment
- latest invoice email activity display
Current invoice statuses:
- draft
- pending
- partial
- paid
- overdue
- cancelled
## Payments
- record payment
- edit payment
- list payments
- overpayment guard on new payment
- overpayment guard on payment edit
- payment status display
- payment void / reversal workflow
- invoice recalculation after payment changes
Current payment statuses:
- confirmed
- reversed
## Credit Ledger
- client credit ledger
- manual credit entries
- client balance color coding
- ledger link visible from client list/edit pages
## Invoice Rendering
- HTML invoice view
- print-friendly layout
- PDF invoice generation
- client details on invoice
- status badge on invoice
- totals, paid, remaining display
- branding/logo support on HTML and PDF
## Exports
- clients CSV export
- invoices CSV export
- payments CSV export
- filtered invoice CSV export
- filtered invoice PDF ZIP export
- monthly/quarterly/yearly accounting package ZIP export
- revenue report JSON export
## Batch / Print
- filtered batch invoice print page
- print-friendly revenue report
## Reports
- revenue report
- report frequency selector
- JSON report export
- email revenue report JSON
## Settings / Configuration System
Accessible from:
/settings
Stored in database table:
app_settings
### Business Identity Settings
- business name
- business tagline
- business logo URL
- business email
- business phone
- business address
- business website
- business registration number
### Tax Settings
- tax label
- tax rate
- tax number
- local country
- apply local tax only flag
### Invoice Behavior Settings
- default currency
- invoice footer
- payment terms
- report frequency
### SMTP / Email Settings
- SMTP host
- SMTP port
- SMTP username
- SMTP password
- SMTP from email
- SMTP from name
- TLS flag
- SSL flag
- report delivery email
## Email Logging
Stored in:
email_log
Tracks:
- email_type
- invoice_id
- recipient_email
- subject
- status
- error_message
- sent_at
Currently logs:
- invoice email sends
- revenue report email sends
- accounting package email sends
---
# Current Known Good State
Confirmed working:
- dashboard
- clients
- services
- invoice creation
- auto invoice numbering
- invoice view
- invoice PDF generation
- invoice email with PDF attachment
- invoice email log display
- payment entry
- payment overpayment prevention
- payment reversal / void
- payments list with invoice status and remaining balance
- settings/config page
- business identity shown on invoice view/PDF
- logo display in HTML and PDF
- clients/invoices/payments CSV export
- filtered invoice export
- filtered invoice PDF ZIP export
- batch invoice print
- revenue report
- revenue report JSON export
- revenue report email
- accounting package ZIP export
- accounting package email
---
# Requirements
Current requirements.txt should include:
- Flask
- mysql-connector-python
- reportlab
- python-dateutil
- pytz
This file must remain complete so installer-driven deployment works in one shot.
---
# Business / Product Direction
This system is intended to grow into a deployable billing product for small contractors and related service businesses.
Target strengths versus typical SaaS billing tools:
- simpler workflow
- data ownership
- exportability
- portability
- contractor-first design
- no hostage-style software design
Long-term success goal:
build something users are happy to use and proud to own.
---
# Planned Next Features
## Near-Term
- invoice defaults from settings
- improved tax application logic
- accountant package scheduling / reminders
- client account statement export
- backup/install polish
## Medium-Term
- quote / estimate system
- recurring invoices
- reminder workflows
- better installer/update flow
- email resend history view
## Long-Term
- client portal
- role-based access
- accountant/export workflows
- job-tracking integration with related contractor platform modules
---
# Advanced Settings Direction
Business identity and SMTP belong in settings UI.
Database credentials should remain installer/config-file driven, not casually editable in standard UI.
If advanced connection settings are ever exposed in UI, they must be clearly marked as dangerous / advanced and should avoid redisplaying stored passwords.
---
# Repository Discipline
For this project going forward:
- keep PROJECT_STATE.md updated
- update README.md with version/build notes
- keep requirements.txt complete
- make full ZIP backup on version bumps
- push milestones to git
Example future archive naming:
otb_billing-v0.3.1.zip
---
# Restart / Run Notes
Development run method:
cd ~/otb_billing
python3 backend/app.py
During active development, run in a visible terminal so logs stay visible.
Do not rely on hidden/background launch during normal debug workflow.

2806
backup_pre_email_log_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

2719
backup_pre_email_send_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

44
backup_pre_email_send_2026-03-09/dashboard.html.bak

@ -1,44 +0,0 @@
<!doctype html>
<html>
<head>
<title>OTB Billing Dashboard</title>
</head>
<body>
{% if app_settings.business_logo_url %}
<div style="margin-bottom:15px;">
<img src="{{ app_settings.business_logo_url }}" style="height:60px;">
</div>
{% endif %}
<h1>{{ app_settings.business_name or 'OTB Billing' }} Dashboard</h1>
<p><a href="/clients">Clients</a></p>
<p><a href="/services">Services</a></p>
<p><a href="/invoices">Invoices</a></p>
<p><a href="/payments">Payments</a></p>
<p><a href="/reports/revenue">Revenue Report</a></p>
<p><a href="/reports/accounting-package.zip">Monthly Accounting Package</a></p>
<p><a href="/settings">Settings / Config</a></p>
<p><a href="/dbtest">DB Test</a></p>
<table border="1" cellpadding="10">
<tr>
<th>Total Clients</th>
<th>Active Services</th>
<th>Outstanding Invoices</th>
<th>Revenue Received (CAD)</th>
</tr>
<tr>
<td>{{ total_clients }}</td>
<td>{{ active_services }}</td>
<td>{{ outstanding_invoices }}</td>
<td>{{ revenue_received|money('CAD') }}</td>
</tr>
</table>
<p>Displayed times are shown in Eastern Time (Toronto).</p>
{% include "footer.html" %}
</body>
</html>

207
backup_pre_email_send_2026-03-09/invoices_view.html.bak

@ -1,207 +0,0 @@
<!doctype html>
<html>
<head>
<title>Invoice {{ invoice.invoice_number }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 30px;
color: #111;
}
.top-links {
margin-bottom: 20px;
}
.top-links a {
margin-right: 15px;
}
.invoice-wrap {
max-width: 900px;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 25px;
}
.title-box h1 {
margin: 0 0 8px 0;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.status-draft { background: #e5e7eb; color: #111827; }
.status-pending { background: #dbeafe; color: #1d4ed8; }
.status-partial { background: #fef3c7; color: #92400e; }
.status-paid { background: #dcfce7; color: #166534; }
.status-overdue { background: #fee2e2; color: #991b1b; }
.status-cancelled { background: #e5e7eb; color: #4b5563; }
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 25px;
}
.info-card {
border: 1px solid #ccc;
padding: 15px;
}
.info-card h3 {
margin-top: 0;
}
.summary-table,
.total-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 25px;
}
.summary-table th,
.summary-table td,
.total-table th,
.total-table td {
border: 1px solid #ccc;
padding: 10px;
text-align: left;
}
.total-table {
max-width: 420px;
margin-left: auto;
}
.notes-box {
border: 1px solid #ccc;
padding: 15px;
white-space: pre-wrap;
margin-top: 20px;
}
.print-only {
display: none;
}
@media print {
.top-links,
.screen-only,
footer {
display: none !important;
}
.print-only {
display: block;
}
body {
margin: 0;
}
}
</style>
</head>
<body>
<div class="invoice-wrap">
<div class="top-links screen-only">
<a href="/">Home</a>
<a href="/invoices">Back to Invoices</a>
<a href="/invoices/edit/{{ invoice.id }}">Edit Invoice</a>
<a href="#" onclick="window.print(); return false;">Print</a>
<a href="/invoices/pdf/{{ invoice.id }}">PDF</a>
</div>
<div class="header-row">
{% if settings.business_logo_url %}
<img src="{{ settings.business_logo_url }}" style="height:70px;margin-bottom:10px;">
{% endif %}
<div class="title-box">
<h1>Invoice {{ invoice.invoice_number }}</h1>
<span class="status-badge status-{{ invoice.status }}">{{ invoice.status }}</span>
</div>
<div style="text-align:right;">
<strong>{{ settings.business_name or 'OTB Billing' }}</strong><br>
{{ settings.business_tagline or '' }}<br>
{% if settings.business_address %}{{ settings.business_address }}<br>{% endif %}
{% if settings.business_email %}{{ settings.business_email }}<br>{% endif %}
{% if settings.business_phone %}{{ settings.business_phone }}<br>{% endif %}
{% if settings.business_website %}{{ settings.business_website }}{% endif %}
</div>
</div>
<div class="info-grid">
<div class="info-card">
<h3>Bill To</h3>
<strong>{{ invoice.company_name }}</strong><br>
{% if invoice.contact_name %}{{ invoice.contact_name }}<br>{% endif %}
{% if invoice.email %}{{ invoice.email }}<br>{% endif %}
{% if invoice.phone %}{{ invoice.phone }}<br>{% endif %}
Client Code: {{ invoice.client_code }}
</div>
<div class="info-card">
<h3>Invoice Details</h3>
Invoice #: {{ invoice.invoice_number }}<br>
Issued: {{ invoice.issued_at|localtime }}<br>
Due: {{ invoice.due_at|localtime }}<br>
{% if invoice.paid_at %}Paid: {{ invoice.paid_at|localtime }}<br>{% endif %}
Currency: {{ invoice.currency_code }}<br>
{% if settings.tax_number %}{{ settings.tax_label or 'Tax' }} Number: {{ settings.tax_number }}<br>{% endif %}
{% if settings.business_number %}Business Number: {{ settings.business_number }}{% endif %}
</div>
</div>
<table class="summary-table">
<tr>
<th>Service Code</th>
<th>Service</th>
<th>Description</th>
<th>Total</th>
</tr>
<tr>
<td>{{ invoice.service_code or '-' }}</td>
<td>{{ invoice.service_name or '-' }}</td>
<td>{{ invoice.notes or '-' }}</td>
<td>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
</table>
<table class="total-table">
<tr>
<th>Subtotal</th>
<td>{{ invoice.subtotal_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
<tr>
<th>{{ settings.tax_label or 'Tax' }}</th>
<td>{{ invoice.tax_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
<tr>
<th>Total</th>
<td><strong>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</strong></td>
</tr>
<tr>
<th>Paid</th>
<td>{{ invoice.amount_paid|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
<tr>
<th>Remaining</th>
<td>{{ (invoice.total_amount - invoice.amount_paid)|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
</table>
{% if settings.payment_terms %}
<div class="notes-box">
<strong>Payment Terms</strong><br><br>
{{ settings.payment_terms }}
</div>
{% endif %}
{% if settings.invoice_footer %}
<div class="notes-box">
<strong>Footer</strong><br><br>
{{ settings.invoice_footer }}
</div>
{% endif %}
</div>
{% include "footer.html" %}
</body>
</html>

73
backup_pre_email_send_2026-03-09/revenue.html.bak

@ -1,73 +0,0 @@
<!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>

199
backup_pre_email_send_2026-03-09/settings.html.bak

@ -1,199 +0,0 @@
<!doctype html>
<html>
<head>
<title>Settings</title>
<style>
body { font-family: Arial, sans-serif; }
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
max-width: 1100px;
}
.card {
border: 1px solid #ccc;
padding: 16px;
}
.card h2 {
margin-top: 0;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
textarea,
select {
width: 100%;
box-sizing: border-box;
margin-top: 4px;
margin-bottom: 12px;
padding: 8px;
}
textarea { min-height: 90px; }
.checkbox-row {
margin: 8px 0 14px 0;
}
.save-row {
margin-top: 18px;
}
.logo-preview {
margin: 10px 0 14px 0;
}
.logo-preview img {
max-height: 70px;
max-width: 220px;
border: 1px solid #ccc;
padding: 6px;
background: #fff;
}
small {
color: #444;
}
</style>
</head>
<body>
<h1>Settings / Config</h1>
<p><a href="/">Home</a></p>
<form method="post">
<div class="form-grid">
<div class="card">
<h2>Business Identity</h2>
Business Name<br>
<input type="text" name="business_name" value="{{ settings.business_name }}"><br>
Business Logo URL<br>
<input type="text" name="business_logo_url" value="{{ settings.business_logo_url }}"><br>
<small>Example: /static/favicon.png or https://site.com/logo.png</small><br>
{% if settings.business_logo_url %}
<div class="logo-preview">
<img src="{{ settings.business_logo_url }}" alt="Business Logo Preview">
</div>
{% endif %}
Slogan / Tagline<br>
<input type="text" name="business_tagline" value="{{ settings.business_tagline }}"><br>
Business Email<br>
<input type="email" name="business_email" value="{{ settings.business_email }}"><br>
Business Phone<br>
<input type="text" name="business_phone" value="{{ settings.business_phone }}"><br>
Business Address<br>
<textarea name="business_address">{{ settings.business_address }}</textarea><br>
Website<br>
<input type="text" name="business_website" value="{{ settings.business_website }}"><br>
Business Number / Registration Number<br>
<input type="text" name="business_number" value="{{ settings.business_number }}"><br>
Default Currency<br>
<select name="default_currency">
<option value="CAD" {% if settings.default_currency == 'CAD' %}selected{% endif %}>CAD</option>
<option value="USD" {% if settings.default_currency == 'USD' %}selected{% endif %}>USD</option>
<option value="ETHO" {% if settings.default_currency == 'ETHO' %}selected{% endif %}>ETHO</option>
<option value="EGAZ" {% if settings.default_currency == 'EGAZ' %}selected{% endif %}>EGAZ</option>
<option value="ALT" {% if settings.default_currency == 'ALT' %}selected{% endif %}>ALT</option>
</select>
Report Frequency<br>
<select name="report_frequency">
<option value="monthly" {% if settings.report_frequency == 'monthly' %}selected{% endif %}>monthly</option>
<option value="quarterly" {% if settings.report_frequency == 'quarterly' %}selected{% endif %}>quarterly</option>
<option value="yearly" {% if settings.report_frequency == 'yearly' %}selected{% endif %}>yearly</option>
</select>
</div>
<div class="card">
<h2>Tax Settings</h2>
Local Country<br>
<input type="text" name="local_country" value="{{ settings.local_country }}"><br>
Tax Label<br>
<input type="text" name="tax_label" value="{{ settings.tax_label }}"><br>
Tax Rate (%)<br>
<input type="number" step="0.01" name="tax_rate" value="{{ settings.tax_rate }}"><br>
Tax Number<br>
<input type="text" name="tax_number" value="{{ settings.tax_number }}"><br>
<div class="checkbox-row">
<label>
<input type="checkbox" name="apply_local_tax_only" value="1" {% if settings.apply_local_tax_only == '1' %}checked{% endif %}>
Apply tax only to local clients
</label>
</div>
Payment Terms<br>
<textarea name="payment_terms">{{ settings.payment_terms }}</textarea><br>
Invoice Footer<br>
<textarea name="invoice_footer">{{ settings.invoice_footer }}</textarea><br>
</div>
<div class="card">
<h2>Advanced / Email / SMTP</h2>
SMTP Host<br>
<input type="text" name="smtp_host" value="{{ settings.smtp_host }}"><br>
SMTP Port<br>
<input type="number" name="smtp_port" value="{{ settings.smtp_port }}"><br>
SMTP Username<br>
<input type="text" name="smtp_user" value="{{ settings.smtp_user }}"><br>
SMTP Password<br>
<input type="password" name="smtp_pass" value="{{ settings.smtp_pass }}"><br>
From Email<br>
<input type="email" name="smtp_from_email" value="{{ settings.smtp_from_email }}"><br>
From Name<br>
<input type="text" name="smtp_from_name" value="{{ settings.smtp_from_name }}"><br>
<div class="checkbox-row">
<label>
<input type="checkbox" name="smtp_use_tls" value="1" {% if settings.smtp_use_tls == '1' %}checked{% endif %}>
Use TLS
</label>
</div>
<div class="checkbox-row">
<label>
<input type="checkbox" name="smtp_use_ssl" value="1" {% if settings.smtp_use_ssl == '1' %}checked{% endif %}>
Use SSL
</label>
</div>
</div>
<div class="card">
<h2>Notes</h2>
<p>
Branding, tax identity, and SMTP values are stored here for this installation.
</p>
<p>
Logo can be a local static path like <strong>/static/favicon.png</strong> or a full external/IPFS URL.
</p>
<p>
Email sending is not wired yet, but these SMTP settings are stored now so the next step can use them.
</p>
</div>
</div>
<div class="save-row">
<button type="submit">Save Settings</button>
</div>
</form>
{% include "footer.html" %}
</body>
</html>

1437
backup_pre_invoice_numbering_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

81
backup_pre_invoice_numbering_2026-03-09/invoices_new.html.bak

@ -1,81 +0,0 @@
<!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" %}

5
backup_pre_invoice_numbering_2026-03-09/requirements.txt.bak

@ -1,5 +0,0 @@
Flask
mysql-connector-python
reportlab
python-dateutil
pytz

1258
backup_pre_invoice_pdf_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

79
backup_pre_invoice_pdf_2026-03-09/invoices_list.html.bak

@ -1,79 +0,0 @@
<!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>

187
backup_pre_invoice_pdf_2026-03-09/invoices_view.html.bak

@ -1,187 +0,0 @@
<!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>

1228
backup_pre_invoice_print_view_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

78
backup_pre_invoice_print_view_2026-03-09/invoices_list.html.bak

@ -1,78 +0,0 @@
<!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>

1813
backup_pre_invoice_range_export_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

81
backup_pre_invoice_range_export_2026-03-09/invoices_list.html.bak

@ -1,81 +0,0 @@
<!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>

1149
backup_pre_new_payment_rebuild_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

1149
backup_pre_overpayment_guard_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

1149
backup_pre_payment_filter_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

103
backup_pre_payment_filter_2026-03-08/payments_new.html.bak

@ -1,103 +0,0 @@
<!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" %}

1184
backup_pre_payment_policy_guard_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

107
backup_pre_payment_policy_guard_2026-03-08/payments_edit.html.bak

@ -1,107 +0,0 @@
<!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>

139
backup_pre_payment_policy_guard_2026-03-08/payments_new.html.bak

@ -1,139 +0,0 @@
<!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>

1184
backup_pre_payment_void_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

100
backup_pre_payment_void_2026-03-08/payments_list.html.bak

@ -1,100 +0,0 @@
<!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>

1224
backup_pre_payments_list_cleanup_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

100
backup_pre_payments_list_cleanup_2026-03-08/payments_list.html.bak

@ -1,100 +0,0 @@
<!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>

1584
backup_pre_pdf_logo_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

2242
backup_pre_revenue_report_json_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

42
backup_pre_revenue_report_json_2026-03-09/dashboard.html.bak

@ -1,42 +0,0 @@
<!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>

192
backup_pre_revenue_report_json_2026-03-09/settings.html.bak

@ -1,192 +0,0 @@
<!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>

1462
backup_pre_settings_config_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

35
backup_pre_settings_config_2026-03-09/dashboard.html.bak

@ -1,35 +0,0 @@
<!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>

188
backup_pre_settings_config_2026-03-09/invoices_view.html.bak

@ -1,188 +0,0 @@
<!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>

1138
backup_pre_status_hardening_2026-03-08/app.py.bak

File diff suppressed because it is too large Load Diff

113
backup_pre_status_hardening_2026-03-08/invoices_edit.html.bak

@ -1,113 +0,0 @@
<!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>

54
backup_pre_status_hardening_2026-03-08/invoices_list.html.bak

@ -1,54 +0,0 @@
<!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>

2342
backup_restore_email_layer_2026-03-09/app.py.bak

File diff suppressed because it is too large Load Diff

235
backup_restore_email_layer_2026-03-09/invoices_view.html.bak

@ -1,235 +0,0 @@
<!doctype html>
<html>
<head>
<title>Invoice {{ invoice.invoice_number }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 30px;
color: #111;
}
.top-links {
margin-bottom: 20px;
}
.top-links a {
margin-right: 15px;
}
.invoice-wrap {
max-width: 900px;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 25px;
}
.title-box h1 {
margin: 0 0 8px 0;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.status-draft { background: #e5e7eb; color: #111827; }
.status-pending { background: #dbeafe; color: #1d4ed8; }
.status-partial { background: #fef3c7; color: #92400e; }
.status-paid { background: #dcfce7; color: #166534; }
.status-overdue { background: #fee2e2; color: #991b1b; }
.status-cancelled { background: #e5e7eb; color: #4b5563; }
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 25px;
}
.info-card {
border: 1px solid #ccc;
padding: 15px;
}
.info-card h3 {
margin-top: 0;
}
.summary-table,
.total-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 25px;
}
.summary-table th,
.summary-table td,
.total-table th,
.total-table td {
border: 1px solid #ccc;
padding: 10px;
text-align: left;
}
.total-table {
max-width: 420px;
margin-left: auto;
}
.notes-box {
border: 1px solid #ccc;
padding: 15px;
white-space: pre-wrap;
margin-top: 20px;
}
.print-only {
display: none;
}
@media print {
.top-links,
.screen-only,
footer {
display: none !important;
}
.print-only {
display: block;
}
body {
margin: 0;
}
}
</style>
</head>
<body>
<div class="invoice-wrap">
{% if request.args.get('email_sent') == '1' %}
<div style="border:1px solid #166534;background:#dcfce7;padding:10px;margin-bottom:15px;">
Invoice email sent successfully.
</div>
{% endif %}
{% if request.args.get('email_failed') == '1' %}
<div style="border:1px solid #991b1b;background:#fee2e2;padding:10px;margin-bottom:15px;">
Invoice email failed. Check SMTP settings or server log.
</div>
{% endif %}
<div class="top-links screen-only">
<a href="/">Home</a>
<a href="/invoices">Back to Invoices</a>
<a href="/invoices/edit/{{ invoice.id }}">Edit Invoice</a>
<a href="#" onclick="window.print(); return false;">Print</a>
<a href="/invoices/pdf/{{ invoice.id }}">PDF</a>
{% if invoice.email %}
<form method="post" action="/invoices/email/{{ invoice.id }}" style="display:inline;">
<button type="submit">Send Email</button>
</form>
{% endif %}
</div>
<div class="header-row">
{% if settings.business_logo_url %}
<img src="{{ settings.business_logo_url }}" style="height:70px;margin-bottom:10px;">
{% endif %}
<div class="title-box">
<h1>Invoice {{ invoice.invoice_number }}</h1>
<span class="status-badge status-{{ invoice.status }}">{{ invoice.status }}</span>
</div>
<div style="text-align:right;">
<strong>{{ settings.business_name or 'OTB Billing' }}</strong><br>
{{ settings.business_tagline or '' }}<br>
{% if settings.business_address %}{{ settings.business_address }}<br>{% endif %}
{% if settings.business_email %}{{ settings.business_email }}<br>{% endif %}
{% if settings.business_phone %}{{ settings.business_phone }}<br>{% endif %}
{% if settings.business_website %}{{ settings.business_website }}{% endif %}
</div>
</div>
<div class="info-grid">
<div class="info-card">
<h3>Bill To</h3>
<strong>{{ invoice.company_name }}</strong><br>
{% if invoice.contact_name %}{{ invoice.contact_name }}<br>{% endif %}
{% if invoice.email %}{{ invoice.email }}<br>{% endif %}
{% if invoice.phone %}{{ invoice.phone }}<br>{% endif %}
Client Code: {{ invoice.client_code }}
</div>
<div class="info-card">
<h3>Invoice Details</h3>
Invoice #: {{ invoice.invoice_number }}<br>
Issued: {{ invoice.issued_at|localtime }}<br>
Due: {{ invoice.due_at|localtime }}<br>
{% if invoice.paid_at %}Paid: {{ invoice.paid_at|localtime }}<br>{% endif %}
Currency: {{ invoice.currency_code }}<br>
{% if settings.tax_number %}{{ settings.tax_label or 'Tax' }} Number: {{ settings.tax_number }}<br>{% endif %}
{% if settings.business_number %}Business Number: {{ settings.business_number }}{% endif %}
</div>
</div>
<table class="summary-table">
<tr>
<th>Service Code</th>
<th>Service</th>
<th>Description</th>
<th>Total</th>
</tr>
<tr>
<td>{{ invoice.service_code or '-' }}</td>
<td>{{ invoice.service_name or '-' }}</td>
<td>{{ invoice.notes or '-' }}</td>
<td>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
</table>
<table class="total-table">
<tr>
<th>Subtotal</th>
<td>{{ invoice.subtotal_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
<tr>
<th>{{ settings.tax_label or 'Tax' }}</th>
<td>{{ invoice.tax_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
<tr>
<th>Total</th>
<td><strong>{{ invoice.total_amount|money(invoice.currency_code) }} {{ invoice.currency_code }}</strong></td>
</tr>
<tr>
<th>Paid</th>
<td>{{ invoice.amount_paid|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
<tr>
<th>Remaining</th>
<td>{{ (invoice.total_amount - invoice.amount_paid)|money(invoice.currency_code) }} {{ invoice.currency_code }}</td>
</tr>
</table>
{% if latest_email_log %}
<div class="notes-box">
<strong>Latest Email Activity</strong><br><br>
Status: {{ latest_email_log.status }}<br>
Recipient: {{ latest_email_log.recipient_email }}<br>
Subject: {{ latest_email_log.subject }}<br>
Sent At: {{ latest_email_log.sent_at|localtime }}<br>
{% if latest_email_log.error_message %}
Error: {{ latest_email_log.error_message }}
{% endif %}
</div>
{% endif %}
{% if settings.payment_terms %}
<div class="notes-box">
<strong>Payment Terms</strong><br><br>
{{ settings.payment_terms }}
</div>
{% endif %}
{% if settings.invoice_footer %}
<div class="notes-box">
<strong>Footer</strong><br><br>
{{ settings.invoice_footer }}
</div>
{% endif %}
</div>
{% include "footer.html" %}
</body>
</html>

0
backup_fix_void_route_2026-03-08/app.py.bak → build-backups/backup_fix_void_route_2026-03-08/app.py.bak

0
backup_fix_void_route_2026-03-08/payments_list.html.bak → build-backups/backup_fix_void_route_2026-03-08/payments_list.html.bak

0
backup_logo_support_2026-03-09/app.py.bak → build-backups/backup_logo_support_2026-03-09/app.py.bak

0
backup_logo_support_2026-03-09/dashboard.html.bak → build-backups/backup_logo_support_2026-03-09/dashboard.html.bak

0
backup_logo_support_2026-03-09/invoice_view.html.bak → build-backups/backup_logo_support_2026-03-09/invoice_view.html.bak

0
backup_logo_support_2026-03-09/settings.html.bak → build-backups/backup_logo_support_2026-03-09/settings.html.bak

0
backup_pre_accounting_package_2026-03-09/app.py.bak → build-backups/backup_restore_email_layer_2026-03-09/app.py.bak

0
backup_restore_email_layer_2026-03-09/dashboard.html.bak → build-backups/backup_restore_email_layer_2026-03-09/dashboard.html.bak

0
backup_pre_email_log_2026-03-09/invoices_view.html.bak → build-backups/backup_restore_email_layer_2026-03-09/invoices_view.html.bak

0
backup_restore_email_layer_2026-03-09/revenue.html.bak → build-backups/backup_restore_email_layer_2026-03-09/revenue.html.bak

0
backup_restore_email_layer_2026-03-09/settings.html.bak → build-backups/backup_restore_email_layer_2026-03-09/settings.html.bak

45
emailfix.sh

@ -0,0 +1,45 @@
cd /home/def/otb_billing/backend || exit 1
STAMP="$(date +%Y%m%d-%H%M%S)"
cp app.py "app.py.email-attachment.${STAMP}.bak"
python3 <<'PY'
from pathlib import Path
p = Path("app.py")
t = p.read_text()
# Find the payment confirmation email block
old = "attachments=None,"
new = """attachments=[{
"filename": f"invoice_{invoice.get('invoice_number')}.pdf",
"content": generate_invoice_pdf_bytes(invoice_id),
"mime": "application/pdf"
}],"""
if old not in t:
raise SystemExit("FAILED: attachments=None not found")
t = t.replace(old, new, 1)
# Add helper function if missing
if "def generate_invoice_pdf_bytes" not in t:
t += """
def generate_invoice_pdf_bytes(invoice_id):
from io import BytesIO
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
c.drawString(100, 750, f"Invoice #{invoice_id}")
c.drawString(100, 730, "Generated by OTB Billing")
c.save()
buffer.seek(0)
return buffer.read()
"""
p.write_text(t)
print("OK: email attachment patch applied")
PY
sudo systemctl restart otb_billing.service

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save