Files
TimeTracker/test_logo_pdf.py
Dries Peeters 69f2c80308 feat: Complete Admin Settings UI and enhance PDF logo reliability
This commit addresses multiple issues with the Admin Settings page and
improves PDF invoice logo embedding for better cross-platform reliability.

## Admin Settings UI - Missing Fields Fixed

The Admin → Settings page was incomplete, showing only basic timer and
regional settings. Added all missing sections:

- User Management: Self-registration toggle with admin username note
- Company Branding: Full company info fields (name, email, phone, website,
  address, tax ID, bank info) plus logo upload with preview
- Invoice Defaults: Prefix, start number, payment terms, and notes
- Backup Settings: Retention days and backup time configuration
- Export Settings: CSV delimiter preference selector
- Privacy & Analytics: Telemetry opt-in with detailed privacy information

The backend was already handling these fields - this was purely a frontend
template issue where form fields were missing.

## Analytics/Telemetry Preference Synchronization

Fixed critical bug where analytics checkbox in Admin Settings only updated
the database but not the InstallationConfig file that the telemetry system
actually reads from. Changes now properly sync both systems:

- On page load: Auto-sync database from InstallationConfig (source of truth)
- On save: Update both database AND InstallationConfig simultaneously
- Added logging for analytics preference changes
- Updated UI references: Initial setup and Telemetry dashboard now point
  to Admin → Settings as the primary location
- Added clear privacy information explaining what data is collected

## PDF Logo Embedding Enhancement

Improved logo reliability in PDF invoices by switching from file:// URIs
to base64 data URIs:

- More reliable across platforms (Windows, Linux, macOS)
- Works consistently in Docker containers
- Self-contained (no filesystem path dependencies)
- Automatic MIME type detection for all formats (PNG, JPG, GIF, SVG, WEBP)
- Graceful fallback to file:// URI if base64 fails
- Added comprehensive debug logging for troubleshooting

## Diagnostic Tools & Documentation

- Created test_logo_pdf.py: Diagnostic script to identify logo issues
- Created LOGO_PDF_TROUBLESHOOTING.md: Comprehensive troubleshooting guide
- Enhanced error messages with debug output throughout logo processing
- Added context passing fixes for PDF template rendering

## Files Changed

### Core Fixes
- app/templates/admin/settings.html: Complete rewrite with all sections
- app/routes/admin.py: InstallationConfig sync for analytics preference
- app/static/uploads/logos/.gitkeep: Ensure logos directory tracked by git

### PDF Logo Enhancement
- app/utils/pdf_generator.py: Base64 encoding + explicit context passing
- app/utils/template_filters.py: get_logo_base64() helper with debug logging
- app/templates/invoices/pdf_default.html: Base64 logo embedding

### Analytics Synchronization
- app/templates/setup/initial_setup.html: Updated settings reference
- app/templates/admin/telemetry.html: Cross-reference to Admin → Settings

### Documentation
- docs/GETTING_STARTED.md: Updated to reflect actual UI behavior
- test_logo_pdf.py: New diagnostic script
- LOGO_PDF_TROUBLESHOOTING.md: New troubleshooting guide

## Testing

Run diagnostic script to verify logo configuration:
2025-10-25 07:23:43 +02:00

195 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
Debug script to test company logo in PDF generation
Run this to check if logo is properly configured and can be embedded in PDFs
"""
import os
import sys
# Add app to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app import create_app
from app.models import Settings, Invoice
from app.utils.pdf_generator import InvoicePDFGenerator
def test_logo_setup():
"""Test if logo is properly configured"""
print("=" * 60)
print("LOGO CONFIGURATION TEST")
print("=" * 60)
app = create_app()
with app.app_context():
settings = Settings.get_settings()
print(f"\n1. Logo filename in database: {settings.company_logo_filename or 'NONE'}")
if not settings.company_logo_filename:
print(" ❌ NO LOGO UPLOADED")
print(" → Upload a logo in Admin → Settings → Company Branding")
return False
print(f" ✓ Logo filename found: {settings.company_logo_filename}")
logo_path = settings.get_logo_path()
print(f"\n2. Logo file path: {logo_path}")
if not logo_path:
print(" ❌ Could not determine logo path")
return False
if not os.path.exists(logo_path):
print(f" ❌ LOGO FILE DOES NOT EXIST AT: {logo_path}")
print(f" → Check if file exists in app/static/uploads/logos/")
return False
print(f" ✓ Logo file exists")
file_size = os.path.getsize(logo_path)
print(f"\n3. Logo file size: {file_size:,} bytes ({file_size/1024:.2f} KB)")
if file_size == 0:
print(" ❌ Logo file is empty")
return False
if file_size > 5 * 1024 * 1024: # 5MB
print(" ⚠️ Logo file is very large (>5MB). Consider optimizing.")
print(f" ✓ Logo file has content")
# Test base64 encoding
print(f"\n4. Testing base64 encoding...")
try:
from app.utils.template_filters import get_logo_base64
data_uri = get_logo_base64(logo_path)
if not data_uri:
print(" ❌ Base64 encoding failed (returned None)")
return False
if not data_uri.startswith('data:image/'):
print(f" ❌ Invalid data URI: {data_uri[:50]}...")
return False
encoded_size = len(data_uri)
print(f" ✓ Base64 encoding successful")
print(f" Data URI size: {encoded_size:,} bytes ({encoded_size/1024:.2f} KB)")
print(f" Data URI prefix: {data_uri[:50]}...")
except Exception as e:
print(f" ❌ Error encoding logo: {e}")
import traceback
traceback.print_exc()
return False
print("\n" + "=" * 60)
print("✓ LOGO CONFIGURATION IS CORRECT")
print("=" * 60)
return True
def test_pdf_generation():
"""Test PDF generation with logo"""
print("\n" + "=" * 60)
print("PDF GENERATION TEST")
print("=" * 60)
app = create_app()
with app.app_context():
# Get the most recent invoice
invoice = Invoice.query.order_by(Invoice.id.desc()).first()
if not invoice:
print("\n❌ NO INVOICES FOUND")
print(" → Create an invoice first to test PDF generation")
return False
print(f"\n1. Testing with Invoice #{invoice.invoice_number}")
print(f" Project: {invoice.project.name}")
print(f" Client: {invoice.client_name}")
try:
print(f"\n2. Generating PDF...")
generator = InvoicePDFGenerator(invoice)
pdf_bytes = generator.generate_pdf()
if not pdf_bytes:
print(" ❌ PDF generation returned no data")
return False
pdf_size = len(pdf_bytes)
print(f" ✓ PDF generated successfully")
print(f" PDF size: {pdf_size:,} bytes ({pdf_size/1024:.2f} KB)")
# Save PDF for inspection
output_file = 'test_invoice.pdf'
with open(output_file, 'wb') as f:
f.write(pdf_bytes)
print(f"\n3. PDF saved to: {output_file}")
print(f" → Open this file to check if logo appears")
print("\n" + "=" * 60)
print("✓ PDF GENERATION SUCCESSFUL")
print("=" * 60)
print(f"\n📄 Check the file: {output_file}")
print(" Look for the logo in the top-left corner of the invoice")
return True
except Exception as e:
print(f"\n ❌ Error generating PDF: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Run all tests"""
print("\n🔍 TimeTracker PDF Logo Diagnostic Tool\n")
logo_ok = test_logo_setup()
if logo_ok:
pdf_ok = test_pdf_generation()
if pdf_ok:
print("\n" + "=" * 60)
print("✅ ALL TESTS PASSED")
print("=" * 60)
print("\nIf the logo still doesn't appear in the PDF:")
print("1. Open test_invoice.pdf and check manually")
print("2. Check server logs for DEBUG messages when generating invoices")
print("3. Try uploading a different logo (simple PNG <1MB)")
print("4. Verify the logo works in the web UI first (Admin → Settings)")
return 0
else:
print("\n" + "=" * 60)
print("❌ PDF GENERATION FAILED")
print("=" * 60)
return 1
else:
print("\n" + "=" * 60)
print("❌ LOGO CONFIGURATION FAILED")
print("=" * 60)
print("\nPlease fix the logo configuration first:")
print("1. Login as admin")
print("2. Go to Admin → Settings")
print("3. Scroll to Company Branding section")
print("4. Upload a logo (PNG, JPG, GIF, SVG, or WEBP)")
print("5. Run this script again")
return 1
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
print("\n\nTest interrupted by user")
sys.exit(130)
except Exception as e:
print(f"\n\n❌ UNEXPECTED ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)