Merge pull request #5 from i701/feat/topups
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 3m15s

feature/topups
This commit is contained in:
Abdulla Aidhaan
2025-07-04 16:34:15 +05:00
committed by GitHub
7 changed files with 98 additions and 11 deletions

View File

@ -26,8 +26,11 @@ class TopupAdmin(admin.ModelAdmin):
"paid",
"paid_at",
"created_at",
"is_expired",
"expires_at",
"updated_at",
)
search_fields = (
"user__first_name",
"user__last_name",
@ -35,6 +38,10 @@ class TopupAdmin(admin.ModelAdmin):
"user__mobile",
)
@admin.display(boolean=True, description="Expired")
def is_expired(self, obj):
return obj.is_expired
admin.site.register(Payment, PaymentAdmin)
admin.site.register(BillFormula)

View File

@ -4,9 +4,6 @@ import random
from django.core.management.base import BaseCommand
from django.utils import timezone
from faker import Faker
from billing.models import (
Payment,
)
from billing.models import Topup
from api.models import User
@ -40,12 +37,12 @@ class Command(BaseCommand):
for _ in range(number):
random_user = random.choice(users)
paid_status = fake.boolean(chance_of_getting_true=80)
paid_at_date = fake.date_time_this_year() if paid_status else None
if paid_at_date and timezone.is_naive(paid_at_date):
paid_at_date = timezone.make_aware(paid_at_date)
expires_at_date = timezone.now() + timezone.timedelta(
minutes=10,
)
print(
f"Creating topup for user {getattr(random_user, 'id', None)} expires at: {expires_at_date}"
)
Topup.objects.create(
amount=fake.pydecimal(
left_digits=4,
@ -54,10 +51,9 @@ class Command(BaseCommand):
min_value=100.00,
max_value=5000.00,
),
paid=paid_status,
user=random_user,
paid_at=paid_at_date,
updated_at=timezone.now(),
expires_at=expires_at_date,
)
self.stdout.write(self.style.SUCCESS(f"Successfully seeded {number} topups."))

View File

@ -0,0 +1,26 @@
# Generated by Django 5.2 on 2025-07-04 06:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("billing", "0007_topup_paid_at"),
]
operations = [
migrations.AlterModelOptions(
name="topup",
options={"ordering": ["-created_at"]},
),
migrations.AddField(
model_name="topup",
name="expired",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="topup",
name="expires_at",
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 5.2 on 2025-07-04 11:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("billing", "0008_alter_topup_options_topup_expired_topup_expires_at"),
]
operations = [
migrations.RemoveField(
model_name="topup",
name="expired",
),
]

View File

@ -53,9 +53,16 @@ class Topup(models.Model):
paid = models.BooleanField(default=False)
paid_at = models.DateTimeField(null=True, blank=True)
mib_reference = models.CharField(default="", null=True, blank=True)
expires_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
@property
def is_expired(self):
if self.expires_at is None:
return False
return timezone.now() > self.expires_at
def __str__(self):
return f"Topup for {self.user}"

View File

@ -21,6 +21,7 @@ class UpdatePaymentSerializer(serializers.ModelSerializer):
class TopupSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
is_expired = serializers.SerializerMethodField()
def get_user(self, obj):
user = obj.user
@ -33,6 +34,9 @@ class TopupSerializer(serializers.ModelSerializer):
}
return None
def get_is_expired(self, obj):
return obj.is_expired
class Meta: # type: ignore
model = Topup
fields = [
@ -41,6 +45,7 @@ class TopupSerializer(serializers.ModelSerializer):
"user",
"paid",
"mib_reference",
"is_expired",
"created_at",
"updated_at",
]

View File

@ -367,3 +367,33 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
{"message": "Topup payment verification failed."},
status=status.HTTP_400_BAD_REQUEST,
)
class DeleteTopupView(StaffEditorPermissionMixin, generics.DestroyAPIView):
queryset = Topup.objects.all()
serializer_class = TopupSerializer
lookup_field = "pk"
def delete(self, request, *args, **kwargs):
instance = self.get_object()
user = request.user
if instance.is_expired:
return Response(
{"message": "Expired topups cannot be deleted."},
status=status.HTTP_400_BAD_REQUEST,
)
if (
instance.user != user
and getattr(user, "is_admin")
and not user.is_superuser
):
return Response(
{"message": "You are not authorized to delete this topup."},
status=status.HTTP_403_FORBIDDEN,
)
if instance.paid:
return Response(
{"message": "Paid topups cannot be deleted."},
status=status.HTTP_400_BAD_REQUEST,
)
return super().delete(request, *args, **kwargs)