add reference field
This commit is contained in:
parent
4393e3878a
commit
c24b52e341
4 changed files with 158 additions and 8 deletions
18
apps/core/migrations/0002_alter_charge_state.py
Normal file
18
apps/core/migrations/0002_alter_charge_state.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 6.0 on 2026-02-07 05:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='charge',
|
||||||
|
name='state',
|
||||||
|
field=models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure'), (8, 'ROLLED_BACK')], default=1),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 6.0 on 2026-02-07 05:57
|
||||||
|
|
||||||
|
import django.contrib.postgres.indexes
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wallet', '0016_rename_category_wallet_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='reference',
|
||||||
|
field=models.UUIDField(db_index=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='state',
|
||||||
|
field=models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure'), (8, 'ROLLED_BACK')], default=1),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='transaction',
|
||||||
|
index=django.contrib.postgres.indexes.GinIndex(fields=['details'], name='wallet_transaction_details_a7ff79aa'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
|
from django.contrib.postgres.indexes import GinIndex
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
@ -32,8 +33,8 @@ class AccountQuerySet(models.QuerySet):
|
||||||
|
|
||||||
class Account(BaseModel):
|
class Account(BaseModel):
|
||||||
owner_uuid = models.UUIDField(db_index=True, null=False)
|
owner_uuid = models.UUIDField(db_index=True, null=False)
|
||||||
wallet = models.ForeignKey('Wallet', on_delete=models.PROTECT, null=True)
|
|
||||||
owner_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
owner_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
||||||
|
wallet = models.ForeignKey('Wallet', on_delete=models.PROTECT, null=True)
|
||||||
balance = models.BigIntegerField(_('balance'), default=0)
|
balance = models.BigIntegerField(_('balance'), default=0)
|
||||||
|
|
||||||
# TODO: unique together wallet and owner_uuid
|
# TODO: unique together wallet and owner_uuid
|
||||||
|
|
@ -111,6 +112,8 @@ class Transaction(BaseModel):
|
||||||
db_index=True)
|
db_index=True)
|
||||||
details = models.JSONField(null=True, blank=True, default=dict)
|
details = models.JSONField(null=True, blank=True, default=dict)
|
||||||
|
|
||||||
|
reference = models.UUIDField(db_index=True, null=True)
|
||||||
|
|
||||||
# detail = {'payer_name', 'payee_name', 'description', 'application_name', 'payer_avatar_url', 'payee_avatar_url',
|
# detail = {'payer_name', 'payee_name', 'description', 'application_name', 'payer_avatar_url', 'payee_avatar_url',
|
||||||
# 'reference_id', 'application_details_url'}
|
# 'reference_id', 'application_details_url'}
|
||||||
|
|
||||||
|
|
@ -118,6 +121,13 @@ class Transaction(BaseModel):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-created_at']
|
ordering = ['-created_at']
|
||||||
|
indexes = [
|
||||||
|
GinIndex(
|
||||||
|
fields=['details'],
|
||||||
|
# hashlib.md5("wallet_transaction_details_GinIndex".encode()).hexdigest()[:8] used for hash
|
||||||
|
name='wallet_transaction_details_a7ff79aa'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -381,3 +391,29 @@ class Transaction(BaseModel):
|
||||||
if is_updated:
|
if is_updated:
|
||||||
print('update details')
|
print('update details')
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
# TODO: bulk
|
||||||
|
# class PaymentRequest(BaseModel):
|
||||||
|
# state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||||
|
# application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
|
# related_name='transactions', null=True)
|
||||||
|
#
|
||||||
|
# payer_uuid = models.UUIDField(db_index=True, null=False)
|
||||||
|
# payer_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
||||||
|
#
|
||||||
|
# payee_uuid = models.UUIDField(db_index=True, null=False)
|
||||||
|
# payee_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
||||||
|
#
|
||||||
|
# details = models.JSONField(null=True, blank=True, default=dict)
|
||||||
|
#
|
||||||
|
# def submit(self):
|
||||||
|
# with transaction.atomic():
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def verify(self):
|
||||||
|
# with transaction.atomic():
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def rollback(self):
|
||||||
|
# with transaction.atomic():
|
||||||
|
# pass
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ from django.views.generic import DetailView, ListView, UpdateView
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
|
||||||
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, mixins
|
||||||
|
from rest_framework.decorators import action
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.viewsets import ViewSet, GenericViewSet
|
||||||
|
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||||
from apps.wallet.models import Transaction, Wallet, Account
|
from apps.wallet.models import Transaction, Wallet, Account
|
||||||
|
|
@ -66,7 +68,6 @@ class TransactionList(generics.ListAPIView):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
return Transaction.objects.filter(Q(payee_id=user.pk, payee_type=TypeChoices.USER.value) | Q(payer_id=user.pk, payer_type=TypeChoices.USER.value)).all()
|
return Transaction.objects.filter(Q(payee_id=user.pk, payee_type=TypeChoices.USER.value) | Q(payer_id=user.pk, payer_type=TypeChoices.USER.value)).all()
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# def perform_create(self, serializer):
|
# def perform_create(self, serializer):
|
||||||
# payer = self.request.user
|
# payer = self.request.user
|
||||||
|
|
@ -166,6 +167,7 @@ class WithdrawSubmit(generics.CreateAPIView):
|
||||||
payee = self.request.auth.application.user
|
payee = self.request.auth.application.user
|
||||||
serializer.save(payee=payee, application=get_application(self.request))
|
serializer.save(payee=payee, application=get_application(self.request))
|
||||||
|
|
||||||
|
|
||||||
# TODO: refactor
|
# TODO: refactor
|
||||||
class WithdrawVerify(generics.RetrieveAPIView):
|
class WithdrawVerify(generics.RetrieveAPIView):
|
||||||
permission_classes = [TokenHasScope]
|
permission_classes = [TokenHasScope]
|
||||||
|
|
@ -226,7 +228,6 @@ class TransactionDetailView(UpdateView):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WalletAccountListAPIView(generics.ListAPIView):
|
class WalletAccountListAPIView(generics.ListAPIView):
|
||||||
'''
|
'''
|
||||||
API for getting authenticated user balance.
|
API for getting authenticated user balance.
|
||||||
|
|
@ -306,3 +307,68 @@ class WalletTransactionListView(generics.ListAPIView):
|
||||||
return Transaction.objects.filter(Q(payee_account=account) | Q(payer_account=account)).all()
|
return Transaction.objects.filter(Q(payee_account=account) | Q(payer_account=account)).all()
|
||||||
|
|
||||||
|
|
||||||
|
class UserTransactionViewSet(mixins.ListModelMixin,
|
||||||
|
# mixins.CreateModelMixin,
|
||||||
|
# mixins.RetrieveModelMixin,
|
||||||
|
# mixins.UpdateModelMixin,
|
||||||
|
# mixins.DestroyModelMixin,
|
||||||
|
GenericViewSet):
|
||||||
|
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
serializer_class = TransactionSerializer
|
||||||
|
required_alternate_scopes = {
|
||||||
|
"GET": [['wallet.user:transaction_list']],
|
||||||
|
# "POST": [['wallet.invoice:create']],
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_backends = (DjangoFilterBackend,)
|
||||||
|
filterset_class = UserTransactionFilter
|
||||||
|
queryset = Transaction.objects.all()
|
||||||
|
|
||||||
|
# TODO: redundant
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
return Transaction.objects.filter(Q(payee_account__owner_uuid=user.uuid) |
|
||||||
|
Q(payer_account__owner_uuid=user.uuid)).all()
|
||||||
|
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
|
if page is not None:
|
||||||
|
serializer = self.get_serializer(page, many=True)
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
@action(detail=False, methods=["GET"])
|
||||||
|
def grouped(self):
|
||||||
|
from collections import defaultdict
|
||||||
|
from django.db.models import Count, Sum
|
||||||
|
|
||||||
|
# فقط ۲ کوئری بهینه
|
||||||
|
# کوئری ۱: آمار کلی (سبک)
|
||||||
|
stats = dict(Transaction.objects.values('refrence').annotate(
|
||||||
|
total=Sum('amount'),
|
||||||
|
count=Count('id')
|
||||||
|
).values_list('refrence', 'total', 'count'))
|
||||||
|
|
||||||
|
# کوئری ۲: همه تراکنشها
|
||||||
|
transactions = Transaction.objects.all().order_by('refrence', 'created_at')
|
||||||
|
|
||||||
|
# گروهبندی در پایتون
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for t in transactions:
|
||||||
|
groups[t.refrence].append(t)
|
||||||
|
|
||||||
|
# ترکیب
|
||||||
|
result = []
|
||||||
|
for ref, items in groups.items():
|
||||||
|
result.append({
|
||||||
|
'refrence': ref,
|
||||||
|
'stats': {'total': stats.get(ref, 0), 'count': len(items)},
|
||||||
|
'transactions': items
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue