| | |
| | """ |
| | 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(): |
| | |
| | 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: |
| | |
| | if user_metal.last_gain_date >= date.today(): |
| | continue |
| |
|
| | |
| | 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 |
| |
|
| | |
| | user.balance += daily_gain |
| | user.total_gains += daily_gain |
| | user_metal.last_gain_date = date.today() |
| | total_gains_distributed += daily_gain |
| |
|
| | |
| | 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) |
| |
|
| | |
| | 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}" |
| | ) |
| |
|
| | |
| | referrer = user.get_referrer() |
| | if referrer: |
| | try: |
| | |
| | commission_amount = daily_gain * daily_gain_commission_rate |
| |
|
| | |
| | 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) |
| |
|
| | |
| | referrer.balance += commission_amount |
| | referrer.referral_earnings = ( |
| | referrer.referral_earnings or 0 |
| | ) + commission_amount |
| |
|
| | total_commissions_paid += commission_amount |
| |
|
| | |
| | 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) |
| |
|
| | |
| | withdrawal_fee_percentage = app.config.get("WITHDRAWAL_FEE_PERCENTAGE", 0.15) |
| | withdrawal_delay_hours = app.config.get("WITHDRAWAL_DELAY_HOURS", 24) |
| |
|
| | |
| | if now.weekday() in [5, 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" |
| | ) |
| |
|
| | |
| | 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_config_summary() |
| |
|
| | |
| | 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") |
| |
|