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>
104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
import os
|
|
import subprocess
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
# Customer data payload matching our jinja variables
|
|
customer_data = {
|
|
"account_number": "C-998877665544",
|
|
"invoice_date": "15/04/2026",
|
|
"invoice_number": "SINV-0000999999",
|
|
"client_name": "Jean Tremblay - Tremblay Avocats",
|
|
"client_address_line1": "123 Rue de la Justice",
|
|
"client_address_line2": "Sherbrooke, QC J1H 4G9",
|
|
|
|
"current_charges_locations": [
|
|
{
|
|
"name": "123 Rue de la Justice, Sherbrooke",
|
|
"items": [
|
|
{
|
|
"description": "Fibre Affaires 50M/50M Illimité",
|
|
"qty": 1,
|
|
"unit_price": "109.95",
|
|
"amount": "109.95",
|
|
"is_credit": False
|
|
},
|
|
{
|
|
"description": "Location modem Giga",
|
|
"qty": 1,
|
|
"unit_price": "15.00",
|
|
"amount": "15.00",
|
|
"is_credit": False
|
|
},
|
|
{
|
|
"description": "Rabais fidélité",
|
|
"qty": 1,
|
|
"unit_price": "-10.00",
|
|
"amount": "-10.00",
|
|
"is_credit": True
|
|
}
|
|
],
|
|
"subtotal": "114.95"
|
|
}
|
|
],
|
|
|
|
"subtotal_before_taxes": "114.95",
|
|
"tps_amount": "5.75",
|
|
"tvq_amount": "11.47",
|
|
"total_taxes": "17.22",
|
|
"current_charges_total": "132.17",
|
|
|
|
"current_page": "1",
|
|
"total_pages": "1",
|
|
|
|
"prev_invoice_date": "15/03/2026",
|
|
"prev_invoice_total": "132.17",
|
|
"recent_payments": [
|
|
{
|
|
"date": "16/04/2026",
|
|
"amount": "132.17"
|
|
}
|
|
],
|
|
"remaining_balance": "0.00",
|
|
|
|
"due_date": "15/05/2026",
|
|
"total_amount_due": "0.00",
|
|
"qr_code_base64": "" # Leaving blank so the template falls back to the "QR" placeholder
|
|
}
|
|
|
|
def main():
|
|
# Setup rendering
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
env = Environment(loader=FileSystemLoader(script_dir))
|
|
template = env.get_template('invoice_preview.jinja')
|
|
|
|
# Render HTML
|
|
html_output = template.render(customer_data)
|
|
|
|
import time
|
|
timestamp = int(time.time())
|
|
|
|
# Save the rendered HTML temporarily
|
|
temp_html_path = os.path.join(script_dir, f'rendered_jinja_invoice_{timestamp}.html')
|
|
with open(temp_html_path, 'w', encoding='utf-8') as f:
|
|
f.write(html_output)
|
|
|
|
print(f"Generated HTML at {temp_html_path}")
|
|
|
|
# Generate PDF via Chrome Headless
|
|
pdf_path = os.path.join(script_dir, f'rendered_jinja_invoice_{timestamp}.pdf')
|
|
chrome_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
cmd = [
|
|
chrome_path,
|
|
"--headless",
|
|
"--no-pdf-header-footer",
|
|
f"--print-to-pdf={pdf_path}",
|
|
temp_html_path
|
|
]
|
|
|
|
print("Running Chrome to generate PDF...")
|
|
subprocess.run(cmd, check=True)
|
|
print(f"Success! Jinja data has been injected and PDF is ready at: {pdf_path}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|