Major additions accumulated over 9 days — single commit per request. Flow editor (new): - Generic visual editor for step trees, usable by project wizard + agent flows - PROJECT_KINDS / AGENT_KINDS catalogs decouple UI from domain - Drag-and-drop reorder via vuedraggable with scope isolation per peer group - Chain-aware depends_on rewrite on reorder (sequential only — DAGs preserved) - Variable picker with per-applies_to catalog (Customer / Quotation / Service Contract / Issue / Subscription), insert + copy-clipboard modes - trigger_condition helper with domain-specific JSONLogic examples - Global FlowEditorDialog mounted once in MainLayout, Odoo inline pattern - Server: targo-hub flow-runtime.js, flow-api.js, flow-templates.js - ERPNext: Flow Template/Run doctypes, scheduler, 5 seeded system templates - depends_on chips resolve to step labels instead of opaque "s4" ids QR/OCR scanner (field app): - Camera capture → Gemini Vision via targo-hub with 8s timeout - IndexedDB offline queue retries photos when signal returns - Watcher merges late-arriving scan results into the live UI Dispatch: - Planning mode (draft → publish) with offer pool for unassigned jobs - Shared presets, recurrence selector, suggested-slots dialog - PublishScheduleModal, unassign confirmation Ops app: - ClientDetailPage composables extraction (useClientData, useDeviceStatus, useWifiDiagnostic, useModemDiagnostic) - Project wizard: shared detail sections, wizard catalog/publish composables - Address pricing composable + pricing-mock data - Settings redesign hosting flow templates Targo-hub: - Contract acceptance (JWT residential + DocuSeal commercial tracks) - Referral system - Modem-bridge diagnostic normalizer - Device extractors consolidated Migration scripts: - Invoice/quote print format setup, Jinja rendering - Additional import + fix scripts (reversals, dates, customers, payments) Docs: - Consolidated: old scattered MDs → HANDOFF, ARCHITECTURE, DATA_AND_FLOWS, FLOW_EDITOR_ARCHITECTURE, BILLING_AND_PAYMENTS, CPE_MANAGEMENT, APP_DESIGN_GUIDELINES - Archived legacy wizard PHP for reference - STATUS snapshots for 2026-04-18/19 Cleanup: - Removed ~40 generated PDFs/HTMLs (invoice_preview*, rendered_jinja*) - .gitignore now covers invoice preview output + nested .DS_Store Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
233 lines
12 KiB
Django/Jinja
233 lines
12 KiB
Django/Jinja
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Facture {{ invoice_number }}</title>
|
|
<style>
|
|
@page { size: Letter; margin: 10mm 15mm 8mm 20mm; }
|
|
* { box-sizing: border-box; }
|
|
body { font-family: -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 8pt; color: #333; line-height: 1.3; margin: 0; }
|
|
@media screen {
|
|
body { padding: 40px; max-width: 8.5in; margin: 0 auto; box-shadow: 0 0 10px rgba(0,0,0,0.1); background: #fff; }
|
|
html { background: #f0f0f0; }
|
|
}
|
|
|
|
/* ── Slogan ── */
|
|
.slogan { font-size: 7pt; color: #019547; text-align: center; padding: 2px 0 4px; font-weight: 500; letter-spacing: 0.3px; }
|
|
|
|
/* ── Metadata Elements ── */
|
|
.doc-label { font-size: 14pt; font-weight: 700; color: #019547; }
|
|
.doc-page { font-size: 7pt; color: #888; }
|
|
|
|
/* ── Meta band (under logo) ── */
|
|
.meta-band { width: 100%; padding: 6px 0; border-top: 1px solid #eee; border-bottom: 1px solid #eee; margin: 15px 0 0 0; }
|
|
.meta-band td { vertical-align: middle; padding: 0 6px; text-align: center; }
|
|
.meta-band .ml { color: #888; display: block; font-size: 5.5pt; text-transform: uppercase; margin-bottom: 2px; }
|
|
.meta-band .mv { font-weight: 700; color: #333; font-size: 7pt; }
|
|
|
|
/* ── Main 2-column layout ── */
|
|
.main { width: 100%; border-collapse: separate; border-spacing: 0; }
|
|
.main td { vertical-align: top; padding: 0; }
|
|
.col-l { width: 60%; padding-right: 0; }
|
|
.col-r { width: 38%; padding-left: 0; }
|
|
|
|
/* ── Left: client address (fixed for #10 envelope window) ── */
|
|
/* Target: 2.5"-3.5" from top of page = 63-89mm. At 10mm page margin, the text needs ~53mm from body top. */
|
|
.cl-block { margin-top: 12mm; margin-bottom: 28px; padding-left: 4px; }
|
|
.cl-lbl { font-size: 7pt; color: #888; text-transform: uppercase; letter-spacing: 0.3px; margin-bottom: 2px; }
|
|
.cl-name { font-size: 11pt; font-weight: 700; color: #222; line-height: 1.4; }
|
|
.cl-addr { font-size: 10pt; color: #333; line-height: 1.5; }
|
|
|
|
/* ── Left: summary ── */
|
|
.summary-hdr { font-size: 8.5pt; font-weight: 700; color: #019547; border-bottom: 1px solid #019547; padding-bottom: 3px; margin-bottom: 5px; }
|
|
.section-hdr { font-weight: 600; font-size: 7.5pt; padding-top: 5px; margin-bottom: 2px; }
|
|
.addr-hdr { font-weight: 600; font-size: 7pt; color: #019547; padding: 5px 0 2px; }
|
|
.prev-row { width: 100%; font-size: 7.5pt; }
|
|
.prev-row td { padding: 3px 4px; }
|
|
.prev-row .r { text-align: right; }
|
|
.prev-row .indent td { padding-left: 8px; color: #555; }
|
|
.prev-row .pmt td { color: #019547; }
|
|
.prev-row .bal td { font-style: italic; color: #888; }
|
|
.prev-row .shdr td { font-weight: 600; padding-top: 6px; }
|
|
.dtl { width: 100%; border-collapse: collapse; font-size: 7.5pt; margin-bottom: 16px; }
|
|
.dtl th { text-align: left; padding: 4px 6px; background: #f3f4f6; font-weight: 600; color: #555; font-size: 6.5pt; text-transform: uppercase; }
|
|
.dtl th.r, .dtl td.r { text-align: right; }
|
|
.dtl td { padding: 4px 6px; border-bottom: 1px solid #f0f0f0; }
|
|
.dtl tr.credit td { color: #019547; }
|
|
.dtl tr.stot td { font-weight: 600; border-top: 1px solid #ddd; border-bottom: none; padding-top: 3px; }
|
|
.dtl tr.tax td { color: #888; font-size: 7pt; border-bottom: none; padding: 2px 6px; }
|
|
.dtl tr.grand td { font-weight: 700; font-size: 8.5pt; border-top: 1px solid #019547; padding-top: 4px; }
|
|
.tax-nums { font-size: 6.5pt; color: #999; margin-top: 4px; }
|
|
|
|
/* ── Right column: all content in a bordered container ── */
|
|
.r-container { border: 1px solid #e5e7eb; border-left: 2px solid #019547; }
|
|
.r-section { padding: 10px 14px; }
|
|
.r-section + .r-section { border-top: 1px solid #e5e7eb; }
|
|
.r-section.r-green { background: #e8f5ee; }
|
|
.r-section.r-gray { background: #f8f9fa; }
|
|
/* Summary in right col */
|
|
.r-summary-hdr { font-size: 8.5pt; font-weight: 700; color: #019547; margin-bottom: 6px; }
|
|
.r-prev { width: 100%; font-size: 7.5pt; }
|
|
.r-prev td { padding: 3px 0; }
|
|
.r-prev .r { text-align: right; white-space: nowrap; }
|
|
.r-prev .indent td { padding-left: 6px; color: #555; }
|
|
.r-prev .pmt td { color: #019547; }
|
|
.r-prev .bal td { font-style: italic; color: #888; }
|
|
.r-prev .shdr td { font-weight: 600; padding-top: 6px; }
|
|
/* Amount due */
|
|
.ab-label { font-size: 8.5pt; font-weight: 600; color: #019547; }
|
|
.ab-val { font-size: 18pt; font-weight: 700; color: #222; text-align: right; white-space: nowrap; }
|
|
.ab-date { font-size: 7pt; color: #555; margin-top: 2px; }
|
|
.ab-table { width: 100%; border-collapse: collapse; }
|
|
.ab-table td { padding: 0; border: none; vertical-align: middle; }
|
|
/* QR */
|
|
.qr-table { width: 100%; border-collapse: collapse; }
|
|
.qr-table td { padding: 2px 6px; border: none; vertical-align: middle; font-size: 7.5pt; }
|
|
.qr-placeholder { width: 40px; height: 40px; background: #ccc; font-size: 5pt; color: #888; text-align: center; line-height: 40px; }
|
|
/* Info block */
|
|
.r-info-text { font-size: 6.5pt; color: #555; line-height: 1.4; }
|
|
.r-info-text strong { color: #333; }
|
|
.r-info-text .ri-title { font-weight: 700; font-size: 7pt; color: #333; margin-bottom: 3px; }
|
|
.r-info-text .ri-sep { border-top: 1px solid #e5e7eb; margin: 6px 0; }
|
|
|
|
/* ── Footer ── */
|
|
.footer-line { font-size: 6pt; color: #bbb; text-align: center; margin-top: 8px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ═══════ HEADER & MAIN 2 COLUMNS ═══════ -->
|
|
<div style="position: relative;">
|
|
<div class="slogan" style="position: absolute; top: 6px; left: 0; right: 0;">Merci de choisir local — Merci de choisir TARGO</div>
|
|
<table class="main"><tr>
|
|
|
|
<!-- ── LEFT 60% ── -->
|
|
<td class="col-l">
|
|
|
|
<!-- Logo -->
|
|
<div style="height: 26px;">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 326.39 70.35" style="height: 26px; width: auto;"><defs><style>.cls-1{fill:#019547;}</style></defs><path class="cls-1" d="M25.83,15H8.41a4.14,4.14,0,0,1-3.89-2.71L0,1.18H66.59L62.07,12.27A4.14,4.14,0,0,1,58.18,15H40.76V69.19H25.81Z"/><path class="cls-1" d="M90.74.68h18.55a5.63,5.63,0,0,1,5.63,5.6l.26,62.91H99.55L99.76,54H71.87L66.41,65.51a6.45,6.45,0,0,1-5.84,3.68H49.35L73.53,12.11A18.7,18.7,0,0,1,90.74.68M100,40.73l.07-26h-8a6.75,6.75,0,0,0-6.26,4.18L76.73,40.73Z"/><path class="cls-1" d="M124.51,6.81a5.64,5.64,0,0,1,5.65-5.65H155.6c8.64,0,15.37,2.44,19.81,6.91,3.81,3.78,5.84,9.14,5.84,15.53v.18c0,11-5.92,17.87-14.59,21.1l16.61,24.29h-14a6.51,6.51,0,0,1-5.39-2.87L151.21,47.41H139.44V69.17h-15Zm30.12,27.41c7.28,0,11.45-3.89,11.45-9.62v-.21c0-6.42-4.46-9.7-11.74-9.7H139.46V34.22Z"/><path class="cls-1" d="M184.74,35.37v-.18C184.74,15.85,199.8,0,220.4,0,232.65,0,240,3.31,247.13,9.33l-6.28,7.57a5.18,5.18,0,0,1-6.84,1,23.71,23.71,0,0,0-14.11-4.13c-10.88,0-19.52,9.62-19.52,21.18v.19c0,12.43,8.54,21.57,20.6,21.57a24,24,0,0,0,14.09-4.07V42.91h-8.68A6.41,6.41,0,0,1,220,36.53V30h29.54V59.52a44,44,0,0,1-29,10.78c-21.18,0-35.74-14.8-35.74-34.93"/><path class="cls-1" d="M254.09,35.37v-.18C254.09,15.85,269.33,0,290.33,0s36.06,15.66,36.06,35v.18c0,19.34-15.27,35.19-36.24,35.19s-36.06-15.64-36.06-35m56.63,0v-.18c0-11.67-8.54-21.39-20.6-21.39S269.73,23.34,269.73,35v.18c0,11.67,8.54,21.37,20.6,21.37S310.72,47,310.72,35.37"/></svg>
|
|
</div>
|
|
|
|
<!-- Account info directly under the logo -->
|
|
<table class="meta-band"><tr>
|
|
<td style="text-align:left;"><span class="ml">Nº compte</span><span class="mv">{{ account_number }}</span></td>
|
|
<td style="text-align:center;"><span class="ml">Date</span><span class="mv">{{ invoice_date }}</span></td>
|
|
<td style="text-align:right;"><span class="ml">Nº facture</span><span class="mv">{{ invoice_number }}</span></td>
|
|
</tr></table>
|
|
|
|
<!-- Client address (calibrated for #10 envelope window) -->
|
|
<div class="cl-block">
|
|
<div class="cl-lbl">Facturer à</div>
|
|
<div class="cl-name">{{ client_name }}</div>
|
|
<div class="cl-addr">
|
|
{{ client_address_line1 }}<br>
|
|
{{ client_address_line2 }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current charges -->
|
|
<div class="summary-hdr">FRAIS COURANTS</div>
|
|
|
|
{% for location in current_charges_locations %}
|
|
<div class="addr-hdr">◎ {{ location.name }}</div>
|
|
<table class="dtl">
|
|
<thead><tr><th style="width:55%">Description</th><th class="r">Qté</th><th class="r">Unit.</th><th class="r">Montant</th></tr></thead>
|
|
<tbody>
|
|
{% for item in location['items'] %}
|
|
<tr class="{% if item.is_credit %}credit{% endif %}">
|
|
<td>{{ item.description }}</td>
|
|
<td class="r">{{ item.qty }}</td>
|
|
<td class="r">{{ item.unit_price }}</td>
|
|
<td class="r">{{ item.amount }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
<tr class="stot"><td colspan="3">Sous-total</td><td class="r">$ {{ location.subtotal }}</td></tr>
|
|
</tbody>
|
|
</table>
|
|
{% endfor %}
|
|
|
|
<table class="dtl" style="margin-top:4px;">
|
|
<tr class="stot"><td colspan="3" style="width:75%">Sous-total avant taxes</td><td class="r">$ {{ subtotal_before_taxes }}</td></tr>
|
|
<tr class="tax"><td colspan="3">Taxes: TPS ($ {{ tps_amount }}), TVQ ($ {{ tvq_amount }})</td><td class="r">$ {{ total_taxes }}</td></tr>
|
|
<tr class="grand"><td colspan="3">TOTAL</td><td class="r">$ {{ current_charges_total }}</td></tr>
|
|
</table>
|
|
|
|
<div class="tax-nums">TPS: 834975559RT0001 | TVQ: 1213765929TQ0001</div>
|
|
</td>
|
|
|
|
<!-- ── SPACER ── -->
|
|
<td style="width: 2%; border-left: 15px solid transparent;"></td>
|
|
|
|
<!-- ── RIGHT 38%: all in one bordered container ── -->
|
|
<td class="col-r">
|
|
<!-- FACTURE Header floating above summary -->
|
|
<div style="text-align: right; height: 41px;">
|
|
<div class="doc-label" style="line-height:1;">FACTURE</div>
|
|
<div class="doc-page" style="line-height:1.2; margin-top:2px;">Page {{ current_page|default('1') }} de {{ total_pages|default('1') }}</div>
|
|
</div>
|
|
|
|
<div class="r-container">
|
|
|
|
<!-- Account summary -->
|
|
<div class="r-section">
|
|
<div class="r-summary-hdr">SOMMAIRE DU COMPTE</div>
|
|
<table class="r-prev">
|
|
<tr class="shdr"><td colspan="2">Facture précédente ({{ prev_invoice_date }})</td></tr>
|
|
<tr class="indent"><td>Total facture précédente</td><td class="r">$ {{ prev_invoice_total }}</td></tr>
|
|
|
|
{% for payment in recent_payments %}
|
|
<tr class="indent pmt"><td>Paiement reçu — Merci</td><td class="r">- $ {{ payment.amount }}</td></tr>
|
|
{% endfor %}
|
|
|
|
<tr class="indent bal"><td>Solde restant</td><td class="r">$ {{ remaining_balance }}</td></tr>
|
|
<tr class="shdr"><td>Frais courants</td><td class="r">$ {{ subtotal_before_taxes }}</td></tr>
|
|
<tr class="indent"><td>Taxes</td><td class="r">$ {{ total_taxes }}</td></tr>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Amount due -->
|
|
<div class="r-section r-green">
|
|
<table class="ab-table"><tr>
|
|
<td><div class="ab-label">Montant dû</div><div class="ab-date">avant le {{ due_date }}</div></td>
|
|
<td class="ab-val">$ {{ total_amount_due }}</td>
|
|
</tr></table>
|
|
</div>
|
|
|
|
<!-- QR -->
|
|
<div class="r-section r-green">
|
|
<table class="qr-table"><tr>
|
|
<td style="width:46px;">
|
|
{% if qr_code_base64 %}
|
|
<img src="data:image/png;base64,{{ qr_code_base64 }}" style="width:40px; height:40px;" />
|
|
{% else %}
|
|
<div class="qr-placeholder">QR</div>
|
|
{% endif %}
|
|
</td>
|
|
<td><strong>Payez en ligne</strong><br>Scannez le code QR ou visitez<br><strong style="color:#019547">client.gigafibre.ca</strong></td>
|
|
</tr></table>
|
|
</div>
|
|
|
|
<!-- Contact + Message -->
|
|
<div class="r-section r-gray r-info-text">
|
|
<div class="ri-title">Contactez-nous</div>
|
|
Service à la clientèle<br>
|
|
<strong>819 758-1555</strong><br>
|
|
Lun-Ven 8h-17h<br>
|
|
info@targo.ca • www.targo.ca
|
|
<div class="ri-sep"></div>
|
|
Prendre note que toute facture non acquittée à la date d'échéance sera sujette à des frais de retard.<br><br>
|
|
Avez-vous une plainte relative à votre service de télécommunication ou de télévision que vous n'êtes pas parvenu à régler? La CPRST pourrait vous aider sans frais : <strong>www.ccts-cprst.ca</strong> ou <strong>1-888-221-1687</strong>.
|
|
</div>
|
|
|
|
</div>
|
|
</td>
|
|
|
|
</tr></table>
|
|
</div>
|
|
|
|
<div class="footer-line">TARGO Communications Inc. • 134 rue Principale, Victoriaville, QC G6P 4E4 • 819 758-1555</div>
|
|
|
|
</body>
|
|
</html>
|