grouped transactions

This commit is contained in:
Sayyid Hamid Mahdavi 2026-02-07 13:21:01 +03:30
parent 27db599e8c
commit e045ffd9b1
2 changed files with 40 additions and 32 deletions

View file

@ -223,3 +223,7 @@ class ApplicationWithdrawSerializer(serializers.ModelSerializer):
) )
read_only_fields = ['updated_at', 'state', 'created_at', 'payee', 'payee_type', 'payee_wallet'] read_only_fields = ['updated_at', 'state', 'created_at', 'payee', 'payee_type', 'payee_wallet']
class TransactionGroupedSerializer(serializers.Serializer):
pass

View file

@ -1,10 +1,12 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Q from django.db.models import Q
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import DetailView, ListView, UpdateView from django.views.generic import DetailView, ListView, UpdateView
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
from rest_framework import generics, permissions, mixins from rest_framework import generics, permissions, mixins
@ -14,10 +16,14 @@ 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 rest_framework.viewsets import ViewSet, GenericViewSet
from apps.wallet.models import Transaction
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Sum, Count, F, StringAgg
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
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \ from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
AccountSerializer AccountSerializer, TransactionGroupedSerializer
from ..constans import StateChoices, TypeChoices from ..constans import StateChoices, TypeChoices
from ..filters import AccountFilter, UserTransactionFilter from ..filters import AccountFilter, UserTransactionFilter
from ..forms import TransactionActionForm from ..forms import TransactionActionForm
@ -308,12 +314,11 @@ class WalletTransactionListView(generics.ListAPIView):
class UserTransactionViewSet(mixins.ListModelMixin, class UserTransactionViewSet(mixins.ListModelMixin,
# mixins.CreateModelMixin, # mixins.CreateModelMixin,
# mixins.RetrieveModelMixin, # mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin, # mixins.UpdateModelMixin,
# mixins.DestroyModelMixin, # mixins.DestroyModelMixin,
GenericViewSet): GenericViewSet):
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
serializer_class = TransactionSerializer serializer_class = TransactionSerializer
required_alternate_scopes = { required_alternate_scopes = {
@ -331,7 +336,6 @@ class UserTransactionViewSet(mixins.ListModelMixin,
return Transaction.objects.filter(Q(payee_account__owner_uuid=user.uuid) | return Transaction.objects.filter(Q(payee_account__owner_uuid=user.uuid) |
Q(payer_account__owner_uuid=user.uuid)).all() Q(payer_account__owner_uuid=user.uuid)).all()
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(self.get_queryset())
@ -343,32 +347,32 @@ class UserTransactionViewSet(mixins.ListModelMixin,
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@extend_schema(responses=TransactionGroupedSerializer(many=True))
@action(detail=False, methods=["GET"]) @action(detail=False, methods=["GET"], serializer_class=TransactionGroupedSerializer)
def grouped(self): def grouped(self):
from collections import defaultdict
from django.db.models import Count, Sum
# فقط ۲ کوئری بهینه queryset = self.filter_queryset(self.get_queryset())
# کوئری ۱: آمار کلی (سبک)
stats = dict(Transaction.objects.values('refrence').annotate(
total=Sum('amount'),
count=Count('id')
).values_list('refrence', 'total', 'count'))
# کوئری ۲: همه تراکنش‌ها grouped_transaction_list = queryset.values('details__reference_id', 'application').annotate(
transactions = Transaction.objects.all().order_by('refrence', 'created_at') total_amount=Sum('amount'),
total_count=Count('uuid'),
transaction_uuid_list=ArrayAgg('uuid'),
)
# گروه‌بندی در پایتون page = list(self.paginate_queryset(grouped_transaction_list))
groups = defaultdict(list)
for t in transactions:
groups[t.refrence].append(t)
# ترکیب transaction_uuid_list = []
result = []
for ref, items in groups.items(): for grouped_transaction in page:
result.append({ transaction_uuid_list += grouped_transaction['transaction_uuid_list']
'refrence': ref,
'stats': {'total': stats.get(ref, 0), 'count': len(items)}, transaction_list = Transaction.objects.filter(uuid__in=transaction_uuid_list)
'transactions': items
}) transaction_dict = {t.pk: t for t in transaction_list}
for grouped_transaction in page:
for i, transaction in enumerate(grouped_transaction['transaction_uuid_list']):
grouped_transaction['transaction_uuid_list'][i] = transaction_dict[transaction]
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)