149fafb834
Also fixed assignment error where year and number were swapped.
270 lines
9.0 KiB
Python
Executable File
270 lines
9.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
FJCCV Donation Receipt Generator
|
|
Generates professional PDF receipts for church donations.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
try:
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.lib.units import inch
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.lib.colors import HexColor
|
|
from reportlab.pdfbase import pdfmetrics
|
|
from reportlab.pdfbase.ttfonts import TTFont
|
|
except ImportError:
|
|
print("ERROR: reportlab not installed. Run: pip3 install reportlab --break-system-packages")
|
|
sys.exit(1)
|
|
|
|
# FJCCV brand colors
|
|
FJCCV_PURPLE = HexColor('#6A0DAD') # Deep purple
|
|
|
|
# Church info
|
|
CHURCH_NAME = "Fellowship of Jesus Christ in California Valley"
|
|
CHURCH_EIN = "33-4360898"
|
|
BIBLE_VERSE = '"Each one must do just as he has purposed in his heart, not grudgingly or under compulsion, for God loves a cheerful giver."'
|
|
BIBLE_REF = "2 Corinthians 9:7 (NASB 1995)"
|
|
|
|
def register_fonts(base_dir: Path) -> bool:
|
|
"""Register FJCCV Serif font family if available"""
|
|
fonts_dir = base_dir / "assets"
|
|
fonts = {
|
|
'FJCCVSerif': 'FJCCVSerif-Regular.ttf',
|
|
'FJCCVSerif-Bold': 'FJCCVSerif-Bold.ttf',
|
|
'FJCCVSerif-Italic': 'FJCCVSerif-Italic.ttf',
|
|
'FJCCVSerif-BoldItalic': 'FJCCVSerif-BoldItalic.ttf',
|
|
}
|
|
|
|
all_found = True
|
|
for font_name, font_file in fonts.items():
|
|
font_path = fonts_dir / font_file
|
|
if font_path.exists():
|
|
try:
|
|
pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
|
|
except Exception as e:
|
|
print(f"Warning: Could not register {font_name}: {e}")
|
|
all_found = False
|
|
else:
|
|
print(f"Warning: Font file not found: {font_file}")
|
|
all_found = False
|
|
|
|
return all_found
|
|
|
|
def generate_receipt(donor: str, amount: float, date_str: str, donation_number: str,
|
|
output_path: Path, transaction: str = None, payment_method: str = None,
|
|
base_dir: Path = None) -> bool:
|
|
"""Generate a donation receipt PDF"""
|
|
|
|
# Setup fonts
|
|
fonts_available = False
|
|
if base_dir:
|
|
fonts_available = register_fonts(base_dir)
|
|
|
|
if fonts_available:
|
|
font_regular = 'FJCCVSerif'
|
|
font_bold = 'FJCCVSerif-Bold'
|
|
font_italic = 'FJCCVSerif-Italic'
|
|
font_bold_italic = 'FJCCVSerif-BoldItalic'
|
|
else:
|
|
print("Using fallback fonts (Times-Roman)")
|
|
font_regular = 'Times-Roman'
|
|
font_bold = 'Times-Bold'
|
|
font_italic = 'Times-Italic'
|
|
font_bold_italic = 'Times-BoldItalic'
|
|
|
|
# Create PDF
|
|
c = canvas.Canvas(str(output_path), pagesize=letter)
|
|
width, height = letter
|
|
|
|
# Add letterhead if available
|
|
if base_dir:
|
|
letterhead_path = base_dir / "assets" / "fjccv_letterhead.png"
|
|
if letterhead_path.exists():
|
|
# Full width letterhead at top
|
|
c.drawImage(str(letterhead_path), 0, height - 1.5*inch,
|
|
width=width, height=1.5*inch, preserveAspectRatio=True)
|
|
y_position = height - 2.5*inch
|
|
else:
|
|
print("Warning: Letterhead image not found, skipping")
|
|
y_position = height - 1.5*inch
|
|
else:
|
|
y_position = height - 1.5*inch
|
|
|
|
# Title
|
|
c.setFont(font_bold, 24)
|
|
c.setFillColor(FJCCV_PURPLE)
|
|
c.drawCentredString(width/2, y_position, "Donation Receipt")
|
|
y_position -= 0.7*inch
|
|
|
|
# Reset to black for body text
|
|
c.setFillColor('black')
|
|
|
|
# Receipt details
|
|
c.setFont(font_regular, 12)
|
|
left_margin = 1.25*inch
|
|
line_height = 0.3*inch
|
|
|
|
# Date
|
|
c.drawString(left_margin, y_position, "Date:")
|
|
c.setFont(font_bold, 12)
|
|
c.drawString(left_margin + 2*inch, y_position, date_str)
|
|
y_position -= line_height
|
|
|
|
# Donation number
|
|
c.setFont(font_regular, 12)
|
|
c.drawString(left_margin, y_position, "Donation Number:")
|
|
c.setFont(font_bold, 12)
|
|
c.drawString(left_margin + 2*inch, y_position, donation_number)
|
|
y_position -= line_height
|
|
|
|
# Donor name
|
|
c.setFont(font_regular, 12)
|
|
c.drawString(left_margin, y_position, "Received from:")
|
|
c.setFont(font_bold, 12)
|
|
c.drawString(left_margin + 2*inch, y_position, donor)
|
|
y_position -= line_height
|
|
|
|
# Amount
|
|
c.setFont(font_regular, 12)
|
|
c.drawString(left_margin, y_position, "Amount:")
|
|
c.setFont(font_bold, 14)
|
|
c.drawString(left_margin + 2*inch, y_position, f"${amount:,.2f}")
|
|
y_position -= line_height
|
|
|
|
# Payment method (if provided)
|
|
if payment_method:
|
|
c.setFont(font_regular, 12)
|
|
c.drawString(left_margin, y_position, "Payment Method:")
|
|
c.setFont(font_bold, 12)
|
|
c.drawString(left_margin + 2*inch, y_position, payment_method)
|
|
y_position -= line_height
|
|
|
|
# Transaction ID (if provided)
|
|
if transaction:
|
|
c.setFont(font_regular, 12)
|
|
c.drawString(left_margin, y_position, "Transaction ID:")
|
|
c.setFont(font_regular, 10)
|
|
c.drawString(left_margin + 2*inch, y_position, transaction)
|
|
y_position -= line_height
|
|
|
|
y_position -= 0.3*inch
|
|
|
|
# Bible verse (centered, italic, wrapped, purple)
|
|
c.setFont(font_bold_italic, 11)
|
|
c.setFillColor(FJCCV_PURPLE)
|
|
# Wrap the verse manually for better readability
|
|
verse_lines = [
|
|
'"Each one must do just as he has purposed in his heart,',
|
|
'not grudgingly or under compulsion,',
|
|
'for God loves a cheerful giver."',
|
|
f"— {BIBLE_REF}"
|
|
]
|
|
for line in verse_lines:
|
|
c.drawCentredString(width/2, y_position, line)
|
|
y_position -= 0.25*inch
|
|
|
|
# Reset to black for remaining text
|
|
c.setFillColor('black')
|
|
|
|
y_position -= 0.3*inch
|
|
|
|
# Tax statement
|
|
c.setFont(font_regular, 10)
|
|
tax_lines = [
|
|
f"{CHURCH_NAME} is a 508(c)(1)(a) church organization.",
|
|
"No goods or services were provided in exchange for this donation.",
|
|
"This receipt may be used for tax deduction purposes as allowed by law."
|
|
]
|
|
for line in tax_lines:
|
|
c.drawCentredString(width/2, y_position, line)
|
|
y_position -= 0.22*inch
|
|
|
|
y_position -= 0.5*inch
|
|
|
|
# Closing
|
|
c.setFont(font_regular, 11)
|
|
c.drawString(left_margin, y_position, "Sincerely,")
|
|
y_position -= 0.25*inch
|
|
|
|
c.setFont(font_bold, 11)
|
|
c.drawString(left_margin, y_position, CHURCH_NAME)
|
|
y_position -= 0.2*inch
|
|
|
|
c.setFont(font_regular, 10)
|
|
c.drawString(left_margin, y_position, f"EIN: {CHURCH_EIN}")
|
|
|
|
# Footer with generation timestamp
|
|
c.setFont(font_regular, 8)
|
|
c.setFillColor('gray')
|
|
footer_text = f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
c.drawCentredString(width/2, 0.5*inch, footer_text)
|
|
|
|
# Save PDF
|
|
c.save()
|
|
return True
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Generate FJCCV donation receipt PDF')
|
|
parser.add_argument('--donor', required=True, help='Full name of donor')
|
|
parser.add_argument('--amount', required=True, type=float, help='Donation amount (numeric)')
|
|
parser.add_argument('--date', required=True, help='Date of donation (MM/DD/YY)')
|
|
parser.add_argument('--donation-number', required=True, help='Donation number (LDDDDDD-YYYY)')
|
|
parser.add_argument('--transaction', help='Transaction ID (optional)')
|
|
parser.add_argument('--payment-method', help='Payment method (optional)')
|
|
parser.add_argument('--output-dir', default=os.path.expanduser('~/fjccv-receipts'),
|
|
help='Output directory for PDF (default: ~/fjccv-receipts)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Determine base directory (where this script lives)
|
|
script_dir = Path(__file__).parent
|
|
base_dir = script_dir.parent # Go up to skill root
|
|
|
|
# Create output directory
|
|
output_dir = Path(args.output_dir)
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Generate filename in format YYYY_LNNN.pdf (e.g., 2026_K001001)
|
|
parts = args.donation_number.rsplit('-', 1)
|
|
if len(parts) == 2:
|
|
number, year = parts
|
|
filename = f"{year}_{number}.pdf"
|
|
else:
|
|
filename = f"{args.donation_number}.pdf"
|
|
output_path = output_dir / filename
|
|
|
|
# Generate receipt
|
|
try:
|
|
success = generate_receipt(
|
|
donor=args.donor,
|
|
amount=args.amount,
|
|
date_str=args.date,
|
|
donation_number=args.donation_number,
|
|
output_path=output_path,
|
|
transaction=args.transaction,
|
|
payment_method=args.payment_method,
|
|
base_dir=base_dir
|
|
)
|
|
|
|
if success:
|
|
print(f"✅ Receipt generated successfully:")
|
|
print(f" {output_path}")
|
|
return 0
|
|
else:
|
|
print("❌ Failed to generate receipt")
|
|
return 1
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error generating receipt: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return 1
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|