120 lines
3.7 KiB
Python
Executable File
120 lines
3.7 KiB
Python
Executable File
"""Invoice routes."""
|
|
|
|
import base64
|
|
import logging
|
|
|
|
from flask import (
|
|
Blueprint,
|
|
Response,
|
|
flash,
|
|
g,
|
|
redirect,
|
|
render_template,
|
|
request,
|
|
url_for,
|
|
)
|
|
|
|
from customer_portal.services.wordpress_api import WordPressAPI
|
|
from customer_portal.web.routes.auth import login_required
|
|
|
|
bp = Blueprint("invoices", __name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@bp.route("/")
|
|
@login_required
|
|
def list_invoices():
|
|
"""List customer invoices."""
|
|
try:
|
|
invoices = WordPressAPI.get_invoices(g.customer.email)
|
|
except ValueError as e:
|
|
logger.error(f"WordPress API not configured: {e}")
|
|
flash("WordPress-Verbindung nicht konfiguriert.", "error")
|
|
invoices = []
|
|
except Exception as e:
|
|
logger.error(f"Error fetching invoices: {e}")
|
|
flash("Rechnungen konnten nicht geladen werden.", "error")
|
|
invoices = []
|
|
|
|
# Extract available years for filter
|
|
years = set()
|
|
for invoice in invoices:
|
|
if invoice.get("created_at"):
|
|
try:
|
|
date_str = invoice["created_at"]
|
|
year = date_str[:4] if "T" in date_str else date_str[:4]
|
|
years.add(year)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
# Sort years descending
|
|
years = sorted(years, reverse=True)
|
|
|
|
# Get filter parameters
|
|
status_filter = request.args.get("status", "")
|
|
year_filter = request.args.get("year", "")
|
|
|
|
# Apply filters
|
|
filtered_invoices = invoices
|
|
if status_filter:
|
|
filtered_invoices = [
|
|
inv for inv in filtered_invoices if inv.get("status") == status_filter
|
|
]
|
|
if year_filter:
|
|
filtered_invoices = [
|
|
inv
|
|
for inv in filtered_invoices
|
|
if inv.get("created_at", "")[:4] == year_filter
|
|
]
|
|
|
|
return render_template(
|
|
"invoices/list.html",
|
|
invoices=filtered_invoices,
|
|
all_invoices=invoices,
|
|
years=years,
|
|
status_filter=status_filter,
|
|
year_filter=year_filter,
|
|
)
|
|
|
|
|
|
@bp.route("/<int:invoice_id>/pdf")
|
|
@login_required
|
|
def download_pdf(invoice_id: int):
|
|
"""Download invoice PDF from WordPress/sevDesk."""
|
|
try:
|
|
# Get PDF via WordPress API (includes ownership verification)
|
|
pdf_data = WordPressAPI.get_invoice_pdf(invoice_id, g.customer.email)
|
|
except ValueError as e:
|
|
logger.error(f"WordPress API not configured: {e}")
|
|
flash("WordPress-Verbindung nicht konfiguriert.", "error")
|
|
return redirect(url_for("invoices.list_invoices"))
|
|
except Exception as e:
|
|
logger.error(f"Error fetching invoice PDF {invoice_id}: {e}")
|
|
flash("PDF konnte nicht geladen werden.", "error")
|
|
return redirect(url_for("invoices.list_invoices"))
|
|
|
|
if not pdf_data:
|
|
flash("Rechnung nicht gefunden oder kein Zugriff.", "error")
|
|
return redirect(url_for("invoices.list_invoices"))
|
|
|
|
# Check for error response
|
|
if "error" in pdf_data:
|
|
flash(f"Fehler: {pdf_data['error']}", "error")
|
|
return redirect(url_for("invoices.list_invoices"))
|
|
|
|
# Decode base64 PDF
|
|
try:
|
|
pdf_content = base64.b64decode(pdf_data["pdf"])
|
|
filename = pdf_data.get("filename", f"Rechnung_{invoice_id}.pdf")
|
|
except Exception as e:
|
|
logger.error(f"Error decoding PDF: {e}")
|
|
flash("PDF konnte nicht dekodiert werden.", "error")
|
|
return redirect(url_for("invoices.list_invoices"))
|
|
|
|
# Return PDF as download
|
|
return Response(
|
|
pdf_content,
|
|
mimetype="application/pdf",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}"},
|
|
)
|