diff --git a/api/tasks.py b/api/tasks.py index b4bbfa1..4a75ac8 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -11,6 +11,7 @@ from api.omada import Omada from apibase.env import env, BASE_DIR from procrastinate.contrib.django import app from procrastinate import builtin_tasks +import time logger = logging.getLogger(__name__) @@ -36,22 +37,64 @@ async def remove_old_jobs(context, timestamp): @app.periodic( - cron="0 0 */28 * *", queue="heavy_tasks", periodic_id="deactivate_expired_devices" + cron="0 22 * * *", + queue="heavy_tasks", + periodic_id="deactivate_expired_devices_and_block_in_omada", ) # type: ignore @app.task -def deactivate_expired_devices(): +def deactivate_expired_devices_and_block_in_omada(): expired_devices = Device.objects.filter( expiry_date__lte=timezone.localtime(timezone.now()), is_active=True ).select_related("user") + print("Expired Devices: ", expired_devices) count = expired_devices.count() + + if count == 0: + return {"total_expired_devices": 0} + user_devices_map = {} + devices_successfully_blocked = [] + devices_failed_to_block = [] + omada_client = Omada() + + # Single loop to collect data and block devices for device in expired_devices: + # Collect devices for SMS notifications if device.user and device.user.mobile: if device.user.mobile not in user_devices_map: user_devices_map[device.user.mobile] = [] user_devices_map[device.user.mobile].append(device.name) + # Try to block device in Omada + try: + omada_client.block_device(mac_address=device.mac, operation="block") + # Only prepare for update if Omada blocking succeeded + device.blocked = True + device.is_active = False + devices_successfully_blocked.append(device) + logger.info(f"Successfully blocked device {device.mac} in Omada") + time.sleep(20) # Sleep to avoid rate limiting + except Exception as e: + logger.error(f"Failed to block device [omada] {device.mac}: {e}") + devices_failed_to_block.append(device) + # Continue to next device without updating this one + + # Bulk update only successfully blocked devices + if devices_successfully_blocked: + try: + Device.objects.bulk_update( + devices_successfully_blocked, ["is_active", "blocked"] + ) + logger.info( + f"Successfully updated {len(devices_successfully_blocked)} devices in database" + ) + except Exception as e: + logger.error(f"Failed to bulk update devices in database: {e}") + # You might want to handle this case - devices are blocked in Omada but not updated in DB + + # Send SMS notifications + sms_count = 0 for mobile, device_names in user_devices_map.items(): if not mobile: continue @@ -59,14 +102,25 @@ def deactivate_expired_devices(): [f"{i + 1}. {name}" for i, name in enumerate(device_names)] ) print("device list: ", device_list) - send_sms( - mobile, - f"Dear {mobile}, \n\nThe following devices have expired: \n{device_list}. \n\nPlease make a payment to keep your devices active. \n\n- SAR Link", - ) - # expired_devices.update(is_active=False) - print(f"Total {count} expired devices.") + try: + send_sms( + mobile, + f"Dear {mobile}, \n\nThe following devices have expired: \n{device_list}. \n\nPlease make a payment to keep your devices active. \n\n- SAR Link", + ) + sms_count += 1 + except Exception as e: + logger.error(f"Failed to send SMS to {mobile}: {e}") + + print(f"Total {count} expired devices processed.") + print(f"Successfully blocked: {len(devices_successfully_blocked)}") + print(f"Failed to block: {len(devices_failed_to_block)}") + print(f"SMS notifications sent: {sms_count}") + return { "total_expired_devices": count, + "successfully_blocked": len(devices_successfully_blocked), + "failed_to_block": len(devices_failed_to_block), + "sms_sent": sms_count, } diff --git a/billing/views.py b/billing/views.py index 4e16139..5629c28 100644 --- a/billing/views.py +++ b/billing/views.py @@ -27,6 +27,7 @@ from .filters import PaymentFilter, TopupFilter, WalletTransactionFilter from dataclasses import dataclass, asdict from typing import Optional from api.models import User +from api.omada import Omada env.read_env(os.path.join(BASE_DIR, ".env")) @@ -77,10 +78,10 @@ class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIVi def create(self, request): data = request.data user = request.user - amount = data.get("amount") number_of_months = data.get("number_of_months") + number_of_devices = 0 device_ids = data.get("device_ids", []) - print(amount, number_of_months, device_ids) + print(number_of_months, device_ids) current_time = timezone.now() expires_at = current_time + timedelta(minutes=10) for device_id in device_ids: @@ -91,9 +92,10 @@ class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIVi {"message": f"Device with id {device_id} not found."}, status=status.HTTP_400_BAD_REQUEST, ) - if not amount or not number_of_months: + number_of_devices += 1 + if not number_of_months: return Response( - {"message": "amount and number_of_months are required."}, + {"message": "number_of_months is required."}, status=status.HTTP_400_BAD_REQUEST, ) if not device_ids: @@ -102,6 +104,7 @@ class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIVi status=status.HTTP_400_BAD_REQUEST, ) # Create payment + amount = 100 payment = Payment.objects.create( amount=amount, number_of_months=number_of_months, @@ -180,8 +183,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView): devices = payment.devices.all() data = request.data user = request.user - print("logged in user", user) - print("Payment user", payment.user) + omada_client = Omada() if payment.paid: return Response( {"message": "Payment has already been verified."}, @@ -211,6 +213,23 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView): payment, devices, ) + device_list = [] + for device in devices: + device_list.append( + { + "mac": device.mac, + "name": device.name, + } + ) + if device.registered: + omada_client.block_device( + mac_address=device.mac, operation="unblock" + ) + if not device.registered: + # Add to omada + add_new_devices_to_omada.defer(new_devices=device_list) + device.registered = True + device.save() return Response( { "status": True, @@ -233,7 +252,6 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView): is_active=True, expiry_date=expiry_date, has_a_pending_payment=False, - registered=True, ) payment.status = "PAID" payment.save() @@ -246,6 +264,10 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView): "name": device.name, } ) + if device.registered: + omada_client.block_device( + mac_address=device.mac, operation="unblock" + ) if not device.registered: # Add to omada add_new_devices_to_omada.defer(new_devices=device_list) @@ -288,7 +310,6 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView): is_active=True, expiry_date=expiry_date, has_a_pending_payment=False, - registered=True, ) payment.save()