Lokalen Rechnungsassistenten mit Vorlagen und PDF-Vorschau ergänzen

This commit is contained in:
admin committed 2026-10-19 16:52:48 +02:00
1 parent f447603af4
commit b9f307fc81
15 files changed
+1423 -26

No files matched your search

+76
View File
@@ -0,0 +1,76 @@
"""PDF-Erzeugung in einem eigenen Prozess ohne Qt-Laufzeit ausführen."""
from __future__ import annotations
import json
import logging
from pathlib import Path
import shutil
import sys
from .create import create_invoice, find_customer, save_customer
from .gui_data import InvoiceForm
from .models import Party
from .render import render_html, render_pdf
def execute(request: dict, progress) -> dict:
"""Nur geprüfte Formulardaten an die gemeinsame Rechnungserstellung übergeben."""
form = InvoiceForm.model_validate(request['form'])
presets, resources = Path(request['presets']), Path(request['resources'])
invoice = form.invoice(presets)
if request['mode'] == 'preview':
progress('Entwurf wird ohne Nummernreservierung erstellt …')
invoice.meta.invoice_number = 'ENTWURF'
invoice.payment.reference = 'Entwurf – noch keine Rechnungsnummer vergeben'
invoice.meta.notes = 'ENTWURF – nicht zum Versand.\n' + (invoice.meta.notes or '')
folder = Path(request['scratch'])
(folder / 'assets').mkdir(exist_ok=True)
logo = presets / 'logo.svg'
if not logo.is_file():
logo = resources / 'assets/logo.svg'
shutil.copy2(logo, folder / 'assets/logo.svg')
result = render_pdf(render_html(invoice, resources / 'templates/invoice.html.j2'),
folder / 'entwurf.pdf', logging.getLogger('gui-preview'), base_url=str(folder))
if not result.ok:
raise RuntimeError(result.error)
return {'pdf': str(result.path)}
if request['mode'] != 'generate':
raise ValueError('Unbekannter Auftrag')
jar = Path(request['jar'])
if not jar.is_file():
raise ValueError('Mustang fehlt. Einmalig jr-einvoice setup-mustang ausführen.')
existing = find_customer(presets, form.buyer.name)
if existing and Party.model_validate(existing[1]['buyer']) != form.buyer:
raise ValueError('Dieser Kundenname ist mit anderen Stammdaten gespeichert. '
'Bitte den gespeicherten Kunden auswählen oder die Kundendatei prüfen.')
# Vorlagen dürfen vorhandene Kundenadressen nicht stillschweigend ersetzen.
match = existing or save_customer(presets, form.buyer.name, form.buyer.model_dump(mode='json'))
first = form.items[0]
result = create_invoice(root=Path(request['root']), resources=resources, presets=presets,
customer=match[0], entry=first.name, volume=first.quantity,
price=first.unit_price_net, items=form.items, notes=form.notes,
jar=jar, logger=logging.getLogger('gui'), number=form.number,
issued=form.issued, due_days=form.due_days,
service_period_start=form.start, service_period_end=form.end,
buyer_reference=form.buyer_reference, order_reference=form.order_reference,
progress=progress)
return {'invoice': result.invoice.model_dump(mode='json'), 'attachment': str(result.attachment),
'build': {key: str(value) if value is not None else None
for key, value in vars(result.build).items()}}
def main(argv=None):
request_path = Path((argv or sys.argv[1:])[0])
result_path = request_path.with_name('result.json')
try:
request = json.loads(request_path.read_text(encoding='utf-8'))
result = execute(request, lambda message: print(json.dumps({'progress': message}), flush=True))
result_path.write_text(json.dumps({'result': result}), encoding='utf-8')
return 0
except Exception as exc:
result_path.write_text(json.dumps({'error': str(exc)}), encoding='utf-8')
return 1
if __name__ == '__main__':
raise SystemExit(main())