8fb6468369
- 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
207 lines
6.7 KiB
Python
Executable File
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())
|