#!/usr/bin/env python3 """ Daily Gains Calculator Script This script should be run daily (via cron job or scheduler) to: 1. Calculate and distribute daily gains for active investments 2. Process referral commissions (configurable % of daily gains to referrers) 3. Mark expired investments as inactive 4. Auto-process withdrawals that have passed their configurable delay Configuration values are read from config.py: - REFERRAL_PURCHASE_COMMISSION: Commission rate on plan purchases (default: 0.15 = 15%) - REFERRAL_DAILY_GAIN_COMMISSION: Commission rate on daily gains (default: 0.03 = 3%) - WITHDRAWAL_FEE_PERCENTAGE: Fee on withdrawals (default: 0.15 = 15%) - WITHDRAWAL_DELAY_HOURS: Hours before auto-processing withdrawals (default: 24) Usage: python scripts/daily_gains.py Cron example (run daily at 00:05): 5 0 * * * cd /path/to/project && python scripts/daily_gains.py """ import os import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from datetime import date, datetime, timezone from app import create_app, db from app.models import ( Notification, ReferralCommission, Transaction, User, UserMetal, ) def calculate_daily_gains(): """ Calculate and distribute daily gains for all active investments. Also processes referral commissions on these gains using config rate. """ app = create_app() with app.app_context(): # Get commission rate from config daily_gain_commission_rate = app.config.get( "REFERRAL_DAILY_GAIN_COMMISSION", 0.03 ) print(f"[{datetime.now()}] Starting daily gains calculation for {date.today()}") print( f" Config: Daily gain commission rate = {daily_gain_commission_rate * 100}%" ) active_metals = UserMetal.query.filter_by(is_active=True).all() processed_count = 0 expired_count = 0 commission_count = 0 total_gains_distributed = 0 total_commissions_paid = 0 for user_metal in active_metals: # Skip if already processed today if user_metal.last_gain_date >= date.today(): continue # Check if investment has expired if datetime.now(timezone.utc) >= user_metal.expiry_date: user_metal.is_active = False expired_count += 1 notification = Notification( user_id=user_metal.user_id, title="Adoption Expirée", message=f"Votre adoption en {user_metal.metal.name} a expiré. Vous avez reçu un total de {user_metal.metal.total_return:.0f} FCFA de gains.", type="expiry", ) db.session.add(notification) print( f" - Expired investment for user {user_metal.user_id} ({user_metal.metal.name})" ) continue metal = user_metal.metal if not metal: continue user = user_metal.user if not user: continue daily_gain = metal.daily_gain # Add daily gain to user balance user.balance += daily_gain user.total_gains += daily_gain user_metal.last_gain_date = date.today() total_gains_distributed += daily_gain # Create notification for user notification = Notification( user_id=user.id, title="Gain Quotidien 💰", message=f"Vous avez gagné {daily_gain:.0f} FCFA aujourd'hui de votre adoption en {metal.name}.", type="gain", ) db.session.add(notification) # Create transaction record transaction = Transaction( user_id=user.id, type="gain", amount=daily_gain, description=f"Gain quotidien - {metal.name}", status="completed", ) db.session.add(transaction) processed_count += 1 print( f" + Added {daily_gain:.0f} FCFA to user {user.phone} for {metal.name}" ) # Process referral commission using config rate referrer = user.get_referrer() if referrer: try: # Calculate commission using config rate commission_amount = daily_gain * daily_gain_commission_rate # Create the commission record commission = ReferralCommission( referrer_id=referrer.id, referred_user_id=user.id, level=1, commission_type="daily_gain", commission_percentage=daily_gain_commission_rate * 100, commission_amount=commission_amount, gain_amount=daily_gain, ) db.session.add(commission) # Add commission to referrer's balance referrer.balance += commission_amount referrer.referral_earnings = ( referrer.referral_earnings or 0 ) + commission_amount total_commissions_paid += commission_amount # Only notify for significant amounts to avoid spam if commission_amount >= 5: referrer_notification = Notification( user_id=referrer.id, title="Commission sur Gains", message=f"Vous avez reçu {commission_amount:.0f} FCFA ({daily_gain_commission_rate * 100:.0f}%) sur les gains de {user.name}.", type="referral", ) db.session.add(referrer_notification) commission_count += 1 print( f" → Commission of {commission_amount:.2f} FCFA to referrer {referrer.phone}" ) except Exception as e: print(f" ! Error processing referral commission: {e}") db.session.commit() print(f"[{datetime.now()}] Daily gains completed:") print(f" - Processed: {processed_count} investments") print(f" - Expired: {expired_count} investments") print(f" - Commissions: {commission_count} paid") print(f" - Total gains distributed: {total_gains_distributed:.0f} FCFA") print(f" - Total commissions paid: {total_commissions_paid:.0f} FCFA") def auto_process_withdrawals(): """ Auto-approve withdrawals that have passed their configurable delay without admin action. Only runs on business days (Monday to Friday). """ app = create_app() with app.app_context(): now = datetime.now(timezone.utc) # Get config values withdrawal_fee_percentage = app.config.get("WITHDRAWAL_FEE_PERCENTAGE", 0.15) withdrawal_delay_hours = app.config.get("WITHDRAWAL_DELAY_HOURS", 24) # Skip weekends if now.weekday() in [5, 6]: # Saturday = 5, Sunday = 6 print(f"[{datetime.now()}] Skipping auto-withdrawal processing (weekend)") return print(f"[{datetime.now()}] Starting auto-withdrawal processing") print( f" Config: Fee = {withdrawal_fee_percentage * 100}%, Delay = {withdrawal_delay_hours}h" ) # Find pending withdrawals that can be auto-processed pending_withdrawals = Transaction.query.filter( Transaction.type == "withdrawal", Transaction.status == "pending", Transaction.admin_action.is_(None), Transaction.scheduled_process_time <= now, ).all() processed_count = 0 total_fees_collected = 0 for transaction in pending_withdrawals: transaction.status = "approved" transaction.processed_at = now transaction.admin_action = "auto_approved" transaction.admin_action_time = now net_amount = transaction.net_amount or transaction.amount fee_amount = transaction.fee_amount or 0 total_fees_collected += fee_amount notification = Notification( user_id=transaction.user_id, title="Retrait Traité ✅", message=( f"Votre retrait de {transaction.amount:.0f} FCFA a été traité automatiquement.\n" f"Frais ({withdrawal_fee_percentage * 100:.0f}%): {fee_amount:.0f} FCFA\n" f"Montant envoyé: {net_amount:.0f} FCFA" ), type="withdrawal", ) db.session.add(notification) processed_count += 1 print( f" + Auto-approved withdrawal #{transaction.id} for user {transaction.user_id} ({net_amount:.0f} FCFA net)" ) db.session.commit() print(f"[{datetime.now()}] Auto-withdrawal processing completed:") print(f" - Processed: {processed_count} withdrawals") print(f" - Total fees collected: {total_fees_collected:.0f} FCFA") def cleanup_old_notifications(): """ Optional: Clean up old read notifications (older than 30 days). """ app = create_app() with app.app_context(): from datetime import timedelta cutoff_date = datetime.now(timezone.utc) - timedelta(days=30) old_notifications = Notification.query.filter( Notification.is_read == True, Notification.created_at < cutoff_date, ).all() count = len(old_notifications) for notification in old_notifications: db.session.delete(notification) db.session.commit() print(f"[{datetime.now()}] Cleaned up {count} old notifications") def print_config_summary(): """Print current configuration values.""" app = create_app() with app.app_context(): print("\n" + "=" * 60) print("CONFIGURATION SUMMARY") print("=" * 60) print(f"Registration Bonus: {app.config.get('REGISTRATION_BONUS', 1000)} FCFA") print(f"Daily Login Bonus: {app.config.get('DAILY_LOGIN_BONUS', 30)} FCFA") print( f"Referral Purchase Commission: {app.config.get('REFERRAL_PURCHASE_COMMISSION', 0.15) * 100}%" ) print( f"Referral Daily Gain Commission: {app.config.get('REFERRAL_DAILY_GAIN_COMMISSION', 0.03) * 100}%" ) print( f"Withdrawal Fee: {app.config.get('WITHDRAWAL_FEE_PERCENTAGE', 0.15) * 100}%" ) print(f"Withdrawal Delay: {app.config.get('WITHDRAWAL_DELAY_HOURS', 24)} hours") print( f"Withdrawal Min Amount: {app.config.get('WITHDRAWAL_MIN_AMOUNT', 500)} FCFA" ) print("=" * 60 + "\n") def print_platform_summary(): """Print a summary of the platform statistics.""" app = create_app() with app.app_context(): total_users = User.query.count() active_investments = UserMetal.query.filter_by(is_active=True).count() pending_withdrawals = Transaction.query.filter_by( type="withdrawal", status="pending" ).count() total_gains_today = ( db.session.query(db.func.sum(Transaction.amount)) .filter( Transaction.type == "gain", Transaction.created_at >= datetime.now(timezone.utc).replace( hour=0, minute=0, second=0, microsecond=0 ), ) .scalar() or 0 ) total_commissions_today = ( db.session.query(db.func.sum(ReferralCommission.commission_amount)) .filter( ReferralCommission.created_at >= datetime.now(timezone.utc).replace( hour=0, minute=0, second=0, microsecond=0 ), ) .scalar() or 0 ) total_withdrawal_fees = Transaction.get_total_withdrawal_fees() print("\n" + "=" * 60) print("PLATFORM SUMMARY") print("=" * 60) print(f"Total Users: {total_users}") print(f"Active Investments: {active_investments}") print(f"Pending Withdrawals: {pending_withdrawals}") print(f"Total Gains Today: {total_gains_today:.0f} FCFA") print(f"Total Commissions Today: {total_commissions_today:.0f} FCFA") print(f"Total Withdrawal Fees Collected: {total_withdrawal_fees:.0f} FCFA") print("=" * 60 + "\n") if __name__ == "__main__": print("\n" + "=" * 60) print("DAILY PROCESSING SCRIPT") print(f"Started at: {datetime.now()}") print("=" * 60 + "\n") # Print current configuration print_config_summary() # Run all daily tasks calculate_daily_gains() auto_process_withdrawals() cleanup_old_notifications() print_platform_summary() print("All daily tasks completed successfully!") print(f"Finished at: {datetime.now()}\n")