Files
fjccv-receipts/process_donation.py
T
Krystie 8fb6468369 Initial commit: FJCCV receipt generator and ledger tools
- generate_receipt.py: Creates PDF donation receipts
- update_ledger.py: Updates Excel ledger with donations
- ledger_helper.py: Receipt number management
- process_donation.py: Donation processing workflow
2026-05-25 02:32:57 -07:00

207 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
FJCCV Donation Processor
Complete workflow: calculate receipt #, generate PDF, update ledger.
"""
import argparse
import sys
import subprocess
from pathlib import Path
from datetime import datetime
def get_script_dir():
return Path(__file__).parent
def get_skill_dir():
return Path(__file__).parent.parent
def process_donation(donor: str, amount: float, date_str: str,
payment_method: str, transaction_id: str = None,
update_bank: bool = True, output_dir: str = None) -> dict:
"""
Complete donation processing workflow.
Returns dict with:
- success: bool
- receipt_number: str
- receipt_pdf: Path
- ledger_updated: bool
- bank_updated: bool
- messages: list of str
"""
script_dir = get_script_dir()
skill_dir = get_skill_dir()
messages = []
# Parse date to get year
try:
if '/' in date_str:
parts = date_str.split('/')
if len(parts[2]) == 2:
year = 2000 + int(parts[2])
else:
year = int(parts[2])
else:
year = int(date_str.split('-')[0])
except:
return {
'success': False,
'messages': [f"Invalid date format: {date_str}"]
}
# Step 1: Get receipt number
messages.append("📋 Calculating receipt number...")
cmd = [
'python3', str(script_dir / 'ledger_helper.py'),
'next-receipt',
'--donor', donor,
'--year', str(year)
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
# Parse receipt number from output (look for pattern like " LDDDDDD-YYYY")
receipt_number = None
for line in result.stdout.split('\n'):
stripped = line.strip()
# Look for lines with format: LDDDDDD-YYYY (letter, 6 digits, dash, 4 digits)
if stripped and len(stripped) > 8 and '-' in stripped:
parts = stripped.split('-')
if len(parts) == 2 and parts[0][0].isalpha() and parts[1].isdigit():
receipt_number = stripped
messages.append(f" Receipt #: {receipt_number}")
break
if not receipt_number:
return {
'success': False,
'messages': messages + ["Could not parse receipt number from output", result.stdout]
}
except subprocess.CalledProcessError as e:
return {
'success': False,
'messages': messages + [f"Error calculating receipt number: {e.stderr}"]
}
# Step 2: Generate PDF receipt
messages.append("\n📄 Generating PDF receipt...")
if not output_dir:
output_dir = str(Path.home() / 'fjccv-receipts')
cmd = [
'python3', str(script_dir / 'generate_receipt.py'),
'--donor', donor,
'--amount', str(amount),
'--date', date_str,
'--donation-number', receipt_number,
'--payment-method', payment_method,
'--output-dir', output_dir
]
if transaction_id:
cmd.extend(['--transaction', transaction_id])
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
# Extract PDF path from output
for line in result.stdout.split('\n'):
if line.strip().startswith('/') and line.endswith('.pdf'):
receipt_pdf = Path(line.strip())
messages.append(f" PDF: {receipt_pdf}")
break
else:
receipt_pdf = None
messages.append(" ⚠️ PDF generated but path not captured")
except subprocess.CalledProcessError as e:
return {
'success': False,
'receipt_number': receipt_number,
'messages': messages + [f"Error generating PDF: {e.stderr}"]
}
# Step 3: Update ledger
messages.append("\n💾 Updating ledger...")
cmd = [
'python3', str(script_dir / 'update_ledger.py'),
'--date', date_str,
'--donor', donor,
'--amount', str(amount),
'--payment-method', payment_method
]
if transaction_id:
cmd.extend(['--transaction', transaction_id])
if update_bank:
cmd.append('--bank-reconciliation')
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
messages.append(" ✅ Ledger updated")
if update_bank:
messages.append(" ✅ Bank reconciliation updated")
ledger_updated = True
bank_updated = update_bank
except subprocess.CalledProcessError as e:
messages.append(f" ❌ Ledger update failed: {e.stderr}")
ledger_updated = False
bank_updated = False
# Summary
messages.append("\n" + "="*50)
messages.append("✅ DONATION PROCESSED SUCCESSFULLY")
messages.append(f" Donor: {donor}")
messages.append(f" Amount: ${amount:,.2f}")
messages.append(f" Date: {date_str}")
messages.append(f" Receipt: {receipt_number}")
if receipt_pdf:
messages.append(f" PDF: {receipt_pdf.name}")
return {
'success': True,
'receipt_number': receipt_number,
'receipt_pdf': receipt_pdf,
'ledger_updated': ledger_updated,
'bank_updated': bank_updated,
'messages': messages
}
def main():
parser = argparse.ArgumentParser(
description='Process FJCCV donation: receipt + ledger update',
epilog='Example: --donor "Svyatoslav Burik" --amount 500 --date "03/18/26" --payment-method Stripe --transaction pi_abc123'
)
parser.add_argument('--donor', required=True, help='Full donor name')
parser.add_argument('--amount', required=True, type=float, help='Gross donation amount')
parser.add_argument('--date', required=True, help='Date (MM/DD/YY or YYYY-MM-DD)')
parser.add_argument('--payment-method', required=True, help='Payment method')
parser.add_argument('--transaction', help='Transaction ID (optional)')
parser.add_argument('--output-dir', help='PDF output directory (default: ~/fjccv-receipts)')
parser.add_argument('--skip-bank', action='store_true', help='Skip bank reconciliation update')
args = parser.parse_args()
result = process_donation(
donor=args.donor,
amount=args.amount,
date_str=args.date,
payment_method=args.payment_method,
transaction_id=args.transaction,
update_bank=not args.skip_bank,
output_dir=args.output_dir
)
# Print all messages
for msg in result['messages']:
print(msg)
return 0 if result['success'] else 1
if __name__ == '__main__':
sys.exit(main())