Skip to main content
File: app/worker/invoice_generator.py
Schedule: Every hour at :00 (e.g., 13:00, 14:00, 15:00)

What It Does

  1. Finds due subscriptions — Queries all ACTIVE subscriptions where current_period_end <= now()
  2. Generates invoices — Creates a DRAFT invoice with all line items calculated
  3. Renews subscriptions — Advances the billing period to the next interval
  4. Prevents duplicates — Checks for existing invoices for the period before creating

The Invoice Calculation

For each subscription:
For each FIXED charge:
  line_total = charge.unit_price

For each USAGE charge:
  usage = aggregate(usage_events in period, meter.aggregation_type)
  line_total = usage × charge.unit_price

subtotal = Σ line_totals
tax = calculate_tax(customer.address, org.address)
total = subtotal + tax
scheduled_finalize_at = now + 1 hour

Log Output

[INFO] worker.invoice_generator | run_id=abc12345 | 🚀 Invoice generation started
[INFO] worker.invoice_generator | run_id=abc12345 | Found 5 subscriptions due for processing
[INFO] worker.invoice_generator | run_id=abc12345 | ✅ Generated INV-2025-0055 | customer=Acme Corp | total=$145.20
[INFO] worker.invoice_generator | run_id=abc12345 | 🏁 Completed | processed=5 | successful=5 | failed=0

Troubleshooting

Invoices not being generated?
  1. Check the worker is running:
    curl http://localhost:8000/api/v1/workers/health
    
  2. Verify subscription current_period_end is in the past:
    SELECT id, status, current_period_end
    FROM subscriptions
    WHERE status = 'ACTIVE' AND current_period_end <= NOW();
    
  3. Check for existing invoices for the period (duplicate prevention may be skipping):
    SELECT invoice_number, period_start, period_end
    FROM invoices
    WHERE subscription_id = '<id>';
    

Manual Trigger

For testing, manually trigger invoice generation:
POST /api/v1/subscriptions/<subscription_id>/generate-invoice
Or from Python:
from app.worker.invoice_generator import InvoiceGenerator
from app.core.database import SessionLocal

db = SessionLocal()
try:
    generator = InvoiceGenerator(db)
    generator.process_due_subscriptions()
finally:
    db.close()

Log Files

logs/workers/invoice_generator.log
View with:
python scripts/view_worker_logs.py --worker invoice_generator --lines 50