From 86090a7d33c2643da0e9d7bbffe6d3a414bcfadb Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Mon, 30 Mar 2026 12:13:21 +0200 Subject: [PATCH] feat(membership): PDF generation for declarations via WeasyPrint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New route: /admin/membership//print → generates PDF - Professional A4 layout with chamber header, data sections, signatures - Button changed from window.print() to PDF link (opens in new tab) - No browser headers/footers — clean PDF output - Signature lines for applicant and reviewer Co-Authored-By: Claude Opus 4.6 (1M context) --- blueprints/admin/routes_membership.py | 32 +++ templates/admin/membership_detail.html | 55 +++-- templates/admin/membership_print.html | 313 +++++++++++++++++++++++++ 3 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 templates/admin/membership_print.html diff --git a/blueprints/admin/routes_membership.py b/blueprints/admin/routes_membership.py index 710ab50..4ecfd63 100644 --- a/blueprints/admin/routes_membership.py +++ b/blueprints/admin/routes_membership.py @@ -386,6 +386,38 @@ def admin_membership(): db.close() +@bp.route('/membership//print') +@login_required +@role_required(SystemRole.OFFICE_MANAGER) +def admin_membership_print(app_id): + """Generate PDF of membership declaration.""" + from flask import Response + import weasyprint + + db = SessionLocal() + try: + application = db.query(MembershipApplication).get(app_id) + if not application: + flash('Nie znaleziono deklaracji.', 'error') + return redirect(url_for('admin.admin_membership')) + + html_content = render_template( + 'admin/membership_print.html', + app=application, + section_choices=MembershipApplication.SECTION_CHOICES, + ) + pdf = weasyprint.HTML(string=html_content).write_pdf() + return Response( + pdf, + mimetype='application/pdf', + headers={ + 'Content-Disposition': f'inline; filename=deklaracja-{app_id}.pdf' + } + ) + finally: + db.close() + + @bp.route('/membership/') @login_required @role_required(SystemRole.OFFICE_MANAGER) diff --git a/templates/admin/membership_detail.html b/templates/admin/membership_detail.html index f0a0aee..fe184db 100644 --- a/templates/admin/membership_detail.html +++ b/templates/admin/membership_detail.html @@ -850,9 +850,9 @@ - + + 🖨 Drukuj deklarację (PDF) +

Zmieni status na "W trakcie rozpatrywania" i odblokuje opcje zatwierdzenia, odrzucenia lub prośby o poprawki. @@ -898,9 +898,9 @@ - + + 🖨 Drukuj deklarację (PDF) +

@@ -1376,15 +1376,40 @@ async function proposeChanges() { } function printDeclaration() { - // Hide browser headers/footers by temporarily clearing title - var origTitle = document.title; - document.title = ' '; - // Also hide dev-notice via JS in case CSS didn't catch it - var devNotice = document.getElementById('devNotice'); - if (devNotice) devNotice.style.display = 'none'; - window.print(); - // Restore after print dialog closes - setTimeout(function() { document.title = origTitle; }, 1000); + // Open a clean print window — no browser headers/footers + var printHeader = document.querySelector('.print-header'); + var mainContent = document.querySelector('.main-content'); + if (!mainContent) return; + + var win = window.open('', '_blank', 'width=800,height=600'); + win.document.write(''); + win.document.write(' '); // Empty title = no header + win.document.write(''); + if (printHeader) win.document.write(printHeader.outerHTML.replace('display: none', 'display: block')); + win.document.write(mainContent.innerHTML); + win.document.write(''); + win.document.close(); + win.focus(); + setTimeout(function() { win.print(); }, 300); } function openApproveModal() { diff --git a/templates/admin/membership_print.html b/templates/admin/membership_print.html new file mode 100644 index 0000000..37518fd --- /dev/null +++ b/templates/admin/membership_print.html @@ -0,0 +1,313 @@ + + + + + + + + +
+

Izba Gospodarcza Norda Biznes

+

Deklaracja Przystapienia do Izby nr {{ app.id }}

+

+ {{ app.company_name }} + | Data zlozenia: {{ app.submitted_at.strftime('%d.%m.%Y, %H:%M') if app.submitted_at else 'brak' }} + | Status: {{ app.status_label }} +

+
+ +
+

Dane firmy

+
+
+

Nazwa firmy

+

{{ app.company_name }}

+
+
+

NIP

+

{{ app.nip or '---' }}

+
+
+

KRS

+

{{ app.krs or '---' }}

+
+
+

REGON

+

{{ app.regon or '---' }}

+
+
+

Forma prawna

+

{{ app.legal_form or '---' }}

+
+
+

Zrodlo danych

+

{{ app.registry_source or '---' }}

+
+
+
+ +
+

Adres siedziby

+
+
+

Ulica

+

{{ app.address_street or '---' }} {{ app.address_building or '' }}

+
+
+

Kod pocztowy i miejscowosc

+

{{ app.address_postal or '' }} {{ app.address_city or '---' }}

+
+
+
+ +
+

Dane kontaktowe

+
+
+

Email

+

{{ app.contact_email or '---' }}

+
+
+

Telefon

+

{{ app.contact_phone or '---' }}

+
+
+

Strona WWW

+

{{ app.contact_website or '---' }}

+
+
+

Nazwa skrocona

+

{{ app.short_name or '---' }}

+
+
+
+ +
+

Delegaci do Walnego Zgromadzenia

+
+
+

Delegat 1 (glowny)

+

{{ app.delegate_1 or '---' }}

+
+
+

Delegat 2

+

{{ app.delegate_2 or '---' }}

+
+
+

Delegat 3

+

{{ app.delegate_3 or '---' }}

+
+
+
+ +{% if app.sections_labels %} +
+

Sekcje tematyczne

+ {% for label in app.sections_labels %} + {{ label }} + {% endfor %} +
+{% endif %} + +{% if app.business_description %} +
+

Opis dzialalnosci

+

{{ app.business_description }}

+
+{% endif %} + +
+

Zgody i oswiadczenia

+ + + + {% if app.declaration_accepted %} +
+ Deklaracja przystapiena: Zaakceptowana + {% if app.declaration_accepted_at %} + | Data: {{ app.declaration_accepted_at.strftime('%d.%m.%Y, %H:%M') }} + {% endif %} + {% if app.declaration_ip %} + | IP: {{ app.declaration_ip }} + {% endif %} +
+ {% endif %} +
+ +
+
+
Podpis wnioskodawcy
+
+
+
Podpis osoby przyjmujacej
+
+
+ + + + +